diff --git a/.codecov.yml b/.codecov.yml new file mode 100644 index 000000000..45de502e0 --- /dev/null +++ b/.codecov.yml @@ -0,0 +1,2 @@ +ignore: + - "UnitTests" diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 000000000..4603ea210 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,31 @@ +--- +name: Bug Report +about: Submit a bug report if something isn't working as expected. +title: "" +labels: bug, triage +assignees: "" +--- + +**Describe the bug** +A clear and concise description of what the bug is. + +**To Reproduce** +Steps to reproduce the behavior: +1. Go to '...' +2. Tap on '....' +3. Scroll down to '....' +4. See error + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Screenshots** +If applicable, add screenshots to help explain your problem. + +**Environment** +- Device: [ e.g. iPhone 13, MacBook Pro, etc ] +- OS: [ e.g. iOS 15, macOS 11, etc ] +- Browser: [ e.g. Safari, Chrome, etc ] + +**Additional context** +Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 000000000..3ba13e0ce --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1 @@ +blank_issues_enabled: false diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 000000000..ab9e65fef --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,19 @@ +--- +name: Feature Request +about: Make a feature request if you have a suggestion for something new. +title: "" +labels: enhancement, triage +assignees: "" +--- + +**Is your feature request related to a problem you're having? Please describe.** +A clear and concise description of what the problem is. + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Additional context** +Add any other context or screenshots about the feature request here. diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 000000000..a14e3b85e --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,66 @@ +name: tests + +on: + push: + branches: + - master + pull_request: + branches: + - master + workflow_dispatch: + +jobs: + + xcode-project-test: + runs-on: macos-14 + strategy: + matrix: + # Check the Github runner images when updating this matrix: https://github.com/actions/runner-images/tree/main/images/macos + flags: [ + "-scheme AppAuth-iOS -destination 'platform=iOS Simulator,name=iPhone 16,OS=18.2' -sdk 'iphonesimulator18.2'", + "-scheme AppAuth-macOS -destination 'platform=macOS,arch=x86_64' -sdk 'macosx15.2'", + "-scheme AppAuth_macOS -destination 'platform=macOS,arch=x86_64' -sdk 'macosx15.2'", + "-scheme AppAuth-tvOS -destination 'platform=tvOS Simulator,name=Apple TV,OS=18.2' -sdk 'appletvsimulator18.2'", + "-scheme AppAuth_tvOS -destination 'platform=tvOS Simulator,name=Apple TV,OS=18.2' -sdk 'appletvsimulator18.2'", + "-scheme AppAuthTV -destination 'platform=tvOS Simulator,name=Apple TV,OS=18.2' -sdk 'appletvsimulator18.2'" + ] + steps: + - uses: actions/checkout@v3 + - name: Select Xcode + run: sudo xcode-select -s /Applications/Xcode_16.2.app/Contents/Developer + - name: Run unit test targets + run: | + xcodebuild test \ + -project AppAuth.xcodeproj \ + ${{ matrix.flags }} + + pod-lib-lint: + runs-on: macos-14 + strategy: + matrix: + flags: [ + '', + '--use-libraries', + '--use-static-frameworks' + ] + steps: + - uses: actions/checkout@v3 + - name: Update Bundler + run: bundle update --bundler + - name: Install Ruby gems with Bundler + run: bundle install + - name: Lint podspec using local source + run: pod lib lint --verbose ${{ matrix.flags }} + + spm-build-test: + runs-on: macos-14 + steps: + - uses: actions/checkout@v3 + - name: Build unit test target + run: swift build + - name: Run unit test target + run: swift test --enable-code-coverage + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v3 + with: + version: v0.7.3 diff --git a/.gitignore b/.gitignore index e826d81f4..5111bb517 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ Docs/ xcuserdata/ +Carthage/ +.gemini/ diff --git a/.travis.yml b/.travis.yml index f0b0f9039..4adfa0042 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,25 +1,38 @@ # Travis CI config for AppAuth # Use the `wwtd` gem to test locally. language: objective-c -osx_image: xcode9 -# Tests iOS, macOS and tvOS: both static library and framework schemes. -env: -# To generate this list, use `xcodebuild -showsdks` to get possible SDK values, and platforms. -# Use `instruments -s devices` to get the device name. - - SCHEME=AppAuth-iOS DESTINATION="'platform=iOS Simulator,name=iPhone 6,OS=11.0'" SDK=iphonesimulator11.0 - - SCHEME=AppAuth_iOS DESTINATION="'platform=iOS Simulator,name=iPhone 6,OS=11.0'" SDK=iphonesimulator11.0 - - SCHEME=AppAuth-macOS DESTINATION="'platform=macOS,arch=x86_64'" SDK=macosx10.13 - - SCHEME=AppAuth_macOS DESTINATION="'platform=macOS,arch=x86_64'" SDK=macosx10.13 - - SCHEME=AppAuth-tvOS DESTINATION="'platform=tvOS Simulator,name=Apple TV,OS=11.0'" SDK=appletvsimulator11.0 - - SCHEME=AppAuth_tvOS DESTINATION="'platform=tvOS Simulator,name=Apple TV,OS=11.0'" SDK=appletvsimulator11.0 -before_script: - - sudo gem install xcpretty -script: -# Breaking down this command: -# eval is used here, otherwise environment variables are not present. -# `set -o pipefail && ` is so that the return code isn't gobbled by xcpretty (per https://github.com/supermarin/xcpretty#usage) -# then it's standard xcodebuild | xcpretty. - - eval "set -o pipefail && xcodebuild -project AppAuth.xcodeproj -scheme $SCHEME -sdk $SDK -destination $DESTINATION -enableCodeCoverage YES GCC_INSTRUMENT_PROGRAM_FLOW_ARCS=YES GCC_GENERATE_TEST_COVERAGE_FILES=YES OTHERCFLAGS='-Werror' test | xcpretty" -after_success: - - bash <(curl -s https://codecov.io/bash) + +jobs: + include: + - stage: Swift PM + osx_image: xcode11.4 + script: + # Build the project using Swift PM + - swift build + # Test the project using Swift PM + - swift test + + - stage: Xcode Tests + osx_image: xcode11 + # Tests iOS, macOS and tvOS: both static library and framework schemes. + env: + # To generate this list, use `xcodebuild -showsdks` to get possible SDK values, and platforms. + # Use `instruments -s devices` to get the device name. + - SCHEME=AppAuth-iOS DESTINATION="'platform=iOS Simulator,name=iPhone 11,OS=13.0'" SDK=iphonesimulator13.0 + - SCHEME=AppAuth_iOS DESTINATION="'platform=iOS Simulator,name=iPhone 11,OS=13.0'" SDK=iphonesimulator13.0 + - SCHEME=AppAuth-macOS DESTINATION="'platform=macOS,arch=x86_64'" SDK=macosx10.15 + - SCHEME=AppAuth_macOS DESTINATION="'platform=macOS,arch=x86_64'" SDK=macosx10.15 + - SCHEME=AppAuth-tvOS DESTINATION="'platform=tvOS Simulator,name=Apple TV,OS=13.0'" SDK=appletvsimulator13.0 + - SCHEME=AppAuth_tvOS DESTINATION="'platform=tvOS Simulator,name=Apple TV,OS=13.0'" SDK=appletvsimulator13.0 + - SCHEME=AppAuthTV DESTINATION="'platform=tvOS Simulator,name=Apple TV,OS=13.0'" SDK=appletvsimulator13.0 + before_script: + - sudo gem install xcpretty + script: + # Breaking down this command: + # eval is used here, otherwise environment variables are not present. + # `set -o pipefail && ` is so that the return code isn't gobbled by xcpretty (per https://github.com/supermarin/xcpretty#usage) + # then it's standard xcodebuild | xcpretty. + - eval "set -o pipefail && xcodebuild -project AppAuth.xcodeproj -scheme $SCHEME -sdk $SDK -destination $DESTINATION -enableCodeCoverage YES GCC_INSTRUMENT_PROGRAM_FLOW_ARCS=YES GCC_GENERATE_TEST_COVERAGE_FILES=YES OTHERCFLAGS='-Werror' test | xcpretty" + after_success: + - bash <(curl -s https://codecov.io/bash) diff --git a/AUTHORS b/AUTHORS index b8f81d146..b181d5ace 100644 --- a/AUTHORS +++ b/AUTHORS @@ -11,4 +11,5 @@ Ping Identity equinux AG Craig Lane Hernan Zalazar +Julien Bodet diff --git a/AppAuth.podspec b/AppAuth.podspec index b1ffbf4da..17af8b6ce 100644 --- a/AppAuth.podspec +++ b/AppAuth.podspec @@ -1,7 +1,7 @@ Pod::Spec.new do |s| s.name = "AppAuth" - s.version = "0.91.0" + s.version = "2.0.0" s.summary = "AppAuth for iOS and macOS is a client SDK for communicating with OAuth 2.0 and OpenID Connect providers." s.description = <<-DESC @@ -23,6 +23,7 @@ It follows the OAuth 2.0 for Native Apps best current practice s.license = "Apache License, Version 2.0" s.authors = { "William Denniss" => "wdenniss@google.com", "Steven E Wright" => "stevewright@google.com", + "Julien Bodet" => "julien.bodet92@gmail.com" } # Note: While watchOS and tvOS are specified here, only iOS and macOS have @@ -30,19 +31,51 @@ It follows the OAuth 2.0 for Native Apps best current practice # classes of AppAuth with tokens on watchOS and tvOS, but currently the # library won't help you obtain authorization grants on those platforms. - s.platforms = { :ios => "7.0", :osx => "10.9", :watchos => "2.0", :tvos => "9.0" } + ios_deployment_target = "12.0" + osx_deployment_target = "10.12" + s.ios.deployment_target = ios_deployment_target + s.osx.deployment_target = osx_deployment_target + s.watchos.deployment_target = "2.0" + s.tvos.deployment_target = "9.0" s.source = { :git => "https://github.com/openid/AppAuth-iOS.git", :tag => s.version } - - s.source_files = "Source/*.{h,m}" s.requires_arc = true - # iOS - s.ios.source_files = "Source/iOS/**/*.{h,m}" - s.ios.deployment_target = "7.0" - s.ios.framework = "SafariServices" + s.pod_target_xcconfig = { + 'DEFINES_MODULE' => 'YES', + } + + # Subspec for the core AppAuth library classes only, suitable for extensions. + s.subspec 'Core' do |core| + core.source_files = "Sources/AppAuthCore.h", "Sources/AppAuthCore/*.{h,m}" + core.resource_bundles = { + "AppAuthCore_Privacy" => ["Sources/AppAuthCore/Resources/PrivacyInfo.xcprivacy"] + } + end + + # Subspec for the full AppAuth library, including platform-dependent external user agents. + s.subspec 'ExternalUserAgent' do |externalUserAgent| + externalUserAgent.dependency 'AppAuth/Core' + + externalUserAgent.source_files = "Sources/AppAuth.h", "Sources/AppAuth/*.{h,m}" + + # iOS + externalUserAgent.ios.source_files = "Sources/AppAuth/iOS/**/*.{h,m}" + externalUserAgent.ios.deployment_target = ios_deployment_target + externalUserAgent.ios.frameworks = "SafariServices" + externalUserAgent.ios.weak_frameworks = "AuthenticationServices" + + # macOS + externalUserAgent.osx.source_files = "Sources/AppAuth/macOS/**/*.{h,m}" + externalUserAgent.osx.deployment_target = osx_deployment_target + externalUserAgent.osx.weak_frameworks = "AuthenticationServices" + end + + # Subspec for the full AppAuth library, including platform-dependent external user agents. + s.subspec 'TV' do |tv| + tv.source_files = "Sources/AppAuthTV.h", "Sources/AppAuthTV/*.{h,m}" + tv.dependency 'AppAuth/Core' + end - # macOS - s.osx.source_files = "Source/macOS/**/*.{h,m}" - s.osx.deployment_target = '10.9' + s.default_subspecs = 'Core', 'ExternalUserAgent' end diff --git a/AppAuth.xcodeproj/project.pbxproj b/AppAuth.xcodeproj/project.pbxproj index 6657b7c8b..e17b22d34 100644 --- a/AppAuth.xcodeproj/project.pbxproj +++ b/AppAuth.xcodeproj/project.pbxproj @@ -9,8 +9,97 @@ /* Begin PBXBuildFile section */ 039697461FA8258D003D1FB2 /* OIDURLSessionProvider.m in Sources */ = {isa = PBXBuildFile; fileRef = 039697451FA8258D003D1FB2 /* OIDURLSessionProvider.m */; }; 0396974D1FA827AD003D1FB2 /* OIDURLSessionProviderTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 0396974C1FA827AD003D1FB2 /* OIDURLSessionProviderTests.m */; }; + 06C19E9A22B4749900C19CE1 /* OIDEndSessionResponse.h in Headers */ = {isa = PBXBuildFile; fileRef = CF6431F21F228A980075B6B5 /* OIDEndSessionResponse.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 06C19E9B22B474A200C19CE1 /* OIDEndSessionResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = CF6431F31F228A980075B6B5 /* OIDEndSessionResponse.m */; }; + 06C19E9C22B474A600C19CE1 /* OIDEndSessionRequest.h in Headers */ = {isa = PBXBuildFile; fileRef = CF37C06B1F1FC21A00662E41 /* OIDEndSessionRequest.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 06C19E9D22B474AD00C19CE1 /* OIDEndSessionRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = CF37C06C1F1FC21A00662E41 /* OIDEndSessionRequest.m */; }; + 2D47AAE1249A87020059B5A4 /* OIDTVAuthorizationResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = 2D47AAD8249A87010059B5A4 /* OIDTVAuthorizationResponse.m */; }; + 2D47AAE4249A87020059B5A4 /* OIDTVServiceConfiguration.m in Sources */ = {isa = PBXBuildFile; fileRef = 2D47AADA249A87010059B5A4 /* OIDTVServiceConfiguration.m */; }; + 2D47AAE8249A87020059B5A4 /* OIDTVAuthorizationRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = 2D47AADD249A87010059B5A4 /* OIDTVAuthorizationRequest.m */; }; + 2D47AAEC249A87020059B5A4 /* OIDTVAuthorizationService.m in Sources */ = {isa = PBXBuildFile; fileRef = 2D47AAE0249A87020059B5A4 /* OIDTVAuthorizationService.m */; }; + 2D8111FA24C0FD4C00984DA7 /* AppAuthTV.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2D9385B724B37CAD009A12D7 /* AppAuthTV.framework */; }; + 2D81120424C1036700984DA7 /* OIDTVAuthorizationRequestTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 2D81120024C1036700984DA7 /* OIDTVAuthorizationRequestTests.m */; }; + 2D81120624C103C800984DA7 /* OIDAuthorizationRequestTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 341742011C5D82D3000EF209 /* OIDAuthorizationRequestTests.m */; }; + 2D81120724C103CC00984DA7 /* OIDAuthorizationResponseTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 341742031C5D82D3000EF209 /* OIDAuthorizationResponseTests.m */; }; + 2D81120824C103F200984DA7 /* OIDAuthStateTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 341742051C5D82D3000EF209 /* OIDAuthStateTests.m */; }; + 2D81120924C103F200984DA7 /* OIDGrantTypesTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 341742061C5D82D3000EF209 /* OIDGrantTypesTests.m */; }; + 2D81120A24C103F200984DA7 /* OIDResponseTypesTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 341742071C5D82D3000EF209 /* OIDResponseTypesTests.m */; }; + 2D81120C24C103F300984DA7 /* OIDScopesTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 341742081C5D82D3000EF209 /* OIDScopesTests.m */; }; + 2D81120D24C103F300984DA7 /* OIDServiceConfigurationTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 3417420A1C5D82D3000EF209 /* OIDServiceConfigurationTests.m */; }; + 2D81120E24C103F300984DA7 /* OIDServiceDiscoveryTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 3417420C1C5D82D3000EF209 /* OIDServiceDiscoveryTests.m */; }; + 2D81120F24C103F300984DA7 /* OIDTokenRequestTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 3417420E1C5D82D3000EF209 /* OIDTokenRequestTests.m */; }; + 2D81121024C103F300984DA7 /* OIDTokenResponseTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 341742101C5D82D3000EF209 /* OIDTokenResponseTests.m */; }; + 2D81121124C103F300984DA7 /* OIDTokenUtilitiesTests.m in Sources */ = {isa = PBXBuildFile; fileRef = A5EEF1FD20CF07760044F470 /* OIDTokenUtilitiesTests.m */; }; + 2D81121224C103F300984DA7 /* OIDURLQueryComponentTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 341742121C5D82D3000EF209 /* OIDURLQueryComponentTests.m */; }; + 2D81121324C103F300984DA7 /* OIDURLQueryComponentTestsIOS7.m in Sources */ = {isa = PBXBuildFile; fileRef = 341742131C5D82D3000EF209 /* OIDURLQueryComponentTestsIOS7.m */; }; + 2D81121424C103F300984DA7 /* OIDRegistrationRequestTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 60140F821DE43BAF00DA0DC3 /* OIDRegistrationRequestTests.m */; }; + 2D81121524C103F300984DA7 /* OIDRegistrationResponseTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 60140F851DE43CC700DA0DC3 /* OIDRegistrationResponseTests.m */; }; + 2D81121624C103F300984DA7 /* OIDRPProfileCode.m in Sources */ = {isa = PBXBuildFile; fileRef = 34A6638A1E8865090060B664 /* OIDRPProfileCode.m */; }; + 2D9385DE24B3861E009A12D7 /* AppAuthTV.h in Headers */ = {isa = PBXBuildFile; fileRef = 2D9385B924B37CAD009A12D7 /* AppAuthTV.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 2D9385DF24B38646009A12D7 /* OIDTVAuthorizationRequest.h in Headers */ = {isa = PBXBuildFile; fileRef = 2D47AAD9249A87010059B5A4 /* OIDTVAuthorizationRequest.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 2D9385E024B38658009A12D7 /* OIDTVAuthorizationResponse.h in Headers */ = {isa = PBXBuildFile; fileRef = 2D47AADC249A87010059B5A4 /* OIDTVAuthorizationResponse.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 2D9385E124B3865E009A12D7 /* OIDTVAuthorizationService.h in Headers */ = {isa = PBXBuildFile; fileRef = 2D47AADF249A87020059B5A4 /* OIDTVAuthorizationService.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 2D9385E224B38669009A12D7 /* OIDTVServiceConfiguration.h in Headers */ = {isa = PBXBuildFile; fileRef = 2D47AADE249A87020059B5A4 /* OIDTVServiceConfiguration.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 2D93861924B38803009A12D7 /* OIDAuthorizationRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = 341741B51C5D8243000EF209 /* OIDAuthorizationRequest.m */; }; + 2D93861A24B3880B009A12D7 /* OIDAuthorizationRequest.h in Headers */ = {isa = PBXBuildFile; fileRef = 341741B41C5D8243000EF209 /* OIDAuthorizationRequest.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 2D93861B24B38810009A12D7 /* OIDAuthorizationResponse.h in Headers */ = {isa = PBXBuildFile; fileRef = 341741B61C5D8243000EF209 /* OIDAuthorizationResponse.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 2D93861C24B38812009A12D7 /* OIDAuthorizationResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = 341741B71C5D8243000EF209 /* OIDAuthorizationResponse.m */; }; + 2D93861D24B38815009A12D7 /* OIDAuthorizationService.h in Headers */ = {isa = PBXBuildFile; fileRef = 341741B81C5D8243000EF209 /* OIDAuthorizationService.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 2D93861E24B3881B009A12D7 /* OIDAuthorizationService.m in Sources */ = {isa = PBXBuildFile; fileRef = 341741B91C5D8243000EF209 /* OIDAuthorizationService.m */; }; + 2D93861F24B3881B009A12D7 /* OIDAuthState.h in Headers */ = {isa = PBXBuildFile; fileRef = 341741BA1C5D8243000EF209 /* OIDAuthState.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 2D93862024B3881B009A12D7 /* OIDAuthState.m in Sources */ = {isa = PBXBuildFile; fileRef = 341741BB1C5D8243000EF209 /* OIDAuthState.m */; }; + 2D93862124B3881B009A12D7 /* OIDAuthStateChangeDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = 341741BC1C5D8243000EF209 /* OIDAuthStateChangeDelegate.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 2D93862224B3881C009A12D7 /* OIDAuthStateErrorDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = 341741BD1C5D8243000EF209 /* OIDAuthStateErrorDelegate.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 2D93862424B3881C009A12D7 /* OIDClientMetadataParameters.m in Sources */ = {isa = PBXBuildFile; fileRef = 60140F791DE4276800DA0DC3 /* OIDClientMetadataParameters.m */; }; + 2D93862624B3881C009A12D7 /* OIDError.h in Headers */ = {isa = PBXBuildFile; fileRef = 341741BF1C5D8243000EF209 /* OIDError.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 2D93862724B3881C009A12D7 /* OIDError.m in Sources */ = {isa = PBXBuildFile; fileRef = 341741C01C5D8243000EF209 /* OIDError.m */; }; + 2D93862824B3881C009A12D7 /* OIDErrorUtilities.h in Headers */ = {isa = PBXBuildFile; fileRef = 341741C11C5D8243000EF209 /* OIDErrorUtilities.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 2D93862924B3881C009A12D7 /* OIDErrorUtilities.m in Sources */ = {isa = PBXBuildFile; fileRef = 341741C21C5D8243000EF209 /* OIDErrorUtilities.m */; }; + 2D93862A24B3881C009A12D7 /* OIDExternalUserAgentSession.h in Headers */ = {isa = PBXBuildFile; fileRef = A6DEAB992018E4A20022AC32 /* OIDExternalUserAgentSession.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 2D93862B24B38825009A12D7 /* OIDExternalUserAgentRequest.h in Headers */ = {isa = PBXBuildFile; fileRef = A6DEAB9A2018E4A20022AC32 /* OIDExternalUserAgentRequest.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 2D93862C24B38826009A12D7 /* OIDExternalUserAgent.h in Headers */ = {isa = PBXBuildFile; fileRef = A6DEAB982018E4A20022AC32 /* OIDExternalUserAgent.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 2D93862E24B38826009A12D7 /* OIDFieldMapping.m in Sources */ = {isa = PBXBuildFile; fileRef = 341741C41C5D8243000EF209 /* OIDFieldMapping.m */; }; + 2D93862F24B38826009A12D7 /* OIDEndSessionResponse.h in Headers */ = {isa = PBXBuildFile; fileRef = CF6431F21F228A980075B6B5 /* OIDEndSessionResponse.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 2D93863024B38826009A12D7 /* OIDEndSessionResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = CF6431F31F228A980075B6B5 /* OIDEndSessionResponse.m */; }; + 2D93863124B38826009A12D7 /* OIDEndSessionRequest.h in Headers */ = {isa = PBXBuildFile; fileRef = CF37C06B1F1FC21A00662E41 /* OIDEndSessionRequest.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 2D93863224B38826009A12D7 /* OIDEndSessionRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = CF37C06C1F1FC21A00662E41 /* OIDEndSessionRequest.m */; }; + 2D93863324B38826009A12D7 /* OIDRegistrationResponse.h in Headers */ = {isa = PBXBuildFile; fileRef = 60140F7E1DE4335200DA0DC3 /* OIDRegistrationResponse.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 2D93863424B38826009A12D7 /* OIDRegistrationResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = 60140F7F1DE4344200DA0DC3 /* OIDRegistrationResponse.m */; }; + 2D93863524B38827009A12D7 /* OIDRegistrationRequest.h in Headers */ = {isa = PBXBuildFile; fileRef = 60140F7D1DE42E3000DA0DC3 /* OIDRegistrationRequest.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 2D93863624B38827009A12D7 /* OIDRegistrationRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = 60140F7B1DE42E1000DA0DC3 /* OIDRegistrationRequest.m */; }; + 2D93863724B38827009A12D7 /* OIDGrantTypes.h in Headers */ = {isa = PBXBuildFile; fileRef = 341741C51C5D8243000EF209 /* OIDGrantTypes.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 2D93863824B38827009A12D7 /* OIDGrantTypes.m in Sources */ = {isa = PBXBuildFile; fileRef = 341741C61C5D8243000EF209 /* OIDGrantTypes.m */; }; + 2D93863924B38827009A12D7 /* OIDIDToken.h in Headers */ = {isa = PBXBuildFile; fileRef = 34A663261E871DD40060B664 /* OIDIDToken.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 2D93863A24B38827009A12D7 /* OIDIDToken.m in Sources */ = {isa = PBXBuildFile; fileRef = 34A663271E871DD40060B664 /* OIDIDToken.m */; }; + 2D93863B24B38827009A12D7 /* OIDResponseTypes.h in Headers */ = {isa = PBXBuildFile; fileRef = 341741C71C5D8243000EF209 /* OIDResponseTypes.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 2D93863C24B38827009A12D7 /* OIDResponseTypes.m in Sources */ = {isa = PBXBuildFile; fileRef = 341741C81C5D8243000EF209 /* OIDResponseTypes.m */; }; + 2D93863D24B38827009A12D7 /* OIDScopes.h in Headers */ = {isa = PBXBuildFile; fileRef = 341741C91C5D8243000EF209 /* OIDScopes.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 2D93863E24B38827009A12D7 /* OIDScopes.m in Sources */ = {isa = PBXBuildFile; fileRef = 341741CA1C5D8243000EF209 /* OIDScopes.m */; }; + 2D93863F24B38828009A12D7 /* OIDScopeUtilities.h in Headers */ = {isa = PBXBuildFile; fileRef = 341741CB1C5D8243000EF209 /* OIDScopeUtilities.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 2D93864024B38828009A12D7 /* OIDScopeUtilities.m in Sources */ = {isa = PBXBuildFile; fileRef = 341741CC1C5D8243000EF209 /* OIDScopeUtilities.m */; }; + 2D93864124B38828009A12D7 /* OIDServiceConfiguration.h in Headers */ = {isa = PBXBuildFile; fileRef = 341741CD1C5D8243000EF209 /* OIDServiceConfiguration.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 2D93864224B38828009A12D7 /* OIDServiceConfiguration.m in Sources */ = {isa = PBXBuildFile; fileRef = 341741CE1C5D8243000EF209 /* OIDServiceConfiguration.m */; }; + 2D93864324B38828009A12D7 /* OIDServiceDiscovery.h in Headers */ = {isa = PBXBuildFile; fileRef = 341741CF1C5D8243000EF209 /* OIDServiceDiscovery.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 2D93864424B38828009A12D7 /* OIDServiceDiscovery.m in Sources */ = {isa = PBXBuildFile; fileRef = 341741D01C5D8243000EF209 /* OIDServiceDiscovery.m */; }; + 2D93864524B38828009A12D7 /* OIDTokenRequest.h in Headers */ = {isa = PBXBuildFile; fileRef = 341741D11C5D8243000EF209 /* OIDTokenRequest.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 2D93864624B38828009A12D7 /* OIDTokenRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = 341741D21C5D8243000EF209 /* OIDTokenRequest.m */; }; + 2D93864724B38828009A12D7 /* OIDTokenResponse.h in Headers */ = {isa = PBXBuildFile; fileRef = 341741D31C5D8243000EF209 /* OIDTokenResponse.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 2D93864824B38828009A12D7 /* OIDTokenResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = 341741D41C5D8243000EF209 /* OIDTokenResponse.m */; }; + 2D93864924B38828009A12D7 /* OIDTokenUtilities.h in Headers */ = {isa = PBXBuildFile; fileRef = 341741D51C5D8243000EF209 /* OIDTokenUtilities.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 2D93864A24B38829009A12D7 /* OIDTokenUtilities.m in Sources */ = {isa = PBXBuildFile; fileRef = 341741D61C5D8243000EF209 /* OIDTokenUtilities.m */; }; + 2D93864C24B38829009A12D7 /* OIDURLQueryComponent.m in Sources */ = {isa = PBXBuildFile; fileRef = 341741D81C5D8243000EF209 /* OIDURLQueryComponent.m */; }; + 2D93864D24B38829009A12D7 /* OIDURLSessionProvider.h in Headers */ = {isa = PBXBuildFile; fileRef = 039697441FA8258D003D1FB2 /* OIDURLSessionProvider.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 2D93864E24B38829009A12D7 /* OIDURLSessionProvider.m in Sources */ = {isa = PBXBuildFile; fileRef = 039697451FA8258D003D1FB2 /* OIDURLSessionProvider.m */; }; + 2D93864F24B38840009A12D7 /* OIDTVAuthorizationRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = 2D47AADD249A87010059B5A4 /* OIDTVAuthorizationRequest.m */; }; + 2D93865024B38840009A12D7 /* OIDTVAuthorizationResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = 2D47AAD8249A87010059B5A4 /* OIDTVAuthorizationResponse.m */; }; + 2D93865124B38840009A12D7 /* OIDTVAuthorizationService.m in Sources */ = {isa = PBXBuildFile; fileRef = 2D47AAE0249A87020059B5A4 /* OIDTVAuthorizationService.m */; }; + 2D93865224B38840009A12D7 /* OIDTVServiceConfiguration.m in Sources */ = {isa = PBXBuildFile; fileRef = 2D47AADA249A87010059B5A4 /* OIDTVServiceConfiguration.m */; }; + 2DA8D82624C6190400FDFB34 /* OIDTVAuthorizationResponseTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 2DA8D82424C6190300FDFB34 /* OIDTVAuthorizationResponseTests.m */; }; + 2DEB065624CA1D9300DF47E7 /* OIDTVTokenRequest.h in Headers */ = {isa = PBXBuildFile; fileRef = 2DEB065424CA1D9300DF47E7 /* OIDTVTokenRequest.h */; }; + 2DEB065724CA1D9300DF47E7 /* OIDTVTokenRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = 2DEB065524CA1D9300DF47E7 /* OIDTVTokenRequest.m */; }; + 2DEB066224CF5CFB00DF47E7 /* OIDTVTokenRequestTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 2DEB066024CF5CE000DF47E7 /* OIDTVTokenRequestTests.m */; }; 340DAE571D5821A100EC285B /* OIDAuthorizationService+Mac.m in Sources */ = {isa = PBXBuildFile; fileRef = 340DAE261D581FE700EC285B /* OIDAuthorizationService+Mac.m */; }; - 340DAE581D5821A100EC285B /* OIDAuthorizationUICoordinatorMac.m in Sources */ = {isa = PBXBuildFile; fileRef = 340DAE281D581FE700EC285B /* OIDAuthorizationUICoordinatorMac.m */; }; + 340DAE581D5821A100EC285B /* OIDExternalUserAgentMac.m in Sources */ = {isa = PBXBuildFile; fileRef = 340DAE281D581FE700EC285B /* OIDExternalUserAgentMac.m */; }; 340DAE591D5821A100EC285B /* OIDAuthState+Mac.m in Sources */ = {isa = PBXBuildFile; fileRef = 340DAE2A1D581FE700EC285B /* OIDAuthState+Mac.m */; }; 340DAE5A1D5821AB00EC285B /* OIDAuthorizationRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = 341741B51C5D8243000EF209 /* OIDAuthorizationRequest.m */; }; 340DAE5C1D5821AB00EC285B /* OIDAuthorizationService.m in Sources */ = {isa = PBXBuildFile; fileRef = 341741B91C5D8243000EF209 /* OIDAuthorizationService.m */; }; @@ -19,7 +108,6 @@ 340DAEBC1D582AF100EC285B /* OIDRedirectHTTPHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = 340DAEBA1D582AF100EC285B /* OIDRedirectHTTPHandler.m */; }; 340DAECB1D582DE100EC285B /* OIDAuthorizationService+IOS.m in Sources */ = {isa = PBXBuildFile; fileRef = F6F60FB11D2BFEFE00325CB3 /* OIDAuthorizationService+IOS.m */; }; 340DAECC1D582DE100EC285B /* OIDAuthState+IOS.m in Sources */ = {isa = PBXBuildFile; fileRef = F6F60FB01D2BFEFE00325CB3 /* OIDAuthState+IOS.m */; }; - 340DAECD1D582DE100EC285B /* OIDAuthorizationUICoordinatorIOS.m in Sources */ = {isa = PBXBuildFile; fileRef = F6F60FB21D2BFEFE00325CB3 /* OIDAuthorizationUICoordinatorIOS.m */; }; 341310BE1E6F943C00D5DEE5 /* OIDClientMetadataParameters.m in Sources */ = {isa = PBXBuildFile; fileRef = 60140F791DE4276800DA0DC3 /* OIDClientMetadataParameters.m */; }; 341310BF1E6F943C00D5DEE5 /* OIDClientMetadataParameters.m in Sources */ = {isa = PBXBuildFile; fileRef = 60140F791DE4276800DA0DC3 /* OIDClientMetadataParameters.m */; }; 341310C21E6F944B00D5DEE5 /* OIDError.m in Sources */ = {isa = PBXBuildFile; fileRef = 341741C01C5D8243000EF209 /* OIDError.m */; }; @@ -116,14 +204,58 @@ 341E70991DE18796004353C1 /* OIDAuthorizationResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = 341741B71C5D8243000EF209 /* OIDAuthorizationResponse.m */; }; 341E709A1DE18796004353C1 /* OIDAuthorizationService.m in Sources */ = {isa = PBXBuildFile; fileRef = 341741B91C5D8243000EF209 /* OIDAuthorizationService.m */; }; 341E709B1DE18796004353C1 /* OIDAuthState.m in Sources */ = {isa = PBXBuildFile; fileRef = 341741BB1C5D8243000EF209 /* OIDAuthState.m */; }; + 342F42872177B1FC00574F24 /* OIDFieldMapping.m in Sources */ = {isa = PBXBuildFile; fileRef = 341741C41C5D8243000EF209 /* OIDFieldMapping.m */; }; + 342F42882177B1FC00574F24 /* OIDIDToken.m in Sources */ = {isa = PBXBuildFile; fileRef = 34A663271E871DD40060B664 /* OIDIDToken.m */; }; + 342F42892177B1FC00574F24 /* OIDAuthState.m in Sources */ = {isa = PBXBuildFile; fileRef = 341741BB1C5D8243000EF209 /* OIDAuthState.m */; }; + 342F428B2177B1FC00574F24 /* OIDTokenResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = 341741D41C5D8243000EF209 /* OIDTokenResponse.m */; }; + 342F428C2177B1FC00574F24 /* OIDErrorUtilities.m in Sources */ = {isa = PBXBuildFile; fileRef = 341741C21C5D8243000EF209 /* OIDErrorUtilities.m */; }; + 342F428D2177B1FC00574F24 /* OIDURLQueryComponent.m in Sources */ = {isa = PBXBuildFile; fileRef = 341741D81C5D8243000EF209 /* OIDURLQueryComponent.m */; }; + 342F428E2177B1FC00574F24 /* OIDAuthorizationRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = 341741B51C5D8243000EF209 /* OIDAuthorizationRequest.m */; }; + 342F428F2177B1FC00574F24 /* OIDAuthorizationService.m in Sources */ = {isa = PBXBuildFile; fileRef = 341741B91C5D8243000EF209 /* OIDAuthorizationService.m */; }; + 342F42902177B1FC00574F24 /* OIDClientMetadataParameters.m in Sources */ = {isa = PBXBuildFile; fileRef = 60140F791DE4276800DA0DC3 /* OIDClientMetadataParameters.m */; }; + 342F42912177B1FC00574F24 /* OIDTokenUtilities.m in Sources */ = {isa = PBXBuildFile; fileRef = 341741D61C5D8243000EF209 /* OIDTokenUtilities.m */; }; + 342F42922177B1FC00574F24 /* OIDServiceDiscovery.m in Sources */ = {isa = PBXBuildFile; fileRef = 341741D01C5D8243000EF209 /* OIDServiceDiscovery.m */; }; + 342F42932177B1FC00574F24 /* OIDTokenRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = 341741D21C5D8243000EF209 /* OIDTokenRequest.m */; }; + 342F42962177B1FC00574F24 /* OIDServiceConfiguration.m in Sources */ = {isa = PBXBuildFile; fileRef = 341741CE1C5D8243000EF209 /* OIDServiceConfiguration.m */; }; + 342F42972177B1FC00574F24 /* OIDRegistrationResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = 60140F7F1DE4344200DA0DC3 /* OIDRegistrationResponse.m */; }; + 342F42982177B1FC00574F24 /* OIDURLSessionProvider.m in Sources */ = {isa = PBXBuildFile; fileRef = 039697451FA8258D003D1FB2 /* OIDURLSessionProvider.m */; }; + 342F42992177B1FC00574F24 /* OIDScopes.m in Sources */ = {isa = PBXBuildFile; fileRef = 341741CA1C5D8243000EF209 /* OIDScopes.m */; }; + 342F429A2177B1FC00574F24 /* OIDScopeUtilities.m in Sources */ = {isa = PBXBuildFile; fileRef = 341741CC1C5D8243000EF209 /* OIDScopeUtilities.m */; }; + 342F429B2177B1FC00574F24 /* OIDGrantTypes.m in Sources */ = {isa = PBXBuildFile; fileRef = 341741C61C5D8243000EF209 /* OIDGrantTypes.m */; }; + 342F429C2177B1FC00574F24 /* OIDRegistrationRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = 60140F7B1DE42E1000DA0DC3 /* OIDRegistrationRequest.m */; }; + 342F429D2177B1FC00574F24 /* OIDResponseTypes.m in Sources */ = {isa = PBXBuildFile; fileRef = 341741C81C5D8243000EF209 /* OIDResponseTypes.m */; }; + 342F429E2177B1FC00574F24 /* OIDAuthorizationResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = 341741B71C5D8243000EF209 /* OIDAuthorizationResponse.m */; }; + 342F429F2177B1FC00574F24 /* OIDError.m in Sources */ = {isa = PBXBuildFile; fileRef = 341741C01C5D8243000EF209 /* OIDError.m */; }; + 342F42A22177B1FC00574F24 /* OIDAuthorizationResponse.h in Headers */ = {isa = PBXBuildFile; fileRef = 341741B61C5D8243000EF209 /* OIDAuthorizationResponse.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 342F42A32177B1FC00574F24 /* OIDScopes.h in Headers */ = {isa = PBXBuildFile; fileRef = 341741C91C5D8243000EF209 /* OIDScopes.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 342F42A42177B1FC00574F24 /* OIDExternalUserAgentRequest.h in Headers */ = {isa = PBXBuildFile; fileRef = A6DEAB9A2018E4A20022AC32 /* OIDExternalUserAgentRequest.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 342F42A52177B1FC00574F24 /* OIDAuthStateChangeDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = 341741BC1C5D8243000EF209 /* OIDAuthStateChangeDelegate.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 342F42A82177B1FC00574F24 /* OIDIDToken.h in Headers */ = {isa = PBXBuildFile; fileRef = 34A663261E871DD40060B664 /* OIDIDToken.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 342F42A92177B1FC00574F24 /* OIDResponseTypes.h in Headers */ = {isa = PBXBuildFile; fileRef = 341741C71C5D8243000EF209 /* OIDResponseTypes.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 342F42AA2177B1FC00574F24 /* OIDTokenRequest.h in Headers */ = {isa = PBXBuildFile; fileRef = 341741D11C5D8243000EF209 /* OIDTokenRequest.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 342F42AB2177B1FC00574F24 /* OIDScopeUtilities.h in Headers */ = {isa = PBXBuildFile; fileRef = 341741CB1C5D8243000EF209 /* OIDScopeUtilities.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 342F42AC2177B1FC00574F24 /* OIDTokenResponse.h in Headers */ = {isa = PBXBuildFile; fileRef = 341741D31C5D8243000EF209 /* OIDTokenResponse.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 342F42AD2177B1FC00574F24 /* OIDServiceDiscovery.h in Headers */ = {isa = PBXBuildFile; fileRef = 341741CF1C5D8243000EF209 /* OIDServiceDiscovery.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 342F42AE2177B1FC00574F24 /* OIDGrantTypes.h in Headers */ = {isa = PBXBuildFile; fileRef = 341741C51C5D8243000EF209 /* OIDGrantTypes.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 342F42AF2177B1FC00574F24 /* OIDURLSessionProvider.h in Headers */ = {isa = PBXBuildFile; fileRef = 039697441FA8258D003D1FB2 /* OIDURLSessionProvider.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 342F42B12177B1FC00574F24 /* OIDRegistrationResponse.h in Headers */ = {isa = PBXBuildFile; fileRef = 60140F7E1DE4335200DA0DC3 /* OIDRegistrationResponse.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 342F42B22177B1FC00574F24 /* OIDExternalUserAgent.h in Headers */ = {isa = PBXBuildFile; fileRef = A6DEAB982018E4A20022AC32 /* OIDExternalUserAgent.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 342F42B32177B1FC00574F24 /* OIDExternalUserAgentSession.h in Headers */ = {isa = PBXBuildFile; fileRef = A6DEAB992018E4A20022AC32 /* OIDExternalUserAgentSession.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 342F42B42177B1FC00574F24 /* OIDServiceConfiguration.h in Headers */ = {isa = PBXBuildFile; fileRef = 341741CD1C5D8243000EF209 /* OIDServiceConfiguration.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 342F42B52177B1FC00574F24 /* OIDAuthStateErrorDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = 341741BD1C5D8243000EF209 /* OIDAuthStateErrorDelegate.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 342F42B62177B1FC00574F24 /* OIDAuthorizationService.h in Headers */ = {isa = PBXBuildFile; fileRef = 341741B81C5D8243000EF209 /* OIDAuthorizationService.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 342F42B72177B1FC00574F24 /* OIDRegistrationRequest.h in Headers */ = {isa = PBXBuildFile; fileRef = 60140F7D1DE42E3000DA0DC3 /* OIDRegistrationRequest.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 342F42B82177B1FC00574F24 /* OIDAuthState.h in Headers */ = {isa = PBXBuildFile; fileRef = 341741BA1C5D8243000EF209 /* OIDAuthState.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 342F42B92177B1FC00574F24 /* OIDErrorUtilities.h in Headers */ = {isa = PBXBuildFile; fileRef = 341741C11C5D8243000EF209 /* OIDErrorUtilities.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 342F42BB2177B1FC00574F24 /* OIDAuthorizationRequest.h in Headers */ = {isa = PBXBuildFile; fileRef = 341741B41C5D8243000EF209 /* OIDAuthorizationRequest.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 342F42BC2177B1FC00574F24 /* OIDTokenUtilities.h in Headers */ = {isa = PBXBuildFile; fileRef = 341741D51C5D8243000EF209 /* OIDTokenUtilities.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 342F42BD2177B1FC00574F24 /* OIDError.h in Headers */ = {isa = PBXBuildFile; fileRef = 341741BF1C5D8243000EF209 /* OIDError.h */; settings = {ATTRIBUTES = (Public, ); }; }; 343AAA5D1E83463400F9D36E /* AppAuth.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 343AAA541E83463400F9D36E /* AppAuth.framework */; }; 343AAA6B1E83465500F9D36E /* AppAuth.h in Headers */ = {isa = PBXBuildFile; fileRef = 343AAA4D1E8345B600F9D36E /* AppAuth.h */; settings = {ATTRIBUTES = (Public, ); }; }; 343AAA6C1E83466B00F9D36E /* OIDAuthorizationService+IOS.h in Headers */ = {isa = PBXBuildFile; fileRef = F6F60FB31D2BFEFE00325CB3 /* OIDAuthorizationService+IOS.h */; settings = {ATTRIBUTES = (Public, ); }; }; 343AAA6D1E83466B00F9D36E /* OIDAuthState+IOS.h in Headers */ = {isa = PBXBuildFile; fileRef = F6F60FB51D2BFEFE00325CB3 /* OIDAuthState+IOS.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 343AAA6E1E83466B00F9D36E /* OIDAuthorizationUICoordinatorIOS.h in Headers */ = {isa = PBXBuildFile; fileRef = F6F60FB41D2BFEFE00325CB3 /* OIDAuthorizationUICoordinatorIOS.h */; settings = {ATTRIBUTES = (Public, ); }; }; 343AAA6F1E83467D00F9D36E /* OIDAuthorizationService+IOS.m in Sources */ = {isa = PBXBuildFile; fileRef = F6F60FB11D2BFEFE00325CB3 /* OIDAuthorizationService+IOS.m */; }; 343AAA701E83467D00F9D36E /* OIDAuthState+IOS.m in Sources */ = {isa = PBXBuildFile; fileRef = F6F60FB01D2BFEFE00325CB3 /* OIDAuthState+IOS.m */; }; - 343AAA711E83467D00F9D36E /* OIDAuthorizationUICoordinatorIOS.m in Sources */ = {isa = PBXBuildFile; fileRef = F6F60FB21D2BFEFE00325CB3 /* OIDAuthorizationUICoordinatorIOS.m */; }; 343AAA721E83469600F9D36E /* OIDAuthorizationRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = 341741B51C5D8243000EF209 /* OIDAuthorizationRequest.m */; }; 343AAA731E8346B400F9D36E /* OIDAuthorizationRequestTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 341742011C5D82D3000EF209 /* OIDAuthorizationRequestTests.m */; }; 343AAA741E8346B400F9D36E /* OIDAuthorizationResponseTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 341742031C5D82D3000EF209 /* OIDAuthorizationResponseTests.m */; }; @@ -158,22 +290,21 @@ 343AAA911E83478900F9D36E /* OIDTokenRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = 341741D21C5D8243000EF209 /* OIDTokenRequest.m */; }; 343AAA921E83478900F9D36E /* OIDTokenResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = 341741D41C5D8243000EF209 /* OIDTokenResponse.m */; }; 343AAA931E83478900F9D36E /* OIDTokenUtilities.m in Sources */ = {isa = PBXBuildFile; fileRef = 341741D61C5D8243000EF209 /* OIDTokenUtilities.m */; }; - 343AAAAF1E83489A00F9D36E /* AppAUth.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 343AAAA61E83489A00F9D36E /* AppAUth.framework */; }; + 343AAAAF1E83489A00F9D36E /* AppAuth.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 343AAAA61E83489A00F9D36E /* AppAuth.framework */; }; 343AAACB1E8348AA00F9D36E /* AppAuth.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 343AAAC21E8348A900F9D36E /* AppAuth.framework */; }; 343AAAD91E83493D00F9D36E /* OIDRedirectHTTPHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = 340DAEBA1D582AF100EC285B /* OIDRedirectHTTPHandler.m */; }; 343AAADA1E83493D00F9D36E /* OIDAuthorizationService+Mac.m in Sources */ = {isa = PBXBuildFile; fileRef = 340DAE261D581FE700EC285B /* OIDAuthorizationService+Mac.m */; }; - 343AAADB1E83493D00F9D36E /* OIDAuthorizationUICoordinatorMac.m in Sources */ = {isa = PBXBuildFile; fileRef = 340DAE281D581FE700EC285B /* OIDAuthorizationUICoordinatorMac.m */; }; + 343AAADB1E83493D00F9D36E /* OIDExternalUserAgentMac.m in Sources */ = {isa = PBXBuildFile; fileRef = 340DAE281D581FE700EC285B /* OIDExternalUserAgentMac.m */; }; 343AAADC1E83493D00F9D36E /* OIDAuthState+Mac.m in Sources */ = {isa = PBXBuildFile; fileRef = 340DAE2A1D581FE700EC285B /* OIDAuthState+Mac.m */; }; 343AAADD1E83494400F9D36E /* OIDRedirectHTTPHandler.h in Headers */ = {isa = PBXBuildFile; fileRef = 340DAEB91D582AF100EC285B /* OIDRedirectHTTPHandler.h */; settings = {ATTRIBUTES = (Public, ); }; }; 343AAADE1E83494400F9D36E /* OIDAuthorizationService+Mac.h in Headers */ = {isa = PBXBuildFile; fileRef = 340DAE251D581FE700EC285B /* OIDAuthorizationService+Mac.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 343AAADF1E83494400F9D36E /* OIDAuthorizationUICoordinatorMac.h in Headers */ = {isa = PBXBuildFile; fileRef = 340DAE271D581FE700EC285B /* OIDAuthorizationUICoordinatorMac.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 343AAADF1E83494400F9D36E /* OIDExternalUserAgentMac.h in Headers */ = {isa = PBXBuildFile; fileRef = 340DAE271D581FE700EC285B /* OIDExternalUserAgentMac.h */; settings = {ATTRIBUTES = (Public, ); }; }; 343AAAE01E83494400F9D36E /* OIDAuthState+Mac.h in Headers */ = {isa = PBXBuildFile; fileRef = 340DAE291D581FE700EC285B /* OIDAuthState+Mac.h */; settings = {ATTRIBUTES = (Public, ); }; }; 343AAAE11E83494A00F9D36E /* OIDLoopbackHTTPServer.h in Headers */ = {isa = PBXBuildFile; fileRef = 34FEA6AC1DB6E083005C9212 /* OIDLoopbackHTTPServer.h */; }; 343AAAE21E83494F00F9D36E /* OIDLoopbackHTTPServer.m in Sources */ = {isa = PBXBuildFile; fileRef = 34FEA6AD1DB6E083005C9212 /* OIDLoopbackHTTPServer.m */; }; 343AAAE31E83499000F9D36E /* OIDAuthorizationRequest.h in Headers */ = {isa = PBXBuildFile; fileRef = 341741B41C5D8243000EF209 /* OIDAuthorizationRequest.h */; settings = {ATTRIBUTES = (Public, ); }; }; 343AAAE41E83499000F9D36E /* OIDAuthorizationResponse.h in Headers */ = {isa = PBXBuildFile; fileRef = 341741B61C5D8243000EF209 /* OIDAuthorizationResponse.h */; settings = {ATTRIBUTES = (Public, ); }; }; 343AAAE51E83499000F9D36E /* OIDAuthorizationService.h in Headers */ = {isa = PBXBuildFile; fileRef = 341741B81C5D8243000EF209 /* OIDAuthorizationService.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 343AAAE61E83499000F9D36E /* OIDAuthorizationUICoordinator.h in Headers */ = {isa = PBXBuildFile; fileRef = F68103B61D2568D10053658E /* OIDAuthorizationUICoordinator.h */; settings = {ATTRIBUTES = (Public, ); }; }; 343AAAE71E83499000F9D36E /* OIDAuthState.h in Headers */ = {isa = PBXBuildFile; fileRef = 341741BA1C5D8243000EF209 /* OIDAuthState.h */; settings = {ATTRIBUTES = (Public, ); }; }; 343AAAE81E83499000F9D36E /* OIDAuthStateChangeDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = 341741BC1C5D8243000EF209 /* OIDAuthStateChangeDelegate.h */; settings = {ATTRIBUTES = (Public, ); }; }; 343AAAE91E83499000F9D36E /* OIDAuthStateErrorDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = 341741BD1C5D8243000EF209 /* OIDAuthStateErrorDelegate.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -193,7 +324,6 @@ 343AAAFB1E83499100F9D36E /* OIDAuthorizationRequest.h in Headers */ = {isa = PBXBuildFile; fileRef = 341741B41C5D8243000EF209 /* OIDAuthorizationRequest.h */; settings = {ATTRIBUTES = (Public, ); }; }; 343AAAFC1E83499100F9D36E /* OIDAuthorizationResponse.h in Headers */ = {isa = PBXBuildFile; fileRef = 341741B61C5D8243000EF209 /* OIDAuthorizationResponse.h */; settings = {ATTRIBUTES = (Public, ); }; }; 343AAAFD1E83499100F9D36E /* OIDAuthorizationService.h in Headers */ = {isa = PBXBuildFile; fileRef = 341741B81C5D8243000EF209 /* OIDAuthorizationService.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 343AAAFE1E83499100F9D36E /* OIDAuthorizationUICoordinator.h in Headers */ = {isa = PBXBuildFile; fileRef = F68103B61D2568D10053658E /* OIDAuthorizationUICoordinator.h */; settings = {ATTRIBUTES = (Public, ); }; }; 343AAAFF1E83499100F9D36E /* OIDAuthState.h in Headers */ = {isa = PBXBuildFile; fileRef = 341741BA1C5D8243000EF209 /* OIDAuthState.h */; settings = {ATTRIBUTES = (Public, ); }; }; 343AAB001E83499100F9D36E /* OIDAuthStateChangeDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = 341741BC1C5D8243000EF209 /* OIDAuthStateChangeDelegate.h */; settings = {ATTRIBUTES = (Public, ); }; }; 343AAB011E83499100F9D36E /* OIDAuthStateErrorDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = 341741BD1C5D8243000EF209 /* OIDAuthStateErrorDelegate.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -213,7 +343,6 @@ 343AAB131E83499200F9D36E /* OIDAuthorizationRequest.h in Headers */ = {isa = PBXBuildFile; fileRef = 341741B41C5D8243000EF209 /* OIDAuthorizationRequest.h */; settings = {ATTRIBUTES = (Public, ); }; }; 343AAB141E83499200F9D36E /* OIDAuthorizationResponse.h in Headers */ = {isa = PBXBuildFile; fileRef = 341741B61C5D8243000EF209 /* OIDAuthorizationResponse.h */; settings = {ATTRIBUTES = (Public, ); }; }; 343AAB151E83499200F9D36E /* OIDAuthorizationService.h in Headers */ = {isa = PBXBuildFile; fileRef = 341741B81C5D8243000EF209 /* OIDAuthorizationService.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 343AAB161E83499200F9D36E /* OIDAuthorizationUICoordinator.h in Headers */ = {isa = PBXBuildFile; fileRef = F68103B61D2568D10053658E /* OIDAuthorizationUICoordinator.h */; settings = {ATTRIBUTES = (Public, ); }; }; 343AAB171E83499200F9D36E /* OIDAuthState.h in Headers */ = {isa = PBXBuildFile; fileRef = 341741BA1C5D8243000EF209 /* OIDAuthState.h */; settings = {ATTRIBUTES = (Public, ); }; }; 343AAB181E83499200F9D36E /* OIDAuthStateChangeDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = 341741BC1C5D8243000EF209 /* OIDAuthStateChangeDelegate.h */; settings = {ATTRIBUTES = (Public, ); }; }; 343AAB191E83499200F9D36E /* OIDAuthStateErrorDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = 341741BD1C5D8243000EF209 /* OIDAuthStateErrorDelegate.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -233,7 +362,6 @@ 343AAB2B1E83499200F9D36E /* OIDAuthorizationRequest.h in Headers */ = {isa = PBXBuildFile; fileRef = 341741B41C5D8243000EF209 /* OIDAuthorizationRequest.h */; settings = {ATTRIBUTES = (Public, ); }; }; 343AAB2C1E83499200F9D36E /* OIDAuthorizationResponse.h in Headers */ = {isa = PBXBuildFile; fileRef = 341741B61C5D8243000EF209 /* OIDAuthorizationResponse.h */; settings = {ATTRIBUTES = (Public, ); }; }; 343AAB2D1E83499200F9D36E /* OIDAuthorizationService.h in Headers */ = {isa = PBXBuildFile; fileRef = 341741B81C5D8243000EF209 /* OIDAuthorizationService.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 343AAB2E1E83499200F9D36E /* OIDAuthorizationUICoordinator.h in Headers */ = {isa = PBXBuildFile; fileRef = F68103B61D2568D10053658E /* OIDAuthorizationUICoordinator.h */; settings = {ATTRIBUTES = (Public, ); }; }; 343AAB2F1E83499200F9D36E /* OIDAuthState.h in Headers */ = {isa = PBXBuildFile; fileRef = 341741BA1C5D8243000EF209 /* OIDAuthState.h */; settings = {ATTRIBUTES = (Public, ); }; }; 343AAB301E83499200F9D36E /* OIDAuthStateChangeDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = 341741BC1C5D8243000EF209 /* OIDAuthStateChangeDelegate.h */; settings = {ATTRIBUTES = (Public, ); }; }; 343AAB311E83499200F9D36E /* OIDAuthStateErrorDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = 341741BD1C5D8243000EF209 /* OIDAuthStateErrorDelegate.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -362,6 +490,42 @@ 347424101E7F4BA000D3E6D6 /* OIDTokenResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = 341741D41C5D8243000EF209 /* OIDTokenResponse.m */; }; 347424111E7F4BA000D3E6D6 /* OIDTokenUtilities.m in Sources */ = {isa = PBXBuildFile; fileRef = 341741D61C5D8243000EF209 /* OIDTokenUtilities.m */; }; 347424121E7F4BA000D3E6D6 /* OIDURLQueryComponent.m in Sources */ = {isa = PBXBuildFile; fileRef = 341741D81C5D8243000EF209 /* OIDURLQueryComponent.m */; }; + 348970812177B3B000ABEED4 /* OIDScopesTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 341742081C5D82D3000EF209 /* OIDScopesTests.m */; }; + 348970822177B3B000ABEED4 /* OIDURLQueryComponentTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 341742121C5D82D3000EF209 /* OIDURLQueryComponentTests.m */; }; + 348970832177B3B000ABEED4 /* OIDServiceConfigurationTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 3417420A1C5D82D3000EF209 /* OIDServiceConfigurationTests.m */; }; + 348970842177B3B000ABEED4 /* OIDURLQueryComponentTestsIOS7.m in Sources */ = {isa = PBXBuildFile; fileRef = 341742131C5D82D3000EF209 /* OIDURLQueryComponentTestsIOS7.m */; }; + 348970852177B3B000ABEED4 /* OIDRegistrationResponseTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 60140F851DE43CC700DA0DC3 /* OIDRegistrationResponseTests.m */; }; + 348970862177B3B000ABEED4 /* OIDServiceDiscoveryTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 3417420C1C5D82D3000EF209 /* OIDServiceDiscoveryTests.m */; }; + 348970872177B3B000ABEED4 /* OIDAuthStateTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 341742051C5D82D3000EF209 /* OIDAuthStateTests.m */; }; + 348970882177B3B000ABEED4 /* OIDTokenResponseTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 341742101C5D82D3000EF209 /* OIDTokenResponseTests.m */; }; + 348970892177B3B000ABEED4 /* OIDTokenRequestTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 3417420E1C5D82D3000EF209 /* OIDTokenRequestTests.m */; }; + 3489708A2177B3B000ABEED4 /* OIDResponseTypesTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 341742071C5D82D3000EF209 /* OIDResponseTypesTests.m */; }; + 3489708B2177B3B000ABEED4 /* OIDRegistrationRequestTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 60140F821DE43BAF00DA0DC3 /* OIDRegistrationRequestTests.m */; }; + 3489708C2177B3B000ABEED4 /* OIDAuthorizationRequestTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 341742011C5D82D3000EF209 /* OIDAuthorizationRequestTests.m */; }; + 3489708D2177B3B000ABEED4 /* OIDGrantTypesTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 341742061C5D82D3000EF209 /* OIDGrantTypesTests.m */; }; + 3489708E2177B3B000ABEED4 /* OIDRPProfileCode.m in Sources */ = {isa = PBXBuildFile; fileRef = 34A6638A1E8865090060B664 /* OIDRPProfileCode.m */; }; + 3489708F2177B3B000ABEED4 /* OIDAuthorizationResponseTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 341742031C5D82D3000EF209 /* OIDAuthorizationResponseTests.m */; }; + 348970902177B3B000ABEED4 /* OIDTokenUtilitiesTests.m in Sources */ = {isa = PBXBuildFile; fileRef = A5EEF1FD20CF07760044F470 /* OIDTokenUtilitiesTests.m */; }; + 348970922177B3B000ABEED4 /* AppAuth.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 343AAA541E83463400F9D36E /* AppAuth.framework */; }; + 3489709C2178F40600ABEED4 /* AppAuthCore.h in Headers */ = {isa = PBXBuildFile; fileRef = 3489709A2178F40600ABEED4 /* AppAuthCore.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 34A663291E871DD40060B664 /* OIDIDToken.h in Headers */ = {isa = PBXBuildFile; fileRef = 34A663261E871DD40060B664 /* OIDIDToken.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 34A6632A1E871DD40060B664 /* OIDIDToken.h in Headers */ = {isa = PBXBuildFile; fileRef = 34A663261E871DD40060B664 /* OIDIDToken.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 34A6632B1E871DD40060B664 /* OIDIDToken.h in Headers */ = {isa = PBXBuildFile; fileRef = 34A663261E871DD40060B664 /* OIDIDToken.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 34A6632C1E871DD40060B664 /* OIDIDToken.h in Headers */ = {isa = PBXBuildFile; fileRef = 34A663261E871DD40060B664 /* OIDIDToken.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 34A6632D1E871DD40060B664 /* OIDIDToken.m in Sources */ = {isa = PBXBuildFile; fileRef = 34A663271E871DD40060B664 /* OIDIDToken.m */; }; + 34A6632E1E871DD40060B664 /* OIDIDToken.m in Sources */ = {isa = PBXBuildFile; fileRef = 34A663271E871DD40060B664 /* OIDIDToken.m */; }; + 34A6632F1E871DD40060B664 /* OIDIDToken.m in Sources */ = {isa = PBXBuildFile; fileRef = 34A663271E871DD40060B664 /* OIDIDToken.m */; }; + 34A663301E871DD40060B664 /* OIDIDToken.m in Sources */ = {isa = PBXBuildFile; fileRef = 34A663271E871DD40060B664 /* OIDIDToken.m */; }; + 34A663311E871DD40060B664 /* OIDIDToken.m in Sources */ = {isa = PBXBuildFile; fileRef = 34A663271E871DD40060B664 /* OIDIDToken.m */; }; + 34A663321E871DD40060B664 /* OIDIDToken.m in Sources */ = {isa = PBXBuildFile; fileRef = 34A663271E871DD40060B664 /* OIDIDToken.m */; }; + 34A663331E871DD40060B664 /* OIDIDToken.m in Sources */ = {isa = PBXBuildFile; fileRef = 34A663271E871DD40060B664 /* OIDIDToken.m */; }; + 34A663341E871DD40060B664 /* OIDIDToken.m in Sources */ = {isa = PBXBuildFile; fileRef = 34A663271E871DD40060B664 /* OIDIDToken.m */; }; + 34A6638B1E8865090060B664 /* OIDRPProfileCode.m in Sources */ = {isa = PBXBuildFile; fileRef = 34A6638A1E8865090060B664 /* OIDRPProfileCode.m */; }; + 34A6638C1E8865090060B664 /* OIDRPProfileCode.m in Sources */ = {isa = PBXBuildFile; fileRef = 34A6638A1E8865090060B664 /* OIDRPProfileCode.m */; }; + 34A6638D1E8865090060B664 /* OIDRPProfileCode.m in Sources */ = {isa = PBXBuildFile; fileRef = 34A6638A1E8865090060B664 /* OIDRPProfileCode.m */; }; + 34A6638E1E8865090060B664 /* OIDRPProfileCode.m in Sources */ = {isa = PBXBuildFile; fileRef = 34A6638A1E8865090060B664 /* OIDRPProfileCode.m */; }; + 34A6638F1E8865090060B664 /* OIDRPProfileCode.m in Sources */ = {isa = PBXBuildFile; fileRef = 34A6638A1E8865090060B664 /* OIDRPProfileCode.m */; }; + 34A663901E8865090060B664 /* OIDRPProfileCode.m in Sources */ = {isa = PBXBuildFile; fileRef = 34A6638A1E8865090060B664 /* OIDRPProfileCode.m */; }; 34AF73671FB4E4B00022335F /* OIDURLSessionProvider.m in Sources */ = {isa = PBXBuildFile; fileRef = 039697451FA8258D003D1FB2 /* OIDURLSessionProvider.m */; }; 34AF73681FB4E4B10022335F /* OIDURLSessionProvider.m in Sources */ = {isa = PBXBuildFile; fileRef = 039697451FA8258D003D1FB2 /* OIDURLSessionProvider.m */; }; 34AF73691FB4E4B20022335F /* OIDURLSessionProvider.m in Sources */ = {isa = PBXBuildFile; fileRef = 039697451FA8258D003D1FB2 /* OIDURLSessionProvider.m */; }; @@ -369,17 +533,91 @@ 34AF736B1FB4E4B30022335F /* OIDURLSessionProvider.m in Sources */ = {isa = PBXBuildFile; fileRef = 039697451FA8258D003D1FB2 /* OIDURLSessionProvider.m */; }; 34AF736C1FB4E4B40022335F /* OIDURLSessionProvider.m in Sources */ = {isa = PBXBuildFile; fileRef = 039697451FA8258D003D1FB2 /* OIDURLSessionProvider.m */; }; 34AF736D1FB4E4B40022335F /* OIDURLSessionProvider.m in Sources */ = {isa = PBXBuildFile; fileRef = 039697451FA8258D003D1FB2 /* OIDURLSessionProvider.m */; }; + 34B822932153602C00D96702 /* AuthenticationServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 34B822922153602C00D96702 /* AuthenticationServices.framework */; }; 34D5EC451E6D1AD900814354 /* OIDSwiftTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34D5EC441E6D1AD900814354 /* OIDSwiftTests.swift */; }; 34FEA6AE1DB6E083005C9212 /* OIDLoopbackHTTPServer.h in Headers */ = {isa = PBXBuildFile; fileRef = 34FEA6AC1DB6E083005C9212 /* OIDLoopbackHTTPServer.h */; }; 34FEA6AF1DB6E083005C9212 /* OIDLoopbackHTTPServer.m in Sources */ = {isa = PBXBuildFile; fileRef = 34FEA6AD1DB6E083005C9212 /* OIDLoopbackHTTPServer.m */; }; + 55A094CF20DFBB10000045D1 /* OIDURLSessionProvider.h in Headers */ = {isa = PBXBuildFile; fileRef = 039697441FA8258D003D1FB2 /* OIDURLSessionProvider.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 55A094D020DFBB11000045D1 /* OIDURLSessionProvider.h in Headers */ = {isa = PBXBuildFile; fileRef = 039697441FA8258D003D1FB2 /* OIDURLSessionProvider.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 55A094D120DFBB12000045D1 /* OIDURLSessionProvider.h in Headers */ = {isa = PBXBuildFile; fileRef = 039697441FA8258D003D1FB2 /* OIDURLSessionProvider.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 55A094D220DFBB12000045D1 /* OIDURLSessionProvider.h in Headers */ = {isa = PBXBuildFile; fileRef = 039697441FA8258D003D1FB2 /* OIDURLSessionProvider.h */; settings = {ATTRIBUTES = (Public, ); }; }; 60140F7A1DE4276800DA0DC3 /* OIDClientMetadataParameters.m in Sources */ = {isa = PBXBuildFile; fileRef = 60140F791DE4276800DA0DC3 /* OIDClientMetadataParameters.m */; }; 60140F7C1DE42E1000DA0DC3 /* OIDRegistrationRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = 60140F7B1DE42E1000DA0DC3 /* OIDRegistrationRequest.m */; }; 60140F801DE4344200DA0DC3 /* OIDRegistrationResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = 60140F7F1DE4344200DA0DC3 /* OIDRegistrationResponse.m */; }; 60140F831DE43BAF00DA0DC3 /* OIDRegistrationRequestTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 60140F821DE43BAF00DA0DC3 /* OIDRegistrationRequestTests.m */; }; 60140F861DE43CC700DA0DC3 /* OIDRegistrationResponseTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 60140F851DE43CC700DA0DC3 /* OIDRegistrationResponseTests.m */; }; + 73F574342B7C42690023FFF0 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 73F574332B7C42690023FFF0 /* PrivacyInfo.xcprivacy */; }; + 73F574352B7C42690023FFF0 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 73F574332B7C42690023FFF0 /* PrivacyInfo.xcprivacy */; }; + 73F574362B7C42690023FFF0 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 73F574332B7C42690023FFF0 /* PrivacyInfo.xcprivacy */; }; + 73F574372B7C42690023FFF0 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 73F574332B7C42690023FFF0 /* PrivacyInfo.xcprivacy */; }; + 73F574382B7C42690023FFF0 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 73F574332B7C42690023FFF0 /* PrivacyInfo.xcprivacy */; }; + 73F574392B7C42690023FFF0 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 73F574332B7C42690023FFF0 /* PrivacyInfo.xcprivacy */; }; + A5EEF29720D821120044F470 /* OIDTokenUtilitiesTests.m in Sources */ = {isa = PBXBuildFile; fileRef = A5EEF1FD20CF07760044F470 /* OIDTokenUtilitiesTests.m */; }; + A5EEF29820D8211A0044F470 /* OIDTokenUtilitiesTests.m in Sources */ = {isa = PBXBuildFile; fileRef = A5EEF1FD20CF07760044F470 /* OIDTokenUtilitiesTests.m */; }; + A5EEF29920D8211B0044F470 /* OIDTokenUtilitiesTests.m in Sources */ = {isa = PBXBuildFile; fileRef = A5EEF1FD20CF07760044F470 /* OIDTokenUtilitiesTests.m */; }; + A5EEF29A20D821960044F470 /* OIDTokenUtilitiesTests.m in Sources */ = {isa = PBXBuildFile; fileRef = A5EEF1FD20CF07760044F470 /* OIDTokenUtilitiesTests.m */; }; + A5EEF29B20D821970044F470 /* OIDTokenUtilitiesTests.m in Sources */ = {isa = PBXBuildFile; fileRef = A5EEF1FD20CF07760044F470 /* OIDTokenUtilitiesTests.m */; }; + A5EEF29C20D821970044F470 /* OIDTokenUtilitiesTests.m in Sources */ = {isa = PBXBuildFile; fileRef = A5EEF1FD20CF07760044F470 /* OIDTokenUtilitiesTests.m */; }; + A6CEB11A2007E49C009D492A /* OIDEndSessionRequestTests.m in Sources */ = {isa = PBXBuildFile; fileRef = A6CEB1182007E384009D492A /* OIDEndSessionRequestTests.m */; }; + A6CEB11B2007E49D009D492A /* OIDEndSessionRequestTests.m in Sources */ = {isa = PBXBuildFile; fileRef = A6CEB1182007E384009D492A /* OIDEndSessionRequestTests.m */; }; + A6CEB11C2007E49E009D492A /* OIDEndSessionRequestTests.m in Sources */ = {isa = PBXBuildFile; fileRef = A6CEB1182007E384009D492A /* OIDEndSessionRequestTests.m */; }; + A6CEB11D2007E49F009D492A /* OIDEndSessionRequestTests.m in Sources */ = {isa = PBXBuildFile; fileRef = A6CEB1182007E384009D492A /* OIDEndSessionRequestTests.m */; }; + A6CEB11E2007E4A1009D492A /* OIDEndSessionRequestTests.m in Sources */ = {isa = PBXBuildFile; fileRef = A6CEB1182007E384009D492A /* OIDEndSessionRequestTests.m */; }; + A6CEB11F2007E4A2009D492A /* OIDEndSessionRequestTests.m in Sources */ = {isa = PBXBuildFile; fileRef = A6CEB1182007E384009D492A /* OIDEndSessionRequestTests.m */; }; + A6DEAB832017A7030022AC32 /* OIDEndSessionResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = CF6431F31F228A980075B6B5 /* OIDEndSessionResponse.m */; }; + A6DEAB842017A7040022AC32 /* OIDEndSessionResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = CF6431F31F228A980075B6B5 /* OIDEndSessionResponse.m */; }; + A6DEAB852017A7050022AC32 /* OIDEndSessionResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = CF6431F31F228A980075B6B5 /* OIDEndSessionResponse.m */; }; + A6DEAB862017A7060022AC32 /* OIDEndSessionResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = CF6431F31F228A980075B6B5 /* OIDEndSessionResponse.m */; }; + A6DEAB872017A70B0022AC32 /* OIDEndSessionResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = CF6431F31F228A980075B6B5 /* OIDEndSessionResponse.m */; }; + A6DEAB882017A70B0022AC32 /* OIDEndSessionResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = CF6431F31F228A980075B6B5 /* OIDEndSessionResponse.m */; }; + A6DEAB892017A70C0022AC32 /* OIDEndSessionResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = CF6431F31F228A980075B6B5 /* OIDEndSessionResponse.m */; }; + A6DEAB8A2017A7140022AC32 /* OIDEndSessionRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = CF37C06C1F1FC21A00662E41 /* OIDEndSessionRequest.m */; }; + A6DEAB8B2017A7160022AC32 /* OIDEndSessionRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = CF37C06C1F1FC21A00662E41 /* OIDEndSessionRequest.m */; }; + A6DEAB8C2017A7160022AC32 /* OIDEndSessionRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = CF37C06C1F1FC21A00662E41 /* OIDEndSessionRequest.m */; }; + A6DEAB8D2017A7170022AC32 /* OIDEndSessionRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = CF37C06C1F1FC21A00662E41 /* OIDEndSessionRequest.m */; }; + A6DEAB9B2018E4AD0022AC32 /* OIDExternalUserAgent.h in Headers */ = {isa = PBXBuildFile; fileRef = A6DEAB982018E4A20022AC32 /* OIDExternalUserAgent.h */; settings = {ATTRIBUTES = (Public, ); }; }; + A6DEAB9C2018E4AD0022AC32 /* OIDExternalUserAgent.h in Headers */ = {isa = PBXBuildFile; fileRef = A6DEAB982018E4A20022AC32 /* OIDExternalUserAgent.h */; settings = {ATTRIBUTES = (Public, ); }; }; + A6DEAB9D2018E4AD0022AC32 /* OIDExternalUserAgent.h in Headers */ = {isa = PBXBuildFile; fileRef = A6DEAB982018E4A20022AC32 /* OIDExternalUserAgent.h */; settings = {ATTRIBUTES = (Public, ); }; }; + A6DEAB9E2018E4AE0022AC32 /* OIDExternalUserAgent.h in Headers */ = {isa = PBXBuildFile; fileRef = A6DEAB982018E4A20022AC32 /* OIDExternalUserAgent.h */; settings = {ATTRIBUTES = (Public, ); }; }; + A6DEAB9F2018E4B00022AC32 /* OIDExternalUserAgentRequest.h in Headers */ = {isa = PBXBuildFile; fileRef = A6DEAB9A2018E4A20022AC32 /* OIDExternalUserAgentRequest.h */; settings = {ATTRIBUTES = (Public, ); }; }; + A6DEABA02018E4B00022AC32 /* OIDExternalUserAgentRequest.h in Headers */ = {isa = PBXBuildFile; fileRef = A6DEAB9A2018E4A20022AC32 /* OIDExternalUserAgentRequest.h */; settings = {ATTRIBUTES = (Public, ); }; }; + A6DEABA22018E4B60022AC32 /* OIDExternalUserAgentRequest.h in Headers */ = {isa = PBXBuildFile; fileRef = A6DEAB9A2018E4A20022AC32 /* OIDExternalUserAgentRequest.h */; settings = {ATTRIBUTES = (Public, ); }; }; + A6DEABA32018E4B70022AC32 /* OIDExternalUserAgentRequest.h in Headers */ = {isa = PBXBuildFile; fileRef = A6DEAB9A2018E4A20022AC32 /* OIDExternalUserAgentRequest.h */; settings = {ATTRIBUTES = (Public, ); }; }; + A6DEABA42018E4B90022AC32 /* OIDExternalUserAgentSession.h in Headers */ = {isa = PBXBuildFile; fileRef = A6DEAB992018E4A20022AC32 /* OIDExternalUserAgentSession.h */; settings = {ATTRIBUTES = (Public, ); }; }; + A6DEABA52018E4BA0022AC32 /* OIDExternalUserAgentSession.h in Headers */ = {isa = PBXBuildFile; fileRef = A6DEAB992018E4A20022AC32 /* OIDExternalUserAgentSession.h */; settings = {ATTRIBUTES = (Public, ); }; }; + A6DEABA62018E4BA0022AC32 /* OIDExternalUserAgentSession.h in Headers */ = {isa = PBXBuildFile; fileRef = A6DEAB992018E4A20022AC32 /* OIDExternalUserAgentSession.h */; settings = {ATTRIBUTES = (Public, ); }; }; + A6DEABA72018E4BA0022AC32 /* OIDExternalUserAgentSession.h in Headers */ = {isa = PBXBuildFile; fileRef = A6DEAB992018E4A20022AC32 /* OIDExternalUserAgentSession.h */; settings = {ATTRIBUTES = (Public, ); }; }; + A6DEABAA2018E5B50022AC32 /* OIDExternalUserAgentIOS.m in Sources */ = {isa = PBXBuildFile; fileRef = A6DEABA82018E5B50022AC32 /* OIDExternalUserAgentIOS.m */; }; + A6DEABAB2018E5C50022AC32 /* OIDExternalUserAgentIOS.h in Headers */ = {isa = PBXBuildFile; fileRef = A6DEABA92018E5B50022AC32 /* OIDExternalUserAgentIOS.h */; settings = {ATTRIBUTES = (Public, ); }; }; + A6DEABAF2018E5D80022AC32 /* OIDExternalUserAgentIOS.m in Sources */ = {isa = PBXBuildFile; fileRef = A6DEABA82018E5B50022AC32 /* OIDExternalUserAgentIOS.m */; }; + A6DEABB02018ECE80022AC32 /* OIDEndSessionRequest.h in Headers */ = {isa = PBXBuildFile; fileRef = CF37C06B1F1FC21A00662E41 /* OIDEndSessionRequest.h */; settings = {ATTRIBUTES = (Public, ); }; }; + A6DEABB12018ECE80022AC32 /* OIDEndSessionRequest.h in Headers */ = {isa = PBXBuildFile; fileRef = CF37C06B1F1FC21A00662E41 /* OIDEndSessionRequest.h */; settings = {ATTRIBUTES = (Public, ); }; }; + A6DEABB22018ECE90022AC32 /* OIDEndSessionRequest.h in Headers */ = {isa = PBXBuildFile; fileRef = CF37C06B1F1FC21A00662E41 /* OIDEndSessionRequest.h */; settings = {ATTRIBUTES = (Public, ); }; }; + A6DEABB32018ECE90022AC32 /* OIDEndSessionRequest.h in Headers */ = {isa = PBXBuildFile; fileRef = CF37C06B1F1FC21A00662E41 /* OIDEndSessionRequest.h */; settings = {ATTRIBUTES = (Public, ); }; }; + A6DEABB42018ECF20022AC32 /* OIDEndSessionResponse.h in Headers */ = {isa = PBXBuildFile; fileRef = CF6431F21F228A980075B6B5 /* OIDEndSessionResponse.h */; settings = {ATTRIBUTES = (Public, ); }; }; + A6DEABB52018ECF30022AC32 /* OIDEndSessionResponse.h in Headers */ = {isa = PBXBuildFile; fileRef = CF6431F21F228A980075B6B5 /* OIDEndSessionResponse.h */; settings = {ATTRIBUTES = (Public, ); }; }; + A6DEABB62018ECF30022AC32 /* OIDEndSessionResponse.h in Headers */ = {isa = PBXBuildFile; fileRef = CF6431F21F228A980075B6B5 /* OIDEndSessionResponse.h */; settings = {ATTRIBUTES = (Public, ); }; }; + A6DEABB72018ECF40022AC32 /* OIDEndSessionResponse.h in Headers */ = {isa = PBXBuildFile; fileRef = CF6431F21F228A980075B6B5 /* OIDEndSessionResponse.h */; settings = {ATTRIBUTES = (Public, ); }; }; + C14E3B6827E3BEFB00CF05A9 /* OIDExternalUserAgentIOSCustomBrowser.h in Headers */ = {isa = PBXBuildFile; fileRef = C14E3B6627E3BEFB00CF05A9 /* OIDExternalUserAgentIOSCustomBrowser.h */; settings = {ATTRIBUTES = (Public, ); }; }; + C14E3B6927E3BEFB00CF05A9 /* OIDExternalUserAgentIOSCustomBrowser.m in Sources */ = {isa = PBXBuildFile; fileRef = C14E3B6727E3BEFB00CF05A9 /* OIDExternalUserAgentIOSCustomBrowser.m */; }; + C14E3B6A27E3BFB800CF05A9 /* OIDExternalUserAgentIOSCustomBrowser.m in Sources */ = {isa = PBXBuildFile; fileRef = C14E3B6727E3BEFB00CF05A9 /* OIDExternalUserAgentIOSCustomBrowser.m */; }; + CF37C06E1F1FC21A00662E41 /* OIDEndSessionRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = CF37C06C1F1FC21A00662E41 /* OIDEndSessionRequest.m */; }; + CF37C06F1F1FC21A00662E41 /* OIDEndSessionRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = CF37C06C1F1FC21A00662E41 /* OIDEndSessionRequest.m */; }; + CF37C0701F1FC21A00662E41 /* OIDEndSessionRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = CF37C06C1F1FC21A00662E41 /* OIDEndSessionRequest.m */; }; + CF37C0711F1FC21A00662E41 /* OIDEndSessionRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = CF37C06C1F1FC21A00662E41 /* OIDEndSessionRequest.m */; }; + CF6431F41F228A980075B6B5 /* OIDEndSessionResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = CF6431F31F228A980075B6B5 /* OIDEndSessionResponse.m */; }; + F9A7082E2355ED74004B3E6D /* OIDExternalUserAgentCatalyst.h in Headers */ = {isa = PBXBuildFile; fileRef = F9A7082C2355ED74004B3E6D /* OIDExternalUserAgentCatalyst.h */; settings = {ATTRIBUTES = (Public, ); }; }; + F9A7082F2355ED74004B3E6D /* OIDExternalUserAgentCatalyst.m in Sources */ = {isa = PBXBuildFile; fileRef = F9A7082D2355ED74004B3E6D /* OIDExternalUserAgentCatalyst.m */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ + 2D8111FB24C0FD4C00984DA7 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 340E73741C5D819B0076B1F6 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 2D9385B624B37CAD009A12D7; + remoteInfo = AppAuthTV; + }; 341741F61C5D8283000EF209 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 340E73741C5D819B0076B1F6 /* Project object */; @@ -422,6 +660,13 @@ remoteGlobalIDString = 343AAAC11E8348A900F9D36E; remoteInfo = AppAuth_macOS; }; + 3489707F2177B3B000ABEED4 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 340E73741C5D819B0076B1F6 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 343AAA531E83463400F9D36E; + remoteInfo = AppAuth_iOS; + }; /* End PBXContainerItemProxy section */ /* Begin PBXCopyFilesBuildPhase section */ @@ -458,10 +703,31 @@ 039697441FA8258D003D1FB2 /* OIDURLSessionProvider.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OIDURLSessionProvider.h; sourceTree = ""; }; 039697451FA8258D003D1FB2 /* OIDURLSessionProvider.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OIDURLSessionProvider.m; sourceTree = ""; }; 0396974C1FA827AD003D1FB2 /* OIDURLSessionProviderTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OIDURLSessionProviderTests.m; sourceTree = ""; }; + 2D47AAD8249A87010059B5A4 /* OIDTVAuthorizationResponse.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OIDTVAuthorizationResponse.m; sourceTree = ""; }; + 2D47AAD9249A87010059B5A4 /* OIDTVAuthorizationRequest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OIDTVAuthorizationRequest.h; sourceTree = ""; }; + 2D47AADA249A87010059B5A4 /* OIDTVServiceConfiguration.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OIDTVServiceConfiguration.m; sourceTree = ""; }; + 2D47AADB249A87010059B5A4 /* AppAuthTV.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppAuthTV.h; sourceTree = ""; }; + 2D47AADC249A87010059B5A4 /* OIDTVAuthorizationResponse.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OIDTVAuthorizationResponse.h; sourceTree = ""; }; + 2D47AADD249A87010059B5A4 /* OIDTVAuthorizationRequest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OIDTVAuthorizationRequest.m; sourceTree = ""; }; + 2D47AADE249A87020059B5A4 /* OIDTVServiceConfiguration.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OIDTVServiceConfiguration.h; sourceTree = ""; }; + 2D47AADF249A87020059B5A4 /* OIDTVAuthorizationService.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OIDTVAuthorizationService.h; sourceTree = ""; }; + 2D47AAE0249A87020059B5A4 /* OIDTVAuthorizationService.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OIDTVAuthorizationService.m; sourceTree = ""; }; + 2D8111F524C0FD4C00984DA7 /* AppAuthTVTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = AppAuthTVTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 2D81120024C1036700984DA7 /* OIDTVAuthorizationRequestTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OIDTVAuthorizationRequestTests.m; sourceTree = ""; }; + 2D81120324C1036700984DA7 /* OIDTVAuthorizationRequestTests.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OIDTVAuthorizationRequestTests.h; sourceTree = ""; }; + 2D9385B724B37CAD009A12D7 /* AppAuthTV.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = AppAuthTV.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 2D9385B924B37CAD009A12D7 /* AppAuthTV.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppAuthTV.h; sourceTree = ""; }; + 2D9385BA24B37CAD009A12D7 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 2DA8D82424C6190300FDFB34 /* OIDTVAuthorizationResponseTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OIDTVAuthorizationResponseTests.m; sourceTree = ""; }; + 2DA8D82524C6190400FDFB34 /* OIDTVAuthorizationResponseTests.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OIDTVAuthorizationResponseTests.h; sourceTree = ""; }; + 2DEB065424CA1D9300DF47E7 /* OIDTVTokenRequest.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OIDTVTokenRequest.h; sourceTree = ""; }; + 2DEB065524CA1D9300DF47E7 /* OIDTVTokenRequest.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OIDTVTokenRequest.m; sourceTree = ""; }; + 2DEB065F24CF5CDF00DF47E7 /* OIDTVTokenRequestTests.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OIDTVTokenRequestTests.h; sourceTree = ""; }; + 2DEB066024CF5CE000DF47E7 /* OIDTVTokenRequestTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OIDTVTokenRequestTests.m; sourceTree = ""; }; 340DAE251D581FE700EC285B /* OIDAuthorizationService+Mac.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "OIDAuthorizationService+Mac.h"; sourceTree = ""; }; 340DAE261D581FE700EC285B /* OIDAuthorizationService+Mac.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "OIDAuthorizationService+Mac.m"; sourceTree = ""; }; - 340DAE271D581FE700EC285B /* OIDAuthorizationUICoordinatorMac.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OIDAuthorizationUICoordinatorMac.h; sourceTree = ""; }; - 340DAE281D581FE700EC285B /* OIDAuthorizationUICoordinatorMac.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OIDAuthorizationUICoordinatorMac.m; sourceTree = ""; }; + 340DAE271D581FE700EC285B /* OIDExternalUserAgentMac.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OIDExternalUserAgentMac.h; sourceTree = ""; }; + 340DAE281D581FE700EC285B /* OIDExternalUserAgentMac.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OIDExternalUserAgentMac.m; sourceTree = ""; }; 340DAE291D581FE700EC285B /* OIDAuthState+Mac.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "OIDAuthState+Mac.h"; sourceTree = ""; }; 340DAE2A1D581FE700EC285B /* OIDAuthState+Mac.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "OIDAuthState+Mac.m"; sourceTree = ""; }; 340DAE4E1D58216A00EC285B /* libAppAuth-macOS.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libAppAuth-macOS.a"; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -533,16 +799,26 @@ 341AA4CE1E7F392C00FCA5C6 /* AppAuth-macOSTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "AppAuth-macOSTests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; 341AA4ED1E7F39F100FCA5C6 /* AppAuth-tvOSTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "AppAuth-tvOSTests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; 341E707E1DE18744004353C1 /* libAppAuth-tvOS.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libAppAuth-tvOS.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + 342F42C32177B1FC00574F24 /* AppAuthCore.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = AppAuthCore.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 343AAA4D1E8345B600F9D36E /* AppAuth.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppAuth.h; sourceTree = ""; }; 343AAA4E1E8345B600F9D36E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 343AAA541E83463400F9D36E /* AppAuth.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = AppAuth.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 343AAA5C1E83463400F9D36E /* AppAuth_iOSTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = AppAuth_iOSTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 343AAA991E83487200F9D36E /* AppAuth.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = AppAuth.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - 343AAAA61E83489A00F9D36E /* AppAUth.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = AppAUth.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 343AAAA61E83489A00F9D36E /* AppAuth.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = AppAuth.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 343AAAAE1E83489A00F9D36E /* AppAuth_tvOSTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = AppAuth_tvOSTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 343AAAC21E8348A900F9D36E /* AppAuth.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = AppAuth.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 343AAACA1E8348AA00F9D36E /* AppAuth_macOSTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = AppAuth_macOSTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 347423F61E7F4B5600D3E6D6 /* libAppAuth-watchOS.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libAppAuth-watchOS.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + 348970972177B3B000ABEED4 /* AppAuthCoreTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = AppAuthCoreTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 3489709A2178F40600ABEED4 /* AppAuthCore.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppAuthCore.h; sourceTree = ""; }; + 3489709B2178F40600ABEED4 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 3489709E21791B0C00ABEED4 /* AppAuthCore.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppAuthCore.h; sourceTree = ""; }; + 34A663261E871DD40060B664 /* OIDIDToken.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OIDIDToken.h; sourceTree = ""; }; + 34A663271E871DD40060B664 /* OIDIDToken.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OIDIDToken.m; sourceTree = ""; }; + 34A6638A1E8865090060B664 /* OIDRPProfileCode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OIDRPProfileCode.m; sourceTree = ""; }; + 34A663911E886AED0060B664 /* OIDRPProfileCode.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OIDRPProfileCode.h; sourceTree = ""; }; + 34B822922153602C00D96702 /* AuthenticationServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AuthenticationServices.framework; path = System/Library/Frameworks/AuthenticationServices.framework; sourceTree = SDKROOT; }; 34D5EC431E6D1AD900814354 /* OIDAppAuthTests-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "OIDAppAuthTests-Bridging-Header.h"; sourceTree = ""; }; 34D5EC441E6D1AD900814354 /* OIDSwiftTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OIDSwiftTests.swift; sourceTree = ""; }; 34FEA6AC1DB6E083005C9212 /* OIDLoopbackHTTPServer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OIDLoopbackHTTPServer.h; sourceTree = ""; }; @@ -557,16 +833,45 @@ 60140F821DE43BAF00DA0DC3 /* OIDRegistrationRequestTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OIDRegistrationRequestTests.m; sourceTree = ""; }; 60140F841DE43C8C00DA0DC3 /* OIDRegistrationResponseTests.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OIDRegistrationResponseTests.h; sourceTree = ""; }; 60140F851DE43CC700DA0DC3 /* OIDRegistrationResponseTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OIDRegistrationResponseTests.m; sourceTree = ""; }; - F68103B61D2568D10053658E /* OIDAuthorizationUICoordinator.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OIDAuthorizationUICoordinator.h; sourceTree = ""; }; - F6F60FB01D2BFEFE00325CB3 /* OIDAuthState+IOS.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "OIDAuthState+IOS.m"; path = "iOS/OIDAuthState+IOS.m"; sourceTree = ""; }; - F6F60FB11D2BFEFE00325CB3 /* OIDAuthorizationService+IOS.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "OIDAuthorizationService+IOS.m"; path = "iOS/OIDAuthorizationService+IOS.m"; sourceTree = ""; }; - F6F60FB21D2BFEFE00325CB3 /* OIDAuthorizationUICoordinatorIOS.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = OIDAuthorizationUICoordinatorIOS.m; path = iOS/OIDAuthorizationUICoordinatorIOS.m; sourceTree = ""; }; - F6F60FB31D2BFEFE00325CB3 /* OIDAuthorizationService+IOS.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "OIDAuthorizationService+IOS.h"; path = "iOS/OIDAuthorizationService+IOS.h"; sourceTree = ""; }; - F6F60FB41D2BFEFE00325CB3 /* OIDAuthorizationUICoordinatorIOS.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = OIDAuthorizationUICoordinatorIOS.h; path = iOS/OIDAuthorizationUICoordinatorIOS.h; sourceTree = ""; }; - F6F60FB51D2BFEFE00325CB3 /* OIDAuthState+IOS.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "OIDAuthState+IOS.h"; path = "iOS/OIDAuthState+IOS.h"; sourceTree = ""; }; + 73F574332B7C42690023FFF0 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = ""; }; + A5EEF1FD20CF07760044F470 /* OIDTokenUtilitiesTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OIDTokenUtilitiesTests.m; sourceTree = ""; }; + A6CEB1172007E384009D492A /* OIDEndSessionRequestTests.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OIDEndSessionRequestTests.h; sourceTree = ""; }; + A6CEB1182007E384009D492A /* OIDEndSessionRequestTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OIDEndSessionRequestTests.m; sourceTree = ""; }; + A6DEAB982018E4A20022AC32 /* OIDExternalUserAgent.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OIDExternalUserAgent.h; sourceTree = ""; }; + A6DEAB992018E4A20022AC32 /* OIDExternalUserAgentSession.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OIDExternalUserAgentSession.h; sourceTree = ""; }; + A6DEAB9A2018E4A20022AC32 /* OIDExternalUserAgentRequest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OIDExternalUserAgentRequest.h; sourceTree = ""; }; + A6DEABA82018E5B50022AC32 /* OIDExternalUserAgentIOS.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OIDExternalUserAgentIOS.m; sourceTree = ""; }; + A6DEABA92018E5B50022AC32 /* OIDExternalUserAgentIOS.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OIDExternalUserAgentIOS.h; sourceTree = ""; }; + C14E3B6627E3BEFB00CF05A9 /* OIDExternalUserAgentIOSCustomBrowser.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OIDExternalUserAgentIOSCustomBrowser.h; sourceTree = ""; }; + C14E3B6727E3BEFB00CF05A9 /* OIDExternalUserAgentIOSCustomBrowser.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OIDExternalUserAgentIOSCustomBrowser.m; sourceTree = ""; }; + CF37C06B1F1FC21A00662E41 /* OIDEndSessionRequest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OIDEndSessionRequest.h; sourceTree = ""; }; + CF37C06C1F1FC21A00662E41 /* OIDEndSessionRequest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OIDEndSessionRequest.m; sourceTree = ""; }; + CF6431F21F228A980075B6B5 /* OIDEndSessionResponse.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OIDEndSessionResponse.h; sourceTree = ""; }; + CF6431F31F228A980075B6B5 /* OIDEndSessionResponse.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OIDEndSessionResponse.m; sourceTree = ""; }; + F6F60FB01D2BFEFE00325CB3 /* OIDAuthState+IOS.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "OIDAuthState+IOS.m"; sourceTree = ""; }; + F6F60FB11D2BFEFE00325CB3 /* OIDAuthorizationService+IOS.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "OIDAuthorizationService+IOS.m"; sourceTree = ""; }; + F6F60FB31D2BFEFE00325CB3 /* OIDAuthorizationService+IOS.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "OIDAuthorizationService+IOS.h"; sourceTree = ""; }; + F6F60FB51D2BFEFE00325CB3 /* OIDAuthState+IOS.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "OIDAuthState+IOS.h"; sourceTree = ""; }; + F9A7082C2355ED74004B3E6D /* OIDExternalUserAgentCatalyst.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OIDExternalUserAgentCatalyst.h; sourceTree = ""; }; + F9A7082D2355ED74004B3E6D /* OIDExternalUserAgentCatalyst.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OIDExternalUserAgentCatalyst.m; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ + 2D8111F224C0FD4C00984DA7 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 2D8111FA24C0FD4C00984DA7 /* AppAuthTV.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 2D9385B424B37CAD009A12D7 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; 340DAE4B1D58216A00EC285B /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; @@ -585,6 +890,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 34B822932153602C00D96702 /* AuthenticationServices.framework in Frameworks */, 3417422D1C5D850C000EF209 /* Security.framework in Frameworks */, 3417422B1C5D8502000EF209 /* SafariServices.framework in Frameworks */, 341741F51C5D8283000EF209 /* libAppAuth-iOS.a in Frameworks */, @@ -614,6 +920,13 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 342F42A02177B1FC00574F24 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; 343AAA501E83463400F9D36E /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; @@ -647,7 +960,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 343AAAAF1E83489A00F9D36E /* AppAUth.framework in Frameworks */, + 343AAAAF1E83489A00F9D36E /* AppAuth.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -673,9 +986,57 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 348970912177B3B000ABEED4 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 348970922177B3B000ABEED4 /* AppAuth.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 2D47AAD7249A86E30059B5A4 /* AppAuthTV */ = { + isa = PBXGroup; + children = ( + 7322C49B2BA20BFA00DF9B2F /* Resources */, + 2D47AAD9249A87010059B5A4 /* OIDTVAuthorizationRequest.h */, + 2D47AADD249A87010059B5A4 /* OIDTVAuthorizationRequest.m */, + 2D47AADC249A87010059B5A4 /* OIDTVAuthorizationResponse.h */, + 2D47AAD8249A87010059B5A4 /* OIDTVAuthorizationResponse.m */, + 2D47AADF249A87020059B5A4 /* OIDTVAuthorizationService.h */, + 2D47AAE0249A87020059B5A4 /* OIDTVAuthorizationService.m */, + 2D47AADE249A87020059B5A4 /* OIDTVServiceConfiguration.h */, + 2D47AADA249A87010059B5A4 /* OIDTVServiceConfiguration.m */, + 2DEB065424CA1D9300DF47E7 /* OIDTVTokenRequest.h */, + 2DEB065524CA1D9300DF47E7 /* OIDTVTokenRequest.m */, + ); + path = AppAuthTV; + sourceTree = ""; + }; + 2D52A08D24C24C260022E402 /* AppAuthTV */ = { + isa = PBXGroup; + children = ( + 2D81120324C1036700984DA7 /* OIDTVAuthorizationRequestTests.h */, + 2D81120024C1036700984DA7 /* OIDTVAuthorizationRequestTests.m */, + 2DA8D82524C6190400FDFB34 /* OIDTVAuthorizationResponseTests.h */, + 2DA8D82424C6190300FDFB34 /* OIDTVAuthorizationResponseTests.m */, + 2DEB065F24CF5CDF00DF47E7 /* OIDTVTokenRequestTests.h */, + 2DEB066024CF5CE000DF47E7 /* OIDTVTokenRequestTests.m */, + ); + path = AppAuthTV; + sourceTree = ""; + }; + 2D9385B824B37CAD009A12D7 /* TVFramework */ = { + isa = PBXGroup; + children = ( + 2D9385B924B37CAD009A12D7 /* AppAuthTV.h */, + 2D9385BA24B37CAD009A12D7 /* Info.plist */, + ); + path = TVFramework; + sourceTree = ""; + }; 340DAE241D581FE700EC285B /* macOS */ = { isa = PBXGroup; children = ( @@ -684,8 +1045,8 @@ 340DAEBA1D582AF100EC285B /* OIDRedirectHTTPHandler.m */, 340DAE251D581FE700EC285B /* OIDAuthorizationService+Mac.h */, 340DAE261D581FE700EC285B /* OIDAuthorizationService+Mac.m */, - 340DAE271D581FE700EC285B /* OIDAuthorizationUICoordinatorMac.h */, - 340DAE281D581FE700EC285B /* OIDAuthorizationUICoordinatorMac.m */, + 340DAE271D581FE700EC285B /* OIDExternalUserAgentMac.h */, + 340DAE281D581FE700EC285B /* OIDExternalUserAgentMac.m */, 340DAE291D581FE700EC285B /* OIDAuthState+Mac.h */, 340DAE2A1D581FE700EC285B /* OIDAuthState+Mac.m */, ); @@ -697,10 +1058,12 @@ children = ( 341742291C5D84D0000EF209 /* Frameworks */, 341741FB1C5D82D3000EF209 /* UnitTests */, - 341741AE1C5D8243000EF209 /* Source */, + 341741AE1C5D8243000EF209 /* Sources */, 340E737D1C5D819B0076B1F6 /* Products */, ); + indentWidth = 2; sourceTree = ""; + tabWidth = 2; }; 340E737D1C5D819B0076B1F6 /* Products */ = { isa = PBXGroup; @@ -715,74 +1078,38 @@ 343AAA541E83463400F9D36E /* AppAuth.framework */, 343AAA5C1E83463400F9D36E /* AppAuth_iOSTests.xctest */, 343AAA991E83487200F9D36E /* AppAuth.framework */, - 343AAAA61E83489A00F9D36E /* AppAUth.framework */, + 343AAAA61E83489A00F9D36E /* AppAuth.framework */, 343AAAAE1E83489A00F9D36E /* AppAuth_tvOSTests.xctest */, 343AAAC21E8348A900F9D36E /* AppAuth.framework */, 343AAACA1E8348AA00F9D36E /* AppAuth_macOSTests.xctest */, + 342F42C32177B1FC00574F24 /* AppAuthCore.framework */, + 348970972177B3B000ABEED4 /* AppAuthCoreTests.xctest */, + 2D9385B724B37CAD009A12D7 /* AppAuthTV.framework */, + 2D8111F524C0FD4C00984DA7 /* AppAuthTVTests.xctest */, ); name = Products; sourceTree = ""; }; - 341741AE1C5D8243000EF209 /* Source */ = { + 341741AE1C5D8243000EF209 /* Sources */ = { isa = PBXGroup; children = ( + 348970992178F40600ABEED4 /* CoreFramework */, 343AAA4C1E8345B600F9D36E /* Framework */, - 340DAE241D581FE700EC285B /* macOS */, - F6F60FAF1D2BFEF000325CB3 /* iOS */, + 2D9385B824B37CAD009A12D7 /* TVFramework */, + 8A9B9D5E24561EC40055353E /* AppAuthCore */, + 8A9B9D632456227D0055353E /* AppAuth */, + 2D47AAD7249A86E30059B5A4 /* AppAuthTV */, 341741AF1C5D8243000EF209 /* AppAuth.h */, - 341741B41C5D8243000EF209 /* OIDAuthorizationRequest.h */, - 341741B51C5D8243000EF209 /* OIDAuthorizationRequest.m */, - 341741B61C5D8243000EF209 /* OIDAuthorizationResponse.h */, - 341741B71C5D8243000EF209 /* OIDAuthorizationResponse.m */, - 341741B81C5D8243000EF209 /* OIDAuthorizationService.h */, - 341741B91C5D8243000EF209 /* OIDAuthorizationService.m */, - F68103B61D2568D10053658E /* OIDAuthorizationUICoordinator.h */, - 341741BA1C5D8243000EF209 /* OIDAuthState.h */, - 341741BB1C5D8243000EF209 /* OIDAuthState.m */, - 341741BC1C5D8243000EF209 /* OIDAuthStateChangeDelegate.h */, - 341741BD1C5D8243000EF209 /* OIDAuthStateErrorDelegate.h */, - 60140F781DE4262000DA0DC3 /* OIDClientMetadataParameters.h */, - 60140F791DE4276800DA0DC3 /* OIDClientMetadataParameters.m */, - 341741BE1C5D8243000EF209 /* OIDDefines.h */, - 341741BF1C5D8243000EF209 /* OIDError.h */, - 341741C01C5D8243000EF209 /* OIDError.m */, - 341741C11C5D8243000EF209 /* OIDErrorUtilities.h */, - 341741C21C5D8243000EF209 /* OIDErrorUtilities.m */, - 341741C31C5D8243000EF209 /* OIDFieldMapping.h */, - 341741C41C5D8243000EF209 /* OIDFieldMapping.m */, - 60140F7E1DE4335200DA0DC3 /* OIDRegistrationResponse.h */, - 60140F7F1DE4344200DA0DC3 /* OIDRegistrationResponse.m */, - 60140F7D1DE42E3000DA0DC3 /* OIDRegistrationRequest.h */, - 60140F7B1DE42E1000DA0DC3 /* OIDRegistrationRequest.m */, - 341741C51C5D8243000EF209 /* OIDGrantTypes.h */, - 341741C61C5D8243000EF209 /* OIDGrantTypes.m */, - 341741C71C5D8243000EF209 /* OIDResponseTypes.h */, - 341741C81C5D8243000EF209 /* OIDResponseTypes.m */, - 341741C91C5D8243000EF209 /* OIDScopes.h */, - 341741CA1C5D8243000EF209 /* OIDScopes.m */, - 341741CB1C5D8243000EF209 /* OIDScopeUtilities.h */, - 341741CC1C5D8243000EF209 /* OIDScopeUtilities.m */, - 341741CD1C5D8243000EF209 /* OIDServiceConfiguration.h */, - 341741CE1C5D8243000EF209 /* OIDServiceConfiguration.m */, - 341741CF1C5D8243000EF209 /* OIDServiceDiscovery.h */, - 341741D01C5D8243000EF209 /* OIDServiceDiscovery.m */, - 341741D11C5D8243000EF209 /* OIDTokenRequest.h */, - 341741D21C5D8243000EF209 /* OIDTokenRequest.m */, - 341741D31C5D8243000EF209 /* OIDTokenResponse.h */, - 341741D41C5D8243000EF209 /* OIDTokenResponse.m */, - 341741D51C5D8243000EF209 /* OIDTokenUtilities.h */, - 341741D61C5D8243000EF209 /* OIDTokenUtilities.m */, - 341741D71C5D8243000EF209 /* OIDURLQueryComponent.h */, - 341741D81C5D8243000EF209 /* OIDURLQueryComponent.m */, - 039697441FA8258D003D1FB2 /* OIDURLSessionProvider.h */, - 039697451FA8258D003D1FB2 /* OIDURLSessionProvider.m */, + 3489709E21791B0C00ABEED4 /* AppAuthCore.h */, + 2D47AADB249A87010059B5A4 /* AppAuthTV.h */, ); - path = Source; + path = Sources; sourceTree = ""; }; 341741FB1C5D82D3000EF209 /* UnitTests */ = { isa = PBXGroup; children = ( + 2D52A08D24C24C260022E402 /* AppAuthTV */, 341742231C5D8317000EF209 /* UnitTestsInfo.plist */, 341742001C5D82D3000EF209 /* OIDAuthorizationRequestTests.h */, 341742011C5D82D3000EF209 /* OIDAuthorizationRequestTests.m */, @@ -793,6 +1120,8 @@ 60140F811DE43B4D00DA0DC3 /* OIDRegistrationRequestTests.h */, 341742061C5D82D3000EF209 /* OIDGrantTypesTests.m */, 341742071C5D82D3000EF209 /* OIDResponseTypesTests.m */, + A6CEB1172007E384009D492A /* OIDEndSessionRequestTests.h */, + A6CEB1182007E384009D492A /* OIDEndSessionRequestTests.m */, 341742081C5D82D3000EF209 /* OIDScopesTests.m */, 341742091C5D82D3000EF209 /* OIDServiceConfigurationTests.h */, 3417420A1C5D82D3000EF209 /* OIDServiceConfigurationTests.m */, @@ -802,12 +1131,15 @@ 3417420E1C5D82D3000EF209 /* OIDTokenRequestTests.m */, 3417420F1C5D82D3000EF209 /* OIDTokenResponseTests.h */, 341742101C5D82D3000EF209 /* OIDTokenResponseTests.m */, + A5EEF1FD20CF07760044F470 /* OIDTokenUtilitiesTests.m */, 341742111C5D82D3000EF209 /* OIDURLQueryComponentTests.h */, 341742121C5D82D3000EF209 /* OIDURLQueryComponentTests.m */, 341742131C5D82D3000EF209 /* OIDURLQueryComponentTestsIOS7.m */, 60140F821DE43BAF00DA0DC3 /* OIDRegistrationRequestTests.m */, 60140F841DE43C8C00DA0DC3 /* OIDRegistrationResponseTests.h */, 60140F851DE43CC700DA0DC3 /* OIDRegistrationResponseTests.m */, + 34A663911E886AED0060B664 /* OIDRPProfileCode.h */, + 34A6638A1E8865090060B664 /* OIDRPProfileCode.m */, 34D5EC441E6D1AD900814354 /* OIDSwiftTests.swift */, 34D5EC431E6D1AD900814354 /* OIDAppAuthTests-Bridging-Header.h */, 0396974C1FA827AD003D1FB2 /* OIDURLSessionProviderTests.m */, @@ -818,6 +1150,7 @@ 341742291C5D84D0000EF209 /* Frameworks */ = { isa = PBXGroup; children = ( + 34B822922153602C00D96702 /* AuthenticationServices.framework */, 3417422C1C5D850C000EF209 /* Security.framework */, 3417422A1C5D8502000EF209 /* SafariServices.framework */, ); @@ -833,6 +1166,15 @@ path = Framework; sourceTree = ""; }; + 348970992178F40600ABEED4 /* CoreFramework */ = { + isa = PBXGroup; + children = ( + 3489709A2178F40600ABEED4 /* AppAuthCore.h */, + 3489709B2178F40600ABEED4 /* Info.plist */, + ); + path = CoreFramework; + sourceTree = ""; + }; 34FEA6AB1DB6E083005C9212 /* LoopbackHTTPServer */ = { isa = PBXGroup; children = ( @@ -842,22 +1184,159 @@ path = LoopbackHTTPServer; sourceTree = ""; }; + 7322C4992BA2095100DF9B2F /* Resources */ = { + isa = PBXGroup; + children = ( + 73F574332B7C42690023FFF0 /* PrivacyInfo.xcprivacy */, + ); + path = Resources; + sourceTree = ""; + }; + 7322C49A2BA20BEF00DF9B2F /* Resources */ = { + isa = PBXGroup; + children = ( + ); + path = Resources; + sourceTree = ""; + }; + 7322C49B2BA20BFA00DF9B2F /* Resources */ = { + isa = PBXGroup; + children = ( + ); + path = Resources; + sourceTree = ""; + }; + 8A9B9D5E24561EC40055353E /* AppAuthCore */ = { + isa = PBXGroup; + children = ( + 7322C4992BA2095100DF9B2F /* Resources */, + 341741B41C5D8243000EF209 /* OIDAuthorizationRequest.h */, + 341741B51C5D8243000EF209 /* OIDAuthorizationRequest.m */, + 341741B61C5D8243000EF209 /* OIDAuthorizationResponse.h */, + 341741B71C5D8243000EF209 /* OIDAuthorizationResponse.m */, + 341741B81C5D8243000EF209 /* OIDAuthorizationService.h */, + 341741B91C5D8243000EF209 /* OIDAuthorizationService.m */, + 341741BA1C5D8243000EF209 /* OIDAuthState.h */, + 341741BB1C5D8243000EF209 /* OIDAuthState.m */, + 341741BC1C5D8243000EF209 /* OIDAuthStateChangeDelegate.h */, + 341741BD1C5D8243000EF209 /* OIDAuthStateErrorDelegate.h */, + 60140F781DE4262000DA0DC3 /* OIDClientMetadataParameters.h */, + 60140F791DE4276800DA0DC3 /* OIDClientMetadataParameters.m */, + 341741BE1C5D8243000EF209 /* OIDDefines.h */, + 341741BF1C5D8243000EF209 /* OIDError.h */, + 341741C01C5D8243000EF209 /* OIDError.m */, + 341741C11C5D8243000EF209 /* OIDErrorUtilities.h */, + 341741C21C5D8243000EF209 /* OIDErrorUtilities.m */, + A6DEAB992018E4A20022AC32 /* OIDExternalUserAgentSession.h */, + A6DEAB9A2018E4A20022AC32 /* OIDExternalUserAgentRequest.h */, + A6DEAB982018E4A20022AC32 /* OIDExternalUserAgent.h */, + 341741C31C5D8243000EF209 /* OIDFieldMapping.h */, + 341741C41C5D8243000EF209 /* OIDFieldMapping.m */, + CF6431F21F228A980075B6B5 /* OIDEndSessionResponse.h */, + CF6431F31F228A980075B6B5 /* OIDEndSessionResponse.m */, + CF37C06B1F1FC21A00662E41 /* OIDEndSessionRequest.h */, + CF37C06C1F1FC21A00662E41 /* OIDEndSessionRequest.m */, + 60140F7E1DE4335200DA0DC3 /* OIDRegistrationResponse.h */, + 60140F7F1DE4344200DA0DC3 /* OIDRegistrationResponse.m */, + 60140F7D1DE42E3000DA0DC3 /* OIDRegistrationRequest.h */, + 60140F7B1DE42E1000DA0DC3 /* OIDRegistrationRequest.m */, + 341741C51C5D8243000EF209 /* OIDGrantTypes.h */, + 341741C61C5D8243000EF209 /* OIDGrantTypes.m */, + 34A663261E871DD40060B664 /* OIDIDToken.h */, + 34A663271E871DD40060B664 /* OIDIDToken.m */, + 341741C71C5D8243000EF209 /* OIDResponseTypes.h */, + 341741C81C5D8243000EF209 /* OIDResponseTypes.m */, + 341741C91C5D8243000EF209 /* OIDScopes.h */, + 341741CA1C5D8243000EF209 /* OIDScopes.m */, + 341741CB1C5D8243000EF209 /* OIDScopeUtilities.h */, + 341741CC1C5D8243000EF209 /* OIDScopeUtilities.m */, + 341741CD1C5D8243000EF209 /* OIDServiceConfiguration.h */, + 341741CE1C5D8243000EF209 /* OIDServiceConfiguration.m */, + 341741CF1C5D8243000EF209 /* OIDServiceDiscovery.h */, + 341741D01C5D8243000EF209 /* OIDServiceDiscovery.m */, + 341741D11C5D8243000EF209 /* OIDTokenRequest.h */, + 341741D21C5D8243000EF209 /* OIDTokenRequest.m */, + 341741D31C5D8243000EF209 /* OIDTokenResponse.h */, + 341741D41C5D8243000EF209 /* OIDTokenResponse.m */, + 341741D51C5D8243000EF209 /* OIDTokenUtilities.h */, + 341741D61C5D8243000EF209 /* OIDTokenUtilities.m */, + 341741D71C5D8243000EF209 /* OIDURLQueryComponent.h */, + 341741D81C5D8243000EF209 /* OIDURLQueryComponent.m */, + 039697441FA8258D003D1FB2 /* OIDURLSessionProvider.h */, + 039697451FA8258D003D1FB2 /* OIDURLSessionProvider.m */, + ); + path = AppAuthCore; + sourceTree = ""; + }; + 8A9B9D632456227D0055353E /* AppAuth */ = { + isa = PBXGroup; + children = ( + 7322C49A2BA20BEF00DF9B2F /* Resources */, + 340DAE241D581FE700EC285B /* macOS */, + F6F60FAF1D2BFEF000325CB3 /* iOS */, + ); + path = AppAuth; + sourceTree = ""; + }; F6F60FAF1D2BFEF000325CB3 /* iOS */ = { isa = PBXGroup; children = ( + C14E3B6627E3BEFB00CF05A9 /* OIDExternalUserAgentIOSCustomBrowser.h */, + C14E3B6727E3BEFB00CF05A9 /* OIDExternalUserAgentIOSCustomBrowser.m */, F6F60FB31D2BFEFE00325CB3 /* OIDAuthorizationService+IOS.h */, F6F60FB11D2BFEFE00325CB3 /* OIDAuthorizationService+IOS.m */, F6F60FB51D2BFEFE00325CB3 /* OIDAuthState+IOS.h */, F6F60FB01D2BFEFE00325CB3 /* OIDAuthState+IOS.m */, - F6F60FB41D2BFEFE00325CB3 /* OIDAuthorizationUICoordinatorIOS.h */, - F6F60FB21D2BFEFE00325CB3 /* OIDAuthorizationUICoordinatorIOS.m */, + A6DEABA92018E5B50022AC32 /* OIDExternalUserAgentIOS.h */, + A6DEABA82018E5B50022AC32 /* OIDExternalUserAgentIOS.m */, + F9A7082C2355ED74004B3E6D /* OIDExternalUserAgentCatalyst.h */, + F9A7082D2355ED74004B3E6D /* OIDExternalUserAgentCatalyst.m */, ); - name = iOS; + path = iOS; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXHeadersBuildPhase section */ + 2D9385B224B37CAD009A12D7 /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + 2D93861B24B38810009A12D7 /* OIDAuthorizationResponse.h in Headers */, + 2D93861F24B3881B009A12D7 /* OIDAuthState.h in Headers */, + 2D93863724B38827009A12D7 /* OIDGrantTypes.h in Headers */, + 2D93862F24B38826009A12D7 /* OIDEndSessionResponse.h in Headers */, + 2D93863924B38827009A12D7 /* OIDIDToken.h in Headers */, + 2D93862824B3881C009A12D7 /* OIDErrorUtilities.h in Headers */, + 2D93863124B38826009A12D7 /* OIDEndSessionRequest.h in Headers */, + 2D93863D24B38827009A12D7 /* OIDScopes.h in Headers */, + 2D93864124B38828009A12D7 /* OIDServiceConfiguration.h in Headers */, + 2D93862124B3881B009A12D7 /* OIDAuthStateChangeDelegate.h in Headers */, + 2D93862A24B3881C009A12D7 /* OIDExternalUserAgentSession.h in Headers */, + 2D93863324B38826009A12D7 /* OIDRegistrationResponse.h in Headers */, + 2D93864324B38828009A12D7 /* OIDServiceDiscovery.h in Headers */, + 2D93862624B3881C009A12D7 /* OIDError.h in Headers */, + 2D93864524B38828009A12D7 /* OIDTokenRequest.h in Headers */, + 2D93864724B38828009A12D7 /* OIDTokenResponse.h in Headers */, + 2D93864924B38828009A12D7 /* OIDTokenUtilities.h in Headers */, + 2D93862C24B38826009A12D7 /* OIDExternalUserAgent.h in Headers */, + 2D93863F24B38828009A12D7 /* OIDScopeUtilities.h in Headers */, + 2D93863B24B38827009A12D7 /* OIDResponseTypes.h in Headers */, + 2D93862224B3881C009A12D7 /* OIDAuthStateErrorDelegate.h in Headers */, + 2D93864D24B38829009A12D7 /* OIDURLSessionProvider.h in Headers */, + 2D93863524B38827009A12D7 /* OIDRegistrationRequest.h in Headers */, + 2D93861D24B38815009A12D7 /* OIDAuthorizationService.h in Headers */, + 2D93862B24B38825009A12D7 /* OIDExternalUserAgentRequest.h in Headers */, + 2DEB065624CA1D9300DF47E7 /* OIDTVTokenRequest.h in Headers */, + 2D93861A24B3880B009A12D7 /* OIDAuthorizationRequest.h in Headers */, + 2D9385DE24B3861E009A12D7 /* AppAuthTV.h in Headers */, + 2D9385DF24B38646009A12D7 /* OIDTVAuthorizationRequest.h in Headers */, + 2D9385E124B3865E009A12D7 /* OIDTVAuthorizationService.h in Headers */, + 2D9385E024B38658009A12D7 /* OIDTVAuthorizationResponse.h in Headers */, + 2D9385E224B38669009A12D7 /* OIDTVServiceConfiguration.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 340DAE4C1D58216A00EC285B /* Headers */ = { isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; @@ -867,34 +1346,76 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 342F42A12177B1FC00574F24 /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + 342F42A22177B1FC00574F24 /* OIDAuthorizationResponse.h in Headers */, + 342F42A32177B1FC00574F24 /* OIDScopes.h in Headers */, + 342F42A42177B1FC00574F24 /* OIDExternalUserAgentRequest.h in Headers */, + 342F42A52177B1FC00574F24 /* OIDAuthStateChangeDelegate.h in Headers */, + 342F42A82177B1FC00574F24 /* OIDIDToken.h in Headers */, + 06C19E9C22B474A600C19CE1 /* OIDEndSessionRequest.h in Headers */, + 342F42A92177B1FC00574F24 /* OIDResponseTypes.h in Headers */, + 342F42AA2177B1FC00574F24 /* OIDTokenRequest.h in Headers */, + 342F42AB2177B1FC00574F24 /* OIDScopeUtilities.h in Headers */, + 342F42AC2177B1FC00574F24 /* OIDTokenResponse.h in Headers */, + 06C19E9A22B4749900C19CE1 /* OIDEndSessionResponse.h in Headers */, + 342F42AD2177B1FC00574F24 /* OIDServiceDiscovery.h in Headers */, + 342F42AE2177B1FC00574F24 /* OIDGrantTypes.h in Headers */, + 342F42AF2177B1FC00574F24 /* OIDURLSessionProvider.h in Headers */, + 342F42B12177B1FC00574F24 /* OIDRegistrationResponse.h in Headers */, + 342F42B22177B1FC00574F24 /* OIDExternalUserAgent.h in Headers */, + 342F42B32177B1FC00574F24 /* OIDExternalUserAgentSession.h in Headers */, + 342F42B42177B1FC00574F24 /* OIDServiceConfiguration.h in Headers */, + 342F42B52177B1FC00574F24 /* OIDAuthStateErrorDelegate.h in Headers */, + 342F42B62177B1FC00574F24 /* OIDAuthorizationService.h in Headers */, + 342F42B72177B1FC00574F24 /* OIDRegistrationRequest.h in Headers */, + 342F42B82177B1FC00574F24 /* OIDAuthState.h in Headers */, + 342F42B92177B1FC00574F24 /* OIDErrorUtilities.h in Headers */, + 342F42BB2177B1FC00574F24 /* OIDAuthorizationRequest.h in Headers */, + 342F42BC2177B1FC00574F24 /* OIDTokenUtilities.h in Headers */, + 3489709C2178F40600ABEED4 /* AppAuthCore.h in Headers */, + 342F42BD2177B1FC00574F24 /* OIDError.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 343AAA511E83463400F9D36E /* Headers */ = { isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( 343AAAE41E83499000F9D36E /* OIDAuthorizationResponse.h in Headers */, 343AAAF31E83499000F9D36E /* OIDScopes.h in Headers */, + A6DEAB9F2018E4B00022AC32 /* OIDExternalUserAgentRequest.h in Headers */, 343AAAE81E83499000F9D36E /* OIDAuthStateChangeDelegate.h in Headers */, 343AAA6B1E83465500F9D36E /* AppAuth.h in Headers */, - 343AAA6E1E83466B00F9D36E /* OIDAuthorizationUICoordinatorIOS.h in Headers */, + F9A7082E2355ED74004B3E6D /* OIDExternalUserAgentCatalyst.h in Headers */, + 34A663291E871DD40060B664 /* OIDIDToken.h in Headers */, 343AAAF21E83499000F9D36E /* OIDResponseTypes.h in Headers */, 343AAAF71E83499000F9D36E /* OIDTokenRequest.h in Headers */, 343AAAF41E83499000F9D36E /* OIDScopeUtilities.h in Headers */, 343AAAF81E83499000F9D36E /* OIDTokenResponse.h in Headers */, + A6DEABB42018ECF20022AC32 /* OIDEndSessionResponse.h in Headers */, 343AAAF61E83499000F9D36E /* OIDServiceDiscovery.h in Headers */, 343AAAF11E83499000F9D36E /* OIDGrantTypes.h in Headers */, + 55A094CF20DFBB10000045D1 /* OIDURLSessionProvider.h in Headers */, 343AAA6D1E83466B00F9D36E /* OIDAuthState+IOS.h in Headers */, 343AAAEF1E83499000F9D36E /* OIDRegistrationResponse.h in Headers */, + A6DEAB9B2018E4AD0022AC32 /* OIDExternalUserAgent.h in Headers */, + A6DEABA42018E4B90022AC32 /* OIDExternalUserAgentSession.h in Headers */, 343AAAF51E83499000F9D36E /* OIDServiceConfiguration.h in Headers */, 343AAAE91E83499000F9D36E /* OIDAuthStateErrorDelegate.h in Headers */, 343AAAE51E83499000F9D36E /* OIDAuthorizationService.h in Headers */, 343AAAF01E83499000F9D36E /* OIDRegistrationRequest.h in Headers */, - 343AAAE61E83499000F9D36E /* OIDAuthorizationUICoordinator.h in Headers */, + A6DEABB02018ECE80022AC32 /* OIDEndSessionRequest.h in Headers */, 343AAAE71E83499000F9D36E /* OIDAuthState.h in Headers */, 343AAAED1E83499000F9D36E /* OIDErrorUtilities.h in Headers */, 343AAA6C1E83466B00F9D36E /* OIDAuthorizationService+IOS.h in Headers */, 343AAAE31E83499000F9D36E /* OIDAuthorizationRequest.h in Headers */, 343AAAF91E83499000F9D36E /* OIDTokenUtilities.h in Headers */, 343AAAEC1E83499000F9D36E /* OIDError.h in Headers */, + C14E3B6827E3BEFB00CF05A9 /* OIDExternalUserAgentIOSCustomBrowser.h in Headers */, + A6DEABAB2018E5C50022AC32 /* OIDExternalUserAgentIOS.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -905,22 +1426,28 @@ 343AAB041E83499100F9D36E /* OIDError.h in Headers */, 343AAB091E83499100F9D36E /* OIDGrantTypes.h in Headers */, 343AAAFF1E83499100F9D36E /* OIDAuthState.h in Headers */, + A6DEABA52018E4BA0022AC32 /* OIDExternalUserAgentSession.h in Headers */, + A6DEABB52018ECF30022AC32 /* OIDEndSessionResponse.h in Headers */, 343AAB0D1E83499100F9D36E /* OIDServiceConfiguration.h in Headers */, + A6DEABA02018E4B00022AC32 /* OIDExternalUserAgentRequest.h in Headers */, 343AAAFD1E83499100F9D36E /* OIDAuthorizationService.h in Headers */, 343AAB0F1E83499100F9D36E /* OIDTokenRequest.h in Headers */, 343AAB071E83499100F9D36E /* OIDRegistrationResponse.h in Headers */, 343AAB0E1E83499100F9D36E /* OIDServiceDiscovery.h in Headers */, 343AAB111E83499100F9D36E /* OIDTokenUtilities.h in Headers */, 343AAB0A1E83499100F9D36E /* OIDResponseTypes.h in Headers */, + A6DEAB9C2018E4AD0022AC32 /* OIDExternalUserAgent.h in Headers */, 343AAB0B1E83499100F9D36E /* OIDScopes.h in Headers */, 343AAB9B1E834A8800F9D36E /* AppAuth.h in Headers */, + A6DEABB12018ECE80022AC32 /* OIDEndSessionRequest.h in Headers */, 343AAB001E83499100F9D36E /* OIDAuthStateChangeDelegate.h in Headers */, 343AAB081E83499100F9D36E /* OIDRegistrationRequest.h in Headers */, + 34A6632A1E871DD40060B664 /* OIDIDToken.h in Headers */, 343AAB101E83499100F9D36E /* OIDTokenResponse.h in Headers */, 343AAAFC1E83499100F9D36E /* OIDAuthorizationResponse.h in Headers */, 343AAB0C1E83499100F9D36E /* OIDScopeUtilities.h in Headers */, + 55A094D020DFBB11000045D1 /* OIDURLSessionProvider.h in Headers */, 343AAB011E83499100F9D36E /* OIDAuthStateErrorDelegate.h in Headers */, - 343AAAFE1E83499100F9D36E /* OIDAuthorizationUICoordinator.h in Headers */, 343AAAFB1E83499100F9D36E /* OIDAuthorizationRequest.h in Headers */, 343AAB051E83499100F9D36E /* OIDErrorUtilities.h in Headers */, ); @@ -933,22 +1460,28 @@ 343AAB1C1E83499200F9D36E /* OIDError.h in Headers */, 343AAB211E83499200F9D36E /* OIDGrantTypes.h in Headers */, 343AAB171E83499200F9D36E /* OIDAuthState.h in Headers */, + A6DEABA62018E4BA0022AC32 /* OIDExternalUserAgentSession.h in Headers */, + A6DEABB62018ECF30022AC32 /* OIDEndSessionResponse.h in Headers */, 343AAB251E83499200F9D36E /* OIDServiceConfiguration.h in Headers */, + A6DEABA22018E4B60022AC32 /* OIDExternalUserAgentRequest.h in Headers */, 343AAB151E83499200F9D36E /* OIDAuthorizationService.h in Headers */, 343AAB271E83499200F9D36E /* OIDTokenRequest.h in Headers */, 343AAB1F1E83499200F9D36E /* OIDRegistrationResponse.h in Headers */, 343AAB261E83499200F9D36E /* OIDServiceDiscovery.h in Headers */, 343AAB291E83499200F9D36E /* OIDTokenUtilities.h in Headers */, 343AAB221E83499200F9D36E /* OIDResponseTypes.h in Headers */, + A6DEAB9D2018E4AD0022AC32 /* OIDExternalUserAgent.h in Headers */, 343AAB231E83499200F9D36E /* OIDScopes.h in Headers */, 343AAB9C1E834A8900F9D36E /* AppAuth.h in Headers */, + A6DEABB22018ECE90022AC32 /* OIDEndSessionRequest.h in Headers */, 343AAB181E83499200F9D36E /* OIDAuthStateChangeDelegate.h in Headers */, 343AAB201E83499200F9D36E /* OIDRegistrationRequest.h in Headers */, + 34A6632B1E871DD40060B664 /* OIDIDToken.h in Headers */, 343AAB281E83499200F9D36E /* OIDTokenResponse.h in Headers */, 343AAB141E83499200F9D36E /* OIDAuthorizationResponse.h in Headers */, 343AAB241E83499200F9D36E /* OIDScopeUtilities.h in Headers */, + 55A094D120DFBB12000045D1 /* OIDURLSessionProvider.h in Headers */, 343AAB191E83499200F9D36E /* OIDAuthStateErrorDelegate.h in Headers */, - 343AAB161E83499200F9D36E /* OIDAuthorizationUICoordinator.h in Headers */, 343AAB131E83499200F9D36E /* OIDAuthorizationRequest.h in Headers */, 343AAB1D1E83499200F9D36E /* OIDErrorUtilities.h in Headers */, ); @@ -959,12 +1492,16 @@ buildActionMask = 2147483647; files = ( 343AAB9D1E834A8A00F9D36E /* AppAuth.h in Headers */, - 343AAADF1E83494400F9D36E /* OIDAuthorizationUICoordinatorMac.h in Headers */, + A6DEABA72018E4BA0022AC32 /* OIDExternalUserAgentSession.h in Headers */, + 343AAADF1E83494400F9D36E /* OIDExternalUserAgentMac.h in Headers */, 343AAAE01E83494400F9D36E /* OIDAuthState+Mac.h in Headers */, 343AAADD1E83494400F9D36E /* OIDRedirectHTTPHandler.h in Headers */, 343AAB3C1E83499200F9D36E /* OIDScopeUtilities.h in Headers */, + A6DEABB32018ECE90022AC32 /* OIDEndSessionRequest.h in Headers */, 343AAB3F1E83499200F9D36E /* OIDTokenRequest.h in Headers */, + 55A094D220DFBB12000045D1 /* OIDURLSessionProvider.h in Headers */, 343AAB411E83499200F9D36E /* OIDTokenUtilities.h in Headers */, + A6DEABA32018E4B70022AC32 /* OIDExternalUserAgentRequest.h in Headers */, 343AAB371E83499200F9D36E /* OIDRegistrationResponse.h in Headers */, 343AAB2B1E83499200F9D36E /* OIDAuthorizationRequest.h in Headers */, 343AAB3B1E83499200F9D36E /* OIDScopes.h in Headers */, @@ -972,6 +1509,7 @@ 343AAB3D1E83499200F9D36E /* OIDServiceConfiguration.h in Headers */, 343AAB351E83499200F9D36E /* OIDErrorUtilities.h in Headers */, 343AAB391E83499200F9D36E /* OIDGrantTypes.h in Headers */, + A6DEABB72018ECF40022AC32 /* OIDEndSessionResponse.h in Headers */, 343AAB341E83499200F9D36E /* OIDError.h in Headers */, 343AAB401E83499200F9D36E /* OIDTokenResponse.h in Headers */, 343AAB3A1E83499200F9D36E /* OIDResponseTypes.h in Headers */, @@ -979,8 +1517,9 @@ 343AAB311E83499200F9D36E /* OIDAuthStateErrorDelegate.h in Headers */, 343AAB2F1E83499200F9D36E /* OIDAuthState.h in Headers */, 343AAB3E1E83499200F9D36E /* OIDServiceDiscovery.h in Headers */, + A6DEAB9E2018E4AE0022AC32 /* OIDExternalUserAgent.h in Headers */, + 34A6632C1E871DD40060B664 /* OIDIDToken.h in Headers */, 343AAADE1E83494400F9D36E /* OIDAuthorizationService+Mac.h in Headers */, - 343AAB2E1E83499200F9D36E /* OIDAuthorizationUICoordinator.h in Headers */, 343AAB301E83499200F9D36E /* OIDAuthStateChangeDelegate.h in Headers */, 343AAB381E83499200F9D36E /* OIDRegistrationRequest.h in Headers */, 343AAB2D1E83499200F9D36E /* OIDAuthorizationService.h in Headers */, @@ -990,6 +1529,42 @@ /* End PBXHeadersBuildPhase section */ /* Begin PBXNativeTarget section */ + 2D8111F424C0FD4C00984DA7 /* AppAuthTVTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 2D8111FF24C0FD4D00984DA7 /* Build configuration list for PBXNativeTarget "AppAuthTVTests" */; + buildPhases = ( + 2D8111F124C0FD4C00984DA7 /* Sources */, + 2D8111F224C0FD4C00984DA7 /* Frameworks */, + 2D8111F324C0FD4C00984DA7 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 2D8111FC24C0FD4C00984DA7 /* PBXTargetDependency */, + ); + name = AppAuthTVTests; + productName = AppAuthTVTests; + productReference = 2D8111F524C0FD4C00984DA7 /* AppAuthTVTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 2D9385B624B37CAD009A12D7 /* AppAuthTV */ = { + isa = PBXNativeTarget; + buildConfigurationList = 2D9385C824B37CAD009A12D7 /* Build configuration list for PBXNativeTarget "AppAuthTV" */; + buildPhases = ( + 2D9385B224B37CAD009A12D7 /* Headers */, + 2D9385B324B37CAD009A12D7 /* Sources */, + 2D9385B424B37CAD009A12D7 /* Frameworks */, + 2D9385B524B37CAD009A12D7 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = AppAuthTV; + productName = AppAuthTV; + productReference = 2D9385B724B37CAD009A12D7 /* AppAuthTV.framework */; + productType = "com.apple.product-type.framework"; + }; 340DAE4D1D58216A00EC285B /* AppAuth-macOS */ = { isa = PBXNativeTarget; buildConfigurationList = 340DAE541D58216A00EC285B /* Build configuration list for PBXNativeTarget "AppAuth-macOS" */; @@ -1095,6 +1670,24 @@ productReference = 341E707E1DE18744004353C1 /* libAppAuth-tvOS.a */; productType = "com.apple.product-type.library.static"; }; + 342F42842177B1FC00574F24 /* AppAuthCore */ = { + isa = PBXNativeTarget; + buildConfigurationList = 342F42C02177B1FC00574F24 /* Build configuration list for PBXNativeTarget "AppAuthCore" */; + buildPhases = ( + 342F42852177B1FC00574F24 /* Sources */, + 342F42A02177B1FC00574F24 /* Frameworks */, + 342F42A12177B1FC00574F24 /* Headers */, + 342F42BF2177B1FC00574F24 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = AppAuthCore; + productName = AppAuth_iOS; + productReference = 342F42C32177B1FC00574F24 /* AppAuthCore.framework */; + productType = "com.apple.product-type.framework"; + }; 343AAA531E83463400F9D36E /* AppAuth_iOS */ = { isa = PBXNativeTarget; buildConfigurationList = 343AAA651E83463400F9D36E /* Build configuration list for PBXNativeTarget "AppAuth_iOS" */; @@ -1164,7 +1757,7 @@ ); name = AppAuth_tvOS; productName = AppAuth_tvOS; - productReference = 343AAAA61E83489A00F9D36E /* AppAUth.framework */; + productReference = 343AAAA61E83489A00F9D36E /* AppAuth.framework */; productType = "com.apple.product-type.framework"; }; 343AAAAD1E83489A00F9D36E /* AppAuth_tvOSTests */ = { @@ -1238,15 +1831,41 @@ productReference = 347423F61E7F4B5600D3E6D6 /* libAppAuth-watchOS.a */; productType = "com.apple.product-type.library.static"; }; + 3489707D2177B3B000ABEED4 /* AppAuthCoreTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 348970942177B3B000ABEED4 /* Build configuration list for PBXNativeTarget "AppAuthCoreTests" */; + buildPhases = ( + 348970802177B3B000ABEED4 /* Sources */, + 348970912177B3B000ABEED4 /* Frameworks */, + 348970932177B3B000ABEED4 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 3489707E2177B3B000ABEED4 /* PBXTargetDependency */, + ); + name = AppAuthCoreTests; + productName = AppAuth_iOSTests; + productReference = 348970972177B3B000ABEED4 /* AppAuthCoreTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ 340E73741C5D819B0076B1F6 /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 0900; + LastUpgradeCheck = 1000; ORGANIZATIONNAME = "OpenID Foundation"; TargetAttributes = { + 2D8111F424C0FD4C00984DA7 = { + CreatedOnToolsVersion = 11.6; + }; + 2D9385B624B37CAD009A12D7 = { + CreatedOnToolsVersion = 11.5; + DevelopmentTeam = AUX79W8H33; + ProvisioningStyle = Automatic; + }; 340DAE4D1D58216A00EC285B = { CreatedOnToolsVersion = 7.3.1; }; @@ -1308,6 +1927,7 @@ developmentRegion = English; hasScannedForEncodings = 0; knownRegions = ( + English, en, ); mainGroup = 340E73731C5D819B0076B1F6; @@ -1329,11 +1949,30 @@ 343AAAAD1E83489A00F9D36E /* AppAuth_tvOSTests */, 343AAAC11E8348A900F9D36E /* AppAuth_macOS */, 343AAAC91E8348AA00F9D36E /* AppAuth_macOSTests */, + 342F42842177B1FC00574F24 /* AppAuthCore */, + 3489707D2177B3B000ABEED4 /* AppAuthCoreTests */, + 2D9385B624B37CAD009A12D7 /* AppAuthTV */, + 2D8111F424C0FD4C00984DA7 /* AppAuthTVTests */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ + 2D8111F324C0FD4C00984DA7 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 2D9385B524B37CAD009A12D7 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 73F574392B7C42690023FFF0 /* PrivacyInfo.xcprivacy in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 341741EE1C5D8283000EF209 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -1355,10 +1994,19 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 342F42BF2177B1FC00574F24 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 73F574382B7C42690023FFF0 /* PrivacyInfo.xcprivacy in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 343AAA521E83463400F9D36E /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + 73F574342B7C42690023FFF0 /* PrivacyInfo.xcprivacy in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1373,6 +2021,7 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + 73F574352B7C42690023FFF0 /* PrivacyInfo.xcprivacy in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1380,6 +2029,7 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + 73F574362B7C42690023FFF0 /* PrivacyInfo.xcprivacy in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1394,6 +2044,7 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + 73F574372B7C42690023FFF0 /* PrivacyInfo.xcprivacy in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1404,9 +2055,78 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 348970932177B3B000ABEED4 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ + 2D8111F124C0FD4C00984DA7 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 2D81120C24C103F300984DA7 /* OIDScopesTests.m in Sources */, + 2DA8D82624C6190400FDFB34 /* OIDTVAuthorizationResponseTests.m in Sources */, + 2D81121224C103F300984DA7 /* OIDURLQueryComponentTests.m in Sources */, + 2DEB066224CF5CFB00DF47E7 /* OIDTVTokenRequestTests.m in Sources */, + 2D81120D24C103F300984DA7 /* OIDServiceConfigurationTests.m in Sources */, + 2D81121324C103F300984DA7 /* OIDURLQueryComponentTestsIOS7.m in Sources */, + 2D81121524C103F300984DA7 /* OIDRegistrationResponseTests.m in Sources */, + 2D81120824C103F200984DA7 /* OIDAuthStateTests.m in Sources */, + 2D81121024C103F300984DA7 /* OIDTokenResponseTests.m in Sources */, + 2D81120F24C103F300984DA7 /* OIDTokenRequestTests.m in Sources */, + 2D81120424C1036700984DA7 /* OIDTVAuthorizationRequestTests.m in Sources */, + 2D81120E24C103F300984DA7 /* OIDServiceDiscoveryTests.m in Sources */, + 2D81120A24C103F200984DA7 /* OIDResponseTypesTests.m in Sources */, + 2D81120724C103CC00984DA7 /* OIDAuthorizationResponseTests.m in Sources */, + 2D81121424C103F300984DA7 /* OIDRegistrationRequestTests.m in Sources */, + 2D81120924C103F200984DA7 /* OIDGrantTypesTests.m in Sources */, + 2D81121624C103F300984DA7 /* OIDRPProfileCode.m in Sources */, + 2D81120624C103C800984DA7 /* OIDAuthorizationRequestTests.m in Sources */, + 2D81121124C103F300984DA7 /* OIDTokenUtilitiesTests.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 2D9385B324B37CAD009A12D7 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 2D93862E24B38826009A12D7 /* OIDFieldMapping.m in Sources */, + 2D93864024B38828009A12D7 /* OIDScopeUtilities.m in Sources */, + 2D93862024B3881B009A12D7 /* OIDAuthState.m in Sources */, + 2D93861924B38803009A12D7 /* OIDAuthorizationRequest.m in Sources */, + 2D93863824B38827009A12D7 /* OIDGrantTypes.m in Sources */, + 2D93864824B38828009A12D7 /* OIDTokenResponse.m in Sources */, + 2D93863624B38827009A12D7 /* OIDRegistrationRequest.m in Sources */, + 2D93863224B38826009A12D7 /* OIDEndSessionRequest.m in Sources */, + 2D93864424B38828009A12D7 /* OIDServiceDiscovery.m in Sources */, + 2DEB065724CA1D9300DF47E7 /* OIDTVTokenRequest.m in Sources */, + 2D93861C24B38812009A12D7 /* OIDAuthorizationResponse.m in Sources */, + 2D93863A24B38827009A12D7 /* OIDIDToken.m in Sources */, + 2D93864F24B38840009A12D7 /* OIDTVAuthorizationRequest.m in Sources */, + 2D93864624B38828009A12D7 /* OIDTokenRequest.m in Sources */, + 2D93865024B38840009A12D7 /* OIDTVAuthorizationResponse.m in Sources */, + 2D93864224B38828009A12D7 /* OIDServiceConfiguration.m in Sources */, + 2D93864A24B38829009A12D7 /* OIDTokenUtilities.m in Sources */, + 2D93862724B3881C009A12D7 /* OIDError.m in Sources */, + 2D93862424B3881C009A12D7 /* OIDClientMetadataParameters.m in Sources */, + 2D93864E24B38829009A12D7 /* OIDURLSessionProvider.m in Sources */, + 2D93864C24B38829009A12D7 /* OIDURLQueryComponent.m in Sources */, + 2D93863024B38826009A12D7 /* OIDEndSessionResponse.m in Sources */, + 2D93865224B38840009A12D7 /* OIDTVServiceConfiguration.m in Sources */, + 2D93861E24B3881B009A12D7 /* OIDAuthorizationService.m in Sources */, + 2D93863C24B38827009A12D7 /* OIDResponseTypes.m in Sources */, + 2D93863424B38826009A12D7 /* OIDRegistrationResponse.m in Sources */, + 2D93862924B3881C009A12D7 /* OIDErrorUtilities.m in Sources */, + 2D93865124B38840009A12D7 /* OIDTVAuthorizationService.m in Sources */, + 2D93863E24B38827009A12D7 /* OIDScopes.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 340DAE4A1D58216A00EC285B /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -1425,13 +2145,16 @@ 341310CC1E6F944B00D5DEE5 /* OIDServiceDiscovery.m in Sources */, 341310CA1E6F944B00D5DEE5 /* OIDScopeUtilities.m in Sources */, 340DAE5C1D5821AB00EC285B /* OIDAuthorizationService.m in Sources */, + CF37C06F1F1FC21A00662E41 /* OIDEndSessionRequest.m in Sources */, + A6DEAB832017A7030022AC32 /* OIDEndSessionResponse.m in Sources */, 341310CD1E6F944B00D5DEE5 /* OIDTokenRequest.m in Sources */, 341310C91E6F944B00D5DEE5 /* OIDScopes.m in Sources */, 341310CE1E6F944B00D5DEE5 /* OIDTokenResponse.m in Sources */, 341310C31E6F944B00D5DEE5 /* OIDErrorUtilities.m in Sources */, - 340DAE581D5821A100EC285B /* OIDAuthorizationUICoordinatorMac.m in Sources */, + 340DAE581D5821A100EC285B /* OIDExternalUserAgentMac.m in Sources */, 340DAE5A1D5821AB00EC285B /* OIDAuthorizationRequest.m in Sources */, 347423E41E7F3C4000D3E6D6 /* OIDAuthorizationResponse.m in Sources */, + 34A6632E1E871DD40060B664 /* OIDIDToken.m in Sources */, 340DAE591D5821A100EC285B /* OIDAuthState+Mac.m in Sources */, 34AF73671FB4E4B00022335F /* OIDURLSessionProvider.m in Sources */, 341310D01E6F944B00D5DEE5 /* OIDURLQueryComponent.m in Sources */, @@ -1444,7 +2167,11 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + C14E3B6A27E3BFB800CF05A9 /* OIDExternalUserAgentIOSCustomBrowser.m in Sources */, + A6DEABAA2018E5B50022AC32 /* OIDExternalUserAgentIOS.m in Sources */, 341741E01C5D8243000EF209 /* OIDErrorUtilities.m in Sources */, + 34A6632D1E871DD40060B664 /* OIDIDToken.m in Sources */, + CF6431F41F228A980075B6B5 /* OIDEndSessionResponse.m in Sources */, 341741EA1C5D8243000EF209 /* OIDTokenUtilities.m in Sources */, 341741E21C5D8243000EF209 /* OIDGrantTypes.m in Sources */, 60140F7C1DE42E1000DA0DC3 /* OIDRegistrationRequest.m in Sources */, @@ -1456,8 +2183,8 @@ 341741E61C5D8243000EF209 /* OIDServiceConfiguration.m in Sources */, 60140F7A1DE4276800DA0DC3 /* OIDClientMetadataParameters.m in Sources */, 341741DE1C5D8243000EF209 /* OIDAuthState.m in Sources */, + CF37C06E1F1FC21A00662E41 /* OIDEndSessionRequest.m in Sources */, 341741DD1C5D8243000EF209 /* OIDAuthorizationService.m in Sources */, - 340DAECD1D582DE100EC285B /* OIDAuthorizationUICoordinatorIOS.m in Sources */, 341741EB1C5D8243000EF209 /* OIDURLQueryComponent.m in Sources */, 341741E11C5D8243000EF209 /* OIDFieldMapping.m in Sources */, 039697461FA8258D003D1FB2 /* OIDURLSessionProvider.m in Sources */, @@ -1482,11 +2209,14 @@ 3417421E1C5D82D3000EF209 /* OIDServiceDiscoveryTests.m in Sources */, 3417421F1C5D82D3000EF209 /* OIDTokenRequestTests.m in Sources */, 341742181C5D82D3000EF209 /* OIDAuthorizationResponseTests.m in Sources */, + 34A6638B1E8865090060B664 /* OIDRPProfileCode.m in Sources */, 341742171C5D82D3000EF209 /* OIDAuthorizationRequestTests.m in Sources */, 0396974D1FA827AD003D1FB2 /* OIDURLSessionProviderTests.m in Sources */, + A6CEB11A2007E49C009D492A /* OIDEndSessionRequestTests.m in Sources */, 3417421A1C5D82D3000EF209 /* OIDGrantTypesTests.m in Sources */, 3417421B1C5D82D3000EF209 /* OIDResponseTypesTests.m in Sources */, 60140F831DE43BAF00DA0DC3 /* OIDRegistrationRequestTests.m in Sources */, + A5EEF29A20D821960044F470 /* OIDTokenUtilitiesTests.m in Sources */, 60140F861DE43CC700DA0DC3 /* OIDRegistrationResponseTests.m in Sources */, 341742191C5D82D3000EF209 /* OIDAuthStateTests.m in Sources */, 3417421D1C5D82D3000EF209 /* OIDServiceConfigurationTests.m in Sources */, @@ -1499,7 +2229,9 @@ buildActionMask = 2147483647; files = ( 341AA50A1E7F3A9B00FCA5C6 /* OIDScopesTests.m in Sources */, + A5EEF29B20D821970044F470 /* OIDTokenUtilitiesTests.m in Sources */, 341AA50F1E7F3A9B00FCA5C6 /* OIDURLQueryComponentTests.m in Sources */, + A6CEB11B2007E49D009D492A /* OIDEndSessionRequestTests.m in Sources */, 341AA50B1E7F3A9B00FCA5C6 /* OIDServiceConfigurationTests.m in Sources */, 341AA50C1E7F3A9B00FCA5C6 /* OIDServiceDiscoveryTests.m in Sources */, 341AA5071E7F3A9B00FCA5C6 /* OIDAuthStateTests.m in Sources */, @@ -1507,6 +2239,7 @@ 341AA50D1E7F3A9B00FCA5C6 /* OIDTokenRequestTests.m in Sources */, 341AA5091E7F3A9B00FCA5C6 /* OIDResponseTypesTests.m in Sources */, 341AA4D91E7F393500FCA5C6 /* OIDAuthorizationRequestTests.m in Sources */, + 34A6638C1E8865090060B664 /* OIDRPProfileCode.m in Sources */, 341AA5101E7F3A9B00FCA5C6 /* OIDRegistrationRequestTests.m in Sources */, 341AA5111E7F3A9B00FCA5C6 /* OIDRegistrationResponseTests.m in Sources */, 341AA5081E7F3A9B00FCA5C6 /* OIDGrantTypesTests.m in Sources */, @@ -1519,7 +2252,9 @@ buildActionMask = 2147483647; files = ( 341AA4FD1E7F3A9400FCA5C6 /* OIDScopesTests.m in Sources */, + A5EEF29C20D821970044F470 /* OIDTokenUtilitiesTests.m in Sources */, 341AA5021E7F3A9400FCA5C6 /* OIDURLQueryComponentTests.m in Sources */, + A6CEB11C2007E49E009D492A /* OIDEndSessionRequestTests.m in Sources */, 341AA4FE1E7F3A9400FCA5C6 /* OIDServiceConfigurationTests.m in Sources */, 341AA4FF1E7F3A9400FCA5C6 /* OIDServiceDiscoveryTests.m in Sources */, 341AA4FA1E7F3A9400FCA5C6 /* OIDAuthStateTests.m in Sources */, @@ -1527,6 +2262,7 @@ 341AA5001E7F3A9400FCA5C6 /* OIDTokenRequestTests.m in Sources */, 341AA4FC1E7F3A9400FCA5C6 /* OIDResponseTypesTests.m in Sources */, 341AA4F81E7F3A3000FCA5C6 /* OIDAuthorizationRequestTests.m in Sources */, + 34A6638D1E8865090060B664 /* OIDRPProfileCode.m in Sources */, 341AA5031E7F3A9400FCA5C6 /* OIDRegistrationRequestTests.m in Sources */, 341AA5041E7F3A9400FCA5C6 /* OIDRegistrationResponseTests.m in Sources */, 341AA4FB1E7F3A9400FCA5C6 /* OIDGrantTypesTests.m in Sources */, @@ -1546,19 +2282,57 @@ 341310D71E6F944D00D5DEE5 /* OIDRegistrationRequest.m in Sources */, 341310DD1E6F944D00D5DEE5 /* OIDServiceDiscovery.m in Sources */, 341E70991DE18796004353C1 /* OIDAuthorizationResponse.m in Sources */, + 34A6632F1E871DD40060B664 /* OIDIDToken.m in Sources */, 341310DB1E6F944D00D5DEE5 /* OIDScopeUtilities.m in Sources */, 341310D61E6F944D00D5DEE5 /* OIDRegistrationResponse.m in Sources */, 341310D31E6F944D00D5DEE5 /* OIDError.m in Sources */, 341310DE1E6F944D00D5DEE5 /* OIDTokenRequest.m in Sources */, 341310DF1E6F944D00D5DEE5 /* OIDTokenResponse.m in Sources */, + 2D47AAEC249A87020059B5A4 /* OIDTVAuthorizationService.m in Sources */, + A6DEAB842017A7040022AC32 /* OIDEndSessionResponse.m in Sources */, 341310DC1E6F944D00D5DEE5 /* OIDServiceConfiguration.m in Sources */, 341310BF1E6F943C00D5DEE5 /* OIDClientMetadataParameters.m in Sources */, 341310E01E6F944D00D5DEE5 /* OIDTokenUtilities.m in Sources */, 341E709A1DE18796004353C1 /* OIDAuthorizationService.m in Sources */, 341310D91E6F944D00D5DEE5 /* OIDResponseTypes.m in Sources */, 341310E11E6F944D00D5DEE5 /* OIDURLQueryComponent.m in Sources */, + CF37C0701F1FC21A00662E41 /* OIDEndSessionRequest.m in Sources */, + 2D47AAE1249A87020059B5A4 /* OIDTVAuthorizationResponse.m in Sources */, 341310D41E6F944D00D5DEE5 /* OIDErrorUtilities.m in Sources */, 341310D81E6F944D00D5DEE5 /* OIDGrantTypes.m in Sources */, + 2D47AAE4249A87020059B5A4 /* OIDTVServiceConfiguration.m in Sources */, + 2D47AAE8249A87020059B5A4 /* OIDTVAuthorizationRequest.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 342F42852177B1FC00574F24 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 342F42872177B1FC00574F24 /* OIDFieldMapping.m in Sources */, + 342F42882177B1FC00574F24 /* OIDIDToken.m in Sources */, + 342F42892177B1FC00574F24 /* OIDAuthState.m in Sources */, + 342F428B2177B1FC00574F24 /* OIDTokenResponse.m in Sources */, + 342F428C2177B1FC00574F24 /* OIDErrorUtilities.m in Sources */, + 342F428D2177B1FC00574F24 /* OIDURLQueryComponent.m in Sources */, + 342F428E2177B1FC00574F24 /* OIDAuthorizationRequest.m in Sources */, + 342F428F2177B1FC00574F24 /* OIDAuthorizationService.m in Sources */, + 342F42902177B1FC00574F24 /* OIDClientMetadataParameters.m in Sources */, + 06C19E9D22B474AD00C19CE1 /* OIDEndSessionRequest.m in Sources */, + 342F42912177B1FC00574F24 /* OIDTokenUtilities.m in Sources */, + 342F42922177B1FC00574F24 /* OIDServiceDiscovery.m in Sources */, + 342F42932177B1FC00574F24 /* OIDTokenRequest.m in Sources */, + 342F42962177B1FC00574F24 /* OIDServiceConfiguration.m in Sources */, + 342F42972177B1FC00574F24 /* OIDRegistrationResponse.m in Sources */, + 342F42982177B1FC00574F24 /* OIDURLSessionProvider.m in Sources */, + 342F42992177B1FC00574F24 /* OIDScopes.m in Sources */, + 342F429A2177B1FC00574F24 /* OIDScopeUtilities.m in Sources */, + 342F429B2177B1FC00574F24 /* OIDGrantTypes.m in Sources */, + 06C19E9B22B474A200C19CE1 /* OIDEndSessionResponse.m in Sources */, + 342F429C2177B1FC00574F24 /* OIDRegistrationRequest.m in Sources */, + 342F429D2177B1FC00574F24 /* OIDResponseTypes.m in Sources */, + 342F429E2177B1FC00574F24 /* OIDAuthorizationResponse.m in Sources */, + 342F429F2177B1FC00574F24 /* OIDError.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1566,12 +2340,16 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + A6DEABAF2018E5D80022AC32 /* OIDExternalUserAgentIOS.m in Sources */, + F9A7082F2355ED74004B3E6D /* OIDExternalUserAgentCatalyst.m in Sources */, 343AAA881E83478900F9D36E /* OIDFieldMapping.m in Sources */, + 34A663311E871DD40060B664 /* OIDIDToken.m in Sources */, + C14E3B6927E3BEFB00CF05A9 /* OIDExternalUserAgentIOSCustomBrowser.m in Sources */, + A6DEAB862017A7060022AC32 /* OIDEndSessionResponse.m in Sources */, 343AAA841E83478900F9D36E /* OIDAuthState.m in Sources */, 343AAA701E83467D00F9D36E /* OIDAuthState+IOS.m in Sources */, 343AAA921E83478900F9D36E /* OIDTokenResponse.m in Sources */, 343AAA871E83478900F9D36E /* OIDErrorUtilities.m in Sources */, - 343AAA711E83467D00F9D36E /* OIDAuthorizationUICoordinatorIOS.m in Sources */, 343AAA811E83477100F9D36E /* OIDURLQueryComponent.m in Sources */, 343AAA721E83469600F9D36E /* OIDAuthorizationRequest.m in Sources */, 343AAA831E83478900F9D36E /* OIDAuthorizationService.m in Sources */, @@ -1579,6 +2357,7 @@ 343AAA931E83478900F9D36E /* OIDTokenUtilities.m in Sources */, 343AAA901E83478900F9D36E /* OIDServiceDiscovery.m in Sources */, 343AAA911E83478900F9D36E /* OIDTokenRequest.m in Sources */, + A6DEAB8A2017A7140022AC32 /* OIDEndSessionRequest.m in Sources */, 343AAA6F1E83467D00F9D36E /* OIDAuthorizationService+IOS.m in Sources */, 343AAA8F1E83478900F9D36E /* OIDServiceConfiguration.m in Sources */, 343AAA891E83478900F9D36E /* OIDRegistrationResponse.m in Sources */, @@ -1610,7 +2389,10 @@ 343AAA7F1E8346B400F9D36E /* OIDRegistrationRequestTests.m in Sources */, 343AAA731E8346B400F9D36E /* OIDAuthorizationRequestTests.m in Sources */, 343AAA761E8346B400F9D36E /* OIDGrantTypesTests.m in Sources */, + 34A6638E1E8865090060B664 /* OIDRPProfileCode.m in Sources */, + A6CEB11D2007E49F009D492A /* OIDEndSessionRequestTests.m in Sources */, 343AAA741E8346B400F9D36E /* OIDAuthorizationResponseTests.m in Sources */, + A5EEF29720D821120044F470 /* OIDTokenUtilitiesTests.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1626,11 +2408,14 @@ 343AAB741E8349B000F9D36E /* OIDRegistrationRequest.m in Sources */, 343AAB7A1E8349B000F9D36E /* OIDServiceDiscovery.m in Sources */, 343AAB6C1E8349B000F9D36E /* OIDAuthorizationResponse.m in Sources */, + 34A663321E871DD40060B664 /* OIDIDToken.m in Sources */, 343AAB781E8349B000F9D36E /* OIDScopeUtilities.m in Sources */, 343AAB731E8349B000F9D36E /* OIDRegistrationResponse.m in Sources */, 343AAB701E8349B000F9D36E /* OIDError.m in Sources */, + A6DEAB8B2017A7160022AC32 /* OIDEndSessionRequest.m in Sources */, 343AAB7B1E8349B000F9D36E /* OIDTokenRequest.m in Sources */, 343AAB7C1E8349B000F9D36E /* OIDTokenResponse.m in Sources */, + A6DEAB872017A70B0022AC32 /* OIDEndSessionResponse.m in Sources */, 343AAB791E8349B000F9D36E /* OIDServiceConfiguration.m in Sources */, 343AAB6F1E8349B000F9D36E /* OIDClientMetadataParameters.m in Sources */, 343AAB7D1E8349B000F9D36E /* OIDTokenUtilities.m in Sources */, @@ -1654,11 +2439,14 @@ 343AAB601E8349B000F9D36E /* OIDRegistrationRequest.m in Sources */, 343AAB661E8349B000F9D36E /* OIDServiceDiscovery.m in Sources */, 343AAB581E8349B000F9D36E /* OIDAuthorizationResponse.m in Sources */, + 34A663331E871DD40060B664 /* OIDIDToken.m in Sources */, 343AAB641E8349B000F9D36E /* OIDScopeUtilities.m in Sources */, 343AAB5F1E8349B000F9D36E /* OIDRegistrationResponse.m in Sources */, 343AAB5C1E8349B000F9D36E /* OIDError.m in Sources */, + A6DEAB8C2017A7160022AC32 /* OIDEndSessionRequest.m in Sources */, 343AAB671E8349B000F9D36E /* OIDTokenRequest.m in Sources */, 343AAB681E8349B000F9D36E /* OIDTokenResponse.m in Sources */, + A6DEAB882017A70B0022AC32 /* OIDEndSessionResponse.m in Sources */, 343AAB651E8349B000F9D36E /* OIDServiceConfiguration.m in Sources */, 343AAB5B1E8349B000F9D36E /* OIDClientMetadataParameters.m in Sources */, 343AAB691E8349B000F9D36E /* OIDTokenUtilities.m in Sources */, @@ -1687,7 +2475,10 @@ 343AAB8B1E8349CE00F9D36E /* OIDRegistrationRequestTests.m in Sources */, 343AAB7F1E8349CE00F9D36E /* OIDAuthorizationRequestTests.m in Sources */, 343AAB821E8349CE00F9D36E /* OIDGrantTypesTests.m in Sources */, + 34A6638F1E8865090060B664 /* OIDRPProfileCode.m in Sources */, + A6CEB11E2007E4A1009D492A /* OIDEndSessionRequestTests.m in Sources */, 343AAB801E8349CE00F9D36E /* OIDAuthorizationResponseTests.m in Sources */, + A5EEF29820D8211A0044F470 /* OIDTokenUtilitiesTests.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1699,6 +2490,7 @@ 343AAADC1E83493D00F9D36E /* OIDAuthState+Mac.m in Sources */, 343AAADA1E83493D00F9D36E /* OIDAuthorizationService+Mac.m in Sources */, 343AAB4C1E8349AF00F9D36E /* OIDRegistrationRequest.m in Sources */, + A6DEAB8D2017A7170022AC32 /* OIDEndSessionRequest.m in Sources */, 343AAB451E8349AF00F9D36E /* OIDAuthorizationService.m in Sources */, 343AAB431E8349AF00F9D36E /* OIDAuthorizationRequest.m in Sources */, 343AAAD91E83493D00F9D36E /* OIDRedirectHTTPHandler.m in Sources */, @@ -1714,12 +2506,14 @@ 343AAB4F1E8349AF00F9D36E /* OIDScopes.m in Sources */, 343AAB541E8349AF00F9D36E /* OIDTokenResponse.m in Sources */, 343AAB491E8349AF00F9D36E /* OIDErrorUtilities.m in Sources */, - 343AAADB1E83493D00F9D36E /* OIDAuthorizationUICoordinatorMac.m in Sources */, + 343AAADB1E83493D00F9D36E /* OIDExternalUserAgentMac.m in Sources */, 343AAB471E8349AF00F9D36E /* OIDClientMetadataParameters.m in Sources */, + 34A663341E871DD40060B664 /* OIDIDToken.m in Sources */, 343AAB461E8349AF00F9D36E /* OIDAuthState.m in Sources */, 34AF736D1FB4E4B40022335F /* OIDURLSessionProvider.m in Sources */, 343AAB561E8349AF00F9D36E /* OIDURLQueryComponent.m in Sources */, 343AAB4E1E8349AF00F9D36E /* OIDResponseTypes.m in Sources */, + A6DEAB892017A70C0022AC32 /* OIDEndSessionResponse.m in Sources */, 343AAB4A1E8349AF00F9D36E /* OIDFieldMapping.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -1741,7 +2535,10 @@ 343AAB991E8349CF00F9D36E /* OIDRegistrationRequestTests.m in Sources */, 343AAB8D1E8349CF00F9D36E /* OIDAuthorizationRequestTests.m in Sources */, 343AAB901E8349CF00F9D36E /* OIDGrantTypesTests.m in Sources */, + 34A663901E8865090060B664 /* OIDRPProfileCode.m in Sources */, + A6CEB11F2007E4A2009D492A /* OIDEndSessionRequestTests.m in Sources */, 343AAB8E1E8349CF00F9D36E /* OIDAuthorizationResponseTests.m in Sources */, + A5EEF29920D8211B0044F470 /* OIDTokenUtilitiesTests.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1757,25 +2554,56 @@ 347424081E7F4BA000D3E6D6 /* OIDRegistrationRequest.m in Sources */, 3474240E1E7F4BA000D3E6D6 /* OIDServiceDiscovery.m in Sources */, 347424001E7F4BA000D3E6D6 /* OIDAuthorizationResponse.m in Sources */, + 34A663301E871DD40060B664 /* OIDIDToken.m in Sources */, 3474240C1E7F4BA000D3E6D6 /* OIDScopeUtilities.m in Sources */, 347424071E7F4BA000D3E6D6 /* OIDRegistrationResponse.m in Sources */, 347424041E7F4BA000D3E6D6 /* OIDError.m in Sources */, 3474240F1E7F4BA000D3E6D6 /* OIDTokenRequest.m in Sources */, 347424101E7F4BA000D3E6D6 /* OIDTokenResponse.m in Sources */, + A6DEAB852017A7050022AC32 /* OIDEndSessionResponse.m in Sources */, 3474240D1E7F4BA000D3E6D6 /* OIDServiceConfiguration.m in Sources */, 347424031E7F4BA000D3E6D6 /* OIDClientMetadataParameters.m in Sources */, 347424111E7F4BA000D3E6D6 /* OIDTokenUtilities.m in Sources */, 347424011E7F4BA000D3E6D6 /* OIDAuthorizationService.m in Sources */, 3474240A1E7F4BA000D3E6D6 /* OIDResponseTypes.m in Sources */, 347424121E7F4BA000D3E6D6 /* OIDURLQueryComponent.m in Sources */, + CF37C0711F1FC21A00662E41 /* OIDEndSessionRequest.m in Sources */, 347424051E7F4BA000D3E6D6 /* OIDErrorUtilities.m in Sources */, 347424091E7F4BA000D3E6D6 /* OIDGrantTypes.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; + 348970802177B3B000ABEED4 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 348970812177B3B000ABEED4 /* OIDScopesTests.m in Sources */, + 348970822177B3B000ABEED4 /* OIDURLQueryComponentTests.m in Sources */, + 348970832177B3B000ABEED4 /* OIDServiceConfigurationTests.m in Sources */, + 348970842177B3B000ABEED4 /* OIDURLQueryComponentTestsIOS7.m in Sources */, + 348970852177B3B000ABEED4 /* OIDRegistrationResponseTests.m in Sources */, + 348970862177B3B000ABEED4 /* OIDServiceDiscoveryTests.m in Sources */, + 348970872177B3B000ABEED4 /* OIDAuthStateTests.m in Sources */, + 348970882177B3B000ABEED4 /* OIDTokenResponseTests.m in Sources */, + 348970892177B3B000ABEED4 /* OIDTokenRequestTests.m in Sources */, + 3489708A2177B3B000ABEED4 /* OIDResponseTypesTests.m in Sources */, + 3489708B2177B3B000ABEED4 /* OIDRegistrationRequestTests.m in Sources */, + 3489708C2177B3B000ABEED4 /* OIDAuthorizationRequestTests.m in Sources */, + 3489708D2177B3B000ABEED4 /* OIDGrantTypesTests.m in Sources */, + 3489708E2177B3B000ABEED4 /* OIDRPProfileCode.m in Sources */, + 3489708F2177B3B000ABEED4 /* OIDAuthorizationResponseTests.m in Sources */, + 348970902177B3B000ABEED4 /* OIDTokenUtilitiesTests.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ + 2D8111FC24C0FD4C00984DA7 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 2D9385B624B37CAD009A12D7 /* AppAuthTV */; + targetProxy = 2D8111FB24C0FD4C00984DA7 /* PBXContainerItemProxy */; + }; 341741F71C5D8283000EF209 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 340E737B1C5D819B0076B1F6 /* AppAuth-iOS */; @@ -1806,13 +2634,114 @@ target = 343AAAC11E8348A900F9D36E /* AppAuth_macOS */; targetProxy = 343AAACC1E8348AA00F9D36E /* PBXContainerItemProxy */; }; + 3489707E2177B3B000ABEED4 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 343AAA531E83463400F9D36E /* AppAuth_iOS */; + targetProxy = 3489707F2177B3B000ABEED4 /* PBXContainerItemProxy */; + }; /* End PBXTargetDependency section */ /* Begin XCBuildConfiguration section */ + 2D8111FD24C0FD4C00984DA7 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ANALYZER_NONNULL = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + HEADER_SEARCH_PATHS = .; + INFOPLIST_FILE = UnitTests/UnitTestsInfo.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = net.openid.AppAuthTVTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = appletvos; + TVOS_DEPLOYMENT_TARGET = 9.0; + }; + name = Debug; + }; + 2D8111FE24C0FD4C00984DA7 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ANALYZER_NONNULL = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + HEADER_SEARCH_PATHS = .; + INFOPLIST_FILE = UnitTests/UnitTestsInfo.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = net.openid.AppAuthTVTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = appletvos; + TVOS_DEPLOYMENT_TARGET = 9.0; + }; + name = Release; + }; + 2D9385C924B37CAD009A12D7 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = AUX79W8H33; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + INFOPLIST_FILE = Sources/TVFramework/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = net.openid.AppAuthTV; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SDKROOT = appletvos; + SKIP_INSTALL = YES; + TARGETED_DEVICE_FAMILY = 3; + TVOS_DEPLOYMENT_TARGET = 9.0; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Debug; + }; + 2D9385CA24B37CAD009A12D7 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = AUX79W8H33; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + GCC_C_LANGUAGE_STANDARD = gnu11; + INFOPLIST_FILE = Sources/TVFramework/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = net.openid.AppAuthTV; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SDKROOT = appletvos; + SKIP_INSTALL = YES; + TARGETED_DEVICE_FAMILY = 3; + TVOS_DEPLOYMENT_TARGET = 9.0; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Release; + }; 340DAE551D58216A00EC285B /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { - ARCHS = "$(ARCHS_STANDARD_32_64_BIT)"; CLANG_ANALYZER_NONNULL = YES; CODE_SIGN_IDENTITY = "-"; EXECUTABLE_PREFIX = lib; @@ -1825,7 +2754,6 @@ 340DAE561D58216A00EC285B /* Release */ = { isa = XCBuildConfiguration; buildSettings = { - ARCHS = "$(ARCHS_STANDARD_32_64_BIT)"; CLANG_ANALYZER_NONNULL = YES; CODE_SIGN_IDENTITY = "-"; EXECUTABLE_PREFIX = lib; @@ -1848,13 +2776,17 @@ CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_MISSING_PROPERTY_SYNTHESIS = NO; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; @@ -1871,20 +2803,26 @@ GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( - "DEBUG=1", "$(inherited)", + "DEBUG=1", ); + GCC_TREAT_WARNINGS_AS_ERRORS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_PEDANTIC = YES; + GCC_WARN_STRICT_SELECTOR_MATCH = YES; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 7.0; - MACOSX_DEPLOYMENT_TARGET = 10.9; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; + MACOSX_DEPLOYMENT_TARGET = 10.12; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; + TVOS_DEPLOYMENT_TARGET = 9.0; + WARNING_CFLAGS = "-Wno-gnu"; + WATCHOS_DEPLOYMENT_TARGET = 2.0; }; name = Debug; }; @@ -1901,13 +2839,17 @@ CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_MISSING_PROPERTY_SYNTHESIS = NO; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; @@ -1921,24 +2863,32 @@ ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_NO_COMMON_BLOCKS = YES; + GCC_PREPROCESSOR_DEFINITIONS = "$(inherited)"; + GCC_TREAT_WARNINGS_AS_ERRORS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_PEDANTIC = YES; + GCC_WARN_STRICT_SELECTOR_MATCH = YES; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 7.0; - MACOSX_DEPLOYMENT_TARGET = 10.9; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; + MACOSX_DEPLOYMENT_TARGET = 10.12; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + TVOS_DEPLOYMENT_TARGET = 9.0; VALIDATE_PRODUCT = YES; + WARNING_CFLAGS = "-Wno-gnu"; + WATCHOS_DEPLOYMENT_TARGET = 2.0; }; name = Release; }; 340E73861C5D819B0076B1F6 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { + IPHONEOS_DEPLOYMENT_TARGET = 10.0; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; }; @@ -1947,6 +2897,7 @@ 340E73871C5D819B0076B1F6 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { + IPHONEOS_DEPLOYMENT_TARGET = 10.0; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; }; @@ -1958,7 +2909,7 @@ CLANG_ENABLE_MODULES = YES; HEADER_SEARCH_PATHS = "$(PROJECT_DIR)/"; INFOPLIST_FILE = UnitTests/UnitTestsInfo.plist; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = net.openid.appauth.AppAuthTests; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -1975,7 +2926,7 @@ CLANG_ENABLE_MODULES = YES; HEADER_SEARCH_PATHS = "$(PROJECT_DIR)/"; INFOPLIST_FILE = UnitTests/UnitTestsInfo.plist; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = net.openid.appauth.AppAuthTests; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -2028,7 +2979,7 @@ PRODUCT_BUNDLE_IDENTIFIER = "net.openid.AppAuth-tvOSTests"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = appletvos; - TVOS_DEPLOYMENT_TARGET = 10.1; + TVOS_DEPLOYMENT_TARGET = 9.0; }; name = Debug; }; @@ -2043,7 +2994,7 @@ PRODUCT_BUNDLE_IDENTIFIER = "net.openid.AppAuth-tvOSTests"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = appletvos; - TVOS_DEPLOYMENT_TARGET = 10.1; + TVOS_DEPLOYMENT_TARGET = 9.0; }; name = Release; }; @@ -2057,7 +3008,7 @@ PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = appletvos; SKIP_INSTALL = YES; - TVOS_DEPLOYMENT_TARGET = 10.1; + TVOS_DEPLOYMENT_TARGET = 9.0; }; name = Debug; }; @@ -2071,7 +3022,59 @@ PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = appletvos; SKIP_INSTALL = YES; - TVOS_DEPLOYMENT_TARGET = 10.1; + TVOS_DEPLOYMENT_TARGET = 9.0; + }; + name = Release; + }; + 342F42C12177B1FC00574F24 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + APPLICATION_EXTENSION_API_ONLY = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CODE_SIGN_IDENTITY = ""; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = Sources/CoreFramework/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = net.openid.AppAuthCore; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + SUPPORTS_MACCATALYST = YES; + TARGETED_DEVICE_FAMILY = "1,2"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Debug; + }; + 342F42C22177B1FC00574F24 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + APPLICATION_EXTENSION_API_ONLY = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CODE_SIGN_IDENTITY = ""; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = Sources/CoreFramework/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = net.openid.AppAuthCore; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + SUPPORTS_MACCATALYST = YES; + TARGETED_DEVICE_FAMILY = "1,2"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; }; name = Release; }; @@ -2086,13 +3089,14 @@ DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; - INFOPLIST_FILE = Source/Framework/Info.plist; + INFOPLIST_FILE = Sources/Framework/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = "net.openid.AppAuth-iOS"; PRODUCT_NAME = AppAuth; SKIP_INSTALL = YES; + SUPPORTS_MACCATALYST = YES; TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; @@ -2110,13 +3114,14 @@ DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; - INFOPLIST_FILE = Source/Framework/Info.plist; + INFOPLIST_FILE = Sources/Framework/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = "net.openid.AppAuth-iOS"; PRODUCT_NAME = AppAuth; SKIP_INSTALL = YES; + SUPPORTS_MACCATALYST = YES; TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; @@ -2130,7 +3135,7 @@ CLANG_WARN_DOCUMENTATION_COMMENTS = YES; HEADER_SEARCH_PATHS = .; INFOPLIST_FILE = UnitTests/UnitTestsInfo.plist; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = "net.openid.AppAuth-iOSTests"; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -2144,7 +3149,7 @@ CLANG_WARN_DOCUMENTATION_COMMENTS = YES; HEADER_SEARCH_PATHS = .; INFOPLIST_FILE = UnitTests/UnitTestsInfo.plist; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = "net.openid.AppAuth-iOSTests"; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -2163,7 +3168,7 @@ DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; - INFOPLIST_FILE = Source/Framework/Info.plist; + INFOPLIST_FILE = Sources/Framework/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = "net.openid.AppAuth-watchOS"; @@ -2189,7 +3194,7 @@ DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; - INFOPLIST_FILE = Source/Framework/Info.plist; + INFOPLIST_FILE = Sources/Framework/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = "net.openid.AppAuth-watchOS"; @@ -2214,11 +3219,11 @@ DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; - INFOPLIST_FILE = Source/Framework/Info.plist; + INFOPLIST_FILE = Sources/Framework/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = "net.openid.AppAuth-tvOS"; - PRODUCT_NAME = AppAUth; + PRODUCT_NAME = AppAuth; SDKROOT = appletvos; SKIP_INSTALL = YES; TARGETED_DEVICE_FAMILY = 3; @@ -2239,11 +3244,11 @@ DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; - INFOPLIST_FILE = Source/Framework/Info.plist; + INFOPLIST_FILE = Sources/Framework/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = "net.openid.AppAuth-tvOS"; - PRODUCT_NAME = AppAUth; + PRODUCT_NAME = AppAuth; SDKROOT = appletvos; SKIP_INSTALL = YES; TARGETED_DEVICE_FAMILY = 3; @@ -2264,7 +3269,7 @@ PRODUCT_BUNDLE_IDENTIFIER = "net.openid.AppAuth-tvOSTests"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = appletvos; - TVOS_DEPLOYMENT_TARGET = 10.1; + TVOS_DEPLOYMENT_TARGET = 9.0; }; name = Debug; }; @@ -2279,14 +3284,13 @@ PRODUCT_BUNDLE_IDENTIFIER = "net.openid.AppAuth-tvOSTests"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = appletvos; - TVOS_DEPLOYMENT_TARGET = 10.1; + TVOS_DEPLOYMENT_TARGET = 9.0; }; name = Release; }; 343AAAD41E8348AA00F9D36E /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { - ARCHS = "$(ARCHS_STANDARD_32_64_BIT)"; CLANG_ANALYZER_NONNULL = YES; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CODE_SIGN_IDENTITY = "-"; @@ -2297,7 +3301,7 @@ DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; FRAMEWORK_VERSION = A; - INFOPLIST_FILE = Source/Framework/Info.plist; + INFOPLIST_FILE = Sources/Framework/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = "net.openid.AppAuth-macOS"; @@ -2312,7 +3316,6 @@ 343AAAD51E8348AA00F9D36E /* Release */ = { isa = XCBuildConfiguration; buildSettings = { - ARCHS = "$(ARCHS_STANDARD_32_64_BIT)"; CLANG_ANALYZER_NONNULL = YES; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CODE_SIGN_IDENTITY = "-"; @@ -2323,7 +3326,7 @@ DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; FRAMEWORK_VERSION = A; - INFOPLIST_FILE = Source/Framework/Info.plist; + INFOPLIST_FILE = Sources/Framework/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = "net.openid.AppAuth-macOS"; @@ -2376,7 +3379,7 @@ PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = watchos; SKIP_INSTALL = YES; - WATCHOS_DEPLOYMENT_TARGET = 3.1; + WATCHOS_DEPLOYMENT_TARGET = 2.0; }; name = Debug; }; @@ -2389,13 +3392,59 @@ PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = watchos; SKIP_INSTALL = YES; - WATCHOS_DEPLOYMENT_TARGET = 3.1; + WATCHOS_DEPLOYMENT_TARGET = 2.0; + }; + name = Release; + }; + 348970952177B3B000ABEED4 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ANALYZER_NONNULL = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + HEADER_SEARCH_PATHS = .; + INFOPLIST_FILE = UnitTests/UnitTestsInfo.plist; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = "net.openid.AppAuth-ExtensionTests"; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Debug; + }; + 348970962177B3B000ABEED4 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ANALYZER_NONNULL = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + HEADER_SEARCH_PATHS = .; + INFOPLIST_FILE = UnitTests/UnitTestsInfo.plist; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = "net.openid.AppAuth-ExtensionTests"; + PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ + 2D8111FF24C0FD4D00984DA7 /* Build configuration list for PBXNativeTarget "AppAuthTVTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 2D8111FD24C0FD4C00984DA7 /* Debug */, + 2D8111FE24C0FD4C00984DA7 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 2D9385C824B37CAD009A12D7 /* Build configuration list for PBXNativeTarget "AppAuthTV" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 2D9385C924B37CAD009A12D7 /* Debug */, + 2D9385CA24B37CAD009A12D7 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; 340DAE541D58216A00EC285B /* Build configuration list for PBXNativeTarget "AppAuth-macOS" */ = { isa = XCConfigurationList; buildConfigurations = ( @@ -2459,6 +3508,15 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; + 342F42C02177B1FC00574F24 /* Build configuration list for PBXNativeTarget "AppAuthCore" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 342F42C12177B1FC00574F24 /* Debug */, + 342F42C22177B1FC00574F24 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; 343AAA651E83463400F9D36E /* Build configuration list for PBXNativeTarget "AppAuth_iOS" */ = { isa = XCConfigurationList; buildConfigurations = ( @@ -2531,6 +3589,15 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; + 348970942177B3B000ABEED4 /* Build configuration list for PBXNativeTarget "AppAuthCoreTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 348970952177B3B000ABEED4 /* Debug */, + 348970962177B3B000ABEED4 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; /* End XCConfigurationList section */ }; rootObject = 340E73741C5D819B0076B1F6 /* Project object */; diff --git a/AppAuth.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/AppAuth.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 000000000..18d981003 --- /dev/null +++ b/AppAuth.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/AppAuth.xcodeproj/xcshareddata/xcschemes/AppAuth-iOS.xcscheme b/AppAuth.xcodeproj/xcshareddata/xcschemes/AppAuth-iOS.xcscheme index 3685f59f8..d2886e5ea 100644 --- a/AppAuth.xcodeproj/xcshareddata/xcschemes/AppAuth-iOS.xcscheme +++ b/AppAuth.xcodeproj/xcshareddata/xcschemes/AppAuth-iOS.xcscheme @@ -1,6 +1,6 @@ + + + + @@ -40,23 +48,11 @@ - - - - - - - - + + + + @@ -40,23 +48,11 @@ - - - - - - - - + + + + @@ -40,23 +48,11 @@ - - - - - - - - @@ -37,7 +36,6 @@ buildConfiguration = "Debug" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" - language = "" launchStyle = "0" useCustomWorkingDirectory = "NO" ignoresPersistentStateOnLaunch = "NO" diff --git a/AppAuth.xcodeproj/xcshareddata/xcschemes/AppAuthCore.xcscheme b/AppAuth.xcodeproj/xcshareddata/xcschemes/AppAuthCore.xcscheme new file mode 100644 index 000000000..52e11aa94 --- /dev/null +++ b/AppAuth.xcodeproj/xcshareddata/xcschemes/AppAuthCore.xcscheme @@ -0,0 +1,99 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/AppAuth.xcodeproj/xcshareddata/xcschemes/AppAuthTV.xcscheme b/AppAuth.xcodeproj/xcshareddata/xcschemes/AppAuthTV.xcscheme new file mode 100644 index 000000000..fd3849bd6 --- /dev/null +++ b/AppAuth.xcodeproj/xcshareddata/xcschemes/AppAuthTV.xcscheme @@ -0,0 +1,91 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/AppAuth.xcodeproj/xcshareddata/xcschemes/AppAuth_iOS.xcscheme b/AppAuth.xcodeproj/xcshareddata/xcschemes/AppAuth_iOS.xcscheme index 717f71b1b..dbbb00b1d 100644 --- a/AppAuth.xcodeproj/xcshareddata/xcschemes/AppAuth_iOS.xcscheme +++ b/AppAuth.xcodeproj/xcshareddata/xcschemes/AppAuth_iOS.xcscheme @@ -1,6 +1,6 @@ @@ -26,8 +26,16 @@ buildConfiguration = "Debug" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" - language = "" shouldUseLaunchSchemeArgsEnv = "YES"> + + + + @@ -40,23 +48,11 @@ - - - - - - - - diff --git a/AppAuth.xcodeproj/xcshareddata/xcschemes/AppAuth_watchOS.xcscheme b/AppAuth.xcodeproj/xcshareddata/xcschemes/AppAuth_watchOS.xcscheme index 8bd81f107..3a06a41b2 100644 --- a/AppAuth.xcodeproj/xcshareddata/xcschemes/AppAuth_watchOS.xcscheme +++ b/AppAuth.xcodeproj/xcshareddata/xcschemes/AppAuth_watchOS.xcscheme @@ -1,6 +1,6 @@ @@ -37,7 +36,6 @@ buildConfiguration = "Debug" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" - language = "" launchStyle = "0" useCustomWorkingDirectory = "NO" ignoresPersistentStateOnLaunch = "NO" diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 000000000..8be94c79a --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,247 @@ +# Unreleased +- Add SwiftUI + Swift Package Manager sample app under `Examples/Example-iOS_Swift-SPM`. ([#952](https://github.com/openid/AppAuth-iOS/pull/952)) +- Removed external browser (Safari) fallback from `OIDExternalUserAgentIOS`. If `ASWebAuthenticationSession` fails to start (e.g., Guided Access is enabled), the authorization flow now fails with an error instead of opening an external browser. + +# 2.0.0 +- Raise minimum supported iOS version to iOS 12. ([#918](https://github.com/openid/AppAuth-iOS/pull/918)) +- Remove deprecated `[UIApplication openURL:]` method to compile with Xcode 16. ([#911](https://github.com/openid/AppAuth-iOS/pull/911)) + +# 1.7.6 +- Fix OIDExternalUserAgentIOSCustomBrowser on versions iOS 10+ ([#871](https://github.com/openid/AppAuth-iOS/pull/871)) +- Update runners in tests.yml to macos-13. ([#886](https://github.com/openid/AppAuth-iOS/pull/886)) + +# 1.7.5 +- Use correct xcconfig syntax for podspec ([#851](https://github.com/openid/AppAuth-iOS/pull/851)) + +# 1.7.4 +- Adds defines module to AppAuth.podspec ([#845](https://github.com/openid/AppAuth-iOS/pull/845)) + +# 1.7.3 +- Fix missing manifest in bundle using SPM ([#833](https://github.com/openid/AppAuth-iOS/pull/833)) + +# 1.7.2 + - Streamline copying of privacy manifest ([#830](https://github.com/openid/AppAuth-iOS/pull/830)) + +# 1.7.1 +- Add back missing method to OIDAuthorizationResponse ([#825](https://github.com/openid/AppAuth-iOS/pull/825)) +- Fix OIDTokenRequest for AppAuthCore and AppAuthTV ([#826](https://github.com/openid/AppAuth-iOS/pull/826)) + +# 1.7.0 +- Introduce addtional http headers to OIDTokenRequest ([#770](https://github.com/openid/AppAuth-iOS/pull/770)) +- Fix nullability annotation for -[OIDExternalUserAgentIOS init] ([#727](https://github.com/openid/AppAuth-iOS/pull/727)) +- Feat: allow custom nonce in OIDAuthorizationRequest ([#788](https://github.com/openid/AppAuth-iOS/pull/788)) +- Add privacy manifest ([#822](https://github.com/openid/AppAuth-iOS/pull/822)) + +# 1.6.2 +- Increased minimum iOS and macOS versions to 9.0 and 10.12 respectively to fix [framework build issue](https://github.com/openid/AppAuth-iOS/issues/765) + +# 1.6.1 +- Increased minimum iOS and macOS versions to fix [build issue](https://github.com/openid/AppAuth-iOS/pull/761) + +# 1.6.0 +- Added a `prefersEphemeralSession` parameter for external user-agents. ([#645](https://github.com/openid/AppAuth-iOS/pull/645)) +- Fixed errors encountered when using secure coding to decode `OIDAuthState`. ([#656](https://github.com/openid/AppAuth-iOS/pull/656), [#721](https://github.com/openid/AppAuth-iOS/pull/721)) + +# 1.5.0 +- Improved tvOS support. ([#111](https://github.com/openid/AppAuth-iOS/issues/111)) +- ASWebAuthenticationSession on macOS. ([#675](https://github.com/openid/AppAuth-iOS/pull/675)) + +# 1.4.0 + +## Added + +1. Support for Swift Package Manager + +# 1.3.1 + +## Fixes + +1. Removed `UIWebView` reference in comment + +# 1.3.0 + +## Notable Changes + +1. Support for Mac Catalyst + +# 1.2.0 + +## Notable Changes + +1. Support for iOS 13 + +# 1.1.0 + +## Notable Changes + +1. [OpenID Connect RP-Initiated Logout](http://openid.net/specs/openid-connect-session-1_0.html#RPLogout) implemented +2. Added logic for the `azp` claim + +## Fixes + +1. Scheme comparison for redirects is now case insensitive +2. Improved error handling during discovery when a non-JSON document + is encountered. + +# 1.0.0 + +1.0.0! 🎉 + +## Notable Changes + +1. **All deprecated APIs removed.** Please ensure your code builds on + version 0.95.0 with no deprecation warnings before upgrading! + Notably, if you started with a version of AppAuth prior to 0.93.0 + you will need to follow the instructions in + [Upgrading to 0.93.0](#upgrading-to-0930) +2. Updated for iOS 12, and Xcode 10. **Xcode 10 is now required.** + NB. per policy, AppAuth supports many older versions of iOS and + macOS, but only the current Xcode toolchain. + If you need to stay on old versions of Xcode for some reason, stay + on the pre-1.0 releases. +3. macOS 32-bit support removed. If you need this support, stay on the + pre-1.0 releases. +4. `AppAuth/Core` subspec, and AppAuthCore Framework added to support + iOS extensions. + +# 1.0.0.beta2 (2018-09-27) + +## Notable Changes + +1. `AppAuth/Core` subspec, and AppAuthCore Framework added to support + iOS extensions. + +# 1.0.0.beta1 (2018-09-27) + +First 1.0.0 beta! HEAD is now tracking changes for the 1.0.0 release. +The `pre-1.0` branch was cut prior to the breaking changes for 1.0.0, +bug fixes for critical issues may be backported for a time. + +## Notable Changes + +1. **All deprecated APIs removed.** Please ensure your code builds on + version 0.95.0 with no deprecation warnings before upgrading! + Notably, if you started with a version of AppAuth prior to 0.93.0 + you will need to follow the instructions in + [Upgrading to 0.93.0](#upgrading-to-0930) +2. Updated for iOS 12, and Xcode 10. **Xcode 10 is now required.** + NB. per policy, AppAuth supports many older versions of iOS and + macOS, but only the current Xcode toolchain. + If you need to stay on old versions of Xcode for some reason, stay + on the pre-1.0 releases. +3. macOS 32-bit support removed. If you need this support, stay on the + pre-1.0 releases. + +## Fixes + +1. All fixes in the 0.95.0 release are incorporated in this release. + +# 0.95.0 (2018-09-27) + +## Fixes + +1. `x-www-form-urlencoded` encoding and decoding should be 100% + spec compliant now, previously the `+` character was not decoded as + 0x20 space. https://github.com/openid/AppAuth-iOS/pull/291 + +2. `scope` no longer sent during token refresh (was redundant) + https://github.com/openid/AppAuth-iOS/pull/301 + +# 0.94.0 (2018-07-13) + +## Fixes +1. `form-urlencode` client ID and client secret in Authorization header + +## Added + +1. Samples have icons now! +2. Output trace logs by defining `_APPAUTHTRACE` + +# 0.93.0 (2018-06-26) + +## Notable Changes + +1. Implements OpenID Connect (ID Token handling) and the OpenID Connect + RP Certification test suite. + https://github.com/openid/AppAuth-iOS/pull/101 + +2. The `OIDAuthorizationUICoordinator` pattern was genericized to + support non-authorization external user-agent flows like logout + (though none are directly implemented by AppAuth, yet). + `OIDAuthorizationUICoordinator*` classes renamed to + `OIDExternalUserAgent*`. + https://github.com/openid/AppAuth-iOS/pull/196 + https://github.com/openid/AppAuth-iOS/pull/212 + See [Upgrading to 0.93.0](#upgrading-to-0930). + +3. Added custom browser support on iOS. Provides several + convenience implementations of alternative external user-agents on + iOS such as Chrome and Firefox. These are intended for + **enterprise use only**, where the app developers have greater + control over the operating environment and have special requirements + that require a custom browser like Chrome. + See the [code example](https://github.com/openid/AppAuth-iOS/issues/200#issuecomment-364610027). + https://github.com/openid/AppAuth-iOS/issues/200 + https://github.com/openid/AppAuth-iOS/pull/201 + +## Upgrading to 0.93.0 + +0.93.0 deprecates several methods. To update your code to avoid the +deprecated methods (which will be required for the 1.0.0 release), +you will need to make changes. + +If you implemented your own `OIDAuthorizationUICoordinator`, or called +the methods which accepted a `UICoordinator` instance, you will need to +update to the new method names. See the deprecation error messages +for the new methods to use in those cases. + +Most users who are using the convenience methods of AppAuth will only +need to make the following 3 minor changes to their AppDelegate: + +### Import: + +Change +```objc +@protocol OIDAuthorizationFlowSession; +``` +to +```objc +@protocol OIDExternalUserAgentSession; +``` + +### Property: + +Change +```objc +@property(nonatomic, strong, nullable) id currentAuthorizationFlow; +``` +to +```objc +@property(nonatomic, strong, nullable) idcurrentAuthorizationFlow; +``` + +### Implementation of `-(BOOL)application:openURL:options:` +Change +```objc +if ([_currentAuthorizationFlow resumeAuthorizationFlowWithURL:url]) { +``` +to +```objc +if ([_currentAuthorizationFlow resumeExternalUserAgentFlowWithURL:url]) { +``` + +See also the changes made to the sample which you can copy: +https://github.com/openid/AppAuth-iOS/commit/619bb7c7d5f83cc2ed19380d425ca8afa279644c?diff=unified + + +# 0.92.0 (2018-01-05) + +## Improvements + +1. Added an official Swift sample, and included Swift testing in the + continuous integration tests. + +# Pre 0.92.0 + +No changelog entries exist for changes prior to 2018, please review the +[git history](https://github.com/openid/AppAuth-iOS/commits/0.91.0). diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 51537d3a0..b319f9ddf 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -48,8 +48,7 @@ discussing your proposal, or email the ## Coding Standards The AppAuth library follows the -[Google Coding Style](https://google.github.io/styleguide/objcguide.xml) for -the Objective-C language. Please review your own code for adherence to the +[Google Objective-C Style Guide](https://google.github.io/styleguide/objcguide.html). Please review your own code for adherence to the standard. ## Pull Request Reviews diff --git a/CONTRIBUTORS b/CONTRIBUTORS index 1c44cfb28..06dbb5823 100644 --- a/CONTRIBUTORS +++ b/CONTRIBUTORS @@ -17,4 +17,7 @@ Rebecka Gulliksson David Waite Craig Lane https://github.com/ProjectLane Hernan Zalazar https://github.com/hzalaz - +Joseph Heenan https://github.com/jogu +Julien Bodet https://github.com/julienbodet +Tobias Schröpf https://github.com/schroepf +Dave MacLachlan https://github.com/dmaclach \ No newline at end of file diff --git a/DESIGN.md b/DESIGN.md new file mode 100644 index 000000000..ac2dcb87b --- /dev/null +++ b/DESIGN.md @@ -0,0 +1,120 @@ +# AppAuth Project Design Principles + +## About this Doc + +The goal of this doc is to define a scope for AppAuth that we can reference when rejecting or +accepting feature requests, and give clarity to extension creators for what features should be +developed outside of the core project. + +## What is AppAuth + +### OAuth and OpenID Connect standards that support native apps + +The goal of AppAuth is to provide a client library that follows best current practices for native +apps to use the OAuth and OpenID Connect authorization and authentication standards. We aim to +implement standards that are designed for, or work well with native apps and are in common use (and +will implement just those components of these standards that are commonly used). + +These standards are currently supported: +1. [OAuth 2.0](https://tools.ietf.org/html/rfc67490) +2. [Proof Key for Code Exchange by OAuth Public Clients (PKCE)](https://tools.ietf.org/html/rfc7636) +3. [OAuth 2.0 for Native Apps](https://tools.ietf.org/html/rfc8252) +4. [OpenID Connect Core 1.0](http://openid.net/specs/openid-connect-core-1_0.html) +5. [Open ID Connect Discovery 1.0](https://openid.net/specs/openid-connect-discovery-1_0.html) +6. [OpenID Connect Dynamic Client Registration 1.0](https://openid.net/specs/openid-connect-registration-1_0.html) + +Support for the following standards is also considered in-scope (see the below section on +prioritization): +1. [OAuth 2.0 Incremental Auth](https://tools.ietf.org/html/draft-ietf-oauth-incremental-auth) +2. [OAuth 2.0 Device Flow for Browserless and Input Constrained Devices](https://tools.ietf.org/html/rfc8628) +3. [OpenID Connect Front-Channel Logout 1.0](http://openid.net/specs/openid-connect-frontchannel-1_0.html) + +### Design Principle + +AppAuth aims to present as close to a 1:1 mapping of the spec as is possible, while following +language-specific idioms (like parameter capitalization). It performs some of the heavy lifting for +you so you don’t need to implement the spec yourself, but it does not hide the complexity of the +underlying specs. + +Providers who want extremely simple user-friendly libraries aimed at developers who know nothing +about authorization and authentication standards are encouraged to wrap AppAuth in their own +purpose-built libraries. + +### Extensibility + +AppAuth aims to be as extensible as possible. Just as you can extend OAuth by adding parameters to a +URL (for example), the same should be possible in AppAuth. + +This helps reduce the surface area of the library, as not everything needs to be hardcoded in as a +first-class citizen. + +## What is out-of-scope for AppAuth + +### Non-best-practice patterns + +AppAuth is a best-practice library, it’s why we built it. We will not support things like embedded +WebViews, that are not considered a best practice. Please don’t ask. + +### Implementing *every* spec parameter or branch + +AppAuth exposes some required and popular parameters directly in its data model. It is a non-goal to +offer this support for every single parameter in the specifications. Less popular parameters, or +ones that simply don’t need special handling can be passed using the “additionalParameters” dictionary. + +Likewise, some specifications document several different sub-protocols. We don’t aim to implement +every single branch of the spec. + +### Non-standard protocols and parameters + +Non-standard protocols and parameters are not supported by AppAuth. However, AppAuth, like the +specs it implements, is largely extensible and you may be able to achieve what you need through +these extension points. For example, simple additional parameters can be passed in the +“additionalParameters” dictionary, and the TokenRequest can support other grant types. + +By way of example, the tvOS device flow support (which may actually be considered in-scope one day) +was initially implemented on top of these extension points without needing to change AppAuth. + +### Provider-specific workarounds, hacks or features + +AppAuth implements the pure authentication and authorization standards. Where these standards are +clear in their meaning, errors in provider implementations are not supported or worked around by +AppAuth. + +Identity standards are well specified, and typically undergo years of review, including security +review before publication. If we accept every provider-specific idiosyncrasy then we are changing +the surface area in ways that are harder to maintain, and are less researched from a security +perspective. There is generally no reason providers can’t offer correct standards-based +implementations, so this is what we expect. + +When all providers follow the standards correctly, interoperability is improved for everyone. The +OpenID Connect foundation has been particularly active in supporting certification efforts to verify +implementations, and the test suits for these are available at no charge. + +If the spec itself has a bug that cannot be worked around, then changes should be proposed through +the IETF and/or OpenID Foundation channels. AppAuth may implement such proposals, even while they +are in the early stages, if they are well received. + +### Provider-specific examples + +As AppAuth is pure standards-based, there is no need for provider-specific samples. Instead, we +encourage providers who have proved their compliance with the relevant standards to provide a +customized readme so their users can configure their own samples. + +We also encourage providers to host their own AppAuth samples, in their own repositories. + +## A word on feature requests + +### Priorities + +Just because something is in-scope, does not mean that the maintainers of AppAuth will add support +for it, or rapidly integrate pull requests. + +The priority of AppAuth is stability and quality, not rapidly integrating feature requests. Pull +requests are thoroughly reviewed for style, the quality of the API, and future maintainability. + +### Review Time + +Expect review periods of 6 months to a year for integrating major new in-scope features. Our +priority as previously stated is stability and quality, not adding features as quickly as possible. +While you work with us to integrate your feature, we highly encourage you to maintain your own fork +so you and others can use the feature immediately – and gain valuable implementation experience. diff --git a/Examples/Example-iOS_ObjC-Carthage/Example.xcodeproj/project.pbxproj b/Examples/Example-iOS_ObjC-Carthage/Example.xcodeproj/project.pbxproj index 9318b369a..839e13e5b 100644 --- a/Examples/Example-iOS_ObjC-Carthage/Example.xcodeproj/project.pbxproj +++ b/Examples/Example-iOS_ObjC-Carthage/Example.xcodeproj/project.pbxproj @@ -7,6 +7,11 @@ objects = { /* Begin PBXBuildFile section */ + 06375C8521A4E46500338E3F /* AppAuthCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 06375C8421A4E46500338E3F /* AppAuthCore.framework */; }; + 06462457205ED68000072191 /* NotificationCenter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 06462456205ED68000072191 /* NotificationCenter.framework */; }; + 0646245B205ED68000072191 /* TodayViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 0646245A205ED68000072191 /* TodayViewController.m */; }; + 0646245E205ED68000072191 /* MainInterface.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 0646245C205ED68000072191 /* MainInterface.storyboard */; }; + 06462463205ED68000072191 /* Example_Extension.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 06462455205ED68000072191 /* Example_Extension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; 346E916E1C29D42800D3620B /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 346E916D1C29D42800D3620B /* main.m */; }; 346E91711C29D42800D3620B /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 346E91701C29D42800D3620B /* AppDelegate.m */; }; 346E91791C29D42800D3620B /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 346E91781C29D42800D3620B /* Assets.xcassets */; }; @@ -17,7 +22,40 @@ 5FEA25251E75C17E00C2D71B /* AppAuth.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5FEA25241E75C17E00C2D71B /* AppAuth.framework */; }; /* End PBXBuildFile section */ +/* Begin PBXContainerItemProxy section */ + 06462460205ED68000072191 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 346E91611C29D42800D3620B /* Project object */; + proxyType = 1; + remoteGlobalIDString = 06462454205ED68000072191; + remoteInfo = Example_Extension; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 06462462205ED68000072191 /* Embed App Extensions */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 13; + files = ( + 06462463205ED68000072191 /* Example_Extension.appex in Embed App Extensions */, + ); + name = "Embed App Extensions"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + /* Begin PBXFileReference section */ + 06375C8421A4E46500338E3F /* AppAuthCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppAuthCore.framework; path = Carthage/Build/iOS/AppAuthCore.framework; sourceTree = ""; }; + 06462455205ED68000072191 /* Example_Extension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = Example_Extension.appex; sourceTree = BUILT_PRODUCTS_DIR; }; + 06462456205ED68000072191 /* NotificationCenter.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = NotificationCenter.framework; path = System/Library/Frameworks/NotificationCenter.framework; sourceTree = SDKROOT; }; + 06462459205ED68000072191 /* TodayViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = TodayViewController.h; sourceTree = ""; }; + 0646245A205ED68000072191 /* TodayViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = TodayViewController.m; sourceTree = ""; }; + 0646245D205ED68000072191 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/MainInterface.storyboard; sourceTree = ""; }; + 0646245F205ED68000072191 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 06462467205EDB5400072191 /* Example.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Example.entitlements; sourceTree = ""; }; + 06462468205EDB9900072191 /* Example_Extension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Example_Extension.entitlements; sourceTree = ""; }; 346E91691C29D42800D3620B /* Example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Example.app; sourceTree = BUILT_PRODUCTS_DIR; }; 346E916D1C29D42800D3620B /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 346E916F1C29D42800D3620B /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; @@ -33,6 +71,15 @@ /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ + 06462452205ED68000072191 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 06375C8521A4E46500338E3F /* AppAuthCore.framework in Frameworks */, + 06462457205ED68000072191 /* NotificationCenter.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 346E91661C29D42800D3620B /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; @@ -45,11 +92,25 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 06462458205ED68000072191 /* Example_Extension */ = { + isa = PBXGroup; + children = ( + 06462459205ED68000072191 /* TodayViewController.h */, + 0646245A205ED68000072191 /* TodayViewController.m */, + 0646245C205ED68000072191 /* MainInterface.storyboard */, + 0646245F205ED68000072191 /* Info.plist */, + 06462468205EDB9900072191 /* Example_Extension.entitlements */, + ); + path = Example_Extension; + sourceTree = ""; + }; 341564001C487ABA00ECA3D9 /* Frameworks */ = { isa = PBXGroup; children = ( + 06375C8421A4E46500338E3F /* AppAuthCore.framework */, 5FEA25241E75C17E00C2D71B /* AppAuth.framework */, 346E91981C2A245000D3620B /* SafariServices.framework */, + 06462456205ED68000072191 /* NotificationCenter.framework */, ); name = Frameworks; sourceTree = ""; @@ -58,6 +119,7 @@ isa = PBXGroup; children = ( 346E916B1C29D42800D3620B /* Source */, + 06462458205ED68000072191 /* Example_Extension */, 341564001C487ABA00ECA3D9 /* Frameworks */, 346E916A1C29D42800D3620B /* Products */, ); @@ -67,6 +129,7 @@ isa = PBXGroup; children = ( 346E91691C29D42800D3620B /* Example.app */, + 06462455205ED68000072191 /* Example_Extension.appex */, ); name = Products; sourceTree = ""; @@ -83,6 +146,7 @@ 346E91781C29D42800D3620B /* Assets.xcassets */, 346E917A1C29D42800D3620B /* LaunchScreen.storyboard */, 346E917D1C29D42800D3620B /* Info.plist */, + 06462467205EDB5400072191 /* Example.entitlements */, ); path = Source; sourceTree = ""; @@ -90,6 +154,24 @@ /* End PBXGroup section */ /* Begin PBXNativeTarget section */ + 06462454205ED68000072191 /* Example_Extension */ = { + isa = PBXNativeTarget; + buildConfigurationList = 06462466205ED68000072191 /* Build configuration list for PBXNativeTarget "Example_Extension" */; + buildPhases = ( + 06462451205ED68000072191 /* Sources */, + 06462452205ED68000072191 /* Frameworks */, + 06462453205ED68000072191 /* Resources */, + 06375C8621A4E48800338E3F /* Carthage */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Example_Extension; + productName = Example_Extension; + productReference = 06462455205ED68000072191 /* Example_Extension.appex */; + productType = "com.apple.product-type.app-extension"; + }; 346E91681C29D42800D3620B /* Example */ = { isa = PBXNativeTarget; buildConfigurationList = 346E91801C29D42800D3620B /* Build configuration list for PBXNativeTarget "Example" */; @@ -98,10 +180,12 @@ 346E91661C29D42800D3620B /* Frameworks */, 346E91671C29D42800D3620B /* Resources */, 5FEA25261E75C1CF00C2D71B /* Carthage */, + 06462462205ED68000072191 /* Embed App Extensions */, ); buildRules = ( ); dependencies = ( + 06462461205ED68000072191 /* PBXTargetDependency */, ); name = Example; productName = Example; @@ -117,8 +201,22 @@ LastUpgradeCheck = 0720; ORGANIZATIONNAME = "William Denniss"; TargetAttributes = { + 06462454205ED68000072191 = { + CreatedOnToolsVersion = 9.2; + ProvisioningStyle = Automatic; + SystemCapabilities = { + com.apple.ApplicationGroups.iOS = { + enabled = 1; + }; + }; + }; 346E91681C29D42800D3620B = { CreatedOnToolsVersion = 7.2; + SystemCapabilities = { + com.apple.ApplicationGroups.iOS = { + enabled = 1; + }; + }; }; }; }; @@ -136,11 +234,20 @@ projectRoot = ""; targets = ( 346E91681C29D42800D3620B /* Example */, + 06462454205ED68000072191 /* Example_Extension */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ + 06462453205ED68000072191 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 0646245E205ED68000072191 /* MainInterface.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 346E91671C29D42800D3620B /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -154,6 +261,25 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ + 06375C8621A4E48800338E3F /* Carthage */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "$(SRCROOT)/Carthage/Build/iOS/AppAuth.framework", + ); + name = Carthage; + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/usr/local/bin/carthage copy-frameworks\n"; + }; 5FEA25261E75C1CF00C2D71B /* Carthage */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; @@ -167,11 +293,19 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "/usr/local/bin/carthage copy-frameworks"; + shellScript = "/usr/local/bin/carthage copy-frameworks\n"; }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ + 06462451205ED68000072191 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 0646245B205ED68000072191 /* TodayViewController.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 346E91651C29D42800D3620B /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -184,7 +318,23 @@ }; /* End PBXSourcesBuildPhase section */ +/* Begin PBXTargetDependency section */ + 06462461205ED68000072191 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 06462454205ED68000072191 /* Example_Extension */; + targetProxy = 06462460205ED68000072191 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + /* Begin PBXVariantGroup section */ + 0646245C205ED68000072191 /* MainInterface.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 0646245D205ED68000072191 /* Base */, + ); + name = MainInterface.storyboard; + sourceTree = ""; + }; 346E917A1C29D42800D3620B /* LaunchScreen.storyboard */ = { isa = PBXVariantGroup; children = ( @@ -196,6 +346,76 @@ /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ + 06462464205ED68000072191 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_ENTITLEMENTS = Example_Extension/Example_Extension.entitlements; + CODE_SIGN_IDENTITY = "iPhone Developer"; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = ""; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Carthage/Build/iOS", + ); + GCC_C_LANGUAGE_STANDARD = gnu11; + INFOPLIST_FILE = Example_Extension/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = "net.openid.appauth.Example.Example-Extension"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 06462465205ED68000072191 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_ENTITLEMENTS = Example_Extension/Example_Extension.entitlements; + CODE_SIGN_IDENTITY = "iPhone Developer"; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = ""; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Carthage/Build/iOS", + ); + GCC_C_LANGUAGE_STANDARD = gnu11; + INFOPLIST_FILE = Example_Extension/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = "net.openid.appauth.Example.Example-Extension"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; 346E917E1C29D42800D3620B /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { @@ -286,6 +506,9 @@ isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_ENTITLEMENTS = Source/Example.entitlements; + CODE_SIGN_IDENTITY = "iPhone Developer"; + DEVELOPMENT_TEAM = ""; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", "$(PROJECT_DIR)/Carthage/Build/iOS", @@ -303,6 +526,9 @@ isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_ENTITLEMENTS = Source/Example.entitlements; + CODE_SIGN_IDENTITY = "iPhone Developer"; + DEVELOPMENT_TEAM = ""; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", "$(PROJECT_DIR)/Carthage/Build/iOS", @@ -319,6 +545,15 @@ /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ + 06462466205ED68000072191 /* Build configuration list for PBXNativeTarget "Example_Extension" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 06462464205ED68000072191 /* Debug */, + 06462465205ED68000072191 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; 346E91641C29D42800D3620B /* Build configuration list for PBXProject "Example" */ = { isa = XCConfigurationList; buildConfigurations = ( diff --git a/Examples/Example-iOS_ObjC-Carthage/Example.xcodeproj/xcshareddata/xcschemes/Example_Extension.xcscheme b/Examples/Example-iOS_ObjC-Carthage/Example.xcodeproj/xcshareddata/xcschemes/Example_Extension.xcscheme new file mode 100644 index 000000000..7dff3414d --- /dev/null +++ b/Examples/Example-iOS_ObjC-Carthage/Example.xcodeproj/xcshareddata/xcschemes/Example_Extension.xcscheme @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Examples/Example-iOS_ObjC-Carthage/Example_Extension/Base.lproj/MainInterface.storyboard b/Examples/Example-iOS_ObjC-Carthage/Example_Extension/Base.lproj/MainInterface.storyboard new file mode 100644 index 000000000..c2f74e69f --- /dev/null +++ b/Examples/Example-iOS_ObjC-Carthage/Example_Extension/Base.lproj/MainInterface.storyboard @@ -0,0 +1,68 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Examples/Example-iOS_ObjC-Carthage/Example_Extension/Example_Extension.entitlements b/Examples/Example-iOS_ObjC-Carthage/Example_Extension/Example_Extension.entitlements new file mode 100644 index 000000000..c250a2746 --- /dev/null +++ b/Examples/Example-iOS_ObjC-Carthage/Example_Extension/Example_Extension.entitlements @@ -0,0 +1,10 @@ + + + + + com.apple.security.application-groups + + group.net.openid.appauth.Example + + + diff --git a/Examples/Example-iOS_ObjC-Carthage/Example_Extension/Info.plist b/Examples/Example-iOS_ObjC-Carthage/Example_Extension/Info.plist new file mode 100644 index 000000000..6ea3c3dc6 --- /dev/null +++ b/Examples/Example-iOS_ObjC-Carthage/Example_Extension/Info.plist @@ -0,0 +1,31 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + Example_Extension + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + XPC! + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + NSExtension + + NSExtensionMainStoryboard + MainInterface + NSExtensionPointIdentifier + com.apple.widget-extension + + + diff --git a/Examples/Example-iOS_ObjC-Carthage/Example_Extension/TodayViewController.h b/Examples/Example-iOS_ObjC-Carthage/Example_Extension/TodayViewController.h new file mode 100644 index 000000000..9053ba197 --- /dev/null +++ b/Examples/Example-iOS_ObjC-Carthage/Example_Extension/TodayViewController.h @@ -0,0 +1,24 @@ +/*! @file TodayViewController.h + @brief AppAuth iOS SDK Example + @copyright + Copyright 2015 Google Inc. All Rights Reserved. + @copydetails + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#import + +@interface TodayViewController : UIViewController + +@end + diff --git a/Examples/Example-iOS_ObjC-Carthage/Example_Extension/TodayViewController.m b/Examples/Example-iOS_ObjC-Carthage/Example_Extension/TodayViewController.m new file mode 100644 index 000000000..82681feb5 --- /dev/null +++ b/Examples/Example-iOS_ObjC-Carthage/Example_Extension/TodayViewController.m @@ -0,0 +1,223 @@ +/*! @file TodayViewController.m + @brief AppAuth iOS SDK Example + @copyright + Copyright 2015 Google Inc. All Rights Reserved. + @copydetails + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#import "TodayViewController.h" +#import +#import + +static NSString *const kAppAuthExampleAuthStateKey = @"authState"; + +@interface TodayViewController () + +@property(nonatomic, readonly, nullable) OIDAuthState *authState; +@property (weak, nonatomic) IBOutlet UITextView *logTextView; + +@end + +@implementation TodayViewController + +- (void)viewDidLoad { + [super viewDidLoad]; + + if (@available(iOS 10.0, *)) { + [self.extensionContext setWidgetLargestAvailableDisplayMode:NCWidgetDisplayModeExpanded]; + } else { + self.preferredContentSize = CGSizeMake(0, 400.0); + } + + [self loadState]; +} + +- (void)widgetActiveDisplayModeDidChange:(NCWidgetDisplayMode)activeDisplayMode + withMaximumSize:(CGSize)maxSize NS_AVAILABLE_IOS(10.0) { + + if (activeDisplayMode == NCWidgetDisplayModeExpanded) { + self.preferredContentSize = CGSizeMake(maxSize.width, 400.0); + } else if (activeDisplayMode == NCWidgetDisplayModeCompact) { + self.preferredContentSize = maxSize; + } +} + +- (void)widgetPerformUpdateWithCompletionHandler:(void (^)(NCUpdateResult))completionHandler { + // Perform any setup necessary in order to update the view. + + // If an error is encountered, use NCUpdateResultFailed + // If there's no update required, use NCUpdateResultNoData + // If there's an update, use NCUpdateResultNewData + + completionHandler(NCUpdateResultNewData); +} + +/*! @brief Loads the @c OIDAuthState from @c NSUSerDefaults. + */ +- (void)loadState { + // loads OIDAuthState from NSUSerDefaults + NSUserDefaults* userDefaults = [[NSUserDefaults alloc] initWithSuiteName:@"group.net.openid.appauth.Example"]; + NSData *archivedAuthState = [userDefaults objectForKey:kAppAuthExampleAuthStateKey]; + OIDAuthState *authState = [NSKeyedUnarchiver unarchiveObjectWithData:archivedAuthState]; + [self setAuthState:authState]; +} + +/*! @brief Saves the @c OIDAuthState to @c NSUSerDefaults. + */ +- (void)saveState { + // for production usage consider using the OS Keychain instead + NSUserDefaults* userDefaults = [[NSUserDefaults alloc] initWithSuiteName:@"group.net.openid.appauth.Example"]; + NSData *archivedAuthState = [NSKeyedArchiver archivedDataWithRootObject:_authState]; + [userDefaults setObject:archivedAuthState + forKey:kAppAuthExampleAuthStateKey]; + [userDefaults synchronize]; +} + +- (void)setAuthState:(nullable OIDAuthState *)authState { + if (_authState == authState) { + return; + } + _authState = authState; + _authState.stateChangeDelegate = self; + [self stateChanged]; +} + +- (void)stateChanged { + [self saveState]; +} + +- (void)didChangeState:(OIDAuthState *)state { + [self stateChanged]; +} + +- (IBAction)getUserInfo:(UIButton *)sender { + NSURL *userinfoEndpoint = + _authState.lastAuthorizationResponse.request.configuration.discoveryDocument.userinfoEndpoint; + if (!userinfoEndpoint) { + [self logMessage:@"Userinfo endpoint not declared in discovery document"]; + return; + } + NSString *currentAccessToken = _authState.lastTokenResponse.accessToken; + + [self logMessage:@"Performing userinfo request"]; + + [_authState performActionWithFreshTokens:^(NSString *_Nonnull accessToken, + NSString *_Nonnull idToken, + NSError *_Nullable error) { + if (error) { + [self logMessage:@"Error fetching fresh tokens: %@", [error localizedDescription]]; + return; + } + + // log whether a token refresh occurred + if (![currentAccessToken isEqual:accessToken]) { + [self logMessage:@"Access token was refreshed automatically (%@ to %@)", + currentAccessToken, + accessToken]; + } else { + [self logMessage:@"Access token was fresh and not updated [%@]", accessToken]; + } + + // creates request to the userinfo endpoint, with access token in the Authorization header + NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:userinfoEndpoint]; + NSString *authorizationHeaderValue = [NSString stringWithFormat:@"Bearer %@", accessToken]; + [request addValue:authorizationHeaderValue forHTTPHeaderField:@"Authorization"]; + + NSURLSessionConfiguration *configuration = + [NSURLSessionConfiguration defaultSessionConfiguration]; + NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration + delegate:nil + delegateQueue:nil]; + + // performs HTTP request + NSURLSessionDataTask *postDataTask = + [session dataTaskWithRequest:request + completionHandler:^(NSData *_Nullable data, + NSURLResponse *_Nullable response, + NSError *_Nullable error) { + dispatch_async(dispatch_get_main_queue(), ^() { + if (error) { + [self logMessage:@"HTTP request failed %@", error]; + return; + } + if (![response isKindOfClass:[NSHTTPURLResponse class]]) { + [self logMessage:@"Non-HTTP response"]; + return; + } + + NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response; + id jsonDictionaryOrArray = + [NSJSONSerialization JSONObjectWithData:data options:0 error:NULL]; + + if (httpResponse.statusCode != 200) { + // server replied with an error + NSString *responseText = [[NSString alloc] initWithData:data + encoding:NSUTF8StringEncoding]; + if (httpResponse.statusCode == 401) { + // "401 Unauthorized" generally indicates there is an issue with the authorization + // grant. Puts OIDAuthState into an error state. + NSError *oauthError = + [OIDErrorUtilities resourceServerAuthorizationErrorWithCode:0 + errorResponse:jsonDictionaryOrArray + underlyingError:error]; + [_authState updateWithAuthorizationError:oauthError]; + // log error + [self logMessage:@"Authorization Error (%@). Response: %@", oauthError, responseText]; + } else { + [self logMessage:@"HTTP: %d. Response: %@", + (int)httpResponse.statusCode, + responseText]; + } + return; + } + + // success response + [self logMessage:@"Success: %@", jsonDictionaryOrArray]; + }); + }]; + + [postDataTask resume]; + }]; +} + +/*! @brief Logs a message to stdout and the textfield. + @param format The format string and arguments. + */ +- (void)logMessage:(NSString *)format, ... NS_FORMAT_FUNCTION(1,2) { + // gets message as string + va_list argp; + va_start(argp, format); + NSString *log = [[NSString alloc] initWithFormat:format arguments:argp]; + va_end(argp); + + // outputs to stdout + NSLog(@"%@", log); + + // appends to output log + NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init]; + dateFormatter.dateFormat = @"hh:mm:ss"; + NSString *dateString = [dateFormatter stringFromDate:[NSDate date]]; + _logTextView.text = [NSString stringWithFormat:@"%@%@%@: %@", + _logTextView.text, + ([_logTextView.text length] > 0) ? @"\n" : @"", + dateString, + log]; +} + +- (IBAction)clearLogTextView:(UIButton *)sender { + _logTextView.text = @""; +} + +@end + diff --git a/Examples/Example-iOS_ObjC-Carthage/Source/AppAuthExampleViewController.m b/Examples/Example-iOS_ObjC-Carthage/Source/AppAuthExampleViewController.m index 68354c436..4d58cf9d2 100644 --- a/Examples/Example-iOS_ObjC-Carthage/Source/AppAuthExampleViewController.m +++ b/Examples/Example-iOS_ObjC-Carthage/Source/AppAuthExampleViewController.m @@ -102,18 +102,19 @@ - (void)viewDidLoad { */ - (void)saveState { // for production usage consider using the OS Keychain instead + NSUserDefaults* userDefaults = [[NSUserDefaults alloc] initWithSuiteName:@"group.net.openid.appauth.Example"]; NSData *archivedAuthState = [ NSKeyedArchiver archivedDataWithRootObject:_authState]; - [[NSUserDefaults standardUserDefaults] setObject:archivedAuthState - forKey:kAppAuthExampleAuthStateKey]; - [[NSUserDefaults standardUserDefaults] synchronize]; + [userDefaults setObject:archivedAuthState + forKey:kAppAuthExampleAuthStateKey]; + [userDefaults synchronize]; } /*! @brief Loads the @c OIDAuthState from @c NSUSerDefaults. */ - (void)loadState { // loads OIDAuthState from NSUSerDefaults - NSData *archivedAuthState = - [[NSUserDefaults standardUserDefaults] objectForKey:kAppAuthExampleAuthStateKey]; + NSUserDefaults* userDefaults = [[NSUserDefaults alloc] initWithSuiteName:@"group.net.openid.appauth.Example"]; + NSData *archivedAuthState = [userDefaults objectForKey:kAppAuthExampleAuthStateKey]; OIDAuthState *authState = [NSKeyedUnarchiver unarchiveObjectWithData:archivedAuthState]; [self setAuthState:authState]; } @@ -176,7 +177,8 @@ - (void)doClientRegistration:(OIDServiceConfiguration *)configuration grantTypes:nil subjectType:nil tokenEndpointAuthMethod:@"client_secret_post" - additionalParameters:nil]; + additionalParameters:nil + additionalHeaders:nil]; // performs registration request [self logMessage:@"Initiating registration request"]; diff --git a/Examples/Example-iOS_ObjC-Carthage/Source/AppDelegate.h b/Examples/Example-iOS_ObjC-Carthage/Source/AppDelegate.h index 4fb93255b..ece00038d 100644 --- a/Examples/Example-iOS_ObjC-Carthage/Source/AppDelegate.h +++ b/Examples/Example-iOS_ObjC-Carthage/Source/AppDelegate.h @@ -17,7 +17,7 @@ */ #import -@protocol OIDAuthorizationFlowSession; +@protocol OIDExternalUserAgentSession; /*! @brief The example application's delegate. */ @@ -32,7 +32,7 @@ incoming URL on UIApplicationDelegate.application:openURL:options:. This property will be nil, except when an authorization flow is in progress. */ -@property(nonatomic, strong, nullable) id currentAuthorizationFlow; +@property(nonatomic, strong, nullable) id currentAuthorizationFlow; @end diff --git a/Examples/Example-iOS_ObjC-Carthage/Source/AppDelegate.m b/Examples/Example-iOS_ObjC-Carthage/Source/AppDelegate.m index e9dfd54b1..716270381 100644 --- a/Examples/Example-iOS_ObjC-Carthage/Source/AppDelegate.m +++ b/Examples/Example-iOS_ObjC-Carthage/Source/AppDelegate.m @@ -49,7 +49,7 @@ - (BOOL)application:(UIApplication *)app options:(NSDictionary *)options { // Sends the URL to the current authorization flow (if any) which will process it if it relates to // an authorization response. - if ([_currentAuthorizationFlow resumeAuthorizationFlowWithURL:url]) { + if ([_currentAuthorizationFlow resumeExternalUserAgentFlowWithURL:url]) { _currentAuthorizationFlow = nil; return YES; } diff --git a/Examples/Example-iOS_ObjC-Carthage/Source/Assets.xcassets/AppIcon.appiconset/AppAuth_Icon_1024.png b/Examples/Example-iOS_ObjC-Carthage/Source/Assets.xcassets/AppIcon.appiconset/AppAuth_Icon_1024.png new file mode 100644 index 000000000..9d74d6575 Binary files /dev/null and b/Examples/Example-iOS_ObjC-Carthage/Source/Assets.xcassets/AppIcon.appiconset/AppAuth_Icon_1024.png differ diff --git a/Examples/Example-iOS_ObjC-Carthage/Source/Assets.xcassets/AppIcon.appiconset/AppAuth_Icon_120.png b/Examples/Example-iOS_ObjC-Carthage/Source/Assets.xcassets/AppIcon.appiconset/AppAuth_Icon_120.png new file mode 100644 index 000000000..f73edc78a Binary files /dev/null and b/Examples/Example-iOS_ObjC-Carthage/Source/Assets.xcassets/AppIcon.appiconset/AppAuth_Icon_120.png differ diff --git a/Examples/Example-iOS_ObjC-Carthage/Source/Assets.xcassets/AppIcon.appiconset/AppAuth_Icon_152.png b/Examples/Example-iOS_ObjC-Carthage/Source/Assets.xcassets/AppIcon.appiconset/AppAuth_Icon_152.png new file mode 100644 index 000000000..bdd5de3c3 Binary files /dev/null and b/Examples/Example-iOS_ObjC-Carthage/Source/Assets.xcassets/AppIcon.appiconset/AppAuth_Icon_152.png differ diff --git a/Examples/Example-iOS_ObjC-Carthage/Source/Assets.xcassets/AppIcon.appiconset/AppAuth_Icon_167.png b/Examples/Example-iOS_ObjC-Carthage/Source/Assets.xcassets/AppIcon.appiconset/AppAuth_Icon_167.png new file mode 100644 index 000000000..1bb56407d Binary files /dev/null and b/Examples/Example-iOS_ObjC-Carthage/Source/Assets.xcassets/AppIcon.appiconset/AppAuth_Icon_167.png differ diff --git a/Examples/Example-iOS_ObjC-Carthage/Source/Assets.xcassets/AppIcon.appiconset/AppAuth_Icon_180.png b/Examples/Example-iOS_ObjC-Carthage/Source/Assets.xcassets/AppIcon.appiconset/AppAuth_Icon_180.png new file mode 100644 index 000000000..17535eb04 Binary files /dev/null and b/Examples/Example-iOS_ObjC-Carthage/Source/Assets.xcassets/AppIcon.appiconset/AppAuth_Icon_180.png differ diff --git a/Examples/Example-iOS_ObjC-Carthage/Source/Assets.xcassets/AppIcon.appiconset/AppAuth_Icon_76.png b/Examples/Example-iOS_ObjC-Carthage/Source/Assets.xcassets/AppIcon.appiconset/AppAuth_Icon_76.png new file mode 100644 index 000000000..dc824d5e7 Binary files /dev/null and b/Examples/Example-iOS_ObjC-Carthage/Source/Assets.xcassets/AppIcon.appiconset/AppAuth_Icon_76.png differ diff --git a/Examples/Example-iOS_ObjC-Carthage/Source/Assets.xcassets/AppIcon.appiconset/Contents.json b/Examples/Example-iOS_ObjC-Carthage/Source/Assets.xcassets/AppIcon.appiconset/Contents.json index eeea76c2d..0b6ca5edb 100644 --- a/Examples/Example-iOS_ObjC-Carthage/Source/Assets.xcassets/AppIcon.appiconset/Contents.json +++ b/Examples/Example-iOS_ObjC-Carthage/Source/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -1,5 +1,15 @@ { "images" : [ + { + "idiom" : "iphone", + "size" : "20x20", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "20x20", + "scale" : "3x" + }, { "idiom" : "iphone", "size" : "29x29", @@ -21,15 +31,27 @@ "scale" : "3x" }, { - "idiom" : "iphone", "size" : "60x60", + "idiom" : "iphone", + "filename" : "AppAuth_Icon_120.png", "scale" : "2x" }, { - "idiom" : "iphone", "size" : "60x60", + "idiom" : "iphone", + "filename" : "AppAuth_Icon_180.png", "scale" : "3x" }, + { + "idiom" : "ipad", + "size" : "20x20", + "scale" : "1x" + }, + { + "idiom" : "ipad", + "size" : "20x20", + "scale" : "2x" + }, { "idiom" : "ipad", "size" : "29x29", @@ -51,19 +73,28 @@ "scale" : "2x" }, { - "idiom" : "ipad", "size" : "76x76", + "idiom" : "ipad", + "filename" : "AppAuth_Icon_76.png", "scale" : "1x" }, { - "idiom" : "ipad", "size" : "76x76", + "idiom" : "ipad", + "filename" : "AppAuth_Icon_152.png", "scale" : "2x" }, { - "idiom" : "ipad", "size" : "83.5x83.5", + "idiom" : "ipad", + "filename" : "AppAuth_Icon_167.png", "scale" : "2x" + }, + { + "size" : "1024x1024", + "idiom" : "ios-marketing", + "filename" : "AppAuth_Icon_1024.png", + "scale" : "1x" } ], "info" : { diff --git a/Examples/Example-iOS_ObjC-Carthage/Source/Example.entitlements b/Examples/Example-iOS_ObjC-Carthage/Source/Example.entitlements new file mode 100644 index 000000000..c250a2746 --- /dev/null +++ b/Examples/Example-iOS_ObjC-Carthage/Source/Example.entitlements @@ -0,0 +1,10 @@ + + + + + com.apple.security.application-groups + + group.net.openid.appauth.Example + + + diff --git a/Examples/Example-iOS_ObjC/Example-iOS_ObjC.xcodeproj/project.pbxproj b/Examples/Example-iOS_ObjC/Example-iOS_ObjC.xcodeproj/project.pbxproj index 0e1a19c74..7f3f2de68 100644 --- a/Examples/Example-iOS_ObjC/Example-iOS_ObjC.xcodeproj/project.pbxproj +++ b/Examples/Example-iOS_ObjC/Example-iOS_ObjC.xcodeproj/project.pbxproj @@ -7,6 +7,10 @@ objects = { /* Begin PBXBuildFile section */ + 06D4812F2055C3D400D9DC32 /* NotificationCenter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 06D4812E2055C3D400D9DC32 /* NotificationCenter.framework */; }; + 06D481332055C3D400D9DC32 /* TodayViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 06D481322055C3D400D9DC32 /* TodayViewController.m */; }; + 06D481362055C3D400D9DC32 /* MainInterface.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 06D481342055C3D400D9DC32 /* MainInterface.storyboard */; }; + 06D4813A2055C3D400D9DC32 /* Example-iOS_ObjC_Extension.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 06D4812D2055C3D400D9DC32 /* Example-iOS_ObjC_Extension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; 341310AA1E6DEF7000D5DEE5 /* AppAuthExampleTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 341310A91E6DEF7000D5DEE5 /* AppAuthExampleTests.m */; }; 346E916E1C29D42800D3620B /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 346E916D1C29D42800D3620B /* main.m */; }; 346E91711C29D42800D3620B /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 346E91701C29D42800D3620B /* AppDelegate.m */; }; @@ -15,10 +19,18 @@ 346E91991C2A245000D3620B /* SafariServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 346E91981C2A245000D3620B /* SafariServices.framework */; }; 34CB09BD1C42007600A54261 /* AppAuthExampleViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 34CB09BB1C42007600A54261 /* AppAuthExampleViewController.m */; }; 34CB09BE1C42007600A54261 /* AppAuthExampleViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 34CB09BC1C42007600A54261 /* AppAuthExampleViewController.xib */; }; - E46F8589CE9E5DDFA69D835B /* libPods-Example-iOS_ObjC.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 8B61A918CBE7B8142CD7D8B3 /* libPods-Example-iOS_ObjC.a */; }; + BE28448F8FD76A9C12A30150 /* Pods_Example_iOS_ObjC_Extension.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3CC004951DF03519C52500EC /* Pods_Example_iOS_ObjC_Extension.framework */; }; + C46B36D00F282572B5747D71 /* Pods_Example_iOS_ObjC.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 43D84745AE558B974A2E64F7 /* Pods_Example_iOS_ObjC.framework */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ + 06D481382055C3D400D9DC32 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 346E91611C29D42800D3620B /* Project object */; + proxyType = 1; + remoteGlobalIDString = 06D4812C2055C3D400D9DC32; + remoteInfo = "Example-iOS_ObjC_Extension"; + }; 341310AC1E6DEF7000D5DEE5 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 346E91611C29D42800D3620B /* Project object */; @@ -28,7 +40,29 @@ }; /* End PBXContainerItemProxy section */ +/* Begin PBXCopyFilesBuildPhase section */ + 06D4813E2055C3D400D9DC32 /* Embed App Extensions */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 13; + files = ( + 06D4813A2055C3D400D9DC32 /* Example-iOS_ObjC_Extension.appex in Embed App Extensions */, + ); + name = "Embed App Extensions"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + /* Begin PBXFileReference section */ + 06462428205EB35400072191 /* Example-iOS_ObjC.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "Example-iOS_ObjC.entitlements"; sourceTree = ""; }; + 06462429205EB37A00072191 /* Example-iOS_ObjC_Extension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "Example-iOS_ObjC_Extension.entitlements"; sourceTree = ""; }; + 06D4812D2055C3D400D9DC32 /* Example-iOS_ObjC_Extension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = "Example-iOS_ObjC_Extension.appex"; sourceTree = BUILT_PRODUCTS_DIR; }; + 06D4812E2055C3D400D9DC32 /* NotificationCenter.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = NotificationCenter.framework; path = System/Library/Frameworks/NotificationCenter.framework; sourceTree = SDKROOT; }; + 06D481312055C3D400D9DC32 /* TodayViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = TodayViewController.h; sourceTree = ""; }; + 06D481322055C3D400D9DC32 /* TodayViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = TodayViewController.m; sourceTree = ""; }; + 06D481352055C3D400D9DC32 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/MainInterface.storyboard; sourceTree = ""; }; + 06D481372055C3D400D9DC32 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 09795EAF079B07A1781675D9 /* libPods-Example.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Example.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 0D29B2A58C931A5D41332144 /* Pods-Example-iOS_ObjC.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Example-iOS_ObjC.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Example-iOS_ObjC/Pods-Example-iOS_ObjC.debug.xcconfig"; sourceTree = ""; }; 341310A71E6DEF7000D5DEE5 /* AppAuthExample-iOS_ObjCTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "AppAuthExample-iOS_ObjCTests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -46,13 +80,25 @@ 34CB09BA1C42007600A54261 /* AppAuthExampleViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppAuthExampleViewController.h; sourceTree = ""; }; 34CB09BB1C42007600A54261 /* AppAuthExampleViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppAuthExampleViewController.m; sourceTree = ""; }; 34CB09BC1C42007600A54261 /* AppAuthExampleViewController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = AppAuthExampleViewController.xib; sourceTree = ""; }; - 8B61A918CBE7B8142CD7D8B3 /* libPods-Example-iOS_ObjC.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Example-iOS_ObjC.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + 3A18DF082AC2FA0980C9DE57 /* Pods-Example-iOS_ObjC_Extension.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Example-iOS_ObjC_Extension.release.xcconfig"; path = "Pods/Target Support Files/Pods-Example-iOS_ObjC_Extension/Pods-Example-iOS_ObjC_Extension.release.xcconfig"; sourceTree = ""; }; + 3CC004951DF03519C52500EC /* Pods_Example_iOS_ObjC_Extension.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Example_iOS_ObjC_Extension.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 43D84745AE558B974A2E64F7 /* Pods_Example_iOS_ObjC.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Example_iOS_ObjC.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 86C7660572AE6FF7A4D1592A /* Pods-Example-iOS_ObjC_Extension.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Example-iOS_ObjC_Extension.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Example-iOS_ObjC_Extension/Pods-Example-iOS_ObjC_Extension.debug.xcconfig"; sourceTree = ""; }; C4C31DB4A4928F246AA03805 /* Pods-Example.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Example.release.xcconfig"; path = "Pods/Target Support Files/Pods-Example/Pods-Example.release.xcconfig"; sourceTree = ""; }; D9867DC6FA9089CD613D4728 /* Pods-Example.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Example.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Example/Pods-Example.debug.xcconfig"; sourceTree = ""; }; ECBCCC4A1A779C83C72044F2 /* Pods-Example-iOS_ObjC.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Example-iOS_ObjC.release.xcconfig"; path = "Pods/Target Support Files/Pods-Example-iOS_ObjC/Pods-Example-iOS_ObjC.release.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ + 06D4812A2055C3D400D9DC32 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 06D4812F2055C3D400D9DC32 /* NotificationCenter.framework in Frameworks */, + BE28448F8FD76A9C12A30150 /* Pods_Example_iOS_ObjC_Extension.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 341310A41E6DEF7000D5DEE5 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; @@ -65,13 +111,25 @@ buildActionMask = 2147483647; files = ( 346E91991C2A245000D3620B /* SafariServices.framework in Frameworks */, - E46F8589CE9E5DDFA69D835B /* libPods-Example-iOS_ObjC.a in Frameworks */, + C46B36D00F282572B5747D71 /* Pods_Example_iOS_ObjC.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 06D481302055C3D400D9DC32 /* Example-iOS_ObjC_Extension */ = { + isa = PBXGroup; + children = ( + 06D481312055C3D400D9DC32 /* TodayViewController.h */, + 06D481322055C3D400D9DC32 /* TodayViewController.m */, + 06D481342055C3D400D9DC32 /* MainInterface.storyboard */, + 06D481372055C3D400D9DC32 /* Info.plist */, + 06462429205EB37A00072191 /* Example-iOS_ObjC_Extension.entitlements */, + ); + path = "Example-iOS_ObjC_Extension"; + sourceTree = ""; + }; 341310A81E6DEF7000D5DEE5 /* Tests */ = { isa = PBXGroup; children = ( @@ -87,7 +145,9 @@ 3474C8DD1DFCB08E00F22B34 /* libAppAuth-iOS.a */, 346E91981C2A245000D3620B /* SafariServices.framework */, 09795EAF079B07A1781675D9 /* libPods-Example.a */, - 8B61A918CBE7B8142CD7D8B3 /* libPods-Example-iOS_ObjC.a */, + 06D4812E2055C3D400D9DC32 /* NotificationCenter.framework */, + 43D84745AE558B974A2E64F7 /* Pods_Example_iOS_ObjC.framework */, + 3CC004951DF03519C52500EC /* Pods_Example_iOS_ObjC_Extension.framework */, ); name = Frameworks; sourceTree = ""; @@ -97,6 +157,7 @@ children = ( 346E916B1C29D42800D3620B /* Source */, 341310A81E6DEF7000D5DEE5 /* Tests */, + 06D481302055C3D400D9DC32 /* Example-iOS_ObjC_Extension */, 341564001C487ABA00ECA3D9 /* Frameworks */, 346E916A1C29D42800D3620B /* Products */, 6DB0B0125441549B9E4A3E6C /* Pods */, @@ -108,6 +169,7 @@ children = ( 346E91691C29D42800D3620B /* Example-iOS_ObjC.app */, 341310A71E6DEF7000D5DEE5 /* AppAuthExample-iOS_ObjCTests.xctest */, + 06D4812D2055C3D400D9DC32 /* Example-iOS_ObjC_Extension.appex */, ); name = Products; sourceTree = ""; @@ -124,6 +186,7 @@ 346E91781C29D42800D3620B /* Assets.xcassets */, 346E917A1C29D42800D3620B /* LaunchScreen.storyboard */, 346E917D1C29D42800D3620B /* Info.plist */, + 06462428205EB35400072191 /* Example-iOS_ObjC.entitlements */, ); path = Source; sourceTree = ""; @@ -135,6 +198,8 @@ C4C31DB4A4928F246AA03805 /* Pods-Example.release.xcconfig */, 0D29B2A58C931A5D41332144 /* Pods-Example-iOS_ObjC.debug.xcconfig */, ECBCCC4A1A779C83C72044F2 /* Pods-Example-iOS_ObjC.release.xcconfig */, + 86C7660572AE6FF7A4D1592A /* Pods-Example-iOS_ObjC_Extension.debug.xcconfig */, + 3A18DF082AC2FA0980C9DE57 /* Pods-Example-iOS_ObjC_Extension.release.xcconfig */, ); name = Pods; sourceTree = ""; @@ -142,6 +207,24 @@ /* End PBXGroup section */ /* Begin PBXNativeTarget section */ + 06D4812C2055C3D400D9DC32 /* Example-iOS_ObjC_Extension */ = { + isa = PBXNativeTarget; + buildConfigurationList = 06D4813D2055C3D400D9DC32 /* Build configuration list for PBXNativeTarget "Example-iOS_ObjC_Extension" */; + buildPhases = ( + 836219F293F319703A16E68D /* [CP] Check Pods Manifest.lock */, + 06D481292055C3D400D9DC32 /* Sources */, + 06D4812A2055C3D400D9DC32 /* Frameworks */, + 06D4812B2055C3D400D9DC32 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = "Example-iOS_ObjC_Extension"; + productName = "Example-iOS_ObjC_Extension"; + productReference = 06D4812D2055C3D400D9DC32 /* Example-iOS_ObjC_Extension.appex */; + productType = "com.apple.product-type.app-extension"; + }; 341310A61E6DEF7000D5DEE5 /* AppAuthExample-iOS_ObjCTests */ = { isa = PBXNativeTarget; buildConfigurationList = 341310AE1E6DEF7000D5DEE5 /* Build configuration list for PBXNativeTarget "AppAuthExample-iOS_ObjCTests" */; @@ -168,12 +251,13 @@ 346E91651C29D42800D3620B /* Sources */, 346E91661C29D42800D3620B /* Frameworks */, 346E91671C29D42800D3620B /* Resources */, - 0B0B46F67786AB6E322D5F2B /* [CP] Embed Pods Frameworks */, - 3CB1FD4B438DD277394F39A7 /* [CP] Copy Pods Resources */, + 06D4813E2055C3D400D9DC32 /* Embed App Extensions */, + 0B7CB92A32140E833CA8FF89 /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); dependencies = ( + 06D481392055C3D400D9DC32 /* PBXTargetDependency */, ); name = "Example-iOS_ObjC"; productName = Example; @@ -189,6 +273,15 @@ LastUpgradeCheck = 0720; ORGANIZATIONNAME = "William Denniss"; TargetAttributes = { + 06D4812C2055C3D400D9DC32 = { + CreatedOnToolsVersion = 9.2; + ProvisioningStyle = Automatic; + SystemCapabilities = { + com.apple.ApplicationGroups.iOS = { + enabled = 1; + }; + }; + }; 341310A61E6DEF7000D5DEE5 = { CreatedOnToolsVersion = 8.2.1; ProvisioningStyle = Automatic; @@ -196,6 +289,11 @@ }; 346E91681C29D42800D3620B = { CreatedOnToolsVersion = 7.2; + SystemCapabilities = { + com.apple.ApplicationGroups.iOS = { + enabled = 1; + }; + }; }; }; }; @@ -204,6 +302,7 @@ developmentRegion = English; hasScannedForEncodings = 0; knownRegions = ( + English, en, Base, ); @@ -214,11 +313,20 @@ targets = ( 346E91681C29D42800D3620B /* Example-iOS_ObjC */, 341310A61E6DEF7000D5DEE5 /* AppAuthExample-iOS_ObjCTests */, + 06D4812C2055C3D400D9DC32 /* Example-iOS_ObjC_Extension */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ + 06D4812B2055C3D400D9DC32 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 06D481362055C3D400D9DC32 /* MainInterface.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 341310A51E6DEF7000D5DEE5 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -239,34 +347,40 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ - 0B0B46F67786AB6E322D5F2B /* [CP] Embed Pods Frameworks */ = { + 0B7CB92A32140E833CA8FF89 /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Example-iOS_ObjC/Pods-Example-iOS_ObjC-frameworks.sh", + "${BUILT_PRODUCTS_DIR}/AppAuth/AppAuth.framework", ); name = "[CP] Embed Pods Frameworks"; outputPaths = ( + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/AppAuth.framework", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Example-iOS_ObjC/Pods-Example-iOS_ObjC-frameworks.sh\"\n"; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Example-iOS_ObjC/Pods-Example-iOS_ObjC-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; - 3CB1FD4B438DD277394F39A7 /* [CP] Copy Pods Resources */ = { + 836219F293F319703A16E68D /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", ); - name = "[CP] Copy Pods Resources"; + name = "[CP] Check Pods Manifest.lock"; outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Example-iOS_ObjC_Extension-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Example-iOS_ObjC/Pods-Example-iOS_ObjC-resources.sh\"\n"; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; C4DC5E3A0D4C7419380FA8C2 /* [CP] Check Pods Manifest.lock */ = { @@ -275,18 +389,29 @@ files = ( ); inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", ); name = "[CP] Check Pods Manifest.lock"; outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Example-iOS_ObjC-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n"; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ + 06D481292055C3D400D9DC32 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 06D481332055C3D400D9DC32 /* TodayViewController.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 341310A31E6DEF7000D5DEE5 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -308,6 +433,11 @@ /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ + 06D481392055C3D400D9DC32 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 06D4812C2055C3D400D9DC32 /* Example-iOS_ObjC_Extension */; + targetProxy = 06D481382055C3D400D9DC32 /* PBXContainerItemProxy */; + }; 341310AD1E6DEF7000D5DEE5 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 346E91681C29D42800D3620B /* Example-iOS_ObjC */; @@ -316,6 +446,14 @@ /* End PBXTargetDependency section */ /* Begin PBXVariantGroup section */ + 06D481342055C3D400D9DC32 /* MainInterface.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 06D481352055C3D400D9DC32 /* Base */, + ); + name = MainInterface.storyboard; + sourceTree = ""; + }; 346E917A1C29D42800D3620B /* LaunchScreen.storyboard */ = { isa = PBXVariantGroup; children = ( @@ -327,6 +465,70 @@ /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ + 06D4813B2055C3D400D9DC32 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 86C7660572AE6FF7A4D1592A /* Pods-Example-iOS_ObjC_Extension.debug.xcconfig */; + buildSettings = { + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_ENTITLEMENTS = "Example-iOS_ObjC_Extension/Example-iOS_ObjC_Extension.entitlements"; + CODE_SIGN_IDENTITY = "iPhone Developer"; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = ""; + GCC_C_LANGUAGE_STANDARD = gnu11; + INFOPLIST_FILE = "Example-iOS_ObjC_Extension/Info.plist"; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = "net.openid.appauth.Example.Example-iOS-ObjC-Extension"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 06D4813C2055C3D400D9DC32 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 3A18DF082AC2FA0980C9DE57 /* Pods-Example-iOS_ObjC_Extension.release.xcconfig */; + buildSettings = { + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_ENTITLEMENTS = "Example-iOS_ObjC_Extension/Example-iOS_ObjC_Extension.entitlements"; + CODE_SIGN_IDENTITY = "iPhone Developer"; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = ""; + GCC_C_LANGUAGE_STANDARD = gnu11; + INFOPLIST_FILE = "Example-iOS_ObjC_Extension/Info.plist"; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = "net.openid.appauth.Example.Example-iOS-ObjC-Extension"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; 341310AF1E6DEF7000D5DEE5 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { @@ -338,7 +540,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; DEVELOPMENT_TEAM = ""; INFOPLIST_FILE = Tests/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 10.2; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = net.openid.AppAuthExampleTests; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -357,7 +559,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; DEVELOPMENT_TEAM = ""; INFOPLIST_FILE = Tests/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 10.2; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = net.openid.AppAuthExampleTests; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -456,8 +658,11 @@ baseConfigurationReference = 0D29B2A58C931A5D41332144 /* Pods-Example-iOS_ObjC.debug.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_ENTITLEMENTS = "Source/Example-iOS_ObjC.entitlements"; + CODE_SIGN_IDENTITY = "iPhone Developer"; + DEVELOPMENT_TEAM = ""; INFOPLIST_FILE = Source/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 7.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = net.openid.appauth.Example; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -469,8 +674,11 @@ baseConfigurationReference = ECBCCC4A1A779C83C72044F2 /* Pods-Example-iOS_ObjC.release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_ENTITLEMENTS = "Source/Example-iOS_ObjC.entitlements"; + CODE_SIGN_IDENTITY = "iPhone Developer"; + DEVELOPMENT_TEAM = ""; INFOPLIST_FILE = Source/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 7.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = net.openid.appauth.Example; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -480,6 +688,15 @@ /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ + 06D4813D2055C3D400D9DC32 /* Build configuration list for PBXNativeTarget "Example-iOS_ObjC_Extension" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 06D4813B2055C3D400D9DC32 /* Debug */, + 06D4813C2055C3D400D9DC32 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; 341310AE1E6DEF7000D5DEE5 /* Build configuration list for PBXNativeTarget "AppAuthExample-iOS_ObjCTests" */ = { isa = XCConfigurationList; buildConfigurations = ( diff --git a/Examples/Example-iOS_ObjC/Example-iOS_ObjC.xcodeproj/xcshareddata/xcschemes/Example-iOS_ObjC_Extension.xcscheme b/Examples/Example-iOS_ObjC/Example-iOS_ObjC.xcodeproj/xcshareddata/xcschemes/Example-iOS_ObjC_Extension.xcscheme new file mode 100644 index 000000000..7a9720c27 --- /dev/null +++ b/Examples/Example-iOS_ObjC/Example-iOS_ObjC.xcodeproj/xcshareddata/xcschemes/Example-iOS_ObjC_Extension.xcscheme @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Examples/Example-iOS_ObjC/Example-iOS_ObjC_Extension/Base.lproj/MainInterface.storyboard b/Examples/Example-iOS_ObjC/Example-iOS_ObjC_Extension/Base.lproj/MainInterface.storyboard new file mode 100644 index 000000000..67a53a714 --- /dev/null +++ b/Examples/Example-iOS_ObjC/Example-iOS_ObjC_Extension/Base.lproj/MainInterface.storyboard @@ -0,0 +1,68 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Examples/Example-iOS_ObjC/Example-iOS_ObjC_Extension/Example-iOS_ObjC_Extension.entitlements b/Examples/Example-iOS_ObjC/Example-iOS_ObjC_Extension/Example-iOS_ObjC_Extension.entitlements new file mode 100644 index 000000000..c250a2746 --- /dev/null +++ b/Examples/Example-iOS_ObjC/Example-iOS_ObjC_Extension/Example-iOS_ObjC_Extension.entitlements @@ -0,0 +1,10 @@ + + + + + com.apple.security.application-groups + + group.net.openid.appauth.Example + + + diff --git a/Examples/Example-iOS_ObjC/Example-iOS_ObjC_Extension/Info.plist b/Examples/Example-iOS_ObjC/Example-iOS_ObjC_Extension/Info.plist new file mode 100644 index 000000000..8839cbabf --- /dev/null +++ b/Examples/Example-iOS_ObjC/Example-iOS_ObjC_Extension/Info.plist @@ -0,0 +1,31 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + Example-iOS_ObjC_Extension + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + XPC! + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + NSExtension + + NSExtensionMainStoryboard + MainInterface + NSExtensionPointIdentifier + com.apple.widget-extension + + + diff --git a/Examples/Example-iOS_ObjC/Example-iOS_ObjC_Extension/TodayViewController.h b/Examples/Example-iOS_ObjC/Example-iOS_ObjC_Extension/TodayViewController.h new file mode 100644 index 000000000..73d2d5e3a --- /dev/null +++ b/Examples/Example-iOS_ObjC/Example-iOS_ObjC_Extension/TodayViewController.h @@ -0,0 +1,23 @@ +/*! @file TodayViewController.h + @brief AppAuth iOS SDK Example + @copyright + Copyright 2015 Google Inc. All Rights Reserved. + @copydetails + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#import + +@interface TodayViewController : UIViewController + +@end diff --git a/Examples/Example-iOS_ObjC/Example-iOS_ObjC_Extension/TodayViewController.m b/Examples/Example-iOS_ObjC/Example-iOS_ObjC_Extension/TodayViewController.m new file mode 100644 index 000000000..1bfbd96ed --- /dev/null +++ b/Examples/Example-iOS_ObjC/Example-iOS_ObjC_Extension/TodayViewController.m @@ -0,0 +1,222 @@ +/*! @file TodayViewController.m + @brief AppAuth iOS SDK Example + @copyright + Copyright 2015 Google Inc. All Rights Reserved. + @copydetails + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#import "TodayViewController.h" +#import +#import + +static NSString *const kAppAuthExampleAuthStateKey = @"authState"; + +@interface TodayViewController () + +@property(nonatomic, readonly, nullable) OIDAuthState *authState; +@property (weak, nonatomic) IBOutlet UITextView *logTextView; + +@end + +@implementation TodayViewController + +- (void)viewDidLoad { + [super viewDidLoad]; + + if (@available(iOS 10, *)) { + [self.extensionContext setWidgetLargestAvailableDisplayMode:NCWidgetDisplayModeExpanded]; + } else { + self.preferredContentSize = CGSizeMake(0, 400.0); + } + + [self loadState]; +} + +- (void)widgetActiveDisplayModeDidChange:(NCWidgetDisplayMode)activeDisplayMode + withMaximumSize:(CGSize)maxSize NS_AVAILABLE_IOS(10.0) { + + if (activeDisplayMode == NCWidgetDisplayModeExpanded) { + self.preferredContentSize = CGSizeMake(maxSize.width, 400.0); + } else if (activeDisplayMode == NCWidgetDisplayModeCompact) { + self.preferredContentSize = maxSize; + } +} + +- (void)widgetPerformUpdateWithCompletionHandler:(void (^)(NCUpdateResult))completionHandler { + // Perform any setup necessary in order to update the view. + + // If an error is encountered, use NCUpdateResultFailed + // If there's no update required, use NCUpdateResultNoData + // If there's an update, use NCUpdateResultNewData + + completionHandler(NCUpdateResultNewData); +} + +/*! @brief Loads the @c OIDAuthState from @c NSUSerDefaults. + */ +- (void)loadState { + // loads OIDAuthState from NSUSerDefaults + NSUserDefaults* userDefaults = [[NSUserDefaults alloc] initWithSuiteName:@"group.net.openid.appauth.Example"]; + NSData *archivedAuthState = [userDefaults objectForKey:kAppAuthExampleAuthStateKey]; + OIDAuthState *authState = [NSKeyedUnarchiver unarchiveObjectWithData:archivedAuthState]; + [self setAuthState:authState]; +} + +/*! @brief Saves the @c OIDAuthState to @c NSUSerDefaults. + */ +- (void)saveState { + // for production usage consider using the OS Keychain instead + NSUserDefaults* userDefaults = [[NSUserDefaults alloc] initWithSuiteName:@"group.net.openid.appauth.Example"]; + NSData *archivedAuthState = [NSKeyedArchiver archivedDataWithRootObject:_authState]; + [userDefaults setObject:archivedAuthState + forKey:kAppAuthExampleAuthStateKey]; + [userDefaults synchronize]; +} + +- (void)setAuthState:(nullable OIDAuthState *)authState { + if (_authState == authState) { + return; + } + _authState = authState; + _authState.stateChangeDelegate = self; + [self stateChanged]; +} + +- (void)stateChanged { + [self saveState]; +} + +- (void)didChangeState:(OIDAuthState *)state { + [self stateChanged]; +} + +- (IBAction)getUserInfo:(UIButton *)sender { + NSURL *userinfoEndpoint = + _authState.lastAuthorizationResponse.request.configuration.discoveryDocument.userinfoEndpoint; + if (!userinfoEndpoint) { + [self logMessage:@"Userinfo endpoint not declared in discovery document"]; + return; + } + NSString *currentAccessToken = _authState.lastTokenResponse.accessToken; + + [self logMessage:@"Performing userinfo request"]; + + [_authState performActionWithFreshTokens:^(NSString *_Nonnull accessToken, + NSString *_Nonnull idToken, + NSError *_Nullable error) { + if (error) { + [self logMessage:@"Error fetching fresh tokens: %@", [error localizedDescription]]; + return; + } + + // log whether a token refresh occurred + if (![currentAccessToken isEqual:accessToken]) { + [self logMessage:@"Access token was refreshed automatically (%@ to %@)", + currentAccessToken, + accessToken]; + } else { + [self logMessage:@"Access token was fresh and not updated [%@]", accessToken]; + } + + // creates request to the userinfo endpoint, with access token in the Authorization header + NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:userinfoEndpoint]; + NSString *authorizationHeaderValue = [NSString stringWithFormat:@"Bearer %@", accessToken]; + [request addValue:authorizationHeaderValue forHTTPHeaderField:@"Authorization"]; + + NSURLSessionConfiguration *configuration = + [NSURLSessionConfiguration defaultSessionConfiguration]; + NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration + delegate:nil + delegateQueue:nil]; + + // performs HTTP request + NSURLSessionDataTask *postDataTask = + [session dataTaskWithRequest:request + completionHandler:^(NSData *_Nullable data, + NSURLResponse *_Nullable response, + NSError *_Nullable error) { + dispatch_async(dispatch_get_main_queue(), ^() { + if (error) { + [self logMessage:@"HTTP request failed %@", error]; + return; + } + if (![response isKindOfClass:[NSHTTPURLResponse class]]) { + [self logMessage:@"Non-HTTP response"]; + return; + } + + NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response; + id jsonDictionaryOrArray = + [NSJSONSerialization JSONObjectWithData:data options:0 error:NULL]; + + if (httpResponse.statusCode != 200) { + // server replied with an error + NSString *responseText = [[NSString alloc] initWithData:data + encoding:NSUTF8StringEncoding]; + if (httpResponse.statusCode == 401) { + // "401 Unauthorized" generally indicates there is an issue with the authorization + // grant. Puts OIDAuthState into an error state. + NSError *oauthError = + [OIDErrorUtilities resourceServerAuthorizationErrorWithCode:0 + errorResponse:jsonDictionaryOrArray + underlyingError:error]; + [_authState updateWithAuthorizationError:oauthError]; + // log error + [self logMessage:@"Authorization Error (%@). Response: %@", oauthError, responseText]; + } else { + [self logMessage:@"HTTP: %d. Response: %@", + (int)httpResponse.statusCode, + responseText]; + } + return; + } + + // success response + [self logMessage:@"Success: %@", jsonDictionaryOrArray]; + }); + }]; + + [postDataTask resume]; + }]; +} + +/*! @brief Logs a message to stdout and the textfield. + @param format The format string and arguments. + */ +- (void)logMessage:(NSString *)format, ... NS_FORMAT_FUNCTION(1,2) { + // gets message as string + va_list argp; + va_start(argp, format); + NSString *log = [[NSString alloc] initWithFormat:format arguments:argp]; + va_end(argp); + + // outputs to stdout + NSLog(@"%@", log); + + // appends to output log + NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init]; + dateFormatter.dateFormat = @"hh:mm:ss"; + NSString *dateString = [dateFormatter stringFromDate:[NSDate date]]; + _logTextView.text = [NSString stringWithFormat:@"%@%@%@: %@", + _logTextView.text, + ([_logTextView.text length] > 0) ? @"\n" : @"", + dateString, + log]; +} + +- (IBAction)clearLogTextView:(UIButton *)sender { + _logTextView.text = @""; +} + +@end diff --git a/Examples/Example-iOS_ObjC/Podfile b/Examples/Example-iOS_ObjC/Podfile index 4d6ab0f6f..600de7d10 100644 --- a/Examples/Example-iOS_ObjC/Podfile +++ b/Examples/Example-iOS_ObjC/Podfile @@ -1,7 +1,15 @@ -target 'Example-iOS_ObjC' do - platform :ios, '9.0' +platform :ios, '11.0' + +use_frameworks! +target 'Example-iOS_ObjC' do # AppAuth Pod # In production, just use `pod 'AppAuth'` without the path reference. pod 'AppAuth', :path => '../../' end + +target 'Example-iOS_ObjC_Extension' do + # AppAuth/Core Pod + # In production, just use `pod 'AppAuth/Core'` without the path reference. + pod 'AppAuth/Core', :path => '../../' +end diff --git a/Examples/Example-iOS_ObjC/Source/AppAuthExampleViewController.m b/Examples/Example-iOS_ObjC/Source/AppAuthExampleViewController.m index 86a32445e..a4accb51b 100644 --- a/Examples/Example-iOS_ObjC/Source/AppAuthExampleViewController.m +++ b/Examples/Example-iOS_ObjC/Source/AppAuthExampleViewController.m @@ -104,18 +104,19 @@ - (void)verifyConfig { */ - (void)saveState { // for production usage consider using the OS Keychain instead - NSData *archivedAuthState = [ NSKeyedArchiver archivedDataWithRootObject:_authState]; - [[NSUserDefaults standardUserDefaults] setObject:archivedAuthState - forKey:kAppAuthExampleAuthStateKey]; - [[NSUserDefaults standardUserDefaults] synchronize]; + NSUserDefaults* userDefaults = [[NSUserDefaults alloc] initWithSuiteName:@"group.net.openid.appauth.Example"]; + NSData *archivedAuthState = [NSKeyedArchiver archivedDataWithRootObject:_authState]; + [userDefaults setObject:archivedAuthState + forKey:kAppAuthExampleAuthStateKey]; + [userDefaults synchronize]; } /*! @brief Loads the @c OIDAuthState from @c NSUSerDefaults. */ - (void)loadState { // loads OIDAuthState from NSUSerDefaults - NSData *archivedAuthState = - [[NSUserDefaults standardUserDefaults] objectForKey:kAppAuthExampleAuthStateKey]; + NSUserDefaults* userDefaults = [[NSUserDefaults alloc] initWithSuiteName:@"group.net.openid.appauth.Example"]; + NSData *archivedAuthState = [userDefaults objectForKey:kAppAuthExampleAuthStateKey]; OIDAuthState *authState = [NSKeyedUnarchiver unarchiveObjectWithData:archivedAuthState]; [self setAuthState:authState]; } @@ -178,8 +179,10 @@ - (void)doClientRegistration:(OIDServiceConfiguration *)configuration grantTypes:nil subjectType:nil tokenEndpointAuthMethod:@"client_secret_post" + initialAccessToken:nil additionalParameters:nil]; - // performs registration request + + // performs registration request [self logMessage:@"Initiating registration request"]; [OIDAuthorizationService performRegistrationRequest:request diff --git a/Examples/Example-iOS_ObjC/Source/AppDelegate.h b/Examples/Example-iOS_ObjC/Source/AppDelegate.h index 4fb93255b..ece00038d 100644 --- a/Examples/Example-iOS_ObjC/Source/AppDelegate.h +++ b/Examples/Example-iOS_ObjC/Source/AppDelegate.h @@ -17,7 +17,7 @@ */ #import -@protocol OIDAuthorizationFlowSession; +@protocol OIDExternalUserAgentSession; /*! @brief The example application's delegate. */ @@ -32,7 +32,7 @@ incoming URL on UIApplicationDelegate.application:openURL:options:. This property will be nil, except when an authorization flow is in progress. */ -@property(nonatomic, strong, nullable) id currentAuthorizationFlow; +@property(nonatomic, strong, nullable) id currentAuthorizationFlow; @end diff --git a/Examples/Example-iOS_ObjC/Source/AppDelegate.m b/Examples/Example-iOS_ObjC/Source/AppDelegate.m index 3422c91fe..cbf861d4a 100644 --- a/Examples/Example-iOS_ObjC/Source/AppDelegate.m +++ b/Examples/Example-iOS_ObjC/Source/AppDelegate.m @@ -49,7 +49,7 @@ - (BOOL)application:(UIApplication *)app options:(NSDictionary *)options { // Sends the URL to the current authorization flow (if any) which will process it if it relates to // an authorization response. - if ([_currentAuthorizationFlow resumeAuthorizationFlowWithURL:url]) { + if ([_currentAuthorizationFlow resumeExternalUserAgentFlowWithURL:url]) { _currentAuthorizationFlow = nil; return YES; } diff --git a/Examples/Example-iOS_ObjC/Source/Assets.xcassets/AppIcon.appiconset/AppAuth_Icon_1024.png b/Examples/Example-iOS_ObjC/Source/Assets.xcassets/AppIcon.appiconset/AppAuth_Icon_1024.png new file mode 100644 index 000000000..9d74d6575 Binary files /dev/null and b/Examples/Example-iOS_ObjC/Source/Assets.xcassets/AppIcon.appiconset/AppAuth_Icon_1024.png differ diff --git a/Examples/Example-iOS_ObjC/Source/Assets.xcassets/AppIcon.appiconset/AppAuth_Icon_120.png b/Examples/Example-iOS_ObjC/Source/Assets.xcassets/AppIcon.appiconset/AppAuth_Icon_120.png new file mode 100644 index 000000000..f73edc78a Binary files /dev/null and b/Examples/Example-iOS_ObjC/Source/Assets.xcassets/AppIcon.appiconset/AppAuth_Icon_120.png differ diff --git a/Examples/Example-iOS_ObjC/Source/Assets.xcassets/AppIcon.appiconset/AppAuth_Icon_152.png b/Examples/Example-iOS_ObjC/Source/Assets.xcassets/AppIcon.appiconset/AppAuth_Icon_152.png new file mode 100644 index 000000000..bdd5de3c3 Binary files /dev/null and b/Examples/Example-iOS_ObjC/Source/Assets.xcassets/AppIcon.appiconset/AppAuth_Icon_152.png differ diff --git a/Examples/Example-iOS_ObjC/Source/Assets.xcassets/AppIcon.appiconset/AppAuth_Icon_167.png b/Examples/Example-iOS_ObjC/Source/Assets.xcassets/AppIcon.appiconset/AppAuth_Icon_167.png new file mode 100644 index 000000000..1bb56407d Binary files /dev/null and b/Examples/Example-iOS_ObjC/Source/Assets.xcassets/AppIcon.appiconset/AppAuth_Icon_167.png differ diff --git a/Examples/Example-iOS_ObjC/Source/Assets.xcassets/AppIcon.appiconset/AppAuth_Icon_180.png b/Examples/Example-iOS_ObjC/Source/Assets.xcassets/AppIcon.appiconset/AppAuth_Icon_180.png new file mode 100644 index 000000000..17535eb04 Binary files /dev/null and b/Examples/Example-iOS_ObjC/Source/Assets.xcassets/AppIcon.appiconset/AppAuth_Icon_180.png differ diff --git a/Examples/Example-iOS_ObjC/Source/Assets.xcassets/AppIcon.appiconset/AppAuth_Icon_76.png b/Examples/Example-iOS_ObjC/Source/Assets.xcassets/AppIcon.appiconset/AppAuth_Icon_76.png new file mode 100644 index 000000000..dc824d5e7 Binary files /dev/null and b/Examples/Example-iOS_ObjC/Source/Assets.xcassets/AppIcon.appiconset/AppAuth_Icon_76.png differ diff --git a/Examples/Example-iOS_ObjC/Source/Assets.xcassets/AppIcon.appiconset/Contents.json b/Examples/Example-iOS_ObjC/Source/Assets.xcassets/AppIcon.appiconset/Contents.json index 1d060ed28..0b6ca5edb 100644 --- a/Examples/Example-iOS_ObjC/Source/Assets.xcassets/AppIcon.appiconset/Contents.json +++ b/Examples/Example-iOS_ObjC/Source/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -31,13 +31,15 @@ "scale" : "3x" }, { - "idiom" : "iphone", "size" : "60x60", + "idiom" : "iphone", + "filename" : "AppAuth_Icon_120.png", "scale" : "2x" }, { - "idiom" : "iphone", "size" : "60x60", + "idiom" : "iphone", + "filename" : "AppAuth_Icon_180.png", "scale" : "3x" }, { @@ -71,19 +73,28 @@ "scale" : "2x" }, { - "idiom" : "ipad", "size" : "76x76", + "idiom" : "ipad", + "filename" : "AppAuth_Icon_76.png", "scale" : "1x" }, { - "idiom" : "ipad", "size" : "76x76", + "idiom" : "ipad", + "filename" : "AppAuth_Icon_152.png", "scale" : "2x" }, { - "idiom" : "ipad", "size" : "83.5x83.5", + "idiom" : "ipad", + "filename" : "AppAuth_Icon_167.png", "scale" : "2x" + }, + { + "size" : "1024x1024", + "idiom" : "ios-marketing", + "filename" : "AppAuth_Icon_1024.png", + "scale" : "1x" } ], "info" : { diff --git a/Examples/Example-iOS_ObjC/Source/Example-iOS_ObjC.entitlements b/Examples/Example-iOS_ObjC/Source/Example-iOS_ObjC.entitlements new file mode 100644 index 000000000..c250a2746 --- /dev/null +++ b/Examples/Example-iOS_ObjC/Source/Example-iOS_ObjC.entitlements @@ -0,0 +1,10 @@ + + + + + com.apple.security.application-groups + + group.net.openid.appauth.Example + + + diff --git a/Examples/Example-iOS_Swift-Carthage/.gitignore b/Examples/Example-iOS_Swift-Carthage/.gitignore new file mode 100644 index 000000000..0d5a93889 --- /dev/null +++ b/Examples/Example-iOS_Swift-Carthage/.gitignore @@ -0,0 +1,40 @@ +## Build generated +build/ +DerivedData/ + +## Various settings +*.pbxuser +!default.pbxuser +*.mode1v3 +!default.mode1v3 +*.mode2v3 +!default.mode2v3 +*.perspectivev3 +!default.perspectivev3 +xcuserdata/ + +## Other +*.moved-aside +*.xccheckout +*.xcscmblueprint + +## Obj-C/Swift specific +*.hmap +*.ipa +*.dSYM.zip +*.dSYM + +# Pods are ignored in the samples as all Pods & their dependencies are either +# development Pods (this repo) or sourced from repos in the same organization. +# Generally we recommend committing Pod artifacts to version control, read about +# the pros & cons here: +# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control +Pods + +# Carthage +# +# Add these lines if you want to avoid checking in source code from Carthage +# dependencies. Generally we recommend committing Carthage artifacts to version +# control. +Carthage/Checkouts +Carthage/Build diff --git a/Examples/Example-iOS_Swift-Carthage/Cartfile b/Examples/Example-iOS_Swift-Carthage/Cartfile new file mode 100644 index 000000000..5cb5fda23 --- /dev/null +++ b/Examples/Example-iOS_Swift-Carthage/Cartfile @@ -0,0 +1,2 @@ +github "openid/AppAuth-iOS" "master" + diff --git a/Examples/Example-iOS_Swift-Carthage/Example.xcodeproj/project.pbxproj b/Examples/Example-iOS_Swift-Carthage/Example.xcodeproj/project.pbxproj new file mode 100644 index 000000000..33f25f87b --- /dev/null +++ b/Examples/Example-iOS_Swift-Carthage/Example.xcodeproj/project.pbxproj @@ -0,0 +1,571 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 48; + objects = { + +/* Begin PBXBuildFile section */ + 06375C8821A4E50E00338E3F /* AppAuthCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 06375C8721A4E50E00338E3F /* AppAuthCore.framework */; }; + 0646249B205F026300072191 /* NotificationCenter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0646249A205F026300072191 /* NotificationCenter.framework */; }; + 0646249E205F026300072191 /* TodayViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0646249D205F026300072191 /* TodayViewController.swift */; }; + 064624A1205F026300072191 /* MainInterface.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 0646249F205F026300072191 /* MainInterface.storyboard */; }; + 064624A5205F026300072191 /* Example_Extension.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 06462499205F026300072191 /* Example_Extension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; + 9F265BD11F9AC69300DC14BF /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 9F265BC91F9AC69300DC14BF /* Assets.xcassets */; }; + 9F265BD31F9AC69300DC14BF /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 9F265BCB1F9AC69300DC14BF /* LaunchScreen.storyboard */; }; + 9F265BD41F9AC69300DC14BF /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 9F265BCD1F9AC69300DC14BF /* Main.storyboard */; }; + 9F265BD51F9AC69300DC14BF /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F265BCF1F9AC69300DC14BF /* AppDelegate.swift */; }; + 9F265BDA1F9AE50400DC14BF /* AppAuth.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9F265BD91F9AE50400DC14BF /* AppAuth.framework */; }; + 9FD378231FB7C6F800436204 /* AppAuthExampleViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FD378221FB7C6F800436204 /* AppAuthExampleViewController.swift */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 064624A3205F026300072191 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 9F265B8F1F9AC5D600DC14BF /* Project object */; + proxyType = 1; + remoteGlobalIDString = 06462498205F026300072191; + remoteInfo = Example_Extension; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 064624A9205F026300072191 /* Embed App Extensions */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 13; + files = ( + 064624A5205F026300072191 /* Example_Extension.appex in Embed App Extensions */, + ); + name = "Embed App Extensions"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 06375C8721A4E50E00338E3F /* AppAuthCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppAuthCore.framework; path = Carthage/Build/iOS/AppAuthCore.framework; sourceTree = ""; }; + 06462499205F026300072191 /* Example_Extension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = Example_Extension.appex; sourceTree = BUILT_PRODUCTS_DIR; }; + 0646249A205F026300072191 /* NotificationCenter.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = NotificationCenter.framework; path = System/Library/Frameworks/NotificationCenter.framework; sourceTree = SDKROOT; }; + 0646249D205F026300072191 /* TodayViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TodayViewController.swift; sourceTree = ""; }; + 064624A0205F026300072191 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/MainInterface.storyboard; sourceTree = ""; }; + 064624A2205F026300072191 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 064624AD205F034A00072191 /* Example.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Example.entitlements; sourceTree = ""; }; + 064624AE205F035D00072191 /* Example_Extension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Example_Extension.entitlements; sourceTree = ""; }; + 9F265B971F9AC5D600DC14BF /* Example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Example.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 9F265BC91F9AC69300DC14BF /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 9F265BCC1F9AC69300DC14BF /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 9F265BCE1F9AC69300DC14BF /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + 9F265BCF1F9AC69300DC14BF /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 9F265BD01F9AC69300DC14BF /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 9F265BD91F9AE50400DC14BF /* AppAuth.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppAuth.framework; path = Carthage/Build/iOS/AppAuth.framework; sourceTree = ""; }; + 9FD378221FB7C6F800436204 /* AppAuthExampleViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppAuthExampleViewController.swift; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 06462496205F026300072191 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 06375C8821A4E50E00338E3F /* AppAuthCore.framework in Frameworks */, + 0646249B205F026300072191 /* NotificationCenter.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 9F265B941F9AC5D600DC14BF /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 9F265BDA1F9AE50400DC14BF /* AppAuth.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 0646249C205F026300072191 /* Example_Extension */ = { + isa = PBXGroup; + children = ( + 0646249D205F026300072191 /* TodayViewController.swift */, + 0646249F205F026300072191 /* MainInterface.storyboard */, + 064624A2205F026300072191 /* Info.plist */, + 064624AE205F035D00072191 /* Example_Extension.entitlements */, + ); + path = Example_Extension; + sourceTree = ""; + }; + 9F265B8E1F9AC5D600DC14BF = { + isa = PBXGroup; + children = ( + 9F265BC81F9AC69300DC14BF /* Source */, + 0646249C205F026300072191 /* Example_Extension */, + 9F265BD81F9AE4CA00DC14BF /* Frameworks */, + 9F265B981F9AC5D600DC14BF /* Products */, + ); + sourceTree = ""; + }; + 9F265B981F9AC5D600DC14BF /* Products */ = { + isa = PBXGroup; + children = ( + 9F265B971F9AC5D600DC14BF /* Example.app */, + 06462499205F026300072191 /* Example_Extension.appex */, + ); + name = Products; + sourceTree = ""; + }; + 9F265BC81F9AC69300DC14BF /* Source */ = { + isa = PBXGroup; + children = ( + 9F265BCF1F9AC69300DC14BF /* AppDelegate.swift */, + 9FD378221FB7C6F800436204 /* AppAuthExampleViewController.swift */, + 9F265BCB1F9AC69300DC14BF /* LaunchScreen.storyboard */, + 9F265BCD1F9AC69300DC14BF /* Main.storyboard */, + 9F265BC91F9AC69300DC14BF /* Assets.xcassets */, + 9F265BD01F9AC69300DC14BF /* Info.plist */, + 064624AD205F034A00072191 /* Example.entitlements */, + ); + path = Source; + sourceTree = ""; + }; + 9F265BD81F9AE4CA00DC14BF /* Frameworks */ = { + isa = PBXGroup; + children = ( + 06375C8721A4E50E00338E3F /* AppAuthCore.framework */, + 9F265BD91F9AE50400DC14BF /* AppAuth.framework */, + 0646249A205F026300072191 /* NotificationCenter.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 06462498205F026300072191 /* Example_Extension */ = { + isa = PBXNativeTarget; + buildConfigurationList = 064624A8205F026300072191 /* Build configuration list for PBXNativeTarget "Example_Extension" */; + buildPhases = ( + 06462495205F026300072191 /* Sources */, + 06462496205F026300072191 /* Frameworks */, + 06462497205F026300072191 /* Resources */, + 06375C8921A4E56B00338E3F /* Carthage */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Example_Extension; + productName = Example_Extension; + productReference = 06462499205F026300072191 /* Example_Extension.appex */; + productType = "com.apple.product-type.app-extension"; + }; + 9F265B961F9AC5D600DC14BF /* Example */ = { + isa = PBXNativeTarget; + buildConfigurationList = 9F265BBF1F9AC5D600DC14BF /* Build configuration list for PBXNativeTarget "Example" */; + buildPhases = ( + 9F265B931F9AC5D600DC14BF /* Sources */, + 9F265B941F9AC5D600DC14BF /* Frameworks */, + 9F265B951F9AC5D600DC14BF /* Resources */, + 9F265BDB1F9AE52C00DC14BF /* Carthage */, + 064624A9205F026300072191 /* Embed App Extensions */, + ); + buildRules = ( + ); + dependencies = ( + 064624A4205F026300072191 /* PBXTargetDependency */, + ); + name = Example; + productName = Example; + productReference = 9F265B971F9AC5D600DC14BF /* Example.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 9F265B8F1F9AC5D600DC14BF /* Project object */ = { + isa = PBXProject; + attributes = { + LastSwiftUpdateCheck = 0920; + LastUpgradeCheck = 0900; + ORGANIZATIONNAME = "Google Inc."; + TargetAttributes = { + 06462498205F026300072191 = { + CreatedOnToolsVersion = 9.2; + ProvisioningStyle = Automatic; + SystemCapabilities = { + com.apple.ApplicationGroups.iOS = { + enabled = 1; + }; + }; + }; + 9F265B961F9AC5D600DC14BF = { + CreatedOnToolsVersion = 9.0.1; + ProvisioningStyle = Automatic; + SystemCapabilities = { + com.apple.ApplicationGroups.iOS = { + enabled = 1; + }; + }; + }; + }; + }; + buildConfigurationList = 9F265B921F9AC5D600DC14BF /* Build configuration list for PBXProject "Example" */; + compatibilityVersion = "Xcode 8.0"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 9F265B8E1F9AC5D600DC14BF; + productRefGroup = 9F265B981F9AC5D600DC14BF /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 9F265B961F9AC5D600DC14BF /* Example */, + 06462498205F026300072191 /* Example_Extension */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 06462497205F026300072191 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 064624A1205F026300072191 /* MainInterface.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 9F265B951F9AC5D600DC14BF /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 9F265BD41F9AC69300DC14BF /* Main.storyboard in Resources */, + 9F265BD11F9AC69300DC14BF /* Assets.xcassets in Resources */, + 9F265BD31F9AC69300DC14BF /* LaunchScreen.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 06375C8921A4E56B00338E3F /* Carthage */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "$(SRCROOT)/Carthage/Build/iOS/AppAuth.framework", + ); + name = Carthage; + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/usr/local/bin/carthage copy-frameworks\n"; + }; + 9F265BDB1F9AE52C00DC14BF /* Carthage */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "$(SRCROOT)/Carthage/Build/iOS/AppAuth.framework", + ); + name = Carthage; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/usr/local/bin/carthage copy-frameworks\n"; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 06462495205F026300072191 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 0646249E205F026300072191 /* TodayViewController.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 9F265B931F9AC5D600DC14BF /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 9FD378231FB7C6F800436204 /* AppAuthExampleViewController.swift in Sources */, + 9F265BD51F9AC69300DC14BF /* AppDelegate.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 064624A4205F026300072191 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 06462498205F026300072191 /* Example_Extension */; + targetProxy = 064624A3205F026300072191 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 0646249F205F026300072191 /* MainInterface.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 064624A0205F026300072191 /* Base */, + ); + name = MainInterface.storyboard; + sourceTree = ""; + }; + 9F265BCB1F9AC69300DC14BF /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 9F265BCC1F9AC69300DC14BF /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; + 9F265BCD1F9AC69300DC14BF /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 9F265BCE1F9AC69300DC14BF /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 064624A6205F026300072191 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_ENTITLEMENTS = Example_Extension/Example_Extension.entitlements; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = ""; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Carthage/Build/iOS", + ); + INFOPLIST_FILE = Example_Extension/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = "net.openid.appauth.Example.Example-Extension"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + SWIFT_VERSION = 4.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 064624A7205F026300072191 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_ENTITLEMENTS = Example_Extension/Example_Extension.entitlements; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = ""; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Carthage/Build/iOS", + ); + INFOPLIST_FILE = Example_Extension/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = "net.openid.appauth.Example.Example-Extension"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + SWIFT_VERSION = 4.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; + 9F265BBD1F9AC5D600DC14BF /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 9F265BBE1F9AC5D600DC14BF /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 9F265BC01F9AC5D600DC14BF /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_ENTITLEMENTS = Source/Example.entitlements; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = ""; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Carthage/Build/iOS", + ); + INFOPLIST_FILE = Source/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = net.openid.appauth.Example; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 4.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 9F265BC11F9AC5D600DC14BF /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_ENTITLEMENTS = Source/Example.entitlements; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = ""; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Carthage/Build/iOS", + ); + INFOPLIST_FILE = Source/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = net.openid.appauth.Example; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 4.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 064624A8205F026300072191 /* Build configuration list for PBXNativeTarget "Example_Extension" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 064624A6205F026300072191 /* Debug */, + 064624A7205F026300072191 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 9F265B921F9AC5D600DC14BF /* Build configuration list for PBXProject "Example" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 9F265BBD1F9AC5D600DC14BF /* Debug */, + 9F265BBE1F9AC5D600DC14BF /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 9F265BBF1F9AC5D600DC14BF /* Build configuration list for PBXNativeTarget "Example" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 9F265BC01F9AC5D600DC14BF /* Debug */, + 9F265BC11F9AC5D600DC14BF /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 9F265B8F1F9AC5D600DC14BF /* Project object */; +} diff --git a/Examples/Example-iOS_Swift-Carthage/Example.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/Examples/Example-iOS_Swift-Carthage/Example.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 000000000..6d2a51bbd --- /dev/null +++ b/Examples/Example-iOS_Swift-Carthage/Example.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/Examples/Example-iOS_Swift-Carthage/Example.xcodeproj/xcshareddata/xcschemes/Example.xcscheme b/Examples/Example-iOS_Swift-Carthage/Example.xcodeproj/xcshareddata/xcschemes/Example.xcscheme new file mode 100644 index 000000000..7ffcb4c0f --- /dev/null +++ b/Examples/Example-iOS_Swift-Carthage/Example.xcodeproj/xcshareddata/xcschemes/Example.xcscheme @@ -0,0 +1,93 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Examples/Example-iOS_Swift-Carthage/Example.xcodeproj/xcshareddata/xcschemes/Example_Extension.xcscheme b/Examples/Example-iOS_Swift-Carthage/Example.xcodeproj/xcshareddata/xcschemes/Example_Extension.xcscheme new file mode 100644 index 000000000..719ebd973 --- /dev/null +++ b/Examples/Example-iOS_Swift-Carthage/Example.xcodeproj/xcshareddata/xcschemes/Example_Extension.xcscheme @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Examples/Example-iOS_Swift-Carthage/Example_Extension/Base.lproj/MainInterface.storyboard b/Examples/Example-iOS_Swift-Carthage/Example_Extension/Base.lproj/MainInterface.storyboard new file mode 100644 index 000000000..170af8176 --- /dev/null +++ b/Examples/Example-iOS_Swift-Carthage/Example_Extension/Base.lproj/MainInterface.storyboard @@ -0,0 +1,66 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Examples/Example-iOS_Swift-Carthage/Example_Extension/Example_Extension.entitlements b/Examples/Example-iOS_Swift-Carthage/Example_Extension/Example_Extension.entitlements new file mode 100644 index 000000000..c250a2746 --- /dev/null +++ b/Examples/Example-iOS_Swift-Carthage/Example_Extension/Example_Extension.entitlements @@ -0,0 +1,10 @@ + + + + + com.apple.security.application-groups + + group.net.openid.appauth.Example + + + diff --git a/Examples/Example-iOS_Swift-Carthage/Example_Extension/Info.plist b/Examples/Example-iOS_Swift-Carthage/Example_Extension/Info.plist new file mode 100644 index 000000000..6ea3c3dc6 --- /dev/null +++ b/Examples/Example-iOS_Swift-Carthage/Example_Extension/Info.plist @@ -0,0 +1,31 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + Example_Extension + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + XPC! + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + NSExtension + + NSExtensionMainStoryboard + MainInterface + NSExtensionPointIdentifier + com.apple.widget-extension + + + diff --git a/Examples/Example-iOS_Swift-Carthage/Example_Extension/TodayViewController.swift b/Examples/Example-iOS_Swift-Carthage/Example_Extension/TodayViewController.swift new file mode 100644 index 000000000..fc663d1bd --- /dev/null +++ b/Examples/Example-iOS_Swift-Carthage/Example_Extension/TodayViewController.swift @@ -0,0 +1,224 @@ +// +// TodayViewController.swift +// +// Copyright (c) 2017 The AppAuth Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import UIKit +import NotificationCenter +import AppAuthCore + +/** + NSCoding key for the authState property. + */ +let kAppAuthExampleAuthStateKey: String = "authState"; + +class TodayViewController: UIViewController, NCWidgetProviding { + + @IBOutlet private weak var logTextView: UITextView! + + private var authState: OIDAuthState? + + override func viewDidLoad() { + super.viewDidLoad() + + if #available(iOS 10.0, *) { + self.extensionContext?.widgetLargestAvailableDisplayMode = .expanded + } else { + self.preferredContentSize = CGSize(width: 0, height: 400.0) + } + + self.loadState() + } + + @available(iOSApplicationExtension 10.0, *) + func widgetActiveDisplayModeDidChange(_ activeDisplayMode: NCWidgetDisplayMode, withMaximumSize maxSize: CGSize) { + if activeDisplayMode == .expanded { + preferredContentSize = CGSize(width: maxSize.width, height: 400.0) + } else if activeDisplayMode == .compact { + preferredContentSize = maxSize + } + } + + func widgetPerformUpdate(completionHandler: (@escaping (NCUpdateResult) -> Void)) { + // Perform any setup necessary in order to update the view. + + // If an error is encountered, use NCUpdateResult.Failed + // If there's no update required, use NCUpdateResult.NoData + // If there's an update, use NCUpdateResult.NewData + + completionHandler(NCUpdateResult.newData) + } + + @IBAction func userinfo(_ sender: UIButton) { + + guard let userinfoEndpoint = self.authState?.lastAuthorizationResponse.request.configuration.discoveryDocument?.userinfoEndpoint else { + self.logMessage("Userinfo endpoint not declared in discovery document") + return + } + + self.logMessage("Performing userinfo request") + + let currentAccessToken: String? = self.authState?.lastTokenResponse?.accessToken + + self.authState?.performAction() { (accessToken, idTOken, error) in + + if error != nil { + self.logMessage("Error fetching fresh tokens: \(error?.localizedDescription ?? "ERROR")") + return + } + + guard let accessToken = accessToken else { + self.logMessage("Error getting accessToken") + return + } + + if currentAccessToken != accessToken { + self.logMessage("Access token was refreshed automatically (\(currentAccessToken ?? "CURRENT_ACCESS_TOKEN") to \(accessToken))") + } else { + self.logMessage("Access token was fresh and not updated \(accessToken)") + } + + var urlRequest = URLRequest(url: userinfoEndpoint) + urlRequest.allHTTPHeaderFields = ["Authorization":"Bearer \(accessToken)"] + + let task = URLSession.shared.dataTask(with: urlRequest) { data, response, error in + + DispatchQueue.main.async { + + guard error == nil else { + self.logMessage("HTTP request failed \(error?.localizedDescription ?? "ERROR")") + return + } + + guard let response = response as? HTTPURLResponse else { + self.logMessage("Non-HTTP response") + return + } + + guard let data = data else { + self.logMessage("HTTP response data is empty") + return + } + + var json: [AnyHashable: Any]? + + do { + json = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] + } catch { + self.logMessage("JSON Serialization Error") + } + + if response.statusCode != 200 { + // server replied with an error + let responseText: String? = String(data: data, encoding: String.Encoding.utf8) + + if response.statusCode == 401 { + // "401 Unauthorized" generally indicates there is an issue with the authorization + // grant. Puts OIDAuthState into an error state. + let oauthError = OIDErrorUtilities.resourceServerAuthorizationError(withCode: 0, + errorResponse: json, + underlyingError: error) + self.authState?.update(withAuthorizationError: oauthError) + self.logMessage("Authorization Error (\(oauthError)). Response: \(responseText ?? "RESPONSE_TEXT")") + } else { + self.logMessage("HTTP: \(response.statusCode), Response: \(responseText ?? "RESPONSE_TEXT")") + } + + return + } + + if let json = json { + self.logMessage("Success: \(json)") + } + } + } + + task.resume() + } + } + + @IBAction func clearLogTextView(_ sender: UIButton) { + self.logTextView.text = "" + } +} + +//MARK: OIDAuthState Delegate +extension TodayViewController: OIDAuthStateChangeDelegate { + + func didChange(_ state: OIDAuthState) { + self.stateChanged() + } +} + +//MARK: Helper Methods +extension TodayViewController { + + func saveState() { + + var data: Data? = nil + + if let authState = self.authState { + data = NSKeyedArchiver.archivedData(withRootObject: authState) + } + + if let userDefaults = UserDefaults(suiteName: "group.net.openid.appauth.Example") { + userDefaults.set(data, forKey: kAppAuthExampleAuthStateKey) + userDefaults.synchronize() + } + } + + func loadState() { + guard let data = UserDefaults(suiteName: "group.net.openid.appauth.Example")?.object(forKey: kAppAuthExampleAuthStateKey) as? Data else { + return + } + + if let authState = NSKeyedUnarchiver.unarchiveObject(with: data) as? OIDAuthState { + self.setAuthState(authState) + } + } + + func setAuthState(_ authState: OIDAuthState?) { + if (self.authState == authState) { + return; + } + self.authState = authState; + self.authState?.stateChangeDelegate = self; + self.stateChanged() + } + + func stateChanged() { + self.saveState() + } + + func logMessage(_ message: String?) { + + guard let message = message else { + return + } + + print(message); + + let dateFormatter = DateFormatter() + dateFormatter.dateFormat = "hh:mm:ss"; + let dateString = dateFormatter.string(from: Date()) + + // appends to output log + DispatchQueue.main.async { + let logText = "\(self.logTextView.text ?? "")\n\(dateString): \(message)" + self.logTextView.text = logText + } + } +} diff --git a/Examples/Example-iOS_Swift-Carthage/README.md b/Examples/Example-iOS_Swift-Carthage/README.md new file mode 100644 index 000000000..8eb120fb0 --- /dev/null +++ b/Examples/Example-iOS_Swift-Carthage/README.md @@ -0,0 +1,45 @@ +# Example Project + +## Setup & Open the Project + +You'll need to have [Carthage](https://github.com/Carthage/Carthage) installed +in order to pull the AppAuth dependency. + +So first run `carthage bootstrap` to build AppAuth framework then open the +`Example.xcodeproj` file. + +## Configuration + +The example doesn't work out of the box, you need to configure it with your own +client ID. + +### Information You'll Need + +* Issuer +* Client ID +* Redirect URI + +How to get this information varies by IdP, but we have +[instructions](../README.md#openid-certified-providers) for some OpenID +Certified providers. + +### Configure the Example + +#### In the file `AppAuthExampleViewController.swift` + +1. Update `kIssuer` with the IdP's issuer. +2. Update `kClientID` with your new client id. +3. Update `kRedirectURI` redirect URI + +#### In the file `Info.plist` + +Fully expand "URL types" (a.k.a. `CFBundleURLTypes`) and replace +`com.example.app` with the *scheme* of your redirect URI. +The scheme is everything before the colon (`:`). For example, if the redirect +URI is `com.example.app:/oauth2redirect/example-provider`, then the scheme +would be `com.example.app`. + +### Running the Example + +Now your example should be ready to run. + diff --git a/Examples/Example-iOS_Swift-Carthage/Source/AppAuthExampleViewController.swift b/Examples/Example-iOS_Swift-Carthage/Source/AppAuthExampleViewController.swift new file mode 100644 index 000000000..91cf79fa4 --- /dev/null +++ b/Examples/Example-iOS_Swift-Carthage/Source/AppAuthExampleViewController.swift @@ -0,0 +1,531 @@ +// +// AppAuthExampleViewController.swift +// +// Copyright (c) 2017 The AppAuth Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import AppAuth +import UIKit + +typealias PostRegistrationCallback = (_ configuration: OIDServiceConfiguration?, _ registrationResponse: OIDRegistrationResponse?) -> Void + +/** + The OIDC issuer from which the configuration will be discovered. +*/ +let kIssuer: String = "https://issuer.example.com"; + +/** + The OAuth client ID. + + For client configuration instructions, see the [README](https://github.com/openid/AppAuth-iOS/blob/master/Examples/Example-iOS_Swift-Carthage/README.md). + Set to nil to use dynamic registration with this example. +*/ +let kClientID: String? = "YOUR_CLIENT_ID"; + +/** + The OAuth redirect URI for the client @c kClientID. + + For client configuration instructions, see the [README](https://github.com/openid/AppAuth-iOS/blob/master/Examples/Example-iOS_Swift-Carthage/README.md). +*/ +let kRedirectURI: String = "com.example.app:/oauth2redirect/example-provider"; + +/** + NSCoding key for the authState property. +*/ +let kAppAuthExampleAuthStateKey: String = "authState"; + + +class AppAuthExampleViewController: UIViewController { + + @IBOutlet private weak var authAutoButton: UIButton! + @IBOutlet private weak var authManual: UIButton! + @IBOutlet private weak var codeExchangeButton: UIButton! + @IBOutlet private weak var userinfoButton: UIButton! + @IBOutlet private weak var logTextView: UITextView! + @IBOutlet private weak var trashButton: UIBarButtonItem! + + private var authState: OIDAuthState? + + override func viewDidLoad() { + super.viewDidLoad() + + self.validateOAuthConfiguration() + + self.loadState() + self.updateUI() + } +} + +extension AppAuthExampleViewController { + + func validateOAuthConfiguration() { + + // The example needs to be configured with your own client details. + // See: https://github.com/openid/AppAuth-iOS/blob/master/Examples/Example-iOS_Swift-Carthage/README.md + + assert(kIssuer != "https://issuer.example.com", + "Update kIssuer with your own issuer.\n" + + "Instructions: https://github.com/openid/AppAuth-iOS/blob/master/Examples/Example-iOS_Swift-Carthage/README.md"); + + assert(kClientID != "YOUR_CLIENT_ID", + "Update kClientID with your own client ID.\n" + + "Instructions: https://github.com/openid/AppAuth-iOS/blob/master/Examples/Example-iOS_Swift-Carthage/README.md"); + + assert(kRedirectURI != "com.example.app:/oauth2redirect/example-provider", + "Update kRedirectURI with your own redirect URI.\n" + + "Instructions: https://github.com/openid/AppAuth-iOS/blob/master/Examples/Example-iOS_Swift-Carthage/README.md"); + + // verifies that the custom URI scheme has been updated in the Info.plist + guard let urlTypes: [AnyObject] = Bundle.main.object(forInfoDictionaryKey: "CFBundleURLTypes") as? [AnyObject], urlTypes.count > 0 else { + assertionFailure("No custom URI scheme has been configured for the project.") + return + } + + guard let items = urlTypes[0] as? [String: AnyObject], + let urlSchemes = items["CFBundleURLSchemes"] as? [AnyObject], urlSchemes.count > 0 else { + assertionFailure("No custom URI scheme has been configured for the project.") + return + } + + guard let urlScheme = urlSchemes[0] as? String else { + assertionFailure("No custom URI scheme has been configured for the project.") + return + } + + assert(urlScheme != "com.example.app", + "Configure the URI scheme in Info.plist (URL Types -> Item 0 -> URL Schemes -> Item 0) " + + "with the scheme of your redirect URI. Full instructions: " + + "https://github.com/openid/AppAuth-iOS/blob/master/Examples/Example-iOS_Swift-Carthage/README.md") + } + +} + +//MARK: IBActions +extension AppAuthExampleViewController { + + @IBAction func authWithAutoCodeExchange(_ sender: UIButton) { + + guard let issuer = URL(string: kIssuer) else { + self.logMessage("Error creating URL for : \(kIssuer)") + return + } + + self.logMessage("Fetching configuration for issuer: \(issuer)") + + // discovers endpoints + OIDAuthorizationService.discoverConfiguration(forIssuer: issuer) { configuration, error in + + guard let config = configuration else { + self.logMessage("Error retrieving discovery document: \(error?.localizedDescription ?? "DEFAULT_ERROR")") + self.setAuthState(nil) + return + } + + self.logMessage("Got configuration: \(config)") + + if let clientId = kClientID { + self.doAuthWithAutoCodeExchange(configuration: config, clientID: clientId, clientSecret: nil) + } else { + self.doClientRegistration(configuration: config) { configuration, response in + + guard let configuration = configuration, let clientID = response?.clientID else { + self.logMessage("Error retrieving configuration OR clientID") + return + } + + self.doAuthWithAutoCodeExchange(configuration: configuration, + clientID: clientID, + clientSecret: response?.clientSecret) + } + } + } + + } + + @IBAction func authNoCodeExchange(_ sender: UIButton) { + + guard let issuer = URL(string: kIssuer) else { + self.logMessage("Error creating URL for : \(kIssuer)") + return + } + + self.logMessage("Fetching configuration for issuer: \(issuer)") + + OIDAuthorizationService.discoverConfiguration(forIssuer: issuer) { configuration, error in + + if let error = error { + self.logMessage("Error retrieving discovery document: \(error.localizedDescription)") + return + } + + guard let configuration = configuration else { + self.logMessage("Error retrieving discovery document. Error & Configuration both are NIL!") + return + } + + self.logMessage("Got configuration: \(configuration)") + + if let clientId = kClientID { + + self.doAuthWithoutCodeExchange(configuration: configuration, clientID: clientId, clientSecret: nil) + + } else { + + self.doClientRegistration(configuration: configuration) { configuration, response in + + guard let configuration = configuration, let response = response else { + return + } + + self.doAuthWithoutCodeExchange(configuration: configuration, + clientID: response.clientID, + clientSecret: response.clientSecret) + } + } + } + } + + @IBAction func codeExchange(_ sender: UIButton) { + + guard let tokenExchangeRequest = self.authState?.lastAuthorizationResponse.tokenExchangeRequest() else { + self.logMessage("Error creating authorization code exchange request") + return + } + + self.logMessage("Performing authorization code exchange with request \(tokenExchangeRequest)") + + OIDAuthorizationService.perform(tokenExchangeRequest) { response, error in + + if let tokenResponse = response { + self.logMessage("Received token response with accessToken: \(tokenResponse.accessToken ?? "DEFAULT_TOKEN")") + } else { + self.logMessage("Token exchange error: \(error?.localizedDescription ?? "DEFAULT_ERROR")") + } + self.authState?.update(with: response, error: error) + } + } + + @IBAction func userinfo(_ sender: UIButton) { + + guard let userinfoEndpoint = self.authState?.lastAuthorizationResponse.request.configuration.discoveryDocument?.userinfoEndpoint else { + self.logMessage("Userinfo endpoint not declared in discovery document") + return + } + + self.logMessage("Performing userinfo request") + + let currentAccessToken: String? = self.authState?.lastTokenResponse?.accessToken + + self.authState?.performAction() { (accessToken, idToken, error) in + + if error != nil { + self.logMessage("Error fetching fresh tokens: \(error?.localizedDescription ?? "ERROR")") + return + } + + guard let accessToken = accessToken else { + self.logMessage("Error getting accessToken") + return + } + + if currentAccessToken != accessToken { + self.logMessage("Access token was refreshed automatically (\(currentAccessToken ?? "CURRENT_ACCESS_TOKEN") to \(accessToken))") + } else { + self.logMessage("Access token was fresh and not updated \(accessToken)") + } + + var urlRequest = URLRequest(url: userinfoEndpoint) + urlRequest.allHTTPHeaderFields = ["Authorization":"Bearer \(accessToken)"] + + let task = URLSession.shared.dataTask(with: urlRequest) { data, response, error in + + DispatchQueue.main.async { + + guard error == nil else { + self.logMessage("HTTP request failed \(error?.localizedDescription ?? "ERROR")") + return + } + + guard let response = response as? HTTPURLResponse else { + self.logMessage("Non-HTTP response") + return + } + + guard let data = data else { + self.logMessage("HTTP response data is empty") + return + } + + var json: [AnyHashable: Any]? + + do { + json = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] + } catch { + self.logMessage("JSON Serialization Error") + } + + if response.statusCode != 200 { + // server replied with an error + let responseText: String? = String(data: data, encoding: String.Encoding.utf8) + + if response.statusCode == 401 { + // "401 Unauthorized" generally indicates there is an issue with the authorization + // grant. Puts OIDAuthState into an error state. + let oauthError = OIDErrorUtilities.resourceServerAuthorizationError(withCode: 0, + errorResponse: json, + underlyingError: error) + self.authState?.update(withAuthorizationError: oauthError) + self.logMessage("Authorization Error (\(oauthError)). Response: \(responseText ?? "RESPONSE_TEXT")") + } else { + self.logMessage("HTTP: \(response.statusCode), Response: \(responseText ?? "RESPONSE_TEXT")") + } + + return + } + + if let json = json { + self.logMessage("Success: \(json)") + } + } + } + + task.resume() + } + } + + @IBAction func trashClicked(_ sender: UIBarButtonItem) { + + let alert = UIAlertController(title: nil, + message: nil, + preferredStyle: UIAlertControllerStyle.actionSheet) + + let clearAuthAction = UIAlertAction(title: "Clear OAuthState", style: .destructive) { (_: UIAlertAction) in + self.setAuthState(nil) + self.updateUI() + } + alert.addAction(clearAuthAction) + + let clearLogs = UIAlertAction(title: "Clear Logs", style: .default) { (_: UIAlertAction) in + DispatchQueue.main.async { + self.logTextView.text = "" + } + } + + let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil) + + + alert.addAction(clearLogs) + alert.addAction(cancelAction) + self.present(alert, animated: true, completion: nil) + + } +} + +//MARK: AppAuth Methods +extension AppAuthExampleViewController { + + func doClientRegistration(configuration: OIDServiceConfiguration, callback: @escaping PostRegistrationCallback) { + + guard let redirectURI = URL(string: kRedirectURI) else { + self.logMessage("Error creating URL for : \(kRedirectURI)") + return + } + + let request: OIDRegistrationRequest = OIDRegistrationRequest(configuration: configuration, + redirectURIs: [redirectURI], + responseTypes: nil, + grantTypes: nil, + subjectType: nil, + tokenEndpointAuthMethod: "client_secret_post", + additionalParameters: nil, + additionalHeaders: nil) + + // performs registration request + self.logMessage("Initiating registration request") + + OIDAuthorizationService.perform(request) { response, error in + + if let regResponse = response { + self.setAuthState(OIDAuthState(registrationResponse: regResponse)) + self.logMessage("Got registration response: \(regResponse)") + callback(configuration, regResponse) + } else { + self.logMessage("Registration error: \(error?.localizedDescription ?? "DEFAULT_ERROR")") + self.setAuthState(nil) + } + } + } + + func doAuthWithAutoCodeExchange(configuration: OIDServiceConfiguration, clientID: String, clientSecret: String?) { + + guard let redirectURI = URL(string: kRedirectURI) else { + self.logMessage("Error creating URL for : \(kRedirectURI)") + return + } + + guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else { + self.logMessage("Error accessing AppDelegate") + return + } + + // builds authentication request + let request = OIDAuthorizationRequest(configuration: configuration, + clientId: clientID, + clientSecret: clientSecret, + scopes: [OIDScopeOpenID, OIDScopeProfile], + redirectURL: redirectURI, + responseType: OIDResponseTypeCode, + additionalParameters: nil) + + // performs authentication request + logMessage("Initiating authorization request with scope: \(request.scope ?? "DEFAULT_SCOPE")") + + appDelegate.currentAuthorizationFlow = OIDAuthState.authState(byPresenting: request, presenting: self) { authState, error in + + if let authState = authState { + self.setAuthState(authState) + self.logMessage("Got authorization tokens. Access token: \(authState.lastTokenResponse?.accessToken ?? "DEFAULT_TOKEN")") + } else { + self.logMessage("Authorization error: \(error?.localizedDescription ?? "DEFAULT_ERROR")") + self.setAuthState(nil) + } + } + } + + func doAuthWithoutCodeExchange(configuration: OIDServiceConfiguration, clientID: String, clientSecret: String?) { + + guard let redirectURI = URL(string: kRedirectURI) else { + self.logMessage("Error creating URL for : \(kRedirectURI)") + return + } + + guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else { + self.logMessage("Error accessing AppDelegate") + return + } + + // builds authentication request + let request = OIDAuthorizationRequest(configuration: configuration, + clientId: clientID, + clientSecret: clientSecret, + scopes: [OIDScopeOpenID, OIDScopeProfile], + redirectURL: redirectURI, + responseType: OIDResponseTypeCode, + additionalParameters: nil) + + // performs authentication request + logMessage("Initiating authorization request with scope: \(request.scope ?? "DEFAULT_SCOPE")") + + appDelegate.currentAuthorizationFlow = OIDAuthorizationService.present(request, presenting: self) { (response, error) in + + if let response = response { + let authState = OIDAuthState(authorizationResponse: response) + self.setAuthState(authState) + self.logMessage("Authorization response with code: \(response.authorizationCode ?? "DEFAULT_CODE")") + // could just call [self tokenExchange:nil] directly, but will let the user initiate it. + } else { + self.logMessage("Authorization error: \(error?.localizedDescription ?? "DEFAULT_ERROR")") + } + } + } +} + +//MARK: OIDAuthState Delegate +extension AppAuthExampleViewController: OIDAuthStateChangeDelegate, OIDAuthStateErrorDelegate { + + func didChange(_ state: OIDAuthState) { + self.stateChanged() + } + + func authState(_ state: OIDAuthState, didEncounterAuthorizationError error: Error) { + self.logMessage("Received authorization error: \(error)") + } +} + +//MARK: Helper Methods +extension AppAuthExampleViewController { + + func saveState() { + + var data: Data? = nil + + if let authState = self.authState { + data = NSKeyedArchiver.archivedData(withRootObject: authState) + } + + if let userDefaults = UserDefaults(suiteName: "group.net.openid.appauth.Example") { + userDefaults.set(data, forKey: kAppAuthExampleAuthStateKey) + userDefaults.synchronize() + } + } + + func loadState() { + guard let data = UserDefaults(suiteName: "group.net.openid.appauth.Example")?.object(forKey: kAppAuthExampleAuthStateKey) as? Data else { + return + } + + if let authState = NSKeyedUnarchiver.unarchiveObject(with: data) as? OIDAuthState { + self.setAuthState(authState) + } + } + + func setAuthState(_ authState: OIDAuthState?) { + if (self.authState == authState) { + return; + } + self.authState = authState; + self.authState?.stateChangeDelegate = self; + self.stateChanged() + } + + func updateUI() { + + self.codeExchangeButton.isEnabled = self.authState?.lastAuthorizationResponse.authorizationCode != nil && !((self.authState?.lastTokenResponse) != nil) + + if let authState = self.authState { + self.authAutoButton.setTitle("1. Re-Auth", for: .normal) + self.authManual.setTitle("1(A) Re-Auth", for: .normal) + self.userinfoButton.isEnabled = authState.isAuthorized ? true : false + } else { + self.authAutoButton.setTitle("1. Auto", for: .normal) + self.authManual.setTitle("1(A) Manual", for: .normal) + self.userinfoButton.isEnabled = false + } + } + + func stateChanged() { + self.saveState() + self.updateUI() + } + + func logMessage(_ message: String?) { + + guard let message = message else { + return + } + + print(message); + + let dateFormatter = DateFormatter() + dateFormatter.dateFormat = "hh:mm:ss"; + let dateString = dateFormatter.string(from: Date()) + + // appends to output log + DispatchQueue.main.async { + let logText = "\(self.logTextView.text ?? "")\n\(dateString): \(message)" + self.logTextView.text = logText + } + } +} diff --git a/Examples/Example-iOS_Swift-Carthage/Source/AppDelegate.swift b/Examples/Example-iOS_Swift-Carthage/Source/AppDelegate.swift new file mode 100644 index 000000000..070c3d71a --- /dev/null +++ b/Examples/Example-iOS_Swift-Carthage/Source/AppDelegate.swift @@ -0,0 +1,44 @@ +// +// AppDelegate.swift +// +// Copyright (c) 2017 The AppAuth Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import AppAuth +import UIKit + +@UIApplicationMain +class AppDelegate: UIResponder, UIApplicationDelegate { + + var window: UIWindow? + var currentAuthorizationFlow: OIDExternalUserAgentSession? + + func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { + // Override point for customization after application launch. + return true + } + + func application(_ app: UIApplication, open url: URL, options: [UIApplicationOpenURLOptionsKey : Any] = [:]) -> Bool { + + if let authorizationFlow = self.currentAuthorizationFlow, authorizationFlow.resumeExternalUserAgentFlow(with: url) { + self.currentAuthorizationFlow = nil + return true + } + + return false + } + +} + diff --git a/Examples/Example-iOS_Swift-Carthage/Source/Assets.xcassets/AppIcon.appiconset/AppAuth_Icon_1024.png b/Examples/Example-iOS_Swift-Carthage/Source/Assets.xcassets/AppIcon.appiconset/AppAuth_Icon_1024.png new file mode 100644 index 000000000..9d74d6575 Binary files /dev/null and b/Examples/Example-iOS_Swift-Carthage/Source/Assets.xcassets/AppIcon.appiconset/AppAuth_Icon_1024.png differ diff --git a/Examples/Example-iOS_Swift-Carthage/Source/Assets.xcassets/AppIcon.appiconset/AppAuth_Icon_120.png b/Examples/Example-iOS_Swift-Carthage/Source/Assets.xcassets/AppIcon.appiconset/AppAuth_Icon_120.png new file mode 100644 index 000000000..f73edc78a Binary files /dev/null and b/Examples/Example-iOS_Swift-Carthage/Source/Assets.xcassets/AppIcon.appiconset/AppAuth_Icon_120.png differ diff --git a/Examples/Example-iOS_Swift-Carthage/Source/Assets.xcassets/AppIcon.appiconset/AppAuth_Icon_152.png b/Examples/Example-iOS_Swift-Carthage/Source/Assets.xcassets/AppIcon.appiconset/AppAuth_Icon_152.png new file mode 100644 index 000000000..bdd5de3c3 Binary files /dev/null and b/Examples/Example-iOS_Swift-Carthage/Source/Assets.xcassets/AppIcon.appiconset/AppAuth_Icon_152.png differ diff --git a/Examples/Example-iOS_Swift-Carthage/Source/Assets.xcassets/AppIcon.appiconset/AppAuth_Icon_167.png b/Examples/Example-iOS_Swift-Carthage/Source/Assets.xcassets/AppIcon.appiconset/AppAuth_Icon_167.png new file mode 100644 index 000000000..1bb56407d Binary files /dev/null and b/Examples/Example-iOS_Swift-Carthage/Source/Assets.xcassets/AppIcon.appiconset/AppAuth_Icon_167.png differ diff --git a/Examples/Example-iOS_Swift-Carthage/Source/Assets.xcassets/AppIcon.appiconset/AppAuth_Icon_180.png b/Examples/Example-iOS_Swift-Carthage/Source/Assets.xcassets/AppIcon.appiconset/AppAuth_Icon_180.png new file mode 100644 index 000000000..17535eb04 Binary files /dev/null and b/Examples/Example-iOS_Swift-Carthage/Source/Assets.xcassets/AppIcon.appiconset/AppAuth_Icon_180.png differ diff --git a/Examples/Example-iOS_Swift-Carthage/Source/Assets.xcassets/AppIcon.appiconset/AppAuth_Icon_76.png b/Examples/Example-iOS_Swift-Carthage/Source/Assets.xcassets/AppIcon.appiconset/AppAuth_Icon_76.png new file mode 100644 index 000000000..dc824d5e7 Binary files /dev/null and b/Examples/Example-iOS_Swift-Carthage/Source/Assets.xcassets/AppIcon.appiconset/AppAuth_Icon_76.png differ diff --git a/Examples/Example-iOS_Swift-Carthage/Source/Assets.xcassets/AppIcon.appiconset/Contents.json b/Examples/Example-iOS_Swift-Carthage/Source/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 000000000..0b6ca5edb --- /dev/null +++ b/Examples/Example-iOS_Swift-Carthage/Source/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,104 @@ +{ + "images" : [ + { + "idiom" : "iphone", + "size" : "20x20", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "20x20", + "scale" : "3x" + }, + { + "idiom" : "iphone", + "size" : "29x29", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "29x29", + "scale" : "3x" + }, + { + "idiom" : "iphone", + "size" : "40x40", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "40x40", + "scale" : "3x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "AppAuth_Icon_120.png", + "scale" : "2x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "AppAuth_Icon_180.png", + "scale" : "3x" + }, + { + "idiom" : "ipad", + "size" : "20x20", + "scale" : "1x" + }, + { + "idiom" : "ipad", + "size" : "20x20", + "scale" : "2x" + }, + { + "idiom" : "ipad", + "size" : "29x29", + "scale" : "1x" + }, + { + "idiom" : "ipad", + "size" : "29x29", + "scale" : "2x" + }, + { + "idiom" : "ipad", + "size" : "40x40", + "scale" : "1x" + }, + { + "idiom" : "ipad", + "size" : "40x40", + "scale" : "2x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "AppAuth_Icon_76.png", + "scale" : "1x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "AppAuth_Icon_152.png", + "scale" : "2x" + }, + { + "size" : "83.5x83.5", + "idiom" : "ipad", + "filename" : "AppAuth_Icon_167.png", + "scale" : "2x" + }, + { + "size" : "1024x1024", + "idiom" : "ios-marketing", + "filename" : "AppAuth_Icon_1024.png", + "scale" : "1x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Examples/Example-iOS_Swift-Carthage/Source/Base.lproj/LaunchScreen.storyboard b/Examples/Example-iOS_Swift-Carthage/Source/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 000000000..f83f6fd58 --- /dev/null +++ b/Examples/Example-iOS_Swift-Carthage/Source/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Examples/Example-iOS_Swift-Carthage/Source/Base.lproj/Main.storyboard b/Examples/Example-iOS_Swift-Carthage/Source/Base.lproj/Main.storyboard new file mode 100644 index 000000000..e85c973e8 --- /dev/null +++ b/Examples/Example-iOS_Swift-Carthage/Source/Base.lproj/Main.storyboard @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Examples/Example-iOS_Swift-Carthage/Source/Example.entitlements b/Examples/Example-iOS_Swift-Carthage/Source/Example.entitlements new file mode 100644 index 000000000..c250a2746 --- /dev/null +++ b/Examples/Example-iOS_Swift-Carthage/Source/Example.entitlements @@ -0,0 +1,10 @@ + + + + + com.apple.security.application-groups + + group.net.openid.appauth.Example + + + diff --git a/Examples/Example-iOS_Swift-Carthage/Source/Info.plist b/Examples/Example-iOS_Swift-Carthage/Source/Info.plist new file mode 100644 index 000000000..7df6bc8c1 --- /dev/null +++ b/Examples/Example-iOS_Swift-Carthage/Source/Info.plist @@ -0,0 +1,56 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleURLTypes + + + CFBundleTypeRole + Editor + CFBundleURLSchemes + + com.example.app + + + + CFBundleVersion + 1 + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UIRequiredDeviceCapabilities + + armv7 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + + diff --git a/Examples/Example-iOS_Swift-SPM/.gitignore b/Examples/Example-iOS_Swift-SPM/.gitignore new file mode 100644 index 000000000..e3680cef6 --- /dev/null +++ b/Examples/Example-iOS_Swift-SPM/.gitignore @@ -0,0 +1 @@ +Config/Example.local.xcconfig diff --git a/Examples/Example-iOS_Swift-SPM/Config/Example.xcconfig b/Examples/Example-iOS_Swift-SPM/Config/Example.xcconfig new file mode 100644 index 000000000..0c33ad3b0 --- /dev/null +++ b/Examples/Example-iOS_Swift-SPM/Config/Example.xcconfig @@ -0,0 +1,14 @@ +// This file holds public placeholder defaults for OAuth configuration. +// Real OAuth client values belong in the sibling, gitignored Config/Example.local.xcconfig, +// which overrides these defaults via the optional-include directive below. + +OIDC_ISSUER = https:/$()/issuer.example.com +OIDC_CLIENT_ID = YOUR_CLIENT_ID +OIDC_REDIRECT_URI = com.example.app:/oauth2redirect/example-provider +OIDC_REDIRECT_URI_SCHEME = com.example.app + +// Code signing. Defaults to Automatic with no team. Override in +// Example.local.xcconfig if you need Manual signing with a specific provisioning profile. +CODE_SIGN_STYLE = Automatic + +#include? "Example.local.xcconfig" diff --git a/Examples/Example-iOS_Swift-SPM/Example.xcodeproj/project.pbxproj b/Examples/Example-iOS_Swift-SPM/Example.xcodeproj/project.pbxproj new file mode 100644 index 000000000..da3b94da0 --- /dev/null +++ b/Examples/Example-iOS_Swift-SPM/Example.xcodeproj/project.pbxproj @@ -0,0 +1,375 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 77; + objects = { + +/* Begin PBXBuildFile section */ + 09BB0F002F91B157004C0D4B /* AppAuth in Frameworks */ = {isa = PBXBuildFile; productRef = 09BB0E002F91B157004C0D4B /* AppAuth */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 09BB0B542F91B157004C0D4B /* Example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Example.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 09BB0F012F91B157004C0D4B /* Config/Example.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Config/Example.xcconfig; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */ + 09BB0C002F91B157004C0D4B /* Exceptions for "Example" folder in "Example" target */ = { + isa = PBXFileSystemSynchronizedBuildFileExceptionSet; + membershipExceptions = ( + Info.plist, + ); + target = 09BB0B532F91B157004C0D4B /* Example */; + }; +/* End PBXFileSystemSynchronizedBuildFileExceptionSet section */ + +/* Begin PBXFileSystemSynchronizedRootGroup section */ + 09BB0B562F91B157004C0D4B /* Example */ = { + isa = PBXFileSystemSynchronizedRootGroup; + exceptions = ( + 09BB0C002F91B157004C0D4B /* Exceptions for "Example" folder in "Example" target */, + ); + path = Example; + sourceTree = ""; + }; +/* End PBXFileSystemSynchronizedRootGroup section */ + +/* Begin PBXFrameworksBuildPhase section */ + 09BB0B512F91B157004C0D4B /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 09BB0F002F91B157004C0D4B /* AppAuth in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 09BB0B4B2F91B157004C0D4B = { + isa = PBXGroup; + children = ( + 09BB0F022F91B157004C0D4B /* Config */, + 09BB0B562F91B157004C0D4B /* Example */, + 09BB0B552F91B157004C0D4B /* Products */, + ); + sourceTree = ""; + }; + 09BB0B552F91B157004C0D4B /* Products */ = { + isa = PBXGroup; + children = ( + 09BB0B542F91B157004C0D4B /* Example.app */, + ); + name = Products; + sourceTree = ""; + }; + 09BB0F022F91B157004C0D4B /* Config */ = { + isa = PBXGroup; + children = ( + 09BB0F012F91B157004C0D4B /* Config/Example.xcconfig */, + ); + name = Config; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 09BB0B532F91B157004C0D4B /* Example */ = { + isa = PBXNativeTarget; + buildConfigurationList = 09BB0B5F2F91B158004C0D4B /* Build configuration list for PBXNativeTarget "Example" */; + buildPhases = ( + 09BB0B502F91B157004C0D4B /* Sources */, + 09BB0B512F91B157004C0D4B /* Frameworks */, + 09BB0B522F91B157004C0D4B /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + fileSystemSynchronizedGroups = ( + 09BB0B562F91B157004C0D4B /* Example */, + ); + name = Example; + packageProductDependencies = ( + 09BB0E002F91B157004C0D4B /* AppAuth */, + ); + productName = Example; + productReference = 09BB0B542F91B157004C0D4B /* Example.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 09BB0B4C2F91B157004C0D4B /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = 1; + LastSwiftUpdateCheck = 2620; + LastUpgradeCheck = 2620; + TargetAttributes = { + 09BB0B532F91B157004C0D4B = { + CreatedOnToolsVersion = 26.2; + }; + }; + }; + buildConfigurationList = 09BB0B4F2F91B157004C0D4B /* Build configuration list for PBXProject "Example" */; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 09BB0B4B2F91B157004C0D4B; + minimizedProjectReferenceProxies = 1; + packageReferences = ( + 09BB0D002F91B157004C0D4B /* XCLocalSwiftPackageReference "../.." */, + ); + preferredProjectObjectVersion = 77; + productRefGroup = 09BB0B552F91B157004C0D4B /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 09BB0B532F91B157004C0D4B /* Example */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 09BB0B522F91B157004C0D4B /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 09BB0B502F91B157004C0D4B /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + 09BB0B5D2F91B158004C0D4B /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + 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_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 26.2; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 09BB0B5E2F91B158004C0D4B /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + 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_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 26.2; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 09BB0B602F91B158004C0D4B /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 09BB0F012F91B157004C0D4B /* Config/Example.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + CURRENT_PROJECT_VERSION = 1; + ENABLE_PREVIEWS = YES; + GENERATE_INFOPLIST_FILE = NO; + INFOPLIST_FILE = Example/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = "AppAuth iOS Demo"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.google.experimental0.dev; + PRODUCT_NAME = "$(TARGET_NAME)"; + STRING_CATALOG_GENERATE_SYMBOLS = YES; + SWIFT_APPROACHABLE_CONCURRENCY = YES; + SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 09BB0B612F91B158004C0D4B /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 09BB0F012F91B157004C0D4B /* Config/Example.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + CURRENT_PROJECT_VERSION = 1; + ENABLE_PREVIEWS = YES; + GENERATE_INFOPLIST_FILE = NO; + INFOPLIST_FILE = Example/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = "AppAuth iOS Demo"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.google.experimental0.dev; + PRODUCT_NAME = "$(TARGET_NAME)"; + STRING_CATALOG_GENERATE_SYMBOLS = YES; + SWIFT_APPROACHABLE_CONCURRENCY = YES; + SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 09BB0B4F2F91B157004C0D4B /* Build configuration list for PBXProject "Example" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 09BB0B5D2F91B158004C0D4B /* Debug */, + 09BB0B5E2F91B158004C0D4B /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 09BB0B5F2F91B158004C0D4B /* Build configuration list for PBXNativeTarget "Example" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 09BB0B602F91B158004C0D4B /* Debug */, + 09BB0B612F91B158004C0D4B /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + +/* Begin XCLocalSwiftPackageReference section */ + 09BB0D002F91B157004C0D4B /* XCLocalSwiftPackageReference "../.." */ = { + isa = XCLocalSwiftPackageReference; + relativePath = ../..; + }; +/* End XCLocalSwiftPackageReference section */ + +/* Begin XCSwiftPackageProductDependency section */ + 09BB0E002F91B157004C0D4B /* AppAuth */ = { + isa = XCSwiftPackageProductDependency; + productName = AppAuth; + }; +/* End XCSwiftPackageProductDependency section */ + }; + rootObject = 09BB0B4C2F91B157004C0D4B /* Project object */; +} diff --git a/Examples/Example-iOS_Swift-SPM/Example.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/Examples/Example-iOS_Swift-SPM/Example.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 000000000..919434a62 --- /dev/null +++ b/Examples/Example-iOS_Swift-SPM/Example.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/Examples/Example-iOS_Swift-SPM/Example/AppDelegate.swift b/Examples/Example-iOS_Swift-SPM/Example/AppDelegate.swift new file mode 100644 index 000000000..a1838b2cd --- /dev/null +++ b/Examples/Example-iOS_Swift-SPM/Example/AppDelegate.swift @@ -0,0 +1,34 @@ +// +// AppDelegate.swift +// +// Copyright (c) 2026 The AppAuth Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import UIKit +import AppAuth + +class AppDelegate: NSObject, UIApplicationDelegate { + + var currentAuthorizationFlow: OIDExternalUserAgentSession? + + func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool { + if let authorizationFlow = self.currentAuthorizationFlow, authorizationFlow.resumeExternalUserAgentFlow(with: url) { + self.currentAuthorizationFlow = nil + return true + } + + return false + } +} diff --git a/Examples/Example-iOS_Swift-SPM/Example/Assets.xcassets/AccentColor.colorset/Contents.json b/Examples/Example-iOS_Swift-SPM/Example/Assets.xcassets/AccentColor.colorset/Contents.json new file mode 100644 index 000000000..eb8789700 --- /dev/null +++ b/Examples/Example-iOS_Swift-SPM/Example/Assets.xcassets/AccentColor.colorset/Contents.json @@ -0,0 +1,11 @@ +{ + "colors" : [ + { + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Examples/Example-iOS_Swift-SPM/Example/Assets.xcassets/AppIcon.appiconset/Contents.json b/Examples/Example-iOS_Swift-SPM/Example/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 000000000..230588010 --- /dev/null +++ b/Examples/Example-iOS_Swift-SPM/Example/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,35 @@ +{ + "images" : [ + { + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "tinted" + } + ], + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Examples/Example-iOS_Swift-SPM/Example/Assets.xcassets/Contents.json b/Examples/Example-iOS_Swift-SPM/Example/Assets.xcassets/Contents.json new file mode 100644 index 000000000..73c00596a --- /dev/null +++ b/Examples/Example-iOS_Swift-SPM/Example/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Examples/Example-iOS_Swift-SPM/Example/AuthManager.swift b/Examples/Example-iOS_Swift-SPM/Example/AuthManager.swift new file mode 100644 index 000000000..b0e01ff84 --- /dev/null +++ b/Examples/Example-iOS_Swift-SPM/Example/AuthManager.swift @@ -0,0 +1,459 @@ +// +// AuthManager.swift +// +// Copyright (c) 2026 The AppAuth Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import AppAuth +import Combine +import SwiftUI +import UIKit + +typealias PostRegistrationCallback = (OIDServiceConfiguration?, OIDRegistrationResponse?) -> Void + +let kIssuer: String = { + guard let issuer = Bundle.main.object(forInfoDictionaryKey: "OIDCIssuer") as? String, + !issuer.isEmpty, + issuer != "https://issuer.example.com" else { + preconditionFailure("Please configure OIDC_ISSUER in Example.local.xcconfig") + } + return issuer +}() +let kClientID: String? = { + let clientID = Bundle.main.object(forInfoDictionaryKey: "OIDCClientID") as? String + if clientID == "YOUR_CLIENT_ID" || clientID?.isEmpty ?? true { + return nil + } + return clientID +}() +let kRedirectURI: String = { + guard let redirectURI = Bundle.main.object(forInfoDictionaryKey: "OIDCRedirectURI") as? String, + !redirectURI.isEmpty, + redirectURI != "com.example.app:/oauth2redirect/example-provider" else { + preconditionFailure("Please configure OIDC_REDIRECT_URI in Example.local.xcconfig") + } + return redirectURI +}() +let kAppAuthExampleAuthStateKey: String = "authState" + +final class AuthManager: NSObject, ObservableObject { + @Published private(set) var authState: OIDAuthState? + @Published private(set) var logText: String = "" + + var isAuthorized: Bool { authState?.isAuthorized ?? false } + var hasAuthorizationCode: Bool { authState?.lastAuthorizationResponse.authorizationCode != nil && authState?.lastTokenResponse == nil } + var hasAuthState: Bool { authState != nil } + + weak var appDelegate: AppDelegate? + + override init() { + super.init() + self.validateOAuthConfiguration() + self.loadState() + } + + // MARK: Public Methods + + func validateOAuthConfiguration() { + guard let urlTypes = Bundle.main.object(forInfoDictionaryKey: "CFBundleURLTypes") as? [[String: Any]], + let urlSchemes = urlTypes.first?["CFBundleURLSchemes"] as? [String], + let urlScheme = urlSchemes.first else { + assertionFailure("CFBundleURLSchemes not configured") + return + } + + assert(urlScheme != "com.example.app", "Register your OIDC Redirect URI scheme in Example.local.xcconfig (OIDC_REDIRECT_URI_SCHEME).") + assert(kIssuer != "https://issuer.example.com", "Register your OIDC Issuer in Example.local.xcconfig (OIDC_ISSUER).") + } + + func authWithAutoCodeExchange() { + guard let issuer = URL(string: kIssuer) else { + self.logMessage("Error creating URL for : \(kIssuer)") + return + } + + self.logMessage("Fetching configuration for issuer: \(issuer)") + + // discovers endpoints + OIDAuthorizationService.discoverConfiguration(forIssuer: issuer) { configuration, error in + guard let config = configuration else { + self.logMessage("Error retrieving discovery document: \(error?.localizedDescription ?? "DEFAULT_ERROR")") + self.setAuthState(nil) + return + } + + self.logMessage("Got configuration: \(config)") + + // If kClientID was provided, use it; otherwise perform RFC 7591 dynamic client registration. + if let clientId = kClientID { + self.doAuthWithAutoCodeExchange(configuration: config, clientID: clientId, clientSecret: nil) + } else { + self.doClientRegistration(configuration: config) { configuration, response in + guard let configuration = configuration, let clientID = response?.clientID else { + self.logMessage("Error retrieving configuration OR clientID") + return + } + + self.doAuthWithAutoCodeExchange(configuration: configuration, + clientID: clientID, + clientSecret: response?.clientSecret) + } + } + } + } + + func authNoCodeExchange() { + guard let issuer = URL(string: kIssuer) else { + self.logMessage("Error creating URL for : \(kIssuer)") + return + } + + self.logMessage("Fetching configuration for issuer: \(issuer)") + + OIDAuthorizationService.discoverConfiguration(forIssuer: issuer) { configuration, error in + if let error = error { + self.logMessage("Error retrieving discovery document: \(error.localizedDescription)") + return + } + + guard let configuration = configuration else { + self.logMessage("Error retrieving discovery document. Error & Configuration both are NIL!") + return + } + + self.logMessage("Got configuration: \(configuration)") + + // If kClientID was provided, use it; otherwise perform RFC 7591 dynamic client registration. + if let clientId = kClientID { + self.doAuthWithoutCodeExchange(configuration: configuration, clientID: clientId, clientSecret: nil) + } else { + self.doClientRegistration(configuration: configuration) { configuration, response in + guard let configuration = configuration, let response = response else { + return + } + + self.doAuthWithoutCodeExchange(configuration: configuration, + clientID: response.clientID, + clientSecret: response.clientSecret) + } + } + } + } + + func codeExchange() { + guard let tokenExchangeRequest = self.authState?.lastAuthorizationResponse.tokenExchangeRequest() else { + self.logMessage("Error creating authorization code exchange request") + return + } + + self.logMessage("Performing authorization code exchange with request \(tokenExchangeRequest)") + + OIDAuthorizationService.perform(tokenExchangeRequest) { response, error in + if let tokenResponse = response { + self.logMessage("Received token response with accessToken: \(tokenResponse.accessToken ?? "DEFAULT_TOKEN")") + } else { + self.logMessage("Token exchange error: \(error?.localizedDescription ?? "DEFAULT_ERROR")") + } + self.authState?.update(with: response, error: error) + } + } + + func userinfo() { + guard let userinfoEndpoint = self.authState?.lastAuthorizationResponse.request.configuration.discoveryDocument?.userinfoEndpoint else { + self.logMessage("Userinfo endpoint not declared in discovery document") + return + } + + self.logMessage("Performing userinfo request") + + let currentAccessToken: String? = self.authState?.lastTokenResponse?.accessToken + + self.authState?.performAction() { (accessToken, idToken, error) in + if error != nil { + self.logMessage("Error fetching fresh tokens: \(error?.localizedDescription ?? "ERROR")") + return + } + + guard let accessToken = accessToken else { + self.logMessage("Error getting accessToken") + return + } + + if currentAccessToken != accessToken { + self.logMessage("Access token was refreshed automatically (\(currentAccessToken ?? "CURRENT_ACCESS_TOKEN") to \(accessToken))") + } else { + self.logMessage("Access token was fresh and not updated \(accessToken)") + } + + var urlRequest = URLRequest(url: userinfoEndpoint) + urlRequest.allHTTPHeaderFields = ["Authorization":"Bearer \(accessToken)"] + + let task = URLSession.shared.dataTask(with: urlRequest) { data, response, error in + DispatchQueue.main.async { + guard error == nil else { + self.logMessage("HTTP request failed \(error?.localizedDescription ?? "ERROR")") + return + } + + guard let response = response as? HTTPURLResponse else { + self.logMessage("Non-HTTP response") + return + } + + guard let data = data else { + self.logMessage("HTTP response data is empty") + return + } + + var json: [AnyHashable: Any]? + + do { + json = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] + } catch { + self.logMessage("JSON Serialization Error") + } + + if response.statusCode != 200 { + // server replied with an error + let responseText: String? = String(data: data, encoding: String.Encoding.utf8) + + if response.statusCode == 401 { + // "401 Unauthorized" generally indicates there is an issue with the authorization + // grant. Puts OIDAuthState into an error state. + let oauthError = OIDErrorUtilities.resourceServerAuthorizationError(withCode: 0, + errorResponse: json, + underlyingError: error) + self.authState?.update(withAuthorizationError: oauthError) + self.logMessage("Authorization Error (\(oauthError)). Response: \(responseText ?? "RESPONSE_TEXT")") + } else { + self.logMessage("HTTP: \(response.statusCode), Response: \(responseText ?? "RESPONSE_TEXT")") + } + + return + } + + if let json = json { + self.logMessage("Success: \(json)") + } + } + } + + task.resume() + } + } + + func clearAuthState() { + setAuthState(nil) + } + + func clearLogs() { + DispatchQueue.main.async { + self.logText = "" + } + } + + // MARK: Private Methods + + private func doClientRegistration(configuration: OIDServiceConfiguration, callback: @escaping PostRegistrationCallback) { + guard let redirectURI = URL(string: kRedirectURI) else { + self.logMessage("Error creating URL for : \(kRedirectURI)") + return + } + + let request: OIDRegistrationRequest = OIDRegistrationRequest(configuration: configuration, + redirectURIs: [redirectURI], + responseTypes: nil, + grantTypes: nil, + subjectType: nil, + tokenEndpointAuthMethod: "client_secret_post", + additionalParameters: nil) + + // performs registration request + self.logMessage("Initiating registration request") + + OIDAuthorizationService.perform(request) { response, error in + if let regResponse = response { + self.setAuthState(OIDAuthState(registrationResponse: regResponse)) + self.logMessage("Got registration response: \(regResponse)") + callback(configuration, regResponse) + } else { + self.logMessage("Registration error: \(error?.localizedDescription ?? "DEFAULT_ERROR")") + self.setAuthState(nil) + } + } + } + + private func doAuthWithAutoCodeExchange(configuration: OIDServiceConfiguration, clientID: String, clientSecret: String?) { + guard let redirectURI = URL(string: kRedirectURI) else { + self.logMessage("Error creating URL for : \(kRedirectURI)") + return + } + + guard let appDelegate = self.appDelegate else { + self.logMessage("Error accessing AppDelegate") + return + } + + guard let presentingVC = self.presentingViewController() else { + return + } + + // builds authentication request + let request = OIDAuthorizationRequest(configuration: configuration, + clientId: clientID, + clientSecret: clientSecret, + scopes: [OIDScopeOpenID, OIDScopeProfile], + redirectURL: redirectURI, + responseType: OIDResponseTypeCode, + additionalParameters: nil) + + // performs authentication request + logMessage("Initiating authorization request with scope: \(request.scope ?? "DEFAULT_SCOPE")") + + appDelegate.currentAuthorizationFlow = OIDAuthState.authState(byPresenting: request, presenting: presentingVC) { authState, error in + if let authState = authState { + self.setAuthState(authState) + self.logMessage("Got authorization tokens. Access token: \(authState.lastTokenResponse?.accessToken ?? "DEFAULT_TOKEN")") + } else { + self.logMessage("Authorization error: \(error?.localizedDescription ?? "DEFAULT_ERROR")") + self.setAuthState(nil) + } + } + } + + private func doAuthWithoutCodeExchange(configuration: OIDServiceConfiguration, clientID: String, clientSecret: String?) { + guard let redirectURI = URL(string: kRedirectURI) else { + self.logMessage("Error creating URL for : \(kRedirectURI)") + return + } + + guard let appDelegate = self.appDelegate else { + self.logMessage("Error accessing AppDelegate") + return + } + + guard let presentingVC = self.presentingViewController() else { + return + } + + // builds authentication request + let request = OIDAuthorizationRequest(configuration: configuration, + clientId: clientID, + clientSecret: clientSecret, + scopes: [OIDScopeOpenID, OIDScopeProfile], + redirectURL: redirectURI, + responseType: OIDResponseTypeCode, + additionalParameters: nil) + + // performs authentication request + logMessage("Initiating authorization request with scope: \(request.scope ?? "DEFAULT_SCOPE")") + + appDelegate.currentAuthorizationFlow = OIDAuthorizationService.present(request, presenting: presentingVC) { (response, error) in + if let response = response { + let authState = OIDAuthState(authorizationResponse: response) + self.setAuthState(authState) + self.logMessage("Authorization response with code: \(response.authorizationCode ?? "DEFAULT_CODE")") + // could just call [self tokenExchange:nil] directly, but will let the user initiate it. + } else { + self.logMessage("Authorization error: \(error?.localizedDescription ?? "DEFAULT_ERROR")") + } + } + } + + private func setAuthState(_ authState: OIDAuthState?) { + if (self.authState == authState) { + return + } + self.authState = authState + self.authState?.stateChangeDelegate = self + self.authState?.errorDelegate = self + self.stateChanged() + } + + private func stateChanged() { + self.saveState() + } + + private func saveState() { + var data: Data? = nil + + if let authState = self.authState { + do { + data = try NSKeyedArchiver.archivedData(withRootObject: authState, requiringSecureCoding: true) + } catch { + logMessage("Error archiving authState: \(error.localizedDescription)") + return + } + } + + UserDefaults.standard.set(data, forKey: kAppAuthExampleAuthStateKey) + } + + private func loadState() { + guard let data = UserDefaults.standard.data(forKey: kAppAuthExampleAuthStateKey) else { + return + } + + do { + if let authState = try NSKeyedUnarchiver.unarchivedObject(ofClass: OIDAuthState.self, from: data) { + self.setAuthState(authState) + } + } catch { + logMessage("Error unarchiving authState: \(error.localizedDescription)") + } + } + + private func logMessage(_ message: String?) { + guard let message = message else { + return + } + + print(message) + + let dateFormatter = DateFormatter() + dateFormatter.dateFormat = "hh:mm:ss" + let dateString = dateFormatter.string(from: Date()) + + // appends to output log + DispatchQueue.main.async { + let logText = "\(self.logText)\n\(dateString): \(message)" + self.logText = logText + } + } + + private func presentingViewController() -> UIViewController? { + let viewController = UIApplication.shared.connectedScenes + .compactMap { $0 as? UIWindowScene } + .first?.windows + .first(where: { $0.isKeyWindow })? + .rootViewController + if viewController == nil { + logMessage("Error: no presenting view controller available") + } + return viewController + } +} + +// MARK: OIDAuthState Delegate + +extension AuthManager: OIDAuthStateChangeDelegate, OIDAuthStateErrorDelegate { + func didChange(_ state: OIDAuthState) { + self.stateChanged() + } + + func authState(_ state: OIDAuthState, didEncounterAuthorizationError error: Error) { + self.logMessage("Received authorization error: \(error)") + } +} diff --git a/Examples/Example-iOS_Swift-SPM/Example/ContentView.swift b/Examples/Example-iOS_Swift-SPM/Example/ContentView.swift new file mode 100644 index 000000000..7ddcbcac4 --- /dev/null +++ b/Examples/Example-iOS_Swift-SPM/Example/ContentView.swift @@ -0,0 +1,98 @@ +// +// ContentView.swift +// +// Copyright (c) 2026 The AppAuth Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import SwiftUI + +struct ContentView: View { + @EnvironmentObject var authManager: AuthManager + + var body: some View { + NavigationStack { + VStack(spacing: 12) { + HStack { + Button(authManager.hasAuthState ? "Re-Auth (Auto)" : "Auto") { + authManager.authWithAutoCodeExchange() + } + .buttonStyle(.borderedProminent) + .frame(maxWidth: .infinity) + + Button(authManager.hasAuthState ? "Re-Auth (Manual)" : "Manual") { + authManager.authNoCodeExchange() + } + .buttonStyle(.borderedProminent) + .frame(maxWidth: .infinity) + } + + HStack { + Button("Code Exchange") { + authManager.codeExchange() + } + .buttonStyle(.borderedProminent) + .disabled(!authManager.hasAuthorizationCode) + .frame(maxWidth: .infinity) + + Button("User Info") { + authManager.userinfo() + } + .buttonStyle(.borderedProminent) + .disabled(!authManager.isAuthorized) + .frame(maxWidth: .infinity) + } + + ScrollViewReader { proxy in + ScrollView { + Text(authManager.logText) + .frame(maxWidth: .infinity, alignment: .leading) + .font(.system(.caption, design: .monospaced)) + .textSelection(.enabled) + .padding(8) + .id("bottom") + } + .overlay( + RoundedRectangle(cornerRadius: 6) + .stroke(Color.secondary.opacity(0.4)) + ) + .onChange(of: authManager.logText) { _ in + proxy.scrollTo("bottom", anchor: .bottom) + } + } + } + .padding() + .navigationTitle("AppAuth Example") + .toolbar { + ToolbarItem(placement: .topBarTrailing) { + Menu { + Button(role: .destructive) { + authManager.clearAuthState() + } label: { + Text("Clear OAuth State") + } + + Button { + authManager.clearLogs() + } label: { + Text("Clear Logs") + } + } label: { + Image(systemName: "trash") + } + } + } + } + } +} diff --git a/Examples/Example-iOS_Swift-SPM/Example/ExampleApp.swift b/Examples/Example-iOS_Swift-SPM/Example/ExampleApp.swift new file mode 100644 index 000000000..462e81fa2 --- /dev/null +++ b/Examples/Example-iOS_Swift-SPM/Example/ExampleApp.swift @@ -0,0 +1,35 @@ +// +// ExampleApp.swift +// +// Copyright (c) 2026 The AppAuth Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import SwiftUI + +@main +struct ExampleApp: App { + @UIApplicationDelegateAdaptor(AppDelegate.self) private var appDelegate + @StateObject private var authManager = AuthManager() + + var body: some Scene { + WindowGroup { + ContentView() + .environmentObject(authManager) + .onAppear { + authManager.appDelegate = appDelegate + } + } + } +} diff --git a/Examples/Example-iOS_Swift-SPM/Example/Info.plist b/Examples/Example-iOS_Swift-SPM/Example/Info.plist new file mode 100644 index 000000000..aaf558068 --- /dev/null +++ b/Examples/Example-iOS_Swift-SPM/Example/Info.plist @@ -0,0 +1,63 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + $(PRODUCT_BUNDLE_PACKAGE_TYPE) + CFBundleShortVersionString + $(MARKETING_VERSION) + CFBundleURLTypes + + + CFBundleTypeRole + Editor + CFBundleURLSchemes + + $(OIDC_REDIRECT_URI_SCHEME) + + + + CFBundleVersion + $(CURRENT_PROJECT_VERSION) + LSRequiresIPhoneOS + + OIDCClientID + $(OIDC_CLIENT_ID) + OIDCIssuer + $(OIDC_ISSUER) + OIDCRedirectURI + $(OIDC_REDIRECT_URI) + UIApplicationSceneManifest + + UIApplicationSupportsMultipleScenes + + UISceneConfigurations + + + UILaunchScreen + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + + diff --git a/Examples/Example-iOS_Swift-SPM/README.md b/Examples/Example-iOS_Swift-SPM/README.md new file mode 100644 index 000000000..403a93146 --- /dev/null +++ b/Examples/Example-iOS_Swift-SPM/README.md @@ -0,0 +1,48 @@ +# Example Project (SwiftUI + Swift Package Manager) + +## Setup & Open the Project + +This sample uses the local AppAuth Swift Package via a relative path (`../..`), so Xcode resolves it automatically. + +Just open `Example.xcodeproj` to get started, and complete the configuration. + +## Configuration + +The example doesn't work out of the box — you need to configure it with your own OpenID Connect client. + +### Information You'll Need + +* Issuer +* Client ID +* Redirect URI + +How to get this information varies by IdP, but we have [instructions](../README.md#openid-certified-providers) for some OpenID Certified providers. + +### Configure the Example + +The sample reads these values (Issuer, Client ID, Redirect URI) from an xcconfig file. Create your local override file by copying the committed defaults: + + cp Config/Example.xcconfig Config/Example.local.xcconfig + +Then edit `Config/Example.local.xcconfig` and set: + + OIDC_ISSUER = + OIDC_CLIENT_ID = + OIDC_REDIRECT_URI = + OIDC_REDIRECT_URI_SCHEME = + +The scheme is everything before the colon (:) of your redirect URI. For example, if the redirect URI is `com.example.app:/oauth2redirect/example-provider`, the scheme is `com.example.app`. + +Note that the local version you create, `Config/Example.local.xcconfig`, is gitignored. + +The same file can also override code-signing. By default the committed xcconfig sets CODE_SIGN_STYLE = Automatic, which causes Xcode to prompt for your team on first build — the same experience as the sibling samples. To use Manual signing with a specific provisioning profile, add these lines to Config/Example.local.xcconfig: + + CODE_SIGN_STYLE = Manual + DEVELOPMENT_TEAM = + PROVISIONING_PROFILE_SPECIFIER = + +Note: Xcode may cache Info.plist substitutions — after editing the xcconfig, run **Product > Clean Build Folder**. + +### Running the Example + +Now your example should be ready to run. diff --git a/Examples/Example-macOS/Example-macOS.xcodeproj/project.pbxproj b/Examples/Example-macOS/Example-macOS.xcodeproj/project.pbxproj index 429bbca91..13bb1c957 100644 --- a/Examples/Example-macOS/Example-macOS.xcodeproj/project.pbxproj +++ b/Examples/Example-macOS/Example-macOS.xcodeproj/project.pbxproj @@ -156,7 +156,6 @@ F6016E6C1D2AC11F003497D7 /* Sources */, F6016E6D1D2AC11F003497D7 /* Frameworks */, F6016E6E1D2AC11F003497D7 /* Resources */, - 52872C7E76CB3EA69F4392F7 /* [CP] Embed Pods Frameworks */, 1C5B2EF60536044DE119E500 /* [CP] Copy Pods Resources */, ); buildRules = ( @@ -192,6 +191,7 @@ developmentRegion = English; hasScannedForEncodings = 0; knownRegions = ( + English, en, Base, ); @@ -233,28 +233,18 @@ files = ( ); inputPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Example-macOS/Pods-Example-macOS-resources.sh", + "${PODS_CONFIGURATION_BUILD_DIR}/AppAuth/AppAuthCore_Privacy.bundle", + "${PODS_CONFIGURATION_BUILD_DIR}/AppAuth/AppAuthExternalUserAgent_Privacy.bundle", ); name = "[CP] Copy Pods Resources"; outputPaths = ( + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/AppAuthCore_Privacy.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/AppAuthExternalUserAgent_Privacy.bundle", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Example-macOS/Pods-Example-macOS-resources.sh\"\n"; - showEnvVarsInLog = 0; - }; - 52872C7E76CB3EA69F4392F7 /* [CP] Embed Pods Frameworks */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - ); - name = "[CP] Embed Pods Frameworks"; - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Example-macOS/Pods-Example-macOS-frameworks.sh\"\n"; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Example-macOS/Pods-Example-macOS-resources.sh\"\n"; showEnvVarsInLog = 0; }; C9395BB900D0A44A3F7EB0BE /* [CP] Check Pods Manifest.lock */ = { @@ -263,13 +253,16 @@ files = ( ); inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", ); name = "[CP] Check Pods Manifest.lock"; outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Example-macOS-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n"; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; /* End PBXShellScriptBuildPhase section */ @@ -442,11 +435,11 @@ isa = XCBuildConfiguration; baseConfigurationReference = 7FF583C8F7036437BE8875B3 /* Pods-Example-macOS.debug.xcconfig */; buildSettings = { - ARCHS = "$(ARCHS_STANDARD_32_64_BIT)"; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; COMBINE_HIDPI_IMAGES = YES; INFOPLIST_FILE = "$(SRCROOT)/Source/Info.plist"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; + MACOSX_DEPLOYMENT_TARGET = 10.12; PRODUCT_BUNDLE_IDENTIFIER = "net.openid.appauth.Example-macOS"; PRODUCT_NAME = "$(TARGET_NAME)"; }; @@ -456,11 +449,11 @@ isa = XCBuildConfiguration; baseConfigurationReference = 60553B3AB5BA3E1BB259E487 /* Pods-Example-macOS.release.xcconfig */; buildSettings = { - ARCHS = "$(ARCHS_STANDARD_32_64_BIT)"; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; COMBINE_HIDPI_IMAGES = YES; INFOPLIST_FILE = "$(SRCROOT)/Source/Info.plist"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; + MACOSX_DEPLOYMENT_TARGET = 10.12; PRODUCT_BUNDLE_IDENTIFIER = "net.openid.appauth.Example-macOS"; PRODUCT_NAME = "$(TARGET_NAME)"; }; diff --git a/Examples/Example-macOS/Podfile b/Examples/Example-macOS/Podfile index 5b8ce01ab..e0c0aced9 100644 --- a/Examples/Example-macOS/Podfile +++ b/Examples/Example-macOS/Podfile @@ -1,5 +1,5 @@ target 'Example-macOS' do - platform :osx, '10.10' + platform :osx, '10.12' # AppAuth Pod # In production, just use `pod 'AppAuth'` without the path reference. diff --git a/Examples/Example-macOS/Source/AppAuthExampleViewController.h b/Examples/Example-macOS/Source/AppAuthExampleViewController.h index 93c05029d..99a460a7f 100644 --- a/Examples/Example-macOS/Source/AppAuthExampleViewController.h +++ b/Examples/Example-macOS/Source/AppAuthExampleViewController.h @@ -20,26 +20,12 @@ @class AppDelegate; @class OIDAuthState; @class OIDServiceConfiguration; -@class OIDRedirectHTTPHandler; NS_ASSUME_NONNULL_BEGIN /*! @brief The example application's view controller. */ -@interface AppAuthExampleViewController : NSViewController { - // private variables - OIDRedirectHTTPHandler *_redirectHTTPHandler; - // property variables - NSButton *_authAutoButton; - NSButton *_authManual; - NSButton *_authAutoHTTPButton; - NSButton *_codeExchangeButton; - NSButton *_userinfoButton; - NSButton *_clearAuthStateButton; - NSTextView *_logTextView; - __weak AppDelegate *_appDelegate; - OIDAuthState *_authState; -} +@interface AppAuthExampleViewController : NSViewController @property(nullable) IBOutlet NSButton *authAutoButton; @property(nullable) IBOutlet NSButton *authManual; diff --git a/Examples/Example-macOS/Source/AppAuthExampleViewController.m b/Examples/Example-macOS/Source/AppAuthExampleViewController.m index 9a282caba..38235cd17 100644 --- a/Examples/Example-macOS/Source/AppAuthExampleViewController.m +++ b/Examples/Example-macOS/Source/AppAuthExampleViewController.m @@ -60,17 +60,9 @@ @interface AppAuthExampleViewController () @end -@implementation AppAuthExampleViewController - -@synthesize authAutoButton = _authAutoButton; -@synthesize authManual = _authManual; -@synthesize authAutoHTTPButton = _authAutoHTTPButton; -@synthesize codeExchangeButton = _codeExchangeButton; -@synthesize userinfoButton = _userinfoButton; -@synthesize clearAuthStateButton = _clearAuthStateButton; -@synthesize logTextView = _logTextView; -@synthesize appDelegate = _appDelegate; -@synthesize authState = _authState; +@implementation AppAuthExampleViewController { + OIDRedirectHTTPHandler *_redirectHTTPHandler; +} - (void)viewDidLoad { [super viewDidLoad]; @@ -222,8 +214,9 @@ - (IBAction)authWithAutoCodeExchange:(nullable id)sender { // performs authentication request self.appDelegate.currentAuthorizationFlow = [OIDAuthState authStateByPresentingAuthorizationRequest:request - callback:^(OIDAuthState *_Nullable authState, - NSError *_Nullable error) { + presentingWindow:self.view.window + callback:^(OIDAuthState *_Nullable authState, + NSError *_Nullable error) { if (authState) { [self setAuthState:authState]; [self logMessage:@"Got authorization tokens. Access token: %@", @@ -290,8 +283,8 @@ - (IBAction)authWithAutoCodeExchangeHTTP:(nullable id)sender { __weak __typeof(self) weakSelf = self; _redirectHTTPHandler.currentAuthorizationFlow = [OIDAuthState authStateByPresentingAuthorizationRequest:request - callback:^(OIDAuthState *_Nullable authState, - NSError *_Nullable error) { + presentingWindow:self.view.window + callback:^(OIDAuthState *_Nullable authState, NSError *_Nullable error) { // Brings this app to the foreground. [[NSRunningApplication currentApplication] activateWithOptions:(NSApplicationActivateAllWindows | @@ -337,11 +330,12 @@ - (IBAction)authNoCodeExchange:(nullable id)sender { additionalParameters:nil]; // performs authentication request [self logMessage:@"Initiating authorization request %@", request]; + self.appDelegate.currentAuthorizationFlow = [OIDAuthorizationService presentAuthorizationRequest:request - callback:^(OIDAuthorizationResponse *_Nullable authorizationResponse, - NSError *_Nullable error) { - + presentingWindow:self.view.window + callback:^(OIDAuthorizationResponse *_Nullable authorizationResponse, + NSError *_Nullable error) { if (authorizationResponse) { OIDAuthState *authState = [[OIDAuthState alloc] initWithAuthorizationResponse:authorizationResponse]; @@ -420,7 +414,7 @@ - (IBAction)userinfo:(nullable id)sender { [request addValue:authorizationHeaderValue forHTTPHeaderField:@"Authorization"]; NSURLSessionConfiguration *configuration = - [NSURLSessionConfiguration defaultSessionConfiguration]; + [NSURLSessionConfiguration defaultSessionConfiguration]; NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration delegate:nil delegateQueue:nil]; @@ -496,7 +490,9 @@ - (void)logMessage:(NSString *)format, ... NS_FORMAT_FUNCTION(1,2) { NSString *dateString = [dateFormatter stringFromDate:[NSDate date]]; NSString *logLine = [NSString stringWithFormat:@"\n%@: %@", dateString, log]; NSAttributedString* logLineAttr = [[NSAttributedString alloc] initWithString:logLine]; - [[_logTextView textStorage] appendAttributedString:logLineAttr]; + dispatch_async(dispatch_get_main_queue(), ^{ + [[_logTextView textStorage] appendAttributedString:logLineAttr]; + }); } @end diff --git a/Examples/Example-macOS/Source/AppDelegate.h b/Examples/Example-macOS/Source/AppDelegate.h index 3a5883c05..8d7b65f04 100644 --- a/Examples/Example-macOS/Source/AppDelegate.h +++ b/Examples/Example-macOS/Source/AppDelegate.h @@ -20,25 +20,19 @@ NS_ASSUME_NONNULL_BEGIN -@protocol OIDAuthorizationFlowSession; +@protocol OIDExternalUserAgentSession; /*! @class AppDelegate @brief The example application's delegate. */ -@interface AppDelegate : NSObject { - // property variables - NSWindow *_window; - id _currentAuthorizationFlow; -} +@interface AppDelegate : NSObject /*! @property currentAuthorizationFlow @brief The authorization flow session which receives the return URL from the browser. @discussion We need to store this in the app delegate as it's that delegate which receives the incoming URL. This property will be nil, except when an authorization flow is in progress. */ -@property(nonatomic, strong, nullable) id currentAuthorizationFlow; - -@property(nullable) IBOutlet NSWindow *window; +@property(nonatomic, strong, nullable) id currentAuthorizationFlow; @end diff --git a/Examples/Example-macOS/Source/AppDelegate.m b/Examples/Example-macOS/Source/AppDelegate.m index 367b02ca5..7e5a8068b 100644 --- a/Examples/Example-macOS/Source/AppDelegate.m +++ b/Examples/Example-macOS/Source/AppDelegate.m @@ -24,10 +24,11 @@ NS_ASSUME_NONNULL_BEGIN -@implementation AppDelegate +@interface AppDelegate () +@property(nullable) IBOutlet NSWindow *window; +@end -@synthesize window = _window; -@synthesize currentAuthorizationFlow = _currentAuthorizationFlow; +@implementation AppDelegate - (void)applicationDidFinishLaunching:(NSNotification *)aNotification { _window.title = @"AppAuth Example for macOS"; @@ -48,7 +49,7 @@ - (void)handleGetURLEvent:(NSAppleEventDescriptor *)event withReplyEvent:(NSAppleEventDescriptor *)replyEvent { NSString *URLString = [[event paramDescriptorForKeyword:keyDirectObject] stringValue]; NSURL *URL = [NSURL URLWithString:URLString]; - [_currentAuthorizationFlow resumeAuthorizationFlowWithURL:URL]; + [_currentAuthorizationFlow resumeExternalUserAgentFlowWithURL:URL]; } @end diff --git a/Examples/Example-macOS/Source/Assets.xcassets/AppIcon.appiconset/AppAuth_macOS_Icon_1024.png b/Examples/Example-macOS/Source/Assets.xcassets/AppIcon.appiconset/AppAuth_macOS_Icon_1024.png new file mode 100644 index 000000000..b7883fbcc Binary files /dev/null and b/Examples/Example-macOS/Source/Assets.xcassets/AppIcon.appiconset/AppAuth_macOS_Icon_1024.png differ diff --git a/Examples/Example-macOS/Source/Assets.xcassets/AppIcon.appiconset/AppAuth_macOS_Icon_512.png b/Examples/Example-macOS/Source/Assets.xcassets/AppIcon.appiconset/AppAuth_macOS_Icon_512.png new file mode 100644 index 000000000..a38111faa Binary files /dev/null and b/Examples/Example-macOS/Source/Assets.xcassets/AppIcon.appiconset/AppAuth_macOS_Icon_512.png differ diff --git a/Examples/Example-macOS/Source/Assets.xcassets/AppIcon.appiconset/Contents.json b/Examples/Example-macOS/Source/Assets.xcassets/AppIcon.appiconset/Contents.json index 2db2b1c7c..be30d8e10 100644 --- a/Examples/Example-macOS/Source/Assets.xcassets/AppIcon.appiconset/Contents.json +++ b/Examples/Example-macOS/Source/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -41,13 +41,15 @@ "scale" : "2x" }, { - "idiom" : "mac", "size" : "512x512", + "idiom" : "mac", + "filename" : "AppAuth_macOS_Icon_512.png", "scale" : "1x" }, { - "idiom" : "mac", "size" : "512x512", + "idiom" : "mac", + "filename" : "AppAuth_macOS_Icon_1024.png", "scale" : "2x" } ], diff --git a/Examples/Example-tvOS/Example-tvOS.xcodeproj/project.pbxproj b/Examples/Example-tvOS/Example-tvOS.xcodeproj/project.pbxproj new file mode 100644 index 000000000..b2e8a7d1a --- /dev/null +++ b/Examples/Example-tvOS/Example-tvOS.xcodeproj/project.pbxproj @@ -0,0 +1,400 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 50; + objects = { + +/* Begin PBXBuildFile section */ + 2D8B83982497C7B800CD51D7 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 2D8B83972497C7B800CD51D7 /* Main.storyboard */; }; + 2D91B7A8248EA17C0005B197 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 2D91B7A7248EA17C0005B197 /* AppDelegate.m */; }; + 2D91B7AB248EA17C0005B197 /* AppAuthTVExampleViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2D91B7AA248EA17C0005B197 /* AppAuthTVExampleViewController.m */; }; + 2D91B7B0248EA17D0005B197 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 2D91B7AF248EA17D0005B197 /* Assets.xcassets */; }; + 2D91B7B3248EA17D0005B197 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 2D91B7B1248EA17D0005B197 /* LaunchScreen.storyboard */; }; + 2D91B7B6248EA17E0005B197 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 2D91B7B5248EA17E0005B197 /* main.m */; }; + FEFBCAF105C70E934E5A4975 /* Pods_Example_tvOS.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F58A1FCA981D60A0EA8A3E5A /* Pods_Example_tvOS.framework */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 0822FAAB579E01CE65770A61 /* Pods-Example-tvOS.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Example-tvOS.release.xcconfig"; path = "Target Support Files/Pods-Example-tvOS/Pods-Example-tvOS.release.xcconfig"; sourceTree = ""; }; + 2D8B83972497C7B800CD51D7 /* Main.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = Main.storyboard; sourceTree = ""; }; + 2D91B7A3248EA17C0005B197 /* AppAuth tvOS.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "AppAuth tvOS.app"; sourceTree = BUILT_PRODUCTS_DIR; }; + 2D91B7A6248EA17C0005B197 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; + 2D91B7A7248EA17C0005B197 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; + 2D91B7A9248EA17C0005B197 /* AppAuthTVExampleViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppAuthTVExampleViewController.h; sourceTree = ""; }; + 2D91B7AA248EA17C0005B197 /* AppAuthTVExampleViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppAuthTVExampleViewController.m; sourceTree = ""; }; + 2D91B7AF248EA17D0005B197 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 2D91B7B2248EA17D0005B197 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 2D91B7B4248EA17D0005B197 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 2D91B7B5248EA17E0005B197 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; + D33A601E3AA7C8647C857C08 /* Pods-Example-tvOS.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Example-tvOS.debug.xcconfig"; path = "Target Support Files/Pods-Example-tvOS/Pods-Example-tvOS.debug.xcconfig"; sourceTree = ""; }; + F58A1FCA981D60A0EA8A3E5A /* Pods_Example_tvOS.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Example_tvOS.framework; sourceTree = BUILT_PRODUCTS_DIR; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 2D91B7A0248EA17C0005B197 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + FEFBCAF105C70E934E5A4975 /* Pods_Example_tvOS.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 2D91B79A248EA17C0005B197 = { + isa = PBXGroup; + children = ( + 2D91B7A5248EA17C0005B197 /* Example-tvOS */, + 2D91B7A4248EA17C0005B197 /* Products */, + D5A14A70EF1AB61275EF2A3E /* Pods */, + 9CCA3BB0D6EA112455518E2C /* Frameworks */, + ); + sourceTree = ""; + }; + 2D91B7A4248EA17C0005B197 /* Products */ = { + isa = PBXGroup; + children = ( + 2D91B7A3248EA17C0005B197 /* AppAuth tvOS.app */, + ); + name = Products; + sourceTree = ""; + }; + 2D91B7A5248EA17C0005B197 /* Example-tvOS */ = { + isa = PBXGroup; + children = ( + 2D91B7A6248EA17C0005B197 /* AppDelegate.h */, + 2D91B7A7248EA17C0005B197 /* AppDelegate.m */, + 2D91B7A9248EA17C0005B197 /* AppAuthTVExampleViewController.h */, + 2D91B7AA248EA17C0005B197 /* AppAuthTVExampleViewController.m */, + 2D8B83972497C7B800CD51D7 /* Main.storyboard */, + 2D91B7AF248EA17D0005B197 /* Assets.xcassets */, + 2D91B7B1248EA17D0005B197 /* LaunchScreen.storyboard */, + 2D91B7B4248EA17D0005B197 /* Info.plist */, + 2D91B7B5248EA17E0005B197 /* main.m */, + ); + path = "Example-tvOS"; + sourceTree = ""; + }; + 9CCA3BB0D6EA112455518E2C /* Frameworks */ = { + isa = PBXGroup; + children = ( + F58A1FCA981D60A0EA8A3E5A /* Pods_Example_tvOS.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + D5A14A70EF1AB61275EF2A3E /* Pods */ = { + isa = PBXGroup; + children = ( + D33A601E3AA7C8647C857C08 /* Pods-Example-tvOS.debug.xcconfig */, + 0822FAAB579E01CE65770A61 /* Pods-Example-tvOS.release.xcconfig */, + ); + path = Pods; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 2D91B7A2248EA17C0005B197 /* Example-tvOS */ = { + isa = PBXNativeTarget; + buildConfigurationList = 2D91B7B9248EA17E0005B197 /* Build configuration list for PBXNativeTarget "Example-tvOS" */; + buildPhases = ( + BE78BAE9067856409CDDD945 /* [CP] Check Pods Manifest.lock */, + 2D91B79F248EA17C0005B197 /* Sources */, + 2D91B7A0248EA17C0005B197 /* Frameworks */, + 2D91B7A1248EA17C0005B197 /* Resources */, + C86108A8D245915451E09E53 /* [CP] Embed Pods Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = "Example-tvOS"; + productName = "Example-tvOS"; + productReference = 2D91B7A3248EA17C0005B197 /* AppAuth tvOS.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 2D91B79B248EA17C0005B197 /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 1150; + ORGANIZATIONNAME = "Souleiman Benhida"; + TargetAttributes = { + 2D91B7A2248EA17C0005B197 = { + CreatedOnToolsVersion = 11.5; + }; + }; + }; + buildConfigurationList = 2D91B79E248EA17C0005B197 /* Build configuration list for PBXProject "Example-tvOS" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 2D91B79A248EA17C0005B197; + productRefGroup = 2D91B7A4248EA17C0005B197 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 2D91B7A2248EA17C0005B197 /* Example-tvOS */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 2D91B7A1248EA17C0005B197 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 2D8B83982497C7B800CD51D7 /* Main.storyboard in Resources */, + 2D91B7B3248EA17D0005B197 /* LaunchScreen.storyboard in Resources */, + 2D91B7B0248EA17D0005B197 /* Assets.xcassets in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + BE78BAE9067856409CDDD945 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Example-tvOS-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + C86108A8D245915451E09E53 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Example-tvOS/Pods-Example-tvOS-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Example-tvOS/Pods-Example-tvOS-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Example-tvOS/Pods-Example-tvOS-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 2D91B79F248EA17C0005B197 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 2D91B7AB248EA17C0005B197 /* AppAuthTVExampleViewController.m in Sources */, + 2D91B7B6248EA17E0005B197 /* main.m in Sources */, + 2D91B7A8248EA17C0005B197 /* AppDelegate.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXVariantGroup section */ + 2D91B7B1248EA17D0005B197 /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 2D91B7B2248EA17D0005B197 /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 2D91B7B7248EA17E0005B197 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = appletvos; + TVOS_DEPLOYMENT_TARGET = 9.0; + }; + name = Debug; + }; + 2D91B7B8248EA17E0005B197 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = appletvos; + TVOS_DEPLOYMENT_TARGET = 9.0; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 2D91B7BA248EA17E0005B197 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = D33A601E3AA7C8647C857C08 /* Pods-Example-tvOS.debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = "App Icon & Top Shelf Image"; + CODE_SIGN_STYLE = Automatic; + INFOPLIST_FILE = "Example-tvOS/Info.plist"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = net.openid.appauthtv.Example; + PRODUCT_NAME = "AppAuth tvOS"; + TARGETED_DEVICE_FAMILY = 3; + }; + name = Debug; + }; + 2D91B7BB248EA17E0005B197 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 0822FAAB579E01CE65770A61 /* Pods-Example-tvOS.release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = "App Icon & Top Shelf Image"; + CODE_SIGN_STYLE = Automatic; + INFOPLIST_FILE = "Example-tvOS/Info.plist"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = net.openid.appauthtv.Example; + PRODUCT_NAME = "AppAuth tvOS"; + TARGETED_DEVICE_FAMILY = 3; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 2D91B79E248EA17C0005B197 /* Build configuration list for PBXProject "Example-tvOS" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 2D91B7B7248EA17E0005B197 /* Debug */, + 2D91B7B8248EA17E0005B197 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 2D91B7B9248EA17E0005B197 /* Build configuration list for PBXNativeTarget "Example-tvOS" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 2D91B7BA248EA17E0005B197 /* Debug */, + 2D91B7BB248EA17E0005B197 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 2D91B79B248EA17C0005B197 /* Project object */; +} diff --git a/Examples/Example-tvOS/Example-tvOS.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/Examples/Example-tvOS/Example-tvOS.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 000000000..eb768ef2b --- /dev/null +++ b/Examples/Example-tvOS/Example-tvOS.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/Examples/Example-tvOS/Example-tvOS.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/Examples/Example-tvOS/Example-tvOS.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 000000000..18d981003 --- /dev/null +++ b/Examples/Example-tvOS/Example-tvOS.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/Examples/Example-tvOS/Example-tvOS/AppAuthTVExampleViewController.h b/Examples/Example-tvOS/Example-tvOS/AppAuthTVExampleViewController.h new file mode 100644 index 000000000..aeccd98b5 --- /dev/null +++ b/Examples/Example-tvOS/Example-tvOS/AppAuthTVExampleViewController.h @@ -0,0 +1,63 @@ +/*! @file AppAuthTVExampleViewController.h + @brief AppAuth tvOS SDK Example + @copyright + Copyright 2016 Google Inc. + @copydetails + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#import + +@class OIDAuthState; + +NS_ASSUME_NONNULL_BEGIN + +/*! @brief The example application's view controller. + */ +@interface AppAuthTVExampleViewController : UIViewController + +@property(nullable) IBOutlet UIView *signInView; +@property(nullable) IBOutlet UILabel *verificationURLLabel; +@property(nullable) IBOutlet UILabel *userCodeLabel; +@property(nullable) IBOutlet UIView *signInButtons; +@property(nullable) IBOutlet UIButton *cancelSignInButton; +@property(nullable) IBOutlet UIView *signedInButtons; +@property(nullable) IBOutlet UITextView *logTextView; + +/*! @brief The authorization state. + */ +@property(nonatomic, nullable) OIDAuthState *authState; + +/*! @brief Initiates the sign-in. + @param sender IBAction sender. + */ +- (IBAction)signin:(nullable id)sender; + +/*! @brief Cancels the active sign-in (if any), has no effect if a sign-in isn't in progress. + @param sender IBAction sender. + */ +- (IBAction)cancelSignIn:(nullable id)sender; + +/*! @brief Forgets the authentication state, used to sign-out the user. + @param sender IBAction sender. + */ +- (IBAction)clearAuthState:(nullable id)sender; + +/*! @brief Performs an authenticated API call. + @param sender IBAction sender. + */ +- (IBAction)userinfo:(nullable id)sender; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Examples/Example-tvOS/Example-tvOS/AppAuthTVExampleViewController.m b/Examples/Example-tvOS/Example-tvOS/AppAuthTVExampleViewController.m new file mode 100644 index 000000000..1cdfb5ccf --- /dev/null +++ b/Examples/Example-tvOS/Example-tvOS/AppAuthTVExampleViewController.m @@ -0,0 +1,404 @@ +/*! @file AppAuthTVExampleViewController.m + @brief AppAuth tvOS SDK Example + @copyright + Copyright 2016 Google Inc. + @copydetails + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#import "AppAuthTVExampleViewController.h" + +#import +#import + +/*! @brief Indicates whether YES to discover endpoints from @c kIssuer or NO to use the + @c kDeviceAuthorizationEndpoint, @c kTokenEndpoint, and @c kUserInfoEndpoint values defined + below. + */ +static BOOL const shouldDiscoverEndpoints = YES; + +/*! @brief OAuth client ID. + */ +static NSString *const kClientID = @"YOUR_CLIENT_ID"; + +/*! @brief OAuth client secret. + */ +static NSString *const kClientSecret = @"YOUR_CLIENT_SECRET"; + +/*! @brief The OIDC issuer from which the configuration will be discovered. + */ +static NSString *const kIssuer = @"https://issuer.example.com"; + +/*! @brief Device authorization endpoint. + */ +static NSString *const kDeviceAuthorizationEndpoint = @"https://www.example.com/device"; + +/*! @brief Token endpoint. + */ +static NSString *const kTokenEndpoint = @"https://www.example.com/token"; + +/*! @brief User info endpoint. + */ +static NSString *const kUserInfoEndpoint = @"https://www.example.com/userinfo"; + +/*! @brief NSCoding key for the authorization property. + */ +static NSString *const kExampleAuthorizerKey = @"authorization"; + +/*! @brief NSCoding key for the authState property. + */ +static NSString *const kExampleAuthStateKey = @"authState"; + +@interface AppAuthTVExampleViewController () +@end + +@implementation AppAuthTVExampleViewController { + OIDTVAuthorizationCancelBlock _cancelBlock; +} + +- (void)viewDidLoad { + [super viewDidLoad]; + + _logTextView.text = @""; + _signInView.hidden = YES; + _cancelSignInButton.hidden = YES; + _logTextView.selectable = YES; + _logTextView.panGestureRecognizer.allowedTouchTypes = @[ @(UITouchTypeIndirect) ]; + + [self verifyConfig]; + + [self loadState]; + [self updateUI]; +} + +- (void)verifyConfig { +#if !defined(NS_BLOCK_ASSERTIONS) + NSAssert(![kClientID isEqualToString:@"YOUR_CLIENT_ID"], + @"Update kClientID with your own client ID. " + "Instructions: " + "https://github.com/openid/AppAuth-iOS/blob/master/Examples/Example-tvOS/README.md"); + + NSAssert(![kClientSecret isEqualToString:@"YOUR_CLIENT_SECRET"], + @"Update kClientSecret with your own client secret. " + "Instructions: " + "https://github.com/openid/AppAuth-iOS/blob/master/Examples/Example-tvOS/README.md"); + + if (shouldDiscoverEndpoints) { + NSAssert(![kIssuer isEqualToString:@"https://issuer.example.com"], + @"Update kIssuer with your own issuer. " + "Instructions: " + "https://github.com/openid/AppAuth-iOS/blob/master/Examples/Example-tvOS/README.md"); + } else { + NSAssert(![kDeviceAuthorizationEndpoint isEqualToString:@"https://www.example.com/device"], + @"Update kDeviceAuthorizationEndpoint with your own device authorization endpoint. " + "Instructions: " + "https://github.com/openid/AppAuth-iOS/blob/master/Examples/Example-tvOS/README.md"); + + NSAssert(![kTokenEndpoint isEqualToString:@"https://www.example.com/token"], + @"Update kTokenEndpoint with your own token endpoint. " + "Instructions: " + "https://github.com/openid/AppAuth-iOS/blob/master/Examples/Example-tvOS/README.md"); + + NSAssert(![kUserInfoEndpoint isEqualToString:@"https://www.example.com/userinfo"], + @"Update kUserInfoEndpoint with your own user info endpoint. " + "Instructions: " + "https://github.com/openid/AppAuth-iOS/blob/master/Examples/Example-tvOS/README.md"); + } +#endif // !defined(NS_BLOCK_ASSERTIONS) +} + +- (void)stateChanged { + [self saveState]; + [self updateUI]; +} + +- (void)didChangeState:(OIDAuthState *)state { + [self stateChanged]; +} + +- (void)authState:(OIDAuthState *)state didEncounterAuthorizationError:(nonnull NSError *)error { + [self logMessage:@"Received authorization error: %@", error]; +} +/*! @brief Initiates the sign-in. + @param sender IBAction sender. +*/ +- (IBAction)signin:(id)sender { + if (_cancelBlock) { + [self cancelSignIn:nil]; + } + + if (shouldDiscoverEndpoints) { + NSURL *issuer = [NSURL URLWithString:kIssuer]; + + // Discover endpoints + [OIDTVAuthorizationService discoverServiceConfigurationForIssuer:issuer + completion:^(OIDTVServiceConfiguration *_Nullable configuration, NSError *_Nullable error) { + if (!configuration) { + [self logMessage:@"Error retrieving discovery document: %@", [error localizedDescription]]; + [self setAuthState:nil]; + return; + } + + [self logMessage:@"Got configuration: %@", configuration]; + + // Perform authorization flow + [self performAuthorizationWithConfiguration:configuration]; + }]; + } else { + NSURL *deviceAuthorizationEndpoint = [NSURL URLWithString:kDeviceAuthorizationEndpoint]; + NSURL *tokenEndpoint = [NSURL URLWithString:kTokenEndpoint]; + + OIDTVServiceConfiguration *configuration = [[OIDTVServiceConfiguration alloc] + initWithDeviceAuthorizationEndpoint:deviceAuthorizationEndpoint + tokenEndpoint:tokenEndpoint]; + + // Perform authorization flow + [self performAuthorizationWithConfiguration:configuration]; + } +} + +- (void)performAuthorizationWithConfiguration:(OIDTVServiceConfiguration *)configuration { + // builds authentication request + __weak __typeof(self) weakSelf = self; + + OIDTVAuthorizationRequest *request = + [[OIDTVAuthorizationRequest alloc] initWithConfiguration:configuration + clientId:kClientID + clientSecret:kClientSecret + scopes:@[ OIDScopeOpenID, OIDScopeProfile ] + additionalParameters:nil]; + + OIDTVAuthorizationInitialization initBlock = + ^(OIDTVAuthorizationResponse *_Nullable response, NSError *_Nullable error) { + if (response) { + [weakSelf logMessage:@"Authorization response: %@", response]; + weakSelf.signInView.hidden = NO; + weakSelf.cancelSignInButton.hidden = NO; + weakSelf.verificationURLLabel.text = response.verificationURI; + weakSelf.userCodeLabel.text = response.userCode; + } else { + [weakSelf logMessage:@"Initialization error %@", error]; + } + }; + + OIDTVAuthorizationCompletion completionBlock = + ^(OIDAuthState *_Nullable authState, NSError *_Nullable error) { + weakSelf.signInView.hidden = YES; + if (authState) { + [weakSelf setAuthState:authState]; + [weakSelf logMessage:@"Token response: %@", authState.lastTokenResponse]; + } else { + [weakSelf setAuthState:nil]; + [weakSelf logMessage:@"Error: %@", error]; + } + }; + + _cancelBlock = [OIDTVAuthorizationService authorizeTVRequest:request + initialization:initBlock + completion:completionBlock]; +} + +/*! @brief Cancels the active sign-in (if any), has no effect if a sign-in isn't in progress. + @param sender IBAction sender. +*/ +- (IBAction)cancelSignIn:(nullable id)sender { + if (_cancelBlock) { + _cancelBlock(); + _cancelBlock = nil; + } + _signInView.hidden = YES; + _cancelSignInButton.hidden = YES; +} + +- (void)setAuthState:(nullable OIDAuthState *)authState { + if (_authState == authState) { + return; + } + _authState = authState; + _authState.stateChangeDelegate = self; + [self stateChanged]; +} + +/*! @brief Saves the @c OIDAuthState to @c NSUSerDefaults. + */ +- (void)saveState { + // for production usage consider using the OS Keychain instead + NSData *archivedAuthState = [NSKeyedArchiver archivedDataWithRootObject:_authState]; + [[NSUserDefaults standardUserDefaults] setObject:archivedAuthState forKey:kExampleAuthStateKey]; + [[NSUserDefaults standardUserDefaults] synchronize]; +} + +/*! @brief Loads the @c OIDAuthState from @c NSUSerDefaults. + */ +- (void)loadState { + // loads OIDAuthState from NSUSerDefaults + NSData *archivedAuthState = + [[NSUserDefaults standardUserDefaults] objectForKey:kExampleAuthStateKey]; + OIDAuthState *authState = [NSKeyedUnarchiver unarchiveObjectWithData:archivedAuthState]; + [self setAuthState:authState]; +} + +/*! @brief Refreshes UI, typically called after the auth state changed. + */ +- (void)updateUI { + _signInButtons.hidden = [_authState isAuthorized]; + _signedInButtons.hidden = !_signInButtons.hidden; +} + +- (void)updateSignInUIWithResponse:(OIDTVAuthorizationResponse *)response { + _signInView.hidden = NO; + _cancelSignInButton.hidden = NO; + _verificationURLLabel.text = response.verificationURI; + _userCodeLabel.text = response.userCode; +} + +/*! @brief Forgets the authentication state, used to sign-out the user. + @param sender IBAction sender. +*/ +- (IBAction)clearAuthState:(nullable id)sender { + [self setAuthState:nil]; + [self logMessage:@"Authorization state cleared."]; + _cancelSignInButton.hidden = TRUE; +} + +- (IBAction)clearLog:(nullable id)sender { + _logTextView.text = @""; +} + +/*! @brief Performs an authenticated API call. + @param sender IBAction sender. +*/ +- (IBAction)userinfo:(nullable id)sender { + NSURL *userinfoEndpoint; + + if (shouldDiscoverEndpoints) { + userinfoEndpoint = _authState.lastAuthorizationResponse.request.configuration.discoveryDocument + .userinfoEndpoint; + } else { + userinfoEndpoint = [NSURL URLWithString:kUserInfoEndpoint]; + } + + NSString *currentAccessToken = _authState.lastTokenResponse.accessToken; + + [self logMessage:@"Performing userinfo request"]; + + [_authState performActionWithFreshTokens:^(NSString *_Nonnull accessToken, + NSString *_Nonnull idToken, NSError *_Nullable error) { + if (error) { + [self logMessage:@"Error fetching fresh tokens: %@", [error localizedDescription]]; + return; + } + + // log whether a token refresh occurred + if (![currentAccessToken isEqual:accessToken]) { + [self logMessage:@"Access token was refreshed automatically (%@ to %@)", currentAccessToken, + accessToken]; + } else { + [self logMessage:@"Access token was fresh and not updated [%@]", accessToken]; + } + + // creates request to the userinfo endpoint, with access token in the Authorization header + NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:userinfoEndpoint]; + NSString *authorizationHeaderValue = [NSString stringWithFormat:@"Bearer %@", accessToken]; + [request addValue:authorizationHeaderValue forHTTPHeaderField:@"Authorization"]; + + NSURLSessionConfiguration *configuration = + [NSURLSessionConfiguration defaultSessionConfiguration]; + NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration + delegate:nil + delegateQueue:nil]; + + // performs HTTP request + NSURLSessionDataTask *postDataTask = [session + dataTaskWithRequest:request + completionHandler:^(NSData *_Nullable data, NSURLResponse *_Nullable response, + NSError *_Nullable error) { + dispatch_async(dispatch_get_main_queue(), ^() { + if (error) { + [self logMessage:@"HTTP request failed %@", error]; + return; + } + if (![response isKindOfClass:[NSHTTPURLResponse class]]) { + [self logMessage:@"Non-HTTP response"]; + return; + } + + NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response; + id jsonDictionaryOrArray = [NSJSONSerialization JSONObjectWithData:data + options:0 + error:NULL]; + + if (httpResponse.statusCode != 200) { + // server replied with an error + NSString *responseText = [[NSString alloc] initWithData:data + encoding:NSUTF8StringEncoding]; + if (httpResponse.statusCode == 401) { + // "401 Unauthorized" generally indicates there is an issue with the authorization + // grant. Puts OIDAuthState into an error state. + NSError *oauthError = [OIDErrorUtilities + resourceServerAuthorizationErrorWithCode:0 + errorResponse:jsonDictionaryOrArray + underlyingError:error]; + [self->_authState updateWithAuthorizationError:oauthError]; + // log error + [self logMessage:@"Authorization Error (%@). Response: %@", oauthError, + responseText]; + } else { + [self logMessage:@"HTTP: %d. Response: %@", (int)httpResponse.statusCode, + responseText]; + } + return; + } + + // success response + [self logMessage:@"Success: %@", jsonDictionaryOrArray]; + }); + }]; + + [postDataTask resume]; + }]; +} + +/*! @brief Logs a message to stdout and the textfield. + @param format The format string and arguments. + */ +- (void)logMessage:(NSString *)format, ... NS_FORMAT_FUNCTION(1, 2) { + // gets message as string + va_list argp; + va_start(argp, format); + NSString *log = [[NSString alloc] initWithFormat:format arguments:argp]; + va_end(argp); + + // outputs to stdout + NSLog(@"%@", log); + + // appends to output log + NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init]; + dateFormatter.dateFormat = @"hh:mm:ss"; + NSString *dateString = [dateFormatter stringFromDate:[NSDate date]]; + NSString *logLine = [NSString stringWithFormat:@"\n%@: %@", dateString, log]; + UIFont *systemFont = [UIFont systemFontOfSize:36.0f]; + NSDictionary *fontAttributes = + [[NSDictionary alloc] initWithObjectsAndKeys:systemFont, NSFontAttributeName, nil]; + NSMutableAttributedString *logLineAttr = + [[NSMutableAttributedString alloc] initWithString:logLine attributes:fontAttributes]; + [[_logTextView textStorage] appendAttributedString:logLineAttr]; + + // Scroll to bottom + if (_logTextView.text.length > 0) { + NSRange bottom = NSMakeRange(_logTextView.text.length - 1, 1); + [_logTextView scrollRangeToVisible:bottom]; + } +} + +@end diff --git a/Examples/Example-tvOS/Example-tvOS/AppDelegate.h b/Examples/Example-tvOS/Example-tvOS/AppDelegate.h new file mode 100644 index 000000000..0a665e487 --- /dev/null +++ b/Examples/Example-tvOS/Example-tvOS/AppDelegate.h @@ -0,0 +1,27 @@ +/*! @file AppDelegate.h + @brief AppAuth tvOS SDK Example + @copyright + Copyright 2016 Google Inc. + @copydetails + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#import + +/*! @brief The example application's delegate. + */ +@interface AppDelegate : UIResponder + +/*! @brief The example application's @c UIWindow. + */ +@property (strong, nonatomic) UIWindow *window; + +@end diff --git a/Examples/Example-tvOS/Example-tvOS/AppDelegate.m b/Examples/Example-tvOS/Example-tvOS/AppDelegate.m new file mode 100644 index 000000000..4ae47a2db --- /dev/null +++ b/Examples/Example-tvOS/Example-tvOS/AppDelegate.m @@ -0,0 +1,20 @@ +/*! @file AppDelegate.m + @brief AppAuth tvOS SDK Example + @copyright + Copyright 2016 Google Inc. + @copydetails + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#import "AppDelegate.h" + +@implementation AppDelegate +@end diff --git a/Examples/Example-tvOS/Example-tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Back.imagestacklayer/Content.imageset/Contents.json b/Examples/Example-tvOS/Example-tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Back.imagestacklayer/Content.imageset/Contents.json new file mode 100644 index 000000000..2e003356c --- /dev/null +++ b/Examples/Example-tvOS/Example-tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Back.imagestacklayer/Content.imageset/Contents.json @@ -0,0 +1,11 @@ +{ + "images" : [ + { + "idiom" : "tv" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Examples/Example-tvOS/Example-tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Back.imagestacklayer/Contents.json b/Examples/Example-tvOS/Example-tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Back.imagestacklayer/Contents.json new file mode 100644 index 000000000..73c00596a --- /dev/null +++ b/Examples/Example-tvOS/Example-tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Back.imagestacklayer/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Examples/Example-tvOS/Example-tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Contents.json b/Examples/Example-tvOS/Example-tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Contents.json new file mode 100644 index 000000000..de59d885a --- /dev/null +++ b/Examples/Example-tvOS/Example-tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Contents.json @@ -0,0 +1,17 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + }, + "layers" : [ + { + "filename" : "Front.imagestacklayer" + }, + { + "filename" : "Middle.imagestacklayer" + }, + { + "filename" : "Back.imagestacklayer" + } + ] +} diff --git a/Examples/Example-tvOS/Example-tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Front.imagestacklayer/Content.imageset/Contents.json b/Examples/Example-tvOS/Example-tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Front.imagestacklayer/Content.imageset/Contents.json new file mode 100644 index 000000000..2e003356c --- /dev/null +++ b/Examples/Example-tvOS/Example-tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Front.imagestacklayer/Content.imageset/Contents.json @@ -0,0 +1,11 @@ +{ + "images" : [ + { + "idiom" : "tv" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Examples/Example-tvOS/Example-tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Front.imagestacklayer/Contents.json b/Examples/Example-tvOS/Example-tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Front.imagestacklayer/Contents.json new file mode 100644 index 000000000..73c00596a --- /dev/null +++ b/Examples/Example-tvOS/Example-tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Front.imagestacklayer/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Examples/Example-tvOS/Example-tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Middle.imagestacklayer/Content.imageset/Contents.json b/Examples/Example-tvOS/Example-tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Middle.imagestacklayer/Content.imageset/Contents.json new file mode 100644 index 000000000..2e003356c --- /dev/null +++ b/Examples/Example-tvOS/Example-tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Middle.imagestacklayer/Content.imageset/Contents.json @@ -0,0 +1,11 @@ +{ + "images" : [ + { + "idiom" : "tv" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Examples/Example-tvOS/Example-tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Middle.imagestacklayer/Contents.json b/Examples/Example-tvOS/Example-tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Middle.imagestacklayer/Contents.json new file mode 100644 index 000000000..73c00596a --- /dev/null +++ b/Examples/Example-tvOS/Example-tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Middle.imagestacklayer/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Examples/Example-tvOS/Example-tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Back.imagestacklayer/Content.imageset/Contents.json b/Examples/Example-tvOS/Example-tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Back.imagestacklayer/Content.imageset/Contents.json new file mode 100644 index 000000000..795cce172 --- /dev/null +++ b/Examples/Example-tvOS/Example-tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Back.imagestacklayer/Content.imageset/Contents.json @@ -0,0 +1,16 @@ +{ + "images" : [ + { + "idiom" : "tv", + "scale" : "1x" + }, + { + "idiom" : "tv", + "scale" : "2x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Examples/Example-tvOS/Example-tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Back.imagestacklayer/Contents.json b/Examples/Example-tvOS/Example-tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Back.imagestacklayer/Contents.json new file mode 100644 index 000000000..73c00596a --- /dev/null +++ b/Examples/Example-tvOS/Example-tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Back.imagestacklayer/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Examples/Example-tvOS/Example-tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Contents.json b/Examples/Example-tvOS/Example-tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Contents.json new file mode 100644 index 000000000..de59d885a --- /dev/null +++ b/Examples/Example-tvOS/Example-tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Contents.json @@ -0,0 +1,17 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + }, + "layers" : [ + { + "filename" : "Front.imagestacklayer" + }, + { + "filename" : "Middle.imagestacklayer" + }, + { + "filename" : "Back.imagestacklayer" + } + ] +} diff --git a/Examples/Example-tvOS/Example-tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Front.imagestacklayer/Content.imageset/Contents.json b/Examples/Example-tvOS/Example-tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Front.imagestacklayer/Content.imageset/Contents.json new file mode 100644 index 000000000..795cce172 --- /dev/null +++ b/Examples/Example-tvOS/Example-tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Front.imagestacklayer/Content.imageset/Contents.json @@ -0,0 +1,16 @@ +{ + "images" : [ + { + "idiom" : "tv", + "scale" : "1x" + }, + { + "idiom" : "tv", + "scale" : "2x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Examples/Example-tvOS/Example-tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Front.imagestacklayer/Contents.json b/Examples/Example-tvOS/Example-tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Front.imagestacklayer/Contents.json new file mode 100644 index 000000000..73c00596a --- /dev/null +++ b/Examples/Example-tvOS/Example-tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Front.imagestacklayer/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Examples/Example-tvOS/Example-tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Middle.imagestacklayer/Content.imageset/Contents.json b/Examples/Example-tvOS/Example-tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Middle.imagestacklayer/Content.imageset/Contents.json new file mode 100644 index 000000000..795cce172 --- /dev/null +++ b/Examples/Example-tvOS/Example-tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Middle.imagestacklayer/Content.imageset/Contents.json @@ -0,0 +1,16 @@ +{ + "images" : [ + { + "idiom" : "tv", + "scale" : "1x" + }, + { + "idiom" : "tv", + "scale" : "2x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Examples/Example-tvOS/Example-tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Middle.imagestacklayer/Contents.json b/Examples/Example-tvOS/Example-tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Middle.imagestacklayer/Contents.json new file mode 100644 index 000000000..73c00596a --- /dev/null +++ b/Examples/Example-tvOS/Example-tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Middle.imagestacklayer/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Examples/Example-tvOS/Example-tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Contents.json b/Examples/Example-tvOS/Example-tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Contents.json new file mode 100644 index 000000000..f47ba43da --- /dev/null +++ b/Examples/Example-tvOS/Example-tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Contents.json @@ -0,0 +1,32 @@ +{ + "assets" : [ + { + "filename" : "App Icon - App Store.imagestack", + "idiom" : "tv", + "role" : "primary-app-icon", + "size" : "1280x768" + }, + { + "filename" : "App Icon.imagestack", + "idiom" : "tv", + "role" : "primary-app-icon", + "size" : "400x240" + }, + { + "filename" : "Top Shelf Image Wide.imageset", + "idiom" : "tv", + "role" : "top-shelf-image-wide", + "size" : "2320x720" + }, + { + "filename" : "Top Shelf Image.imageset", + "idiom" : "tv", + "role" : "top-shelf-image", + "size" : "1920x720" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Examples/Example-tvOS/Example-tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image Wide.imageset/Contents.json b/Examples/Example-tvOS/Example-tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image Wide.imageset/Contents.json new file mode 100644 index 000000000..b65f0cddc --- /dev/null +++ b/Examples/Example-tvOS/Example-tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image Wide.imageset/Contents.json @@ -0,0 +1,24 @@ +{ + "images" : [ + { + "idiom" : "tv", + "scale" : "1x" + }, + { + "idiom" : "tv", + "scale" : "2x" + }, + { + "idiom" : "tv-marketing", + "scale" : "1x" + }, + { + "idiom" : "tv-marketing", + "scale" : "2x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Examples/Example-tvOS/Example-tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image.imageset/Contents.json b/Examples/Example-tvOS/Example-tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image.imageset/Contents.json new file mode 100644 index 000000000..b65f0cddc --- /dev/null +++ b/Examples/Example-tvOS/Example-tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image.imageset/Contents.json @@ -0,0 +1,24 @@ +{ + "images" : [ + { + "idiom" : "tv", + "scale" : "1x" + }, + { + "idiom" : "tv", + "scale" : "2x" + }, + { + "idiom" : "tv-marketing", + "scale" : "1x" + }, + { + "idiom" : "tv-marketing", + "scale" : "2x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Examples/Example-tvOS/Example-tvOS/Assets.xcassets/Contents.json b/Examples/Example-tvOS/Example-tvOS/Assets.xcassets/Contents.json new file mode 100644 index 000000000..73c00596a --- /dev/null +++ b/Examples/Example-tvOS/Example-tvOS/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Examples/Example-tvOS/Example-tvOS/Base.lproj/LaunchScreen.storyboard b/Examples/Example-tvOS/Example-tvOS/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 000000000..660ba53de --- /dev/null +++ b/Examples/Example-tvOS/Example-tvOS/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Examples/Example-tvOS/Example-tvOS/Info.plist b/Examples/Example-tvOS/Example-tvOS/Info.plist new file mode 100644 index 000000000..959d2174c --- /dev/null +++ b/Examples/Example-tvOS/Example-tvOS/Info.plist @@ -0,0 +1,32 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + $(PRODUCT_BUNDLE_PACKAGE_TYPE) + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UIRequiredDeviceCapabilities + + arm64 + + UIUserInterfaceStyle + Automatic + + diff --git a/Examples/Example-tvOS/Example-tvOS/Main.storyboard b/Examples/Example-tvOS/Example-tvOS/Main.storyboard new file mode 100644 index 000000000..b0725a43e --- /dev/null +++ b/Examples/Example-tvOS/Example-tvOS/Main.storyboard @@ -0,0 +1,133 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + Lorem ipsum dolor sit er elit lamet, consectetaur cillium adipisicing pecu, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Nam liber te conscient to factor tum poen legum odioque civiuda. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Examples/Example-tvOS/Example-tvOS/main.m b/Examples/Example-tvOS/Example-tvOS/main.m new file mode 100644 index 000000000..151d7c872 --- /dev/null +++ b/Examples/Example-tvOS/Example-tvOS/main.m @@ -0,0 +1,24 @@ +/*! @file main.m + @brief AppAuth tvOS SDK Example + @copyright + Copyright 2016 Google Inc. + @copydetails + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#import +#import "AppDelegate.h" + +int main(int argc, char * argv[]) { + @autoreleasepool { + return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); + } +} diff --git a/Examples/Example-tvOS/Podfile b/Examples/Example-tvOS/Podfile new file mode 100644 index 000000000..3e7ac1135 --- /dev/null +++ b/Examples/Example-tvOS/Podfile @@ -0,0 +1,9 @@ +platform :tvos, '9.0' + +use_frameworks! + +target 'Example-tvOS' do + # AppAuth Pod, TV subspec + # In production, just use `pod 'AppAuth/TV'` without the path reference. + pod 'AppAuth/TV', :path => '../../' +end diff --git a/Examples/Example-tvOS/README.md b/Examples/Example-tvOS/README.md new file mode 100644 index 000000000..4787247b6 --- /dev/null +++ b/Examples/Example-tvOS/README.md @@ -0,0 +1,64 @@ +# Example Project + +## Setup & Open the Project + +1. In the `Example-tvOS` folder, run the following command to install the AppAuth pod with the TV +subspec. + +``` +pod install +``` + +2. Open the `Example-tvOS.xcworkspace` workspace. + +``` +open Example-tvOS.xcworkspace +``` + +This workspace is configured to include AppAuth via CocoaPods. You can also include AppAuthTV using +Carthage or Swift Package Manager, please see the main [README](../../README.md) for instructions. + +## Configuration + +The example doesn't work out of the box; you need to configure it with your own client and IdP details. + +### Information You'll Need + +* Client ID +* Client Secret (optional) + +If you are choosing to automatically discover endpoints: + +* Issuer URL + +If you are choosing to manually specify endpoints: + +* Device Authorization Endpoint +* Token Endpoint +* User Info Endpoint + +How to get this information varies by IdP, but we have +[instructions](../README.md#openid-certified-providers) for some OpenID Certified providers. + +### Configure the Example + +#### In the file `AppAuthTVExampleViewController.m` + +1. Update `kClientID` with your new client ID. +2. Update `kClientSecret` with your client ID's secret, or set to `""` if not using. + +If you are choosing to automatically discover endpoints, also: + +1. Update `kIssuer` with the issuer URL. +2. Set `shouldDiscoverEndpoints` to `YES` + +If you are choosing to manually specify endpoints, also: + +1. Set `shouldDiscoverEndpoints` to `NO` +2. Update `kDeviceAuthorizationEndpoint` with the device authorization endpoint. +3. Update `kTokenEndpoint` with the token endpoint. +4. Update `kUserInfoEndpoint` with the token endpoint. + +### Running the Example + +Now your example should be ready to run. diff --git a/Examples/README-Google.md b/Examples/README-Google.md index 5a9c3f720..5b572b60b 100644 --- a/Examples/README-Google.md +++ b/Examples/README-Google.md @@ -21,7 +21,6 @@ Then, setup the example with your configuration: | Client ID | The value named `Client ID` in the console, has the format `IDENTIFIER.apps.googleusercontent.com`.| | Client Secret | Google's iOS clients do not have a secret.| | Redirect URI | The value for `iOS URL scheme` wil be the scheme of your redirect URI. This is the Client ID in reverse domain name notation, e.g. ` com.googleusercontent.apps.IDENTIFIER`. To construct the redirect URI, add your own path component. E.g. ` com.googleusercontent.apps.IDENTIFIER:/oauth2redirect/google`. Note that there is only a single slash (`/`) after the scheme.| -| ## macOS @@ -36,3 +35,14 @@ Then, setup the example with your configuration: | Client Secret | The value named `Client secret` in the console.| | Redirect URI | For macOS, you can use either the loopback interface (where AppAuth will generate the redirect URI for you), or a custom scheme. To create a custom scheme redirect URI, reverse the client id to get the URI scheme, for example ` com.googleusercontent.apps.IDENTIFIER` and, add your own path component. E.g. `com.googleusercontent.apps.IDENTIFIER:/oauth2redirect/google`. Note that there is only a single slash (`/`) after the scheme.| +## tvOS + +Select "TVs and Limited Input devices" as the application type. + +Then, setup the example with your configuration. + +| Configuration | Description | +|---------------------------|------------------| +| Issuer | `https://accounts.google.com` | +| Client ID | The value named `Client ID` in the console, has the format `IDENTIFIER.apps.googleusercontent.com`.| +| Client Secret | The value named `Client secret` in the console.| diff --git a/Examples/README.md b/Examples/README.md index ca394f7fe..accbc23a8 100644 --- a/Examples/README.md +++ b/Examples/README.md @@ -10,7 +10,10 @@ Each example has docs on how to configure: * [Example for iOS (Objective-C)](Example-iOS_ObjC/README.md) * [Example for iOS w/ Carthage (Objective-C)](Example-iOS_ObjC-Carthage/README.md) +* [Example for iOS w/ Carthage (Swift)](Example-iOS_Swift-Carthage/README.md) +* [Example for iOS w/ SPM (SwiftUI)](Example-iOS_Swift-SPM/README.md) * [Example for macOS](Example-macOS/README.md) +* [Example for tvOS](Example-tvOS/README.md) To get the Issuer, Client ID, and Redirect URI, for your particular IdP, you may view the IdP-specific information in the next section. @@ -18,11 +21,12 @@ may view the IdP-specific information in the next section. ## OpenID Certified Providers All [Certified OpenID providers](http://openid.net/certification/) that support -[RFC 8252](https://tools.ietf.org/html/rfc8252-08#appendix-A) +[RFC 8252](https://tools.ietf.org/html/rfc8252#appendix-A) are welcome to submit a README with IdP information. Those with instructions on file: * [Google](README-Google.md) * [IdentityServer](README-IdentityServer.md) +* [Okta](README-Okta.md) * [PingFederate](README-PingFederate.md) diff --git a/Gemfile b/Gemfile new file mode 100644 index 000000000..4e9ba5251 --- /dev/null +++ b/Gemfile @@ -0,0 +1,3 @@ +source 'https://rubygems.org' + +gem 'cocoapods', '1.11.3' diff --git a/Gemfile.lock b/Gemfile.lock new file mode 100644 index 000000000..9ed5f1d66 --- /dev/null +++ b/Gemfile.lock @@ -0,0 +1,97 @@ +GEM + remote: https://rubygems.org/ + specs: + CFPropertyList (3.0.5) + rexml + activesupport (6.1.5) + concurrent-ruby (~> 1.0, >= 1.0.2) + i18n (>= 1.6, < 2) + minitest (>= 5.1) + tzinfo (~> 2.0) + zeitwerk (~> 2.3) + addressable (2.8.0) + public_suffix (>= 2.0.2, < 5.0) + algoliasearch (1.27.5) + httpclient (~> 2.8, >= 2.8.3) + json (>= 1.5.1) + atomos (0.1.3) + claide (1.1.0) + cocoapods (1.11.3) + addressable (~> 2.8) + claide (>= 1.0.2, < 2.0) + cocoapods-core (= 1.11.3) + cocoapods-deintegrate (>= 1.0.3, < 2.0) + cocoapods-downloader (>= 1.4.0, < 2.0) + cocoapods-plugins (>= 1.0.0, < 2.0) + cocoapods-search (>= 1.0.0, < 2.0) + cocoapods-trunk (>= 1.4.0, < 2.0) + cocoapods-try (>= 1.1.0, < 2.0) + colored2 (~> 3.1) + escape (~> 0.0.4) + fourflusher (>= 2.3.0, < 3.0) + gh_inspector (~> 1.0) + molinillo (~> 0.8.0) + nap (~> 1.0) + ruby-macho (>= 1.0, < 3.0) + xcodeproj (>= 1.21.0, < 2.0) + cocoapods-core (1.11.3) + activesupport (>= 5.0, < 7) + addressable (~> 2.8) + algoliasearch (~> 1.0) + concurrent-ruby (~> 1.1) + fuzzy_match (~> 2.0.4) + nap (~> 1.0) + netrc (~> 0.11) + public_suffix (~> 4.0) + typhoeus (~> 1.0) + cocoapods-deintegrate (1.0.5) + cocoapods-downloader (1.6.3) + cocoapods-plugins (1.0.0) + nap + cocoapods-search (1.0.1) + cocoapods-trunk (1.6.0) + nap (>= 0.8, < 2.0) + netrc (~> 0.11) + cocoapods-try (1.2.0) + colored2 (3.1.2) + concurrent-ruby (1.1.9) + escape (0.0.4) + ethon (0.15.0) + ffi (>= 1.15.0) + ffi (1.15.5) + fourflusher (2.3.1) + fuzzy_match (2.0.4) + gh_inspector (1.1.3) + httpclient (2.8.3) + i18n (1.10.0) + concurrent-ruby (~> 1.0) + json (2.6.1) + minitest (5.15.0) + molinillo (0.8.0) + nanaimo (0.3.0) + nap (1.1.0) + netrc (0.11.0) + public_suffix (4.0.6) + rexml (3.2.5) + ruby-macho (2.5.1) + typhoeus (1.4.0) + ethon (>= 0.9.0) + tzinfo (2.0.4) + concurrent-ruby (~> 1.0) + xcodeproj (1.21.0) + CFPropertyList (>= 2.3.3, < 4.0) + atomos (~> 0.1.3) + claide (>= 1.0.2, < 2.0) + colored2 (~> 3.1) + nanaimo (~> 0.3.0) + rexml (~> 3.2.4) + zeitwerk (2.5.4) + +PLATFORMS + ruby + +DEPENDENCIES + cocoapods (= 1.11.3) + +BUNDLED WITH + 1.17.2 diff --git a/Package.swift b/Package.swift new file mode 100644 index 000000000..208eaf467 --- /dev/null +++ b/Package.swift @@ -0,0 +1,88 @@ +// swift-tools-version:5.3 +// The swift-tools-version declares the minimum version of Swift required to build this package. +import PackageDescription + +/*! @file Package.swift + @brief AppAuth iOS SDK + @copyright + Copyright 2020 The AppAuth Authors. All Rights Reserved. + @copydetails + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +let package = Package( + name: "AppAuth", + platforms: [ + .macOS(.v10_12), + .iOS(.v12), + .tvOS(.v9), + .watchOS(.v2) + ], + products: [ + .library( + name: "AppAuthCore", + targets: ["AppAuthCore"]), + .library( + name: "AppAuth", + targets: ["AppAuth"]), + .library( + name: "AppAuthTV", + targets: ["AppAuthTV"]) + ], + dependencies: [], + targets: [ + .target( + name: "AppAuthCore", + path: "Sources/AppAuthCore", + resources: [.copy("Resources/PrivacyInfo.xcprivacy")], + publicHeadersPath: "" + ), + .target( + name: "AppAuth", + dependencies: ["AppAuthCore"], + path: "Sources/AppAuth", + sources: ["iOS", "macOS"], + resources: [.copy("Resources/PrivacyInfo.xcprivacy")], + publicHeadersPath: "", + cSettings: [ + .headerSearchPath("iOS"), + .headerSearchPath("macOS"), + .headerSearchPath("macOS/LoopbackHTTPServer"), + ] + ), + .target( + name: "AppAuthTV", + dependencies: ["AppAuthCore"], + path: "Sources/AppAuthTV", + resources: [.copy("Resources/PrivacyInfo.xcprivacy")], + publicHeadersPath: "" + ), + .testTarget( + name: "AppAuthCoreTests", + dependencies: ["AppAuthCore"], + path: "UnitTests", + exclude: ["OIDSwiftTests.swift", "AppAuthTV"] + ), + .testTarget( + name: "AppAuthCoreSwiftTests", + dependencies: ["AppAuthCore"], + path: "UnitTests", + sources: ["OIDSwiftTests.swift"] + ), + .testTarget( + name: "AppAuthTVTests", + dependencies: ["AppAuthTV"], + path: "UnitTests/AppAuthTV" + ), + ] +) diff --git a/README.md b/README.md index 84a2e7ca2..c9f44a41c 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,14 @@ ![AppAuth for iOS and macOS](https://rawgit.com/openid/AppAuth-iOS/master/appauth_lockup.svg) -[![Build Status](https://travis-ci.org/openid/AppAuth-iOS.svg?branch=master)](https://travis-ci.org/openid/AppAuth-iOS) -[![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage) - -AppAuth for iOS and macOS is a client SDK for communicating with +[![tests](https://github.com/openid/AppAuth-iOS/actions/workflows/tests.yml/badge.svg?event=push)](https://github.com/openid/AppAuth-iOS/actions/workflows/tests.yml) +[![codecov](https://codecov.io/gh/openid/AppAuth-iOS/branch/master/graph/badge.svg)](https://codecov.io/gh/openid/AppAuth-iOS) +[![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-brightgreen.svg?style=flat)](https://github.com/Carthage/Carthage) +[![SwiftPM compatible](https://img.shields.io/badge/SwiftPM-compatible-brightgreen.svg?style=flat)](https://swift.org/package-manager) +[![Pod Version](https://img.shields.io/cocoapods/v/AppAuth.svg?style=flat)](https://cocoapods.org/pods/AppAuth) +[![Pod License](https://img.shields.io/cocoapods/l/AppAuth.svg?style=flat)](https://github.com/openid/AppAuth-iOS/blob/master/LICENSE) +[![Pod Platform](https://img.shields.io/cocoapods/p/AppAuth.svg?style=flat)](https://cocoapods.org/pods/AppAuth) +[![Catalyst compatible](https://img.shields.io/badge/Catalyst-compatible-brightgreen.svg?style=flat)](https://developer.apple.com/documentation/xcode/creating_a_mac_version_of_your_ipad_app) + +AppAuth for iOS and macOS, and tvOS is a client SDK for communicating with [OAuth 2.0](https://tools.ietf.org/html/rfc6749) and [OpenID Connect](http://openid.net/specs/openid-connect-core-1_0.html) providers. It strives to @@ -19,51 +25,62 @@ supported due to the security and usability reasons explained in [Section 8.12 of RFC 8252](https://tools.ietf.org/html/rfc8252#section-8.12). It also supports the [PKCE](https://tools.ietf.org/html/rfc7636) extension to -OAuth which was created to secure authorization codes in public clients when +OAuth, which was created to secure authorization codes in public clients when custom URI scheme redirects are used. The library is friendly to other -extensions (standard or otherwise) with the ability to handle additional params +extensions (standard or otherwise), with the ability to handle additional params in all protocol requests and responses. +For tvOS, AppAuth implements [OAuth 2.0 Device Authorization Grant +](https://tools.ietf.org/html/rfc8628) to allow for tvOS sign-ins through a secondary device. + ## Specification ### iOS #### Supported Versions -AppAuth supports iOS 7 and above. +AppAuth supports iOS 12 and above. -iOS 9+ uses the in-app browser tab pattern -(via `SFSafariViewController`), and falls back to the system browser (mobile -Safari) on earlier versions. +Authentication is performed using `ASWebAuthenticationSession`. #### Authorization Server Requirements Both Custom URI Schemes (all supported versions of iOS) and Universal Links (iOS 9+) can be used with the library. -In general, AppAuth can work with any Authorization Server (AS) that supports -native apps as documented in [RFC 8252](https://tools.ietf.org/html/rfc8252), +In general, AppAuth can work with any authorization server that supports +native apps, as documented in [RFC 8252](https://tools.ietf.org/html/rfc8252), either through custom URI scheme redirects, or universal links. -AS's that assume all clients are web-based or require clients to maintain +Authorization servers that assume all clients are web-based, or require clients to maintain confidentiality of the client secrets may not work well. ### macOS #### Supported Versions -AppAuth supports macOS (OS X) 10.8 and above. +AppAuth supports macOS (OS X) 10.9 and above. #### Authorization Server Requirements -AppAuth for macOS supports both custom schemes, a loopback HTTP redirects +AppAuth for macOS supports both custom schemes; a loopback HTTP redirects via a small embedded server. -In general, AppAuth can work with any Authorization Server (AS) that supports -native apps as documented in [RFC 8252](https://tools.ietf.org/html/rfc8252), -either through custom URI scheme, or loopback HTTP redirects. -AS's that assume all clients are web-based or require clients to maintain +In general, AppAuth can work with any authorization server that supports +native apps, as documented in [RFC 8252](https://tools.ietf.org/html/rfc8252); +either through custom URI schemes, or loopback HTTP redirects. +Authorization servers that assume all clients are web-based, or require clients to maintain confidentiality of the client secrets may not work well. +### tvOS + +#### Supported Versions + +AppAuth supports tvOS 9.0 and above. Please note that while it is possible to run the standard AppAuth library on tvOS, the documentation below describes implementing [OAuth 2.0 Device Authorization Grant](https://tools.ietf.org/html/rfc8628) (AppAuthTV). + +#### Authorization Server Requirements + +AppAuthTV is designed for servers that support the device authorization flow as documented in [RFC 8628](https://tools.ietf.org/html/rfc8628). + ## Try Want to try out AppAuth? Just run: @@ -71,12 +88,12 @@ Want to try out AppAuth? Just run: pod try AppAuth Follow the instructions in [Examples/README.md](Examples/README.md) to configure -with your own OAuth client (you need to update 3 configuration points with your +with your own OAuth client (you need to update three configuration points with your client info to try the demo). ## Setup -AppAuth supports three options for dependency management. +AppAuth supports four options for dependency management. ### CocoaPods @@ -85,7 +102,25 @@ add the following line to your `Podfile`: pod 'AppAuth' -Then run `pod install`. +Then, run `pod install`. + +**tvOS:** Use the `TV` subspec: + + pod 'AppAuth/TV' + + +### Swift Package Manager + +With [Swift Package Manager](https://swift.org/package-manager), +add the following `dependency` to your `Package.swift`: + +```swift +dependencies: [ + .package(url: "https://github.com/openid/AppAuth-iOS.git", .upToNextMajor(from: "1.3.0")) +] +``` + +**tvOS:** Use the `AppAuthTV` target. ### Carthage @@ -94,26 +129,30 @@ line to your `Cartfile`: github "openid/AppAuth-iOS" "master" -Then run `carthage bootstrap`. +Then, run `carthage bootstrap`. + +**tvOS:** Use the `AppAuthTV` framework. ### Static Library You can also use AppAuth as a static library. This requires linking the library -and your project and including the headers. Suggested configuration: +and your project, and including the headers. Here is a suggested configuration: -1. Create an XCode Workspace. +1. Create an Xcode Workspace. 2. Add `AppAuth.xcodeproj` to your Workspace. 3. Include libAppAuth as a linked library for your target (in the "General -> Linked Framework and Libraries" section of your target). 4. Add `AppAuth-iOS/Source` to your search paths of your target ("Build Settings -> "Header Search Paths"). +*Note: There is no static library for AppAuthTV.* + ## Auth Flow -AppAuth supports both manual interaction with the Authorization Server +AppAuth supports both manual interaction with the authorization server where you need to perform your own token exchanges, as well as convenience methods that perform some of this logic for you. This example uses the -convenience method which returns either an `OIDAuthState` object, or an error. +convenience method, which returns either an `OIDAuthState` object, or an error. `OIDAuthState` is a class that keeps track of the authorization and token requests and responses, and provides a convenience method to call an API with @@ -124,6 +163,7 @@ authorization state of the session. You can configure AppAuth by specifying the endpoints directly: +Objective-C ```objc NSURL *authorizationEndpoint = [NSURL URLWithString:@"https://accounts.google.com/o/oauth2/v2/auth"]; @@ -138,8 +178,37 @@ OIDServiceConfiguration *configuration = // perform the auth request... ``` +Swift +```swift +let authorizationEndpoint = URL(string: "https://accounts.google.com/o/oauth2/v2/auth")! +let tokenEndpoint = URL(string: "https://www.googleapis.com/oauth2/v4/token")! +let configuration = OIDServiceConfiguration(authorizationEndpoint: authorizationEndpoint, + tokenEndpoint: tokenEndpoint) + +// perform the auth request... +``` + +**tvOS** + +Objective-C +```objc +NSURL *deviceAuthorizationEndpoint = + [NSURL URLWithString:@"https://oauth2.googleapis.com/device/code"]; +NSURL *tokenEndpoint = + [NSURL URLWithString:@"https://www.googleapis.com/oauth2/v4/token"]; + +OIDTVServiceConfiguration *configuration = + [[OIDTVServiceConfiguration alloc] + initWithDeviceAuthorizationEndpoint:deviceAuthorizationEndpoint + tokenEndpoint:tokenEndpoint]; + +// perform the auth request... +``` + + Or through discovery: +Objective-C ```objc NSURL *issuer = [NSURL URLWithString:@"https://accounts.google.com"]; @@ -157,31 +226,88 @@ NSURL *issuer = [NSURL URLWithString:@"https://accounts.google.com"]; }]; ``` +Swift +```swift +let issuer = URL(string: "https://accounts.google.com")! + +// discovers endpoints +OIDAuthorizationService.discoverConfiguration(forIssuer: issuer) { configuration, error in + guard let config = configuration else { + print("Error retrieving discovery document: \(error?.localizedDescription ?? "Unknown error")") + return + } + + // perform the auth request... +} +``` + +**tvOS** + +Objective-C +```objc +NSURL *issuer = [NSURL URLWithString:@"https://accounts.google.com"]; + +[OIDTVAuthorizationService discoverServiceConfigurationForIssuer:issuer + completion:^(OIDTVServiceConfiguration *_Nullable configuration, + NSError *_Nullable error) { + + if (!configuration) { + NSLog(@"Error retrieving discovery document: %@", + [error localizedDescription]); + return; + } + + // perform the auth request... +}]; +``` + ### Authorizing – iOS -First you need to have a property in your AppDelegate to hold the session, in -order to continue the authorization flow from the redirect. +First, you need to have a property in your `UIApplicationDelegate` +implementation to hold the session, in order to continue the authorization flow +from the redirect. In this example, the implementation of this delegate is +a class named `AppDelegate`, if your app's application delegate has a different +name, please update the class name in samples below accordingly. +Objective-C ```objc +@interface AppDelegate : UIResponder // property of the app's AppDelegate -@property(nonatomic, strong, nullable) - id currentAuthorizationFlow; +@property(nonatomic, strong, nullable) id currentAuthorizationFlow; +@end ``` +Swift +```swift +class AppDelegate: UIResponder, UIApplicationDelegate { + // property of the app's AppDelegate + var currentAuthorizationFlow: OIDExternalUserAgentSession? +} +``` + + And your main class, a property to store the auth state: +Objective-C ```objc // property of the containing class @property(nonatomic, strong, nullable) OIDAuthState *authState; ``` +Swift +```swift +// property of the containing class +private var authState: OIDAuthState? +``` + Then, initiate the authorization request. By using the `authStateByPresentingAuthorizationRequest` convenience method, the token exchange will be performed automatically, and everything will be protected with -PKCE (if the server supports it). AppAuth also allows you to perform these +PKCE (if the server supports it). AppAuth also lets you perform these requests manually. See the `authNoCodeExchange` method in the included Example -app for a demonstration. +app for a demonstration: +Objective-C ```objc // builds authentication request OIDAuthorizationRequest *request = @@ -189,7 +315,7 @@ OIDAuthorizationRequest *request = clientId:kClientID scopes:@[OIDScopeOpenID, OIDScopeProfile] - redirectURL:KRedirectURI + redirectURL:kRedirectURI responseType:OIDResponseTypeCode additionalParameters:nil]; @@ -212,19 +338,49 @@ appDelegate.currentAuthorizationFlow = }]; ``` +Swift +```swift +// builds authentication request +let request = OIDAuthorizationRequest(configuration: configuration, + clientId: clientID, + clientSecret: clientSecret, + scopes: [OIDScopeOpenID, OIDScopeProfile], + redirectURL: redirectURI, + responseType: OIDResponseTypeCode, + additionalParameters: nil) + +// performs authentication request +print("Initiating authorization request with scope: \(request.scope ?? "nil")") + +let appDelegate = UIApplication.shared.delegate as! AppDelegate + +appDelegate.currentAuthorizationFlow = + OIDAuthState.authState(byPresenting: request, presenting: self) { authState, error in + if let authState = authState { + self.setAuthState(authState) + print("Got authorization tokens. Access token: " + + "\(authState.lastTokenResponse?.accessToken ?? "nil")") + } else { + print("Authorization error: \(error?.localizedDescription ?? "Unknown error")") + self.setAuthState(nil) + } +} +``` + *Handling the Redirect* The authorization response URL is returned to the app via the iOS openURL app delegate method, so you need to pipe this through to the current -authorization session (created in the previous session). +authorization session (created in the previous session): +Objective-C ```objc - (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary *)options { // Sends the URL to the current authorization flow (if any) which will // process it if it relates to an authorization response. - if ([_currentAuthorizationFlow resumeAuthorizationFlowWithURL:url]) { + if ([_currentAuthorizationFlow resumeExternalUserAgentFlowWithURL:url]) { _currentAuthorizationFlow = nil; return YES; } @@ -235,6 +391,25 @@ authorization session (created in the previous session). } ``` +Swift +```swift +func application(_ app: UIApplication, + open url: URL, + options: [UIApplicationOpenURLOptionsKey : Any] = [:]) -> Bool { + // Sends the URL to the current authorization flow (if any) which will + // process it if it relates to an authorization response. + if let authorizationFlow = self.currentAuthorizationFlow, + authorizationFlow.resumeExternalUserAgentFlow(with: url) { + self.currentAuthorizationFlow = nil + return true + } + + // Your additional URL handling (if any) + + return false +} +``` + ### Authorizing – MacOS On macOS, the most popular way to get the authorization response redirect is to @@ -246,21 +421,23 @@ lifecycle for you. > #### :bulb: Alternative: Custom URI Schemes > Custom URI schemes are also supported on macOS, but some browsers display -> an interstitial which reduces the usability. For an example on using custom +> an interstitial, which reduces the usability. For an example on using custom > URI schemes with macOS, See `Example-Mac`. To receive the authorization response using a local HTTP server, first you need to have an instance variable in your main class to retain the HTTP redirect -handler. +handler: +Objective-C ```objc OIDRedirectHTTPHandler *_redirectHTTPHandler; ``` Then, as the port used by the local HTTP server varies, you need to start it -before building the authorization request in order to get the exact redirect -URI to use. +before building the authorization request, in order to get the exact redirect +URI to use: +Objective-C ```objc static NSString *const kSuccessURLString = @"http://openid.github.io/AppAuth-iOS/redirect/"; @@ -277,7 +454,7 @@ Then, initiate the authorization request. By using the exchange will be performed automatically, and everything will be protected with PKCE (if the server supports it). By assigning the return value to the `OIDRedirectHTTPHandler`'s `currentAuthorizationFlow`, the authorization will -continue automatically once the user makes their choice. +continue automatically once the user makes their choice: ```objc // builds authentication request @@ -311,13 +488,72 @@ _redirectHTTPHandler.currentAuthorizationFlow = }]; ``` + +### Authorizing – tvOS + +Ensure that your main class is a delegate of `OIDAuthStateChangeDelegate`, `OIDAuthStateErrorDelegate`, implement the corresponding methods, and include the following property and instance variable: + +Objective-C +```objc +// property of the containing class +@property(nonatomic, strong, nullable) OIDAuthState *authState; + +// instance variable of the containing class +OIDTVAuthorizationCancelBlock _cancelBlock; +``` + +Then, build and perform the authorization request. + +Objective-C +```objc +// builds authentication request +__weak __typeof(self) weakSelf = self; + +OIDTVAuthorizationRequest *request = + [[OIDTVAuthorizationRequest alloc] initWithConfiguration:configuration + clientId:kClientID + clientSecret:kClientSecret + scopes:@[ OIDScopeOpenID, OIDScopeProfile ] + additionalParameters:nil + additionalHeaders:nil]; + +// performs authentication request +OIDTVAuthorizationInitialization initBlock = + ^(OIDTVAuthorizationResponse *_Nullable response, NSError *_Nullable error) { + if (response) { + // process authorization response + NSLog(@"Got authorization response: %@", response); + } else { + // handle initialization error + NSLog(@"Error: %@", error); + } + }; + +OIDTVAuthorizationCompletion completionBlock = + ^(OIDAuthState *_Nullable authState, NSError *_Nullable error) { + weakSelf.signInView.hidden = YES; + if (authState) { + NSLog(@"Token response: %@", authState.lastTokenResponse); + [weakSelf setAuthState:authState]; + } else { + NSLog(@"Error: %@", error); + [weakSelf setAuthState:nil]; + } + }; + +_cancelBlock = [OIDTVAuthorizationService authorizeTVRequest:request + initialization:initBlock + completion:completionBlock]; +``` + ### Making API Calls -AppAuth gives you the raw token information, if you need it. However we +AppAuth gives you the raw token information, if you need it. However, we recommend that users of the `OIDAuthState` convenience wrapper use the provided `performActionWithFreshTokens:` method to perform their API calls to avoid -needing to worry about token freshness. +needing to worry about token freshness: +Objective-C ```objc [_authState performActionWithFreshTokens:^(NSString *_Nonnull accessToken, NSString *_Nonnull idToken, @@ -331,23 +567,150 @@ needing to worry about token freshness. }]; ``` +Swift +```swift +let userinfoEndpoint = URL(string:"https://openidconnect.googleapis.com/v1/userinfo")! +self.authState?.performAction() { (accessToken, idToken, error) in + + if error != nil { + print("Error fetching fresh tokens: \(error?.localizedDescription ?? "Unknown error")") + return + } + guard let accessToken = accessToken else { + return + } + + // Add Bearer token to request + var urlRequest = URLRequest(url: userinfoEndpoint) + urlRequest.allHTTPHeaderFields = ["Authorization": "Bearer \(accessToken)"] + + // Perform request... +} +``` + +### Custom User-Agents (iOS and macOS) + +Each OAuth flow involves presenting an external user-agent to the user, that +allows them to interact with the OAuth authorization server. Typical examples +of a user-agent are the user's browser, or an in-app browser tab incarnation +like `ASWebAuthenticationSession` on iOS. + +AppAuth ships with several implementations of an external user-agent out of the +box, including defaults for iOS and macOS suitable for most cases. The default +user-agents typically share persistent cookies with the system default browser, +to improve the chance that the user doesn't need to sign-in all over again. + +It is possible to change the user-agent that AppAuth uses, and even write your +own - all without needing to fork the library. + +All implementations of the external user-agent, be they included or created by +you need to conform to the +[`OIDExternalUserAgent`](http://openid.github.io/AppAuth-iOS/docs/latest/protocol_o_i_d_external_user_agent-p.html) +protocol. + +Instances of the `OIDExternalUserAgent`are passed into +[`OIDAuthState.authStateByPresentingAuthorizationRequest:externalUserAgent:callback`](http://openid.github.io/AppAuth-iOS/docs/latest/interface_o_i_d_auth_state.html#ac762fe2bf95c116f0b437419be211fa1) +and/or +[`OIDAuthorizationService.presentAuthorizationRequest:externalUserAgent:callback:`](http://openid.github.io/AppAuth-iOS/docs/latest/interface_o_i_d_authorization_service.html#ae551f8e6887366a46e49b09b37389b8f) +rather than using the platform-specific convenience methods (which use the +default user-agents for their respective platforms), like +[`OIDAuthState.authStateByPresentingAuthorizationRequest:presentingViewController:callback:`](http://openid.github.io/AppAuth-iOS/docs/latest/category_o_i_d_auth_state_07_i_o_s_08.html#ae32fd0732cd3192cd5219f2655a4c85c). + +Popular use-cases for writing your own user-agent implementation include needing +to style the user-agent in ways not supported by AppAuth, and implementing a +fully custom flow with your own business logic. You can take one of the existing +implementations as a starting point to copy, rename, and customize to your +needs. + +#### Custom Browser User-Agent + +AppAuth for iOS includes a few extra user-agent implementations which you can +try, or use as a reference for your own implementation. One of them, +[`OIDExternalUserAgentIOSCustomBrowser`](http://openid.github.io/AppAuth-iOS/docs/latest/interface_o_i_d_external_user_agent_i_o_s_custom_browser.html) +enables you to use a different browser for authentication, like Chrome for iOS +or Firefox for iOS. + +Here's how to configure AppAuth to use a custom browser using the +`OIDExternalUserAgentIOSCustomBrowser` user agent: + +First, add the following array to your +[Info.plist](https://github.com/openid/AppAuth-iOS/blob/135f99d2cb4e9d18d310ac2588b905e612461561/Examples/Example-iOS_ObjC/Source/Info.plist#L34) +(in XCode, right click -> Open As -> Source Code) + +``` + LSApplicationQueriesSchemes + + googlechromes + opera-https + firefox + +``` + +This is required so that AppAuth can test for the browser and open the app store +if it's not installed (the default behavior of this user-agent). You only need +to include the URL scheme of the actual browser you intend to use. + +Objective-C +```objc +// performs authentication request +AppDelegate *appDelegate = + (AppDelegate *)[UIApplication sharedApplication].delegate; +id userAgent = + [OIDExternalUserAgentIOSCustomBrowser CustomBrowserChrome]; +appDelegate.currentAuthorizationFlow = + [OIDAuthState authStateByPresentingAuthorizationRequest:request + externalUserAgent:userAgent + callback:^(OIDAuthState *_Nullable authState, + NSError *_Nullable error) { + if (authState) { + NSLog(@"Got authorization tokens. Access token: %@", + authState.lastTokenResponse.accessToken); + [self setAuthState:authState]; + } else { + NSLog(@"Authorization error: %@", [error localizedDescription]); + [self setAuthState:nil]; + } +}]; +``` + +Swift +``` +guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else { + self.logMessage("Error accessing AppDelegate") + return + } +let userAgent = OIDExternalUserAgentIOSCustomBrowser.customBrowserChrome() +appDelegate.currentAuthorizationFlow = OIDAuthState.authState(byPresenting: request, externalUserAgent: userAgent) { authState, error in + if let authState = authState { + self.setAuthState(authState) + self.logMessage("Got authorization tokens. Access token: \(authState.lastTokenResponse?.accessToken ?? "DEFAULT_TOKEN")") + } else { + self.logMessage("Authorization error: \(error?.localizedDescription ?? "DEFAULT_ERROR")") + self.setAuthState(nil) + } +} +``` + +That's it! With those two changes (which you can try on the included sample), +AppAuth will use Chrome iOS for the authorization request (and open Chrome in +the App Store if it's not installed). + +⚠️**Note: the `OIDExternalUserAgentIOSCustomBrowser` user-agent is not intended for consumer apps**. It is designed for +advanced enterprise use-cases where the app developers have greater control over +the operating environment and have special requirements that require a custom +browser like Chrome. + +You don't need to stop with the included external user agents either! Since the +[`OIDExternalUserAgent`](http://openid.github.io/AppAuth-iOS/docs/latest/protocol_o_i_d_external_user_agent-p.html) +protocol is part of AppAuth's public API, you can implement your own versions of +it. In the above example, +`userAgent = [OIDExternalUserAgentIOSCustomBrowser CustomBrowserChrome]` would +be replaced with an instantiation of your user-agent implementation. + ## API Documentation -Browse the [API documentation] -(http://openid.github.io/AppAuth-iOS/docs/latest/annotated.html). +Browse the [API documentation](http://openid.github.io/AppAuth-iOS/docs/latest/annotated.html). ## Included Samples -You can try out the iOS sample included in the source distribution by opening -`Example/Example.xcworkspace`. You can easily convert the Example -workspace to a Pod workspace by deleting the `AppAuth` project, and -[configuring the pod](#setup). You can also -[try out the sample via CocoaPods](#try). Be sure to follow the instructions in -[Example/README.md](Example/README.md) to configure your own OAuth client ID -for use with the example. - -You can try out the macOS sample included in the source distribution by -executing `pod install` in the `Example-Mac` folder, then opening -`Example-Mac.xcworkspace`. Be sure to follow the instructions in -[Example-Mac/README.md](Example-Mac/README.md) to configure your own OAuth -client ID for use with the example. +Sample apps that explore core AppAuth features are available for iOS, macOS and tvOS; follow the instructions in [Examples/README.md](Examples/README.md) to get started. diff --git a/Source/OIDAuthorizationService.m b/Source/OIDAuthorizationService.m deleted file mode 100644 index a475e37ed..000000000 --- a/Source/OIDAuthorizationService.m +++ /dev/null @@ -1,468 +0,0 @@ -/*! @file OIDAuthorizationService.m - @brief AppAuth iOS SDK - @copyright - Copyright 2015 Google Inc. All Rights Reserved. - @copydetails - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - */ - -#import "OIDAuthorizationService.h" - -#import "OIDAuthorizationRequest.h" -#import "OIDAuthorizationResponse.h" -#import "OIDAuthorizationUICoordinator.h" -#import "OIDDefines.h" -#import "OIDErrorUtilities.h" -#import "OIDRegistrationRequest.h" -#import "OIDRegistrationResponse.h" -#import "OIDServiceConfiguration.h" -#import "OIDServiceDiscovery.h" -#import "OIDTokenRequest.h" -#import "OIDTokenResponse.h" -#import "OIDURLQueryComponent.h" -#import "OIDURLSessionProvider.h" - -/*! @brief Path appended to an OpenID Connect issuer for discovery - @see https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfig - */ -static NSString *const kOpenIDConfigurationWellKnownPath = @".well-known/openid-configuration"; - -/*! @brief The state authorization parameter. - */ -static NSString *const kStateParameter = @"state"; - -NS_ASSUME_NONNULL_BEGIN - -@interface OIDAuthorizationFlowSessionImplementation : NSObject { - // private variables - OIDAuthorizationRequest *_request; - id _UICoordinator; - OIDAuthorizationCallback _pendingauthorizationFlowCallback; -} - -- (instancetype)init NS_UNAVAILABLE; - -- (instancetype)initWithRequest:(OIDAuthorizationRequest *)request - NS_DESIGNATED_INITIALIZER; - -@end - -@implementation OIDAuthorizationFlowSessionImplementation - -- (instancetype)initWithRequest:(OIDAuthorizationRequest *)request { - self = [super init]; - if (self) { - _request = [request copy]; - } - return self; -} - -- (void)presentAuthorizationWithCoordinator:(id)UICoordinator - callback:(OIDAuthorizationCallback)authorizationFlowCallback { - _UICoordinator = UICoordinator; - _pendingauthorizationFlowCallback = authorizationFlowCallback; - BOOL authorizationFlowStarted = - [_UICoordinator presentAuthorizationRequest:_request session:self]; - if (!authorizationFlowStarted) { - NSError *safariError = [OIDErrorUtilities errorWithCode:OIDErrorCodeSafariOpenError - underlyingError:nil - description:@"Unable to open Safari."]; - [self didFinishWithResponse:nil error:safariError]; - } -} - -- (void)cancel { - [_UICoordinator dismissAuthorizationAnimated:YES - completion:^{ - NSError *error = [OIDErrorUtilities - errorWithCode:OIDErrorCodeUserCanceledAuthorizationFlow - underlyingError:nil - description:nil]; - [self didFinishWithResponse:nil error:error]; - }]; -} - -- (BOOL)shouldHandleURL:(NSURL *)URL { - NSURL *standardizedURL = [URL standardizedURL]; - NSURL *standardizedRedirectURL = [_request.redirectURL standardizedURL]; - - return OIDIsEqualIncludingNil(standardizedURL.scheme, standardizedRedirectURL.scheme) && - OIDIsEqualIncludingNil(standardizedURL.user, standardizedRedirectURL.user) && - OIDIsEqualIncludingNil(standardizedURL.password, standardizedRedirectURL.password) && - OIDIsEqualIncludingNil(standardizedURL.host, standardizedRedirectURL.host) && - OIDIsEqualIncludingNil(standardizedURL.port, standardizedRedirectURL.port) && - OIDIsEqualIncludingNil(standardizedURL.path, standardizedRedirectURL.path); -} - -- (BOOL)resumeAuthorizationFlowWithURL:(NSURL *)URL { - // rejects URLs that don't match redirect (these may be completely unrelated to the authorization) - if (![self shouldHandleURL:URL]) { - return NO; - } - // checks for an invalid state - if (!_pendingauthorizationFlowCallback) { - [NSException raise:OIDOAuthExceptionInvalidAuthorizationFlow - format:@"%@", OIDOAuthExceptionInvalidAuthorizationFlow, nil]; - } - - OIDURLQueryComponent *query = [[OIDURLQueryComponent alloc] initWithURL:URL]; - - NSError *error; - OIDAuthorizationResponse *response = nil; - - // checks for an OAuth error response as per RFC6749 Section 4.1.2.1 - if (query.dictionaryValue[OIDOAuthErrorFieldError]) { - error = [OIDErrorUtilities OAuthErrorWithDomain:OIDOAuthAuthorizationErrorDomain - OAuthResponse:query.dictionaryValue - underlyingError:nil]; - } - - // verifies that the state in the response matches the state in the request, or both are nil - if (!OIDIsEqualIncludingNil(_request.state, query.dictionaryValue[kStateParameter])) { - NSMutableDictionary *userInfo = [query.dictionaryValue mutableCopy]; - userInfo[NSLocalizedDescriptionKey] = - [NSString stringWithFormat:@"State mismatch, expecting %@ but got %@ in authorization " - "response %@", - _request.state, - response.state, - response]; - response = nil; - error = [NSError errorWithDomain:OIDOAuthAuthorizationErrorDomain - code:OIDErrorCodeOAuthAuthorizationClientError - userInfo:userInfo]; - } - - // no error, should be a valid OAuth 2.0 response - if (!error) { - response = [[OIDAuthorizationResponse alloc] initWithRequest:_request - parameters:query.dictionaryValue]; - } - - [_UICoordinator dismissAuthorizationAnimated:YES - completion:^{ - [self didFinishWithResponse:response error:error]; - }]; - - return YES; -} - -- (void)failAuthorizationFlowWithError:(NSError *)error { - [self didFinishWithResponse:nil error:error]; -} - -/*! @brief Invokes the pending callback and performs cleanup. - @param response The authorization response, if any to return to the callback. - @param error The error, if any, to return to the callback. - */ -- (void)didFinishWithResponse:(nullable OIDAuthorizationResponse *)response - error:(nullable NSError *)error { - OIDAuthorizationCallback callback = _pendingauthorizationFlowCallback; - _pendingauthorizationFlowCallback = nil; - _UICoordinator = nil; - if (callback) { - callback(response, error); - } -} - -@end - -@implementation OIDAuthorizationService - -@synthesize configuration = _configuration; - -+ (void)discoverServiceConfigurationForIssuer:(NSURL *)issuerURL - completion:(OIDDiscoveryCallback)completion { - NSURL *fullDiscoveryURL = - [issuerURL URLByAppendingPathComponent:kOpenIDConfigurationWellKnownPath]; - - return [[self class] discoverServiceConfigurationForDiscoveryURL:fullDiscoveryURL - completion:completion]; -} - -+ (void)discoverServiceConfigurationForDiscoveryURL:(NSURL *)discoveryURL - completion:(OIDDiscoveryCallback)completion { - - NSURLSession *session = [OIDURLSessionProvider session]; - NSURLSessionDataTask *task = - [session dataTaskWithURL:discoveryURL - completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) { - // If we got any sort of error, just report it. - if (error || !data) { - error = [OIDErrorUtilities errorWithCode:OIDErrorCodeNetworkError - underlyingError:error - description:error.localizedDescription]; - dispatch_async(dispatch_get_main_queue(), ^{ - completion(nil, error); - }); - return; - } - - NSHTTPURLResponse *urlResponse = (NSHTTPURLResponse *)response; - - // Check for non-200 status codes. - // https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfigurationResponse - if (urlResponse.statusCode != 200) { - NSError *URLResponseError = [OIDErrorUtilities HTTPErrorWithHTTPResponse:urlResponse - data:data]; - error = [OIDErrorUtilities errorWithCode:OIDErrorCodeNetworkError - underlyingError:URLResponseError - description:nil]; - dispatch_async(dispatch_get_main_queue(), ^{ - completion(nil, error); - }); - return; - } - - // Construct an OIDServiceDiscovery with the received JSON. - OIDServiceDiscovery *discovery = - [[OIDServiceDiscovery alloc] initWithJSONData:data error:&error]; - if (error || !discovery) { - error = [OIDErrorUtilities errorWithCode:OIDErrorCodeNetworkError - underlyingError:error - description:nil]; - dispatch_async(dispatch_get_main_queue(), ^{ - completion(nil, error); - }); - return; - } - - // Create our service configuration with the discovery document and return it. - OIDServiceConfiguration *configuration = - [[OIDServiceConfiguration alloc] initWithDiscoveryDocument:discovery]; - dispatch_async(dispatch_get_main_queue(), ^{ - completion(configuration, nil); - }); - }]; - [task resume]; -} - -#pragma mark - Authorization Endpoint - -+ (id) - presentAuthorizationRequest:(OIDAuthorizationRequest *)request - UICoordinator:(id)UICoordinator - callback:(OIDAuthorizationCallback)callback { - OIDAuthorizationFlowSessionImplementation *flowSession = - [[OIDAuthorizationFlowSessionImplementation alloc] initWithRequest:request]; - [flowSession presentAuthorizationWithCoordinator:UICoordinator callback:callback]; - return flowSession; -} - -#pragma mark - Token Endpoint - -+ (void)performTokenRequest:(OIDTokenRequest *)request callback:(OIDTokenCallback)callback { - NSURLRequest *URLRequest = [request URLRequest]; - NSURLSession *session = [OIDURLSessionProvider session]; - [[session dataTaskWithRequest:URLRequest - completionHandler:^(NSData *_Nullable data, - NSURLResponse *_Nullable response, - NSError *_Nullable error) { - if (error) { - // A network error or server error occurred. - NSError *returnedError = - [OIDErrorUtilities errorWithCode:OIDErrorCodeNetworkError - underlyingError:error - description:nil]; - dispatch_async(dispatch_get_main_queue(), ^{ - callback(nil, returnedError); - }); - return; - } - - NSHTTPURLResponse *HTTPURLResponse = (NSHTTPURLResponse *)response; - NSInteger statusCode = HTTPURLResponse.statusCode; - if (statusCode != 200) { - // A server error occurred. - NSError *serverError = - [OIDErrorUtilities HTTPErrorWithHTTPResponse:HTTPURLResponse data:data]; - - // HTTP 400 may indicate an RFC6749 Section 5.2 error response. - // HTTP 429 may occur during polling for device-flow requests for the slow_down error - // https://tools.ietf.org/html/draft-ietf-oauth-device-flow-03#section-3.5 - if (statusCode == 400 || statusCode == 429) { - NSError *jsonDeserializationError; - NSDictionary *> *json = - [NSJSONSerialization JSONObjectWithData:data options:0 error:&jsonDeserializationError]; - - // if the HTTP 400 response parses as JSON and has an 'error' key, it's an OAuth error - // these errors are special as they indicate a problem with the authorization grant - if (json[OIDOAuthErrorFieldError]) { - NSError *oauthError = - [OIDErrorUtilities OAuthErrorWithDomain:OIDOAuthTokenErrorDomain - OAuthResponse:json - underlyingError:serverError]; - dispatch_async(dispatch_get_main_queue(), ^{ - callback(nil, oauthError); - }); - return; - } - } - - // not an OAuth error, just a generic server error - NSError *returnedError = - [OIDErrorUtilities errorWithCode:OIDErrorCodeServerError - underlyingError:serverError - description:nil]; - dispatch_async(dispatch_get_main_queue(), ^{ - callback(nil, returnedError); - }); - return; - } - - NSError *jsonDeserializationError; - NSDictionary *> *json = - [NSJSONSerialization JSONObjectWithData:data options:0 error:&jsonDeserializationError]; - if (jsonDeserializationError) { - // A problem occurred deserializing the response/JSON. - NSError *returnedError = - [OIDErrorUtilities errorWithCode:OIDErrorCodeJSONDeserializationError - underlyingError:jsonDeserializationError - description:nil]; - dispatch_async(dispatch_get_main_queue(), ^{ - callback(nil, returnedError); - }); - return; - } - - OIDTokenResponse *tokenResponse = - [[OIDTokenResponse alloc] initWithRequest:request parameters:json]; - if (!tokenResponse) { - // A problem occurred constructing the token response from the JSON. - NSError *returnedError = - [OIDErrorUtilities errorWithCode:OIDErrorCodeTokenResponseConstructionError - underlyingError:jsonDeserializationError - description:nil]; - dispatch_async(dispatch_get_main_queue(), ^{ - callback(nil, returnedError); - }); - return; - } - - // Success - dispatch_async(dispatch_get_main_queue(), ^{ - callback(tokenResponse, nil); - }); - }] resume]; -} - - -#pragma mark - Registration Endpoint - -+ (void)performRegistrationRequest:(OIDRegistrationRequest *)request - completion:(OIDRegistrationCompletion)completion { - NSURLRequest *URLRequest = [request URLRequest]; - if (!URLRequest) { - // A problem occurred deserializing the response/JSON. - NSError *returnedError = [OIDErrorUtilities errorWithCode:OIDErrorCodeJSONSerializationError - underlyingError:nil - description:@"The registration request could not " - "be serialized as JSON."]; - dispatch_async(dispatch_get_main_queue(), ^{ - completion(nil, returnedError); - }); - return; - } - - NSURLSession *session = [OIDURLSessionProvider session]; - [[session dataTaskWithRequest:URLRequest - completionHandler:^(NSData *_Nullable data, - NSURLResponse *_Nullable response, - NSError *_Nullable error) { - if (error) { - // A network error or server error occurred. - NSError *returnedError = [OIDErrorUtilities errorWithCode:OIDErrorCodeNetworkError - underlyingError:error - description:nil]; - dispatch_async(dispatch_get_main_queue(), ^{ - completion(nil, returnedError); - }); - return; - } - - NSHTTPURLResponse *HTTPURLResponse = (NSHTTPURLResponse *) response; - - if (HTTPURLResponse.statusCode != 201 && HTTPURLResponse.statusCode != 200) { - // A server error occurred. - NSError *serverError = [OIDErrorUtilities HTTPErrorWithHTTPResponse:HTTPURLResponse - data:data]; - - // HTTP 400 may indicate an OpenID Connect Dynamic Client Registration 1.0 Section 3.3 error - // response, checks for that - if (HTTPURLResponse.statusCode == 400) { - NSError *jsonDeserializationError; - NSDictionary *> *json = - [NSJSONSerialization JSONObjectWithData:data options:0 error:&jsonDeserializationError]; - - // if the HTTP 400 response parses as JSON and has an 'error' key, it's an OAuth error - // these errors are special as they indicate a problem with the authorization grant - if (json[OIDOAuthErrorFieldError]) { - NSError *oauthError = - [OIDErrorUtilities OAuthErrorWithDomain:OIDOAuthRegistrationErrorDomain - OAuthResponse:json - underlyingError:serverError]; - dispatch_async(dispatch_get_main_queue(), ^{ - completion(nil, oauthError); - }); - return; - } - } - - // not an OAuth error, just a generic server error - NSError *returnedError = [OIDErrorUtilities errorWithCode:OIDErrorCodeServerError - underlyingError:serverError - description:nil]; - dispatch_async(dispatch_get_main_queue(), ^{ - completion(nil, returnedError); - }); - return; - } - - NSError *jsonDeserializationError; - NSDictionary *> *json = - [NSJSONSerialization JSONObjectWithData:data options:0 error:&jsonDeserializationError]; - if (jsonDeserializationError) { - // A problem occurred deserializing the response/JSON. - NSError *returnedError = [OIDErrorUtilities errorWithCode:OIDErrorCodeJSONDeserializationError - underlyingError:jsonDeserializationError - description:nil]; - dispatch_async(dispatch_get_main_queue(), ^{ - completion(nil, returnedError); - }); - return; - } - - OIDRegistrationResponse *registrationResponse = - [[OIDRegistrationResponse alloc] initWithRequest:request - parameters:json]; - if (!registrationResponse) { - // A problem occurred constructing the registration response from the JSON. - NSError *returnedError = - [OIDErrorUtilities errorWithCode:OIDErrorCodeRegistrationResponseConstructionError - underlyingError:jsonDeserializationError - description:nil]; - dispatch_async(dispatch_get_main_queue(), ^{ - completion(nil, returnedError); - }); - return; - } - - // Success - dispatch_async(dispatch_get_main_queue(), ^{ - completion(registrationResponse, nil); - }); - }] resume]; -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/Source/OIDAuthorizationUICoordinator.h b/Source/OIDAuthorizationUICoordinator.h deleted file mode 100644 index d42d8c5f5..000000000 --- a/Source/OIDAuthorizationUICoordinator.h +++ /dev/null @@ -1,53 +0,0 @@ -/*! @file OIDAuthorizationUICoordinator.h - @brief AppAuth iOS SDK - @copyright - Copyright 2016 Google Inc. All Rights Reserved. - @copydetails - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - */ - -#import - -@class OIDAuthorizationRequest; -@protocol OIDAuthorizationFlowSession; - -NS_ASSUME_NONNULL_BEGIN - -/*! @protocol OIDAuthorizationUICoordinator - @brief An authorization UI coordinator that presents an authorization request. Clients may - provide custom implementations of an authorization UI coordinator to customize the way the - authorization request is presented to the user. - */ -@protocol OIDAuthorizationUICoordinator - -/*! @brief Presents the authroization request in the user agent. - @param request The authorizatoin request to be presented in the user agent. - @param session The @c OIDAuthorizationFlowSession instance that initiates presenting the - authorization UI. Concrete implementations of a @c OIDAuthorizationUICoordinator may call - resumeAuthorizationFlowWithURL or failAuthorizationFlowWithError on session to either - resume or fail the authorization. - @return YES If the authorization UI was successfully presented to the user. - */ -- (BOOL)presentAuthorizationRequest:(OIDAuthorizationRequest *)request - session:(id)session; - -/*! @brief Dimisses the authorization UI and calls completion when the dismiss operation ends. - @param animated Wheter or not the dismiss operation should be animated. - @remarks Has no effect if no authorization UI is presented. - @param completion The block to be called when the dismiss operations ends - */ -- (void)dismissAuthorizationAnimated:(BOOL)animated completion:(void (^)(void))completion; - -@end - -NS_ASSUME_NONNULL_END diff --git a/Source/iOS/OIDAuthState+IOS.h b/Source/iOS/OIDAuthState+IOS.h deleted file mode 100644 index 80f162587..000000000 --- a/Source/iOS/OIDAuthState+IOS.h +++ /dev/null @@ -1,46 +0,0 @@ -/*! @file OIDAuthState+IOS.h - @brief AppAuth iOS SDK - @copyright - Copyright 2016 Google Inc. All Rights Reserved. - @copydetails - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - */ - -#import - -#import "OIDAuthState.h" - -NS_ASSUME_NONNULL_BEGIN - -/*! @brief iOS specific convenience methods for @c OIDAuthState. - */ -@interface OIDAuthState (IOS) - -/*! @brief Convenience method to create a @c OIDAuthState by presenting an authorization request - and performing the authorization code exchange in the case of code flow requests. - @param authorizationRequest The authorization request to present. - @param presentingViewController The view controller from which to present the - @c SFSafariViewController. - @param callback The method called when the request has completed or failed. - @return A @c OIDAuthorizationFlowSession instance which will terminate when it - receives a @c OIDAuthorizationFlowSession.cancel message, or after processing a - @c OIDAuthorizationFlowSession.resumeAuthorizationFlowWithURL: message. - */ -+ (id) - authStateByPresentingAuthorizationRequest:(OIDAuthorizationRequest *)authorizationRequest - presentingViewController:(UIViewController *)presentingViewController - callback:(OIDAuthStateAuthorizationCallback)callback; - -@end - -NS_ASSUME_NONNULL_END diff --git a/Source/iOS/OIDAuthState+IOS.m b/Source/iOS/OIDAuthState+IOS.m deleted file mode 100644 index 3e32a8be9..000000000 --- a/Source/iOS/OIDAuthState+IOS.m +++ /dev/null @@ -1,36 +0,0 @@ -/*! @file OIDAuthState+IOS.m - @brief AppAuth iOS SDK - @copyright - Copyright 2016 Google Inc. All Rights Reserved. - @copydetails - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - */ - -#import "OIDAuthState+IOS.h" - -#import "OIDAuthorizationUICoordinatorIOS.h" - -@implementation OIDAuthState (IOS) - -+ (id) - authStateByPresentingAuthorizationRequest:(OIDAuthorizationRequest *)authorizationRequest - presentingViewController:(UIViewController *)presentingViewController - callback:(OIDAuthStateAuthorizationCallback)callback { - OIDAuthorizationUICoordinatorIOS *coordinator = [[OIDAuthorizationUICoordinatorIOS alloc] - initWithPresentingViewController:presentingViewController]; - return [self authStateByPresentingAuthorizationRequest:authorizationRequest - UICoordinator:coordinator - callback:callback]; -} - -@end diff --git a/Source/iOS/OIDAuthorizationService+IOS.h b/Source/iOS/OIDAuthorizationService+IOS.h deleted file mode 100644 index 3d76340e8..000000000 --- a/Source/iOS/OIDAuthorizationService+IOS.h +++ /dev/null @@ -1,44 +0,0 @@ -/*! @file OIDAuthorizationService+IOS.h - @brief AppAuth iOS SDK - @copyright - Copyright 2016 Google Inc. All Rights Reserved. - @copydetails - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - */ - -#import - -#import "OIDAuthorizationService.h" - -NS_ASSUME_NONNULL_BEGIN - -/*! @brief Provides iOS specific authorization request handling. - */ -@interface OIDAuthorizationService (IOS) - -/*! @brief Perform an authorization flow using \SFSafariViewController. - @param request The authorization request. - @param presentingViewController The view controller from which to present the - \SFSafariViewController. - @param callback The method called when the request has completed or failed. - @return A @c OIDAuthorizationFlowSession instance which will terminate when it - receives a @c OIDAuthorizationFlowSession.cancel message, or after processing a - @c OIDAuthorizationFlowSession.resumeAuthorizationFlowWithURL: message. - */ -+ (id) - presentAuthorizationRequest:(OIDAuthorizationRequest *)request - presentingViewController:(UIViewController *)presentingViewController - callback:(OIDAuthorizationCallback)callback; -@end - -NS_ASSUME_NONNULL_END diff --git a/Source/iOS/OIDAuthorizationService+IOS.m b/Source/iOS/OIDAuthorizationService+IOS.m deleted file mode 100644 index 2a8d6da86..000000000 --- a/Source/iOS/OIDAuthorizationService+IOS.m +++ /dev/null @@ -1,38 +0,0 @@ -/*! @file OIDAuthorizationService+IOS.m - @brief AppAuth iOS SDK - @copyright - Copyright 2016 Google Inc. All Rights Reserved. - @copydetails - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - */ - -#import "OIDAuthorizationService+IOS.h" - -#import "OIDAuthorizationUICoordinatorIOS.h" - -NS_ASSUME_NONNULL_BEGIN - -@implementation OIDAuthorizationService (IOS) - -+ (id) - presentAuthorizationRequest:(OIDAuthorizationRequest *)request - presentingViewController:(UIViewController *)presentingViewController - callback:(OIDAuthorizationCallback)callback { - OIDAuthorizationUICoordinatorIOS *coordinator = [[OIDAuthorizationUICoordinatorIOS alloc] - initWithPresentingViewController:presentingViewController]; - return [self presentAuthorizationRequest:request UICoordinator:coordinator callback:callback]; -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/Source/iOS/OIDAuthorizationUICoordinatorIOS.h b/Source/iOS/OIDAuthorizationUICoordinatorIOS.h deleted file mode 100644 index 2e6295f24..000000000 --- a/Source/iOS/OIDAuthorizationUICoordinatorIOS.h +++ /dev/null @@ -1,67 +0,0 @@ -/*! @file OIDAuthorizationUICoordinator.h - @brief AppAuth iOS SDK - @copyright - Copyright 2016 Google Inc. All Rights Reserved. - @copydetails - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - */ - -#import - -#import "OIDAuthorizationUICoordinator.h" - -@class SFSafariViewController; - -NS_ASSUME_NONNULL_BEGIN - -/*! @brief Allows library consumers to bootstrap an @c SFSafariViewController as they see fit. - @remarks Useful for customizing tint colors and presentation styles. - */ -@protocol OIDSafariViewControllerFactory - -/*! @brief Creates and returns a new @c SFSafariViewController. - @param URL The URL which the @c SFSafariViewController should load initially. - */ -- (SFSafariViewController *)safariViewControllerWithURL:(NSURL *)URL; - -@end - -/*! @brief An iOS specific authorization UI Coordinator that uses a \SFSafariViewController to - present an authorization request. - */ -@interface OIDAuthorizationUICoordinatorIOS : NSObject - -/*! @brief Allows library consumers to change the @c OIDSafariViewControllerFactory used to create - new instances of @c SFSafariViewController. - @remarks Useful for customizing tint colors and presentation styles. - @param factory The @c OIDSafariViewControllerFactory to use for creating new instances of - @c SFSafariViewController. - */ -+ (void)setSafariViewControllerFactory:(id)factory; - -/*! @internal - @brief Unavailable. Please use @c initWithPresentingViewController: - */ -- (nonnull instancetype)init NS_UNAVAILABLE; - -/*! @brief The designated initializer. - @param presentingViewController The view controller from which to present the - \SFSafariViewController. - */ -- (nullable instancetype)initWithPresentingViewController: - (UIViewController *)presentingViewController - NS_DESIGNATED_INITIALIZER; - -@end - -NS_ASSUME_NONNULL_END diff --git a/Source/iOS/OIDAuthorizationUICoordinatorIOS.m b/Source/iOS/OIDAuthorizationUICoordinatorIOS.m deleted file mode 100644 index 70bd3104e..000000000 --- a/Source/iOS/OIDAuthorizationUICoordinatorIOS.m +++ /dev/null @@ -1,200 +0,0 @@ -/*! @file OIDAuthorizationUICoordinatorIOS.m - @brief AppAuth iOS SDK - @copyright - Copyright 2016 Google Inc. All Rights Reserved. - @copydetails - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - */ - -#import "OIDAuthorizationUICoordinatorIOS.h" - -#import - -#import "OIDAuthorizationRequest.h" -#import "OIDAuthorizationService.h" -#import "OIDErrorUtilities.h" - -NS_ASSUME_NONNULL_BEGIN - -/** @brief The global/shared Safari view controller factory. Responsible for creating all new - instances of @c SFSafariViewController. - */ -static id __nullable gSafariViewControllerFactory; - -/** @brief The default @c OIDSafariViewControllerFactory which creates new instances of - @c SFSafariViewController using known best practices. - */ -@interface OIDDefaultSafariViewControllerFactory : NSObject -@end - -@interface OIDAuthorizationUICoordinatorIOS () -@end - -@implementation OIDAuthorizationUICoordinatorIOS { - UIViewController *_presentingViewController; - - BOOL _authorizationFlowInProgress; - __weak id _session; -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wpartial-availability" - __weak SFSafariViewController *_safariVC; - SFAuthenticationSession *_authenticationVC; -#pragma clang diagnostic pop -} - -/** @brief Obtains the current @c OIDSafariViewControllerFactory; creating a new default instance if - required. - */ -+ (id)safariViewControllerFactory { - if (!gSafariViewControllerFactory) { - gSafariViewControllerFactory = [[OIDDefaultSafariViewControllerFactory alloc] init]; - } - return gSafariViewControllerFactory; -} - -+ (void)setSafariViewControllerFactory:(id)factory { - NSAssert(factory, @"Parameter: |factory| must be non-nil."); - gSafariViewControllerFactory = factory; -} - -- (nullable instancetype)initWithPresentingViewController: - (UIViewController *)presentingViewController { - self = [super init]; - if (self) { - _presentingViewController = presentingViewController; - } - return self; -} - -- (BOOL)presentAuthorizationRequest:(OIDAuthorizationRequest *)request - session:(id)session { - if (_authorizationFlowInProgress) { - // TODO: Handle errors as authorization is already in progress. - return NO; - } - - _authorizationFlowInProgress = YES; - _session = session; - BOOL openedSafari = NO; - NSURL *requestURL = [request authorizationRequestURL]; - - if (@available(iOS 11.0, *)) { - NSString *redirectScheme = request.redirectURL.scheme; - SFAuthenticationSession* authenticationVC = - [[SFAuthenticationSession alloc] initWithURL:requestURL - callbackURLScheme:redirectScheme - completionHandler:^(NSURL * _Nullable callbackURL, - NSError * _Nullable error) { - _authenticationVC = nil; - if (callbackURL) { - [_session resumeAuthorizationFlowWithURL:callbackURL]; - } else { - NSError *safariError = - [OIDErrorUtilities errorWithCode:OIDErrorCodeUserCanceledAuthorizationFlow - underlyingError:error - description:nil]; - [_session failAuthorizationFlowWithError:safariError]; - } - }]; - _authenticationVC = authenticationVC; - openedSafari = [authenticationVC start]; - } else if (@available(iOS 9.0, *)) { - SFSafariViewController *safariVC = - [[[self class] safariViewControllerFactory] safariViewControllerWithURL:requestURL]; - safariVC.delegate = self; - _safariVC = safariVC; - [_presentingViewController presentViewController:safariVC animated:YES completion:nil]; - openedSafari = YES; - } else { - openedSafari = [[UIApplication sharedApplication] openURL:requestURL]; - } - - if (!openedSafari) { - [self cleanUp]; - NSError *safariError = [OIDErrorUtilities errorWithCode:OIDErrorCodeSafariOpenError - underlyingError:nil - description:@"Unable to open Safari."]; - [session failAuthorizationFlowWithError:safariError]; - } - return openedSafari; -} - -- (void)dismissAuthorizationAnimated:(BOOL)animated completion:(void (^)(void))completion { - if (!_authorizationFlowInProgress) { - // Ignore this call if there is no authorization flow in progress. - return; - } - -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wpartial-availability" - SFSafariViewController *safariVC = _safariVC; - SFAuthenticationSession *authenticationVC = _authenticationVC; -#pragma clang diagnostic pop - - [self cleanUp]; - - if (@available(iOS 11.0, *)) { - [authenticationVC cancel]; - if (completion) completion(); - } else if (@available(iOS 9.0, *)) { - if (safariVC) { - [safariVC dismissViewControllerAnimated:YES completion:completion]; - } else { - if (completion) completion(); - } - } else { - if (completion) completion(); - } -} - -- (void)cleanUp { - // The weak references to |_safariVC| and |_session| are set to nil to avoid accidentally using - // them while not in an authorization flow. - _safariVC = nil; - _authenticationVC = nil; - _session = nil; - _authorizationFlowInProgress = NO; -} - -#pragma mark - SFSafariViewControllerDelegate - -- (void)safariViewControllerDidFinish:(SFSafariViewController *)controller NS_AVAILABLE_IOS(9.0) { - if (controller != _safariVC) { - // Ignore this call if the safari view controller do not match. - return; - } - if (!_authorizationFlowInProgress) { - // Ignore this call if there is no authorization flow in progress. - return; - } - id session = _session; - [self cleanUp]; - NSError *error = [OIDErrorUtilities errorWithCode:OIDErrorCodeProgramCanceledAuthorizationFlow - underlyingError:nil - description:nil]; - [session failAuthorizationFlowWithError:error]; -} - -@end - -@implementation OIDDefaultSafariViewControllerFactory - -- (SFSafariViewController *)safariViewControllerWithURL:(NSURL *)URL NS_AVAILABLE_IOS(9.0) { - SFSafariViewController *safariViewController = - [[SFSafariViewController alloc] initWithURL:URL entersReaderIfAvailable:NO]; - return safariViewController; -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/Source/macOS/OIDAuthState+Mac.h b/Source/macOS/OIDAuthState+Mac.h deleted file mode 100644 index ddb6e377f..000000000 --- a/Source/macOS/OIDAuthState+Mac.h +++ /dev/null @@ -1,40 +0,0 @@ -/*! @file OIDAuthState+Mac.h - @brief AppAuth iOS SDK - @copyright - Copyright 2016 Google Inc. All Rights Reserved. - @copydetails - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - */ - -#import "OIDAuthState.h" - -NS_ASSUME_NONNULL_BEGIN - -/*! @brief macOS specific convenience methods for @c OIDAuthState. - */ -@interface OIDAuthState (Mac) - -/*! @brief Convenience method to create a @c OIDAuthState by presenting an authorization request - and performing the authorization code exchange in the case of code flow requests. - @param authorizationRequest The authorization request to present. - @param callback The method called when the request has completed or failed. - @return A @c OIDAuthorizationFlowSession instance which will terminate when it - receives a @c OIDAuthorizationFlowSession.cancel message, or after processing a - @c OIDAuthorizationFlowSession.resumeAuthorizationFlowWithURL: message. - */ -+ (id) - authStateByPresentingAuthorizationRequest:(OIDAuthorizationRequest *)authorizationRequest - callback:(OIDAuthStateAuthorizationCallback)callback; -@end - -NS_ASSUME_NONNULL_END diff --git a/Source/macOS/OIDAuthState+Mac.m b/Source/macOS/OIDAuthState+Mac.m deleted file mode 100644 index 02f367b6d..000000000 --- a/Source/macOS/OIDAuthState+Mac.m +++ /dev/null @@ -1,34 +0,0 @@ -/*! @file OIDAuthState+Mac.m - @brief AppAuth iOS SDK - @copyright - Copyright 2016 Google Inc. All Rights Reserved. - @copydetails - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - */ - -#import "OIDAuthState+Mac.h" - -#import "OIDAuthorizationUICoordinatorMac.h" - -@implementation OIDAuthState (Mac) - -+ (id) - authStateByPresentingAuthorizationRequest:(OIDAuthorizationRequest *)authorizationRequest - callback:(OIDAuthStateAuthorizationCallback)callback { - OIDAuthorizationUICoordinatorMac *coordinator = [[OIDAuthorizationUICoordinatorMac alloc] init]; - return [self authStateByPresentingAuthorizationRequest:authorizationRequest - UICoordinator:coordinator - callback:callback]; -} - -@end diff --git a/Source/macOS/OIDAuthorizationService+Mac.h b/Source/macOS/OIDAuthorizationService+Mac.h deleted file mode 100644 index c8c98c15d..000000000 --- a/Source/macOS/OIDAuthorizationService+Mac.h +++ /dev/null @@ -1,38 +0,0 @@ -/*! @file OIDAuthorizationService+Mac.h - @brief AppAuth iOS SDK - @copyright - Copyright 2016 Google Inc. All Rights Reserved. - @copydetails - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - */ - -#import "OIDAuthorizationService.h" - -NS_ASSUME_NONNULL_BEGIN - -/*! @brief Provides macOS specific authorization request handling. - */ -@interface OIDAuthorizationService (Mac) - -/*! @brief Perform an authorization flow using the default browser. - @param request The authorization request. - @param callback The method called when the request has completed or failed. - @return A @c OIDAuthorizationFlowSession instance which will terminate when it - receives a @c OIDAuthorizationFlowSession.cancel message, or after processing a - @c OIDAuthorizationFlowSession.resumeAuthorizationFlowWithURL: message. - */ -+ (id)presentAuthorizationRequest:(OIDAuthorizationRequest *)request - callback:(OIDAuthorizationCallback)callback; -@end - -NS_ASSUME_NONNULL_END diff --git a/Source/macOS/OIDAuthorizationUICoordinatorMac.m b/Source/macOS/OIDAuthorizationUICoordinatorMac.m deleted file mode 100644 index 165d34694..000000000 --- a/Source/macOS/OIDAuthorizationUICoordinatorMac.m +++ /dev/null @@ -1,71 +0,0 @@ -/*! @file OIDAuthorizationUICoordinatorMac.m - @brief AppAuth iOS SDK - @copyright - Copyright 2016 Google Inc. All Rights Reserved. - @copydetails - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - */ - -#import "OIDAuthorizationUICoordinatorMac.h" - -#import - -#import "OIDAuthorizationRequest.h" -#import "OIDAuthorizationService.h" -#import "OIDErrorUtilities.h" - -NS_ASSUME_NONNULL_BEGIN - -@implementation OIDAuthorizationUICoordinatorMac - -- (BOOL)presentAuthorizationRequest:(OIDAuthorizationRequest *)request - session:(id)session { - if (_authorizationFlowInProgress) { - // TODO: Handle errors as authorization is already in progress. - return NO; - } - - _authorizationFlowInProgress = YES; - _session = session; - NSURL *requestURL = [request authorizationRequestURL]; - - BOOL openedBrowser = [[NSWorkspace sharedWorkspace] openURL:requestURL]; - if (!openedBrowser) { - [self cleanUp]; - NSError *safariError = [OIDErrorUtilities errorWithCode:OIDErrorCodeBrowserOpenError - underlyingError:nil - description:@"Unable to open the browser."]; - [session failAuthorizationFlowWithError:safariError]; - } - return openedBrowser; -} - -- (void)dismissAuthorizationAnimated:(BOOL)animated completion:(void (^)(void))completion { - if (!_authorizationFlowInProgress) { - // Ignore this call if there is no authorization flow in progress. - return; - } - // Ideally the browser tab with the URL should be closed here, but the AppAuth library does not - // control the browser. - [self cleanUp]; - if (completion) completion(); -} - -- (void)cleanUp { - _session = nil; - _authorizationFlowInProgress = NO; -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/Source/AppAuth.h b/Sources/AppAuth.h similarity index 86% rename from Source/AppAuth.h rename to Sources/AppAuth.h index cfd3f9c62..19abc55e1 100644 --- a/Source/AppAuth.h +++ b/Sources/AppAuth.h @@ -22,10 +22,13 @@ #import "OIDAuthorizationRequest.h" #import "OIDAuthorizationResponse.h" #import "OIDAuthorizationService.h" -#import "OIDAuthorizationUICoordinator.h" #import "OIDError.h" #import "OIDErrorUtilities.h" +#import "OIDExternalUserAgent.h" +#import "OIDExternalUserAgentRequest.h" +#import "OIDExternalUserAgentSession.h" #import "OIDGrantTypes.h" +#import "OIDIDToken.h" #import "OIDRegistrationRequest.h" #import "OIDRegistrationResponse.h" #import "OIDResponseTypes.h" @@ -37,23 +40,26 @@ #import "OIDTokenResponse.h" #import "OIDTokenUtilities.h" #import "OIDURLSessionProvider.h" +#import "OIDEndSessionRequest.h" +#import "OIDEndSessionResponse.h" #if TARGET_OS_TV #elif TARGET_OS_WATCH -#elif TARGET_OS_IOS +#elif TARGET_OS_IOS || TARGET_OS_MACCATALYST #import "OIDAuthState+IOS.h" #import "OIDAuthorizationService+IOS.h" -#import "OIDAuthorizationUICoordinatorIOS.h" -#elif TARGET_OS_MAC +#import "OIDExternalUserAgentIOS.h" +#import "OIDExternalUserAgentIOSCustomBrowser.h" +#import "OIDExternalUserAgentCatalyst.h" +#elif TARGET_OS_OSX #import "OIDAuthState+Mac.h" #import "OIDAuthorizationService+Mac.h" -#import "OIDAuthorizationUICoordinatorMac.h" +#import "OIDExternalUserAgentMac.h" #import "OIDRedirectHTTPHandler.h" #else #error "Platform Undefined" #endif - /*! @mainpage AppAuth for iOS and macOS @section introduction Introduction @@ -69,7 +75,7 @@ It follows the best practices set out in [RFC 8252 - OAuth 2.0 for Native Apps](https://tools.ietf.org/html/rfc8252) including using `SFAuthenticationSession` and `SFSafariViewController` on iOS - for the auth request. `UIWebView` and `WKWebView` are explicitly *not* + for the auth request. Web view and `WKWebView` are explicitly *not* supported due to the security and usability reasons explained in [Section 8.12 of RFC 8252](https://tools.ietf.org/html/rfc8252#section-8.12). diff --git a/Sources/AppAuth/Resources/PrivacyInfo.xcprivacy b/Sources/AppAuth/Resources/PrivacyInfo.xcprivacy new file mode 100644 index 000000000..cc6746dcb --- /dev/null +++ b/Sources/AppAuth/Resources/PrivacyInfo.xcprivacy @@ -0,0 +1,16 @@ + + + + + NSPrivacyAccessedAPITypes + + + NSPrivacyCollectedDataTypes + + + NSPrivacyTrackingDomains + + NSPrivacyTracking + + + diff --git a/Sources/AppAuth/iOS/OIDAuthState+IOS.h b/Sources/AppAuth/iOS/OIDAuthState+IOS.h new file mode 100644 index 000000000..1a1ee63a0 --- /dev/null +++ b/Sources/AppAuth/iOS/OIDAuthState+IOS.h @@ -0,0 +1,84 @@ +/*! @file OIDAuthState+IOS.h + @brief AppAuth iOS SDK + @copyright + Copyright 2016 Google Inc. All Rights Reserved. + @copydetails + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#import + +#if TARGET_OS_IOS || TARGET_OS_MACCATALYST + +#import + +#import "OIDAuthState.h" + +NS_ASSUME_NONNULL_BEGIN + +/*! @brief iOS specific convenience methods for @c OIDAuthState. + */ +@interface OIDAuthState (IOS) + +/*! @brief Convenience method to create a @c OIDAuthState by presenting an authorization request + and performing the authorization code exchange in the case of code flow requests. For + the hybrid flow, the caller should validate the id_token and c_hash, then perform the token + request (@c OIDAuthorizationService.performTokenRequest:callback:) + and update the OIDAuthState with the results (@c + OIDAuthState.updateWithTokenResponse:error:). + @param authorizationRequest The authorization request to present. + @param presentingViewController The view controller to use for presenting the authentication UI. + @param callback The method called when the request has completed or failed. + @return A @c OIDExternalUserAgentSession instance which will terminate when it + receives a @c OIDExternalUserAgentSession.cancel message, or after processing a + @c OIDExternalUserAgentSession.resumeExternalUserAgentFlowWithURL: message. + */ ++ (id) + authStateByPresentingAuthorizationRequest:(OIDAuthorizationRequest *)authorizationRequest + presentingViewController:(UIViewController *)presentingViewController + callback:(OIDAuthStateAuthorizationCallback)callback; + +/*! @brief Convenience method to create a @c OIDAuthState by presenting an authorization request + (optionally using an emphemeral browser session that shares no cookies or data with the + normal browser session) and performing the authorization code exchange in the case of code + flow requests. For the hybrid flow, the caller should validate the id_token and c_hash, then + perform the token request (@c OIDAuthorizationService.performTokenRequest:callback:) + and update the OIDAuthState with the results (@c + OIDAuthState.updateWithTokenResponse:error:). + @param authorizationRequest The authorization request to present. + @param presentingViewController The view controller to use for presenting the authentication UI. + @param prefersEphemeralSession Whether the caller prefers to use a private authentication + session. See @c ASWebAuthenticationSession.prefersEphemeralWebBrowserSession for more. + @param callback The method called when the request has completed or failed. + @return A @c OIDExternalUserAgentSession instance which will terminate when it + receives a @c OIDExternalUserAgentSession.cancel message, or after processing a + @c OIDExternalUserAgentSession.resumeExternalUserAgentFlowWithURL: message. + */ ++ (id) + authStateByPresentingAuthorizationRequest:(OIDAuthorizationRequest *)authorizationRequest + presentingViewController:(UIViewController *)presentingViewController + prefersEphemeralSession:(BOOL)prefersEphemeralSession + callback:(OIDAuthStateAuthorizationCallback)callback + API_AVAILABLE(ios(13)); + ++ (id) + authStateByPresentingAuthorizationRequest:(OIDAuthorizationRequest *)authorizationRequest + callback:(OIDAuthStateAuthorizationCallback)callback API_AVAILABLE(ios(11)) API_UNAVAILABLE(macCatalyst) + __deprecated_msg("This method will not work on iOS 13. Use " + "authStateByPresentingAuthorizationRequest:presentingViewController:callback:"); + +@end + +NS_ASSUME_NONNULL_END + +#endif // TARGET_OS_IOS || TARGET_OS_MACCATALYST diff --git a/Sources/AppAuth/iOS/OIDAuthState+IOS.m b/Sources/AppAuth/iOS/OIDAuthState+IOS.m new file mode 100644 index 000000000..c474a77d1 --- /dev/null +++ b/Sources/AppAuth/iOS/OIDAuthState+IOS.m @@ -0,0 +1,78 @@ +/*! @file OIDAuthState+IOS.m + @brief AppAuth iOS SDK + @copyright + Copyright 2016 Google Inc. All Rights Reserved. + @copydetails + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#import + +#if TARGET_OS_IOS || TARGET_OS_MACCATALYST + +#import "OIDAuthState+IOS.h" +#import "OIDExternalUserAgentIOS.h" +#import "OIDExternalUserAgentCatalyst.h" + +@implementation OIDAuthState (IOS) + ++ (id) + authStateByPresentingAuthorizationRequest:(OIDAuthorizationRequest *)authorizationRequest + presentingViewController:(UIViewController *)presentingViewController + callback:(OIDAuthStateAuthorizationCallback)callback { + id externalUserAgent; +#if TARGET_OS_MACCATALYST + externalUserAgent = [[OIDExternalUserAgentCatalyst alloc] + initWithPresentingViewController:presentingViewController]; +#else // TARGET_OS_MACCATALYST + externalUserAgent = [[OIDExternalUserAgentIOS alloc] initWithPresentingViewController:presentingViewController]; +#endif // TARGET_OS_MACCATALYST + return [self authStateByPresentingAuthorizationRequest:authorizationRequest + externalUserAgent:externalUserAgent + callback:callback]; +} + ++ (id) + authStateByPresentingAuthorizationRequest:(OIDAuthorizationRequest *)authorizationRequest + presentingViewController:(UIViewController *)presentingViewController + prefersEphemeralSession:(BOOL)prefersEphemeralSession + callback:(OIDAuthStateAuthorizationCallback)callback { + id externalUserAgent; +#if TARGET_OS_MACCATALYST + externalUserAgent = [[OIDExternalUserAgentCatalyst alloc] + initWithPresentingViewController:presentingViewController + prefersEphemeralSession:prefersEphemeralSession]; +#else // TARGET_OS_MACCATALYST + externalUserAgent = [[OIDExternalUserAgentIOS alloc] + initWithPresentingViewController:presentingViewController + prefersEphemeralSession:prefersEphemeralSession]; +#endif // TARGET_OS_MACCATALYST + return [self authStateByPresentingAuthorizationRequest:authorizationRequest + externalUserAgent:externalUserAgent + callback:callback]; +} + +#if !TARGET_OS_MACCATALYST ++ (id) + authStateByPresentingAuthorizationRequest:(OIDAuthorizationRequest *)authorizationRequest + callback:(OIDAuthStateAuthorizationCallback)callback { + OIDExternalUserAgentIOS *externalUserAgent = [[OIDExternalUserAgentIOS alloc] init]; + return [self authStateByPresentingAuthorizationRequest:authorizationRequest + externalUserAgent:externalUserAgent + callback:callback]; +} +#endif // !TARGET_OS_MACCATALYST + +@end + +#endif // TARGET_OS_IOS || TARGET_OS_MACCATALYST diff --git a/Sources/AppAuth/iOS/OIDAuthorizationService+IOS.h b/Sources/AppAuth/iOS/OIDAuthorizationService+IOS.h new file mode 100644 index 000000000..c7c685d28 --- /dev/null +++ b/Sources/AppAuth/iOS/OIDAuthorizationService+IOS.h @@ -0,0 +1,67 @@ +/*! @file OIDAuthorizationService+IOS.h + @brief AppAuth iOS SDK + @copyright + Copyright 2016 Google Inc. All Rights Reserved. + @copydetails + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#import + +#if TARGET_OS_IOS || TARGET_OS_MACCATALYST + +#import + +#import "OIDAuthorizationService.h" +#import "OIDExternalUserAgentSession.h" + +NS_ASSUME_NONNULL_BEGIN + +/*! @brief Provides iOS specific authorization request handling. + */ +@interface OIDAuthorizationService (IOS) + +/*! @brief Perform an authorization flow, presenting an appropriate browser for the user to + authenticate. + @param request The authorization request. + @param presentingViewController The view controller from which to present authentication UI. + @param callback The method called when the request has completed or failed. + @return A @c OIDExternalUserAgentSession instance which will terminate when it + receives a @c OIDExternalUserAgentSession.cancel message, or after processing a + @c OIDExternalUserAgentSession.resumeExternalUserAgentFlowWithURL: message. + */ ++ (id) presentAuthorizationRequest:(OIDAuthorizationRequest *)request + presentingViewController:(UIViewController *)presentingViewController + callback:(OIDAuthorizationCallback)callback; + +/*! @brief Perform an authorization flow using the @c ASWebAuthenticationSession optionally using an + emphemeral browser session that shares no cookies or data with the normal browser session. + @param request The authorization request. + @param presentingViewController The view controller from which to present authentication UI. + @param prefersEphemeralSession Whether the caller prefers to use a private authentication + session. See @c ASWebAuthenticationSession.prefersEphemeralWebBrowserSession for more. + @param callback The method called when the request has completed or failed. + @return A @c OIDExternalUserAgentSession instance which will terminate when it + receives a @c OIDExternalUserAgentSession.cancel message, or after processing a + @c OIDExternalUserAgentSession.resumeExternalUserAgentFlowWithURL: message. + */ ++ (id) presentAuthorizationRequest:(OIDAuthorizationRequest *)request + presentingViewController:(UIViewController *)presentingViewController + prefersEphemeralSession:(BOOL)prefersEphemeralSession + callback:(OIDAuthorizationCallback)callback API_AVAILABLE(ios(13)); + +@end + +NS_ASSUME_NONNULL_END + +#endif // TARGET_OS_IOS || TARGET_OS_MACCATALYST diff --git a/Sources/AppAuth/iOS/OIDAuthorizationService+IOS.m b/Sources/AppAuth/iOS/OIDAuthorizationService+IOS.m new file mode 100644 index 000000000..4ca07c55e --- /dev/null +++ b/Sources/AppAuth/iOS/OIDAuthorizationService+IOS.m @@ -0,0 +1,64 @@ +/*! @file OIDAuthorizationService+IOS.m + @brief AppAuth iOS SDK + @copyright + Copyright 2016 Google Inc. All Rights Reserved. + @copydetails + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#import + +#if TARGET_OS_IOS || TARGET_OS_MACCATALYST + +#import "OIDAuthorizationService+IOS.h" +#import "OIDExternalUserAgentIOS.h" +#import "OIDExternalUserAgentCatalyst.h" + +NS_ASSUME_NONNULL_BEGIN + +@implementation OIDAuthorizationService (IOS) + ++ (id) presentAuthorizationRequest:(OIDAuthorizationRequest *)request + presentingViewController:(UIViewController *)presentingViewController + callback:(OIDAuthorizationCallback)callback { + id externalUserAgent; +#if TARGET_OS_MACCATALYST + externalUserAgent = [[OIDExternalUserAgentCatalyst alloc] + initWithPresentingViewController:presentingViewController]; +#else // TARGET_OS_MACCATALYST + externalUserAgent = [[OIDExternalUserAgentIOS alloc] initWithPresentingViewController:presentingViewController]; +#endif // TARGET_OS_MACCATALYST + return [self presentAuthorizationRequest:request externalUserAgent:externalUserAgent callback:callback]; +} + ++ (id) presentAuthorizationRequest:(OIDAuthorizationRequest *)request + presentingViewController:(UIViewController *)presentingViewController + prefersEphemeralSession:(BOOL)prefersEphemeralSession + callback:(OIDAuthorizationCallback)callback { + id externalUserAgent; +#if TARGET_OS_MACCATALYST + externalUserAgent = [[OIDExternalUserAgentCatalyst alloc] + initWithPresentingViewController:presentingViewController + prefersEphemeralSession:prefersEphemeralSession]; +#else // TARGET_OS_MACCATALYST + externalUserAgent = [[OIDExternalUserAgentIOS alloc] initWithPresentingViewController:presentingViewController + prefersEphemeralSession:prefersEphemeralSession]; +#endif // TARGET_OS_MACCATALYST + return [self presentAuthorizationRequest:request externalUserAgent:externalUserAgent callback:callback]; +} + +@end + +NS_ASSUME_NONNULL_END + +#endif // TARGET_OS_IOS || TARGET_OS_MACCATALYST diff --git a/Sources/AppAuth/iOS/OIDExternalUserAgentCatalyst.h b/Sources/AppAuth/iOS/OIDExternalUserAgentCatalyst.h new file mode 100644 index 000000000..910d0bb86 --- /dev/null +++ b/Sources/AppAuth/iOS/OIDExternalUserAgentCatalyst.h @@ -0,0 +1,61 @@ +/*! @file OIDExternalUserAgentCatalyst.h + @brief AppAuth iOS SDK + @copyright + Copyright 2019 The AppAuth Authors. All Rights Reserved. + @copydetails + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +#import + +#if TARGET_OS_IOS || TARGET_OS_MACCATALYST + +#import + +#import "OIDExternalUserAgent.h" + +NS_ASSUME_NONNULL_BEGIN + +/*! @brief A Catalyst specific external user-agent that uses `ASWebAuthenticationSession` to + present the request. +*/ +API_AVAILABLE(macCatalyst(13)) API_UNAVAILABLE(ios) +@interface OIDExternalUserAgentCatalyst : NSObject + +/*! @internal + @brief Unavailable. Please use @c initWithPresentingViewController: + */ +- (nonnull instancetype)init NS_UNAVAILABLE; + +/*! @brief The designated initializer. + @param presentingViewController The view controller from which to present the + \SFSafariViewController. + */ +- (nullable instancetype)initWithPresentingViewController: + (UIViewController *)presentingViewController + NS_DESIGNATED_INITIALIZER; + +/*! @brief Create an external user-agent which optionally uses a private authentication session. + @param presentingViewController The view controller from which to present the browser. + @param prefersEphemeralSession Whether the caller prefers to use a private authentication + session. See @c ASWebAuthenticationSession.prefersEphemeralWebBrowserSession for more. + */ +- (nullable instancetype)initWithPresentingViewController: + (UIViewController *)presentingViewController + prefersEphemeralSession:(BOOL)prefersEphemeralSession; + +@end + +NS_ASSUME_NONNULL_END + +#endif // TARGET_OS_IOS || TARGET_OS_MACCATALYST diff --git a/Sources/AppAuth/iOS/OIDExternalUserAgentCatalyst.m b/Sources/AppAuth/iOS/OIDExternalUserAgentCatalyst.m new file mode 100644 index 000000000..d6771b3e9 --- /dev/null +++ b/Sources/AppAuth/iOS/OIDExternalUserAgentCatalyst.m @@ -0,0 +1,157 @@ +/*! @file OIDExternalUserAgentCatalyst.m + @brief AppAuth iOS SDK + @copyright + Copyright 2019 The AppAuth Authors. All Rights Reserved. + @copydetails + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +#import + +#if TARGET_OS_IOS || TARGET_OS_MACCATALYST + +#import "OIDExternalUserAgentCatalyst.h" + +#import +#import + +#import "OIDErrorUtilities.h" +#import "OIDExternalUserAgentSession.h" +#import "OIDExternalUserAgentRequest.h" + +#if TARGET_OS_MACCATALYST + +NS_ASSUME_NONNULL_BEGIN + +@interface OIDExternalUserAgentCatalyst () +@end + +@implementation OIDExternalUserAgentCatalyst { + UIViewController *_presentingViewController; + BOOL _prefersEphemeralSession; + + BOOL _externalUserAgentFlowInProgress; + __weak id _session; + ASWebAuthenticationSession *_webAuthenticationVC; +} + +- (nullable instancetype)initWithPresentingViewController: + (UIViewController *)presentingViewController { + self = [super init]; + if (self) { + _presentingViewController = presentingViewController; + } + return self; +} + +- (nullable instancetype)initWithPresentingViewController: + (UIViewController *)presentingViewController + prefersEphemeralSession:(BOOL)prefersEphemeralSession { + self = [self initWithPresentingViewController:presentingViewController]; + if (self) { + _prefersEphemeralSession = prefersEphemeralSession; + } + return self; +} + +- (BOOL)presentExternalUserAgentRequest:(id)request + session:(id)session { + if (_externalUserAgentFlowInProgress) { + // TODO: Handle errors as authorization is already in progress. + return NO; + } + + _externalUserAgentFlowInProgress = YES; + _session = session; + BOOL openedUserAgent = NO; + NSURL *requestURL = [request externalUserAgentRequestURL]; + + __weak OIDExternalUserAgentCatalyst *weakSelf = self; + NSString *redirectScheme = request.redirectScheme; + ASWebAuthenticationSession *authenticationVC = + [[ASWebAuthenticationSession alloc] initWithURL:requestURL + callbackURLScheme:redirectScheme + completionHandler:^(NSURL * _Nullable callbackURL, + NSError * _Nullable error) { + __strong OIDExternalUserAgentCatalyst *strongSelf = weakSelf; + if (!strongSelf) { + return; + } + strongSelf->_webAuthenticationVC = nil; + if (callbackURL) { + [strongSelf->_session resumeExternalUserAgentFlowWithURL:callbackURL]; + } else { + NSError *safariError = + [OIDErrorUtilities errorWithCode:OIDErrorCodeUserCanceledAuthorizationFlow + underlyingError:error + description:nil]; + [strongSelf->_session failExternalUserAgentFlowWithError:safariError]; + } + }]; + + authenticationVC.presentationContextProvider = self; + authenticationVC.prefersEphemeralWebBrowserSession = _prefersEphemeralSession; + _webAuthenticationVC = authenticationVC; + openedUserAgent = [authenticationVC start]; + + if (!openedUserAgent) { + [self cleanUp]; + NSError *safariError = [OIDErrorUtilities errorWithCode:OIDErrorCodeSafariOpenError + underlyingError:nil + description:@"Unable to open ASWebAuthenticationSession view controller."]; + [session failExternalUserAgentFlowWithError:safariError]; + } + return openedUserAgent; +} + +- (void)dismissExternalUserAgentAnimated:(BOOL)animated completion:(void (^)(void))completion { + if (!_externalUserAgentFlowInProgress) { + // Ignore this call if there is no authorization flow in progress. + if (completion) completion(); + return; + } + + ASWebAuthenticationSession *webAuthenticationVC = _webAuthenticationVC; + + [self cleanUp]; + + if (webAuthenticationVC) { + // dismiss the ASWebAuthenticationSession + [webAuthenticationVC cancel]; + if (completion) completion(); + } else { + if (completion) completion(); + } +} + +- (void)cleanUp { + // The weak reference to |_session| is set to nil to avoid accidentally using + // it while not in an authorization flow. + _webAuthenticationVC = nil; + _session = nil; + _externalUserAgentFlowInProgress = NO; +} + +#pragma mark - ASWebAuthenticationPresentationContextProviding + +- (ASPresentationAnchor)presentationAnchorForWebAuthenticationSession:(ASWebAuthenticationSession *)session { + return _presentingViewController.view.window; +} + +@end + +NS_ASSUME_NONNULL_END + +#endif // TARGET_OS_MACCATALYST + +#endif // TARGET_OS_IOS || TARGET_OS_MACCATALYST diff --git a/Sources/AppAuth/iOS/OIDExternalUserAgentIOS.h b/Sources/AppAuth/iOS/OIDExternalUserAgentIOS.h new file mode 100644 index 000000000..c7916a52e --- /dev/null +++ b/Sources/AppAuth/iOS/OIDExternalUserAgentIOS.h @@ -0,0 +1,68 @@ +/*! @file OIDExternalUserAgentIOS.h + @brief AppAuth iOS SDK + @copyright + Copyright 2016 Google Inc. All Rights Reserved. + @copydetails + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#import + +#if TARGET_OS_IOS || TARGET_OS_MACCATALYST + +#import + +#import "OIDExternalUserAgent.h" + +@class SFSafariViewController; + +NS_ASSUME_NONNULL_BEGIN + +/*! @brief An iOS specific external user-agent that uses the best possible user-agent available + depending on the version of iOS to present the request. + */ +API_UNAVAILABLE(macCatalyst) +@interface OIDExternalUserAgentIOS : NSObject + +- (null_unspecified instancetype)init API_AVAILABLE(ios(11)) + __deprecated_msg("This method will not work on iOS 13, use " + "initWithPresentingViewController:presentingViewController"); + +/*! @brief The designated initializer. + @param presentingViewController The view controller from which to present the authentication UI. + @discussion The specific authentication UI used depends on the iOS version and accessibility + options. Uses @c ASWebAuthenticationSession. If Guided Access is enabled or the session + cannot be started, the method returns NO and the authorization flow fails with an error. + */ +- (nullable instancetype)initWithPresentingViewController: + (UIViewController *)presentingViewController + NS_DESIGNATED_INITIALIZER; + +/*! @brief Create an external user-agent which optionally uses a private authentication session. + @param presentingViewController The view controller from which to present the browser. + @param prefersEphemeralSession Whether the caller prefers to use a private authentication + session. See @c ASWebAuthenticationSession.prefersEphemeralWebBrowserSession for more. + @discussion Authentication is performed with @c ASWebAuthenticationSession, setting the + ephemerality based on the argument. If Guided Access is enabled or the session cannot + be started, the method returns NO and the authorization flow fails with an error. + */ +- (nullable instancetype)initWithPresentingViewController: + (UIViewController *)presentingViewController + prefersEphemeralSession:(BOOL)prefersEphemeralSession + API_AVAILABLE(ios(13)); + +@end + +NS_ASSUME_NONNULL_END + +#endif // TARGET_OS_IOS || TARGET_OS_MACCATALYST diff --git a/Sources/AppAuth/iOS/OIDExternalUserAgentIOS.m b/Sources/AppAuth/iOS/OIDExternalUserAgentIOS.m new file mode 100644 index 000000000..95acdc16c --- /dev/null +++ b/Sources/AppAuth/iOS/OIDExternalUserAgentIOS.m @@ -0,0 +1,213 @@ +/*! @file OIDExternalUserAgentIOS.m + @brief AppAuth iOS SDK + @copyright + Copyright 2016 Google Inc. All Rights Reserved. + @copydetails + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#import + +#if TARGET_OS_IOS || TARGET_OS_MACCATALYST + +#import "OIDExternalUserAgentIOS.h" + +#import +#import + +#import "OIDErrorUtilities.h" +#import "OIDExternalUserAgentSession.h" +#import "OIDExternalUserAgentRequest.h" + +#if !TARGET_OS_MACCATALYST + +NS_ASSUME_NONNULL_BEGIN + +#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 130000 +@interface OIDExternalUserAgentIOS () +@end +#else +@interface OIDExternalUserAgentIOS () +@end +#endif + +@implementation OIDExternalUserAgentIOS { + UIViewController *_presentingViewController; + BOOL _prefersEphemeralSession; + + BOOL _externalUserAgentFlowInProgress; + __weak id _session; +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wpartial-availability" + __weak SFSafariViewController *_safariVC; + ASWebAuthenticationSession *_webAuthenticationVC; +#pragma clang diagnostic pop +} + +- (null_unspecified instancetype)init { +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wnonnull" + return [self initWithPresentingViewController:nil]; +#pragma clang diagnostic pop +} + +- (nullable instancetype)initWithPresentingViewController: + (UIViewController *)presentingViewController { + self = [super init]; + if (self) { +#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 130000 + NSAssert(presentingViewController != nil, + @"presentingViewController cannot be nil on iOS 13"); +#endif // __IPHONE_OS_VERSION_MAX_ALLOWED >= 130000 + + _presentingViewController = presentingViewController; + } + return self; +} + +- (nullable instancetype)initWithPresentingViewController: + (UIViewController *)presentingViewController + prefersEphemeralSession:(BOOL)prefersEphemeralSession { + self = [self initWithPresentingViewController:presentingViewController]; + if (self) { + _prefersEphemeralSession = prefersEphemeralSession; + } + return self; +} + +- (BOOL)presentExternalUserAgentRequest:(id)request + session:(id)session { + if (_externalUserAgentFlowInProgress) { + // TODO: Handle errors as authorization is already in progress. + return NO; + } + + _externalUserAgentFlowInProgress = YES; + _session = session; + BOOL openedUserAgent = NO; + NSURL *requestURL = [request externalUserAgentRequestURL]; + + // iOS 12 and later, use ASWebAuthenticationSession + if (@available(iOS 12.0, *)) { + // ASWebAuthenticationSession doesn't work with guided access (rdar://40809553) + if (!UIAccessibilityIsGuidedAccessEnabled()) { + __weak OIDExternalUserAgentIOS *weakSelf = self; + NSString *redirectScheme = request.redirectScheme; + ASWebAuthenticationSession *authenticationVC = + [[ASWebAuthenticationSession alloc] initWithURL:requestURL + callbackURLScheme:redirectScheme + completionHandler:^(NSURL * _Nullable callbackURL, + NSError * _Nullable error) { + __strong OIDExternalUserAgentIOS *strongSelf = weakSelf; + if (!strongSelf) { + return; + } + strongSelf->_webAuthenticationVC = nil; + if (callbackURL) { + [strongSelf->_session resumeExternalUserAgentFlowWithURL:callbackURL]; + } else { + NSError *safariError = + [OIDErrorUtilities errorWithCode:OIDErrorCodeUserCanceledAuthorizationFlow + underlyingError:error + description:nil]; + [strongSelf->_session failExternalUserAgentFlowWithError:safariError]; + } + }]; +#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 130000 + if (@available(iOS 13.0, *)) { + authenticationVC.presentationContextProvider = self; + authenticationVC.prefersEphemeralWebBrowserSession = _prefersEphemeralSession; + } +#endif + _webAuthenticationVC = authenticationVC; + openedUserAgent = [authenticationVC start]; + } + } + if (!openedUserAgent) { + [self cleanUp]; + return NO; + } + + return openedUserAgent; +} + +- (void)dismissExternalUserAgentAnimated:(BOOL)animated completion:(void (^)(void))completion { + if (!_externalUserAgentFlowInProgress) { + // Ignore this call if there is no authorization flow in progress. + if (completion) completion(); + return; + } + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wpartial-availability" + SFSafariViewController *safariVC = _safariVC; + ASWebAuthenticationSession *webAuthenticationVC = _webAuthenticationVC; +#pragma clang diagnostic pop + + [self cleanUp]; + + if (webAuthenticationVC) { + // dismiss the ASWebAuthenticationSession + [webAuthenticationVC cancel]; + if (completion) completion(); + } else if (safariVC) { + // dismiss the SFSafariViewController + [safariVC dismissViewControllerAnimated:YES completion:completion]; + } else { + if (completion) completion(); + } +} + +- (void)cleanUp { + // The weak references to |_safariVC| and |_session| are set to nil to avoid accidentally using + // them while not in an authorization flow. + _safariVC = nil; + _webAuthenticationVC = nil; + _session = nil; + _externalUserAgentFlowInProgress = NO; +} + +#pragma mark - SFSafariViewControllerDelegate + +- (void)safariViewControllerDidFinish:(SFSafariViewController *)controller NS_AVAILABLE_IOS(9.0) { + if (controller != _safariVC) { + // Ignore this call if the safari view controller do not match. + return; + } + if (!_externalUserAgentFlowInProgress) { + // Ignore this call if there is no authorization flow in progress. + return; + } + id session = _session; + [self cleanUp]; + NSError *error = [OIDErrorUtilities errorWithCode:OIDErrorCodeUserCanceledAuthorizationFlow + underlyingError:nil + description:@"No external user agent flow in progress."]; + [session failExternalUserAgentFlowWithError:error]; +} + +#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 130000 +#pragma mark - ASWebAuthenticationPresentationContextProviding + +- (ASPresentationAnchor)presentationAnchorForWebAuthenticationSession:(ASWebAuthenticationSession *)session API_AVAILABLE(ios(13.0)){ + return _presentingViewController.view.window; +} +#endif // __IPHONE_OS_VERSION_MAX_ALLOWED >= 130000 + +@end + +NS_ASSUME_NONNULL_END + +#endif // !TARGET_OS_MACCATALYST + +#endif // TARGET_OS_IOS || TARGET_OS_MACCATALYST diff --git a/Sources/AppAuth/iOS/OIDExternalUserAgentIOSCustomBrowser.h b/Sources/AppAuth/iOS/OIDExternalUserAgentIOSCustomBrowser.h new file mode 100644 index 000000000..2032e8c91 --- /dev/null +++ b/Sources/AppAuth/iOS/OIDExternalUserAgentIOSCustomBrowser.h @@ -0,0 +1,113 @@ +/*! @file OIDExternalUserAgentIOSCustomBrowser.h + @brief AppAuth iOS SDK + @copyright + Copyright 2018 Google LLC + @copydetails + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#import + +#if TARGET_OS_IOS || TARGET_OS_MACCATALYST + +#import + +#import "OIDExternalUserAgent.h" + +NS_ASSUME_NONNULL_BEGIN + +/*! @brief A block that transforms a regular http/https URL into one that will open in an + alternative browser. + @param requestURL the http/https request URL to be transformed. + @return transformed URL. + */ +typedef NSURL *_Nullable (^OIDCustomBrowserURLTransformation)(NSURL *_Nullable requestURL); + +/*! @brief An implementation of the OIDExternalUserAgent protocol for iOS that uses + a custom browser (i.e. not Safari) for external requests. It is suitable for browsers that + offer a custom url scheme that simply replaces the "https" scheme. It is not designed + for browsers that require other modifications to the URL. If the browser is not installed + the user will be prompted to install it. + */ +API_UNAVAILABLE(macCatalyst) +@interface OIDExternalUserAgentIOSCustomBrowser : NSObject + +/*! @brief URL transformation block for the browser. + */ +@property(nonatomic, readonly) OIDCustomBrowserURLTransformation URLTransformation; + +/*! @brief URL Scheme used to test for whether the browser is installed. + */ +@property(nonatomic, readonly, nullable) NSString *canOpenURLScheme; + +/*! @brief URL of the browser's App Store listing. + */ +@property(nonatomic, readonly, nullable) NSURL *appStoreURL; + +/*! @brief An instance of @c OIDExternalUserAgentIOSCustomBrowser for Chrome. + */ ++ (instancetype)CustomBrowserChrome; + +/*! @brief An instance of @c OIDExternalUserAgentIOSCustomBrowser for Firefox. + */ ++ (instancetype)CustomBrowserFirefox; + +/*! @brief An instance of @c OIDExternalUserAgentIOSCustomBrowser for Opera. + */ ++ (instancetype)CustomBrowserOpera; + +/*! @brief An instance of @c OIDExternalUserAgentIOSCustomBrowser for Safari. + */ ++ (instancetype)CustomBrowserSafari; + +/*! @brief Creates a @c OIDCustomBrowserURLTransformation using the scheme substitution method used + iOS browsers like Chrome and Firefox. + */ ++ (OIDCustomBrowserURLTransformation) + URLTransformationSchemeSubstitutionHTTPS:(NSString *)browserSchemeHTTPS + HTTP:(nullable NSString *)browserSchemeHTTP; + +/*! @brief Creates a @c OIDCustomBrowserURLTransformation with the URL prefix method used by + iOS browsers like Firefox. + */ ++ (OIDCustomBrowserURLTransformation) URLTransformationSchemeConcatPrefix:(NSString*)URLprefix; + +/*! @internal + @brief Unavailable. Please use @c initWithURLTransformation:canOpenURLScheme:appStoreURL: + */ +- (nonnull instancetype)init NS_UNAVAILABLE; + +/*! @brief OIDExternalUserAgent for a custom browser. @c presentExternalUserAgentRequest:session method + will return NO if the browser isn't installed. + */ +- (nullable instancetype)initWithURLTransformation:(OIDCustomBrowserURLTransformation)URLTransformation; + +/*! @brief The designated initializer. + @param URLTransformation the transformation block to translate the URL into one that will open + in the desired custom browser. + @param canOpenURLScheme any scheme supported by the browser used to check if the browser is + installed. + @param appStoreURL URL of the browser in the app store. When this and @c canOpenURLScheme + are non-nil, @c presentExternalUserAgentRequest:session will redirect the user to the app store + if the browser is not installed. + */ +- (nullable instancetype)initWithURLTransformation:(OIDCustomBrowserURLTransformation)URLTransformation + canOpenURLScheme:(nullable NSString *)canOpenURLScheme + appStoreURL:(nullable NSURL *)appStoreURL + NS_DESIGNATED_INITIALIZER; + +@end + +NS_ASSUME_NONNULL_END + +#endif // TARGET_OS_IOS || TARGET_OS_MACCATALYST diff --git a/Sources/AppAuth/iOS/OIDExternalUserAgentIOSCustomBrowser.m b/Sources/AppAuth/iOS/OIDExternalUserAgentIOSCustomBrowser.m new file mode 100644 index 000000000..900bfbe19 --- /dev/null +++ b/Sources/AppAuth/iOS/OIDExternalUserAgentIOSCustomBrowser.m @@ -0,0 +1,172 @@ +/*! @file OIDExternalUserAgentIOSCustomBrowser.m + @brief AppAuth iOS SDK + @copyright + Copyright 2018 Google LLC + @copydetails + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#import + +#if TARGET_OS_IOS || TARGET_OS_MACCATALYST + +#import "OIDExternalUserAgentIOSCustomBrowser.h" + +#import + +#import "OIDAuthorizationRequest.h" +#import "OIDAuthorizationService.h" +#import "OIDErrorUtilities.h" +#import "OIDURLQueryComponent.h" + +#if !TARGET_OS_MACCATALYST + +NS_ASSUME_NONNULL_BEGIN + +@implementation OIDExternalUserAgentIOSCustomBrowser + ++ (instancetype)CustomBrowserChrome { + // Chrome iOS documentation: https://developer.chrome.com/multidevice/ios/links + OIDCustomBrowserURLTransformation transform = [[self class] URLTransformationSchemeSubstitutionHTTPS:@"googlechromes" HTTP:@"googlechrome"]; + NSURL *appStoreURL = + [NSURL URLWithString:@"https://itunes.apple.com/us/app/chrome/id535886823"]; + return [[[self class] alloc] initWithURLTransformation:transform + canOpenURLScheme:@"googlechromes" + appStoreURL:appStoreURL]; +} + ++ (instancetype)CustomBrowserFirefox { + // Firefox iOS documentation: https://github.com/mozilla-mobile/firefox-ios-open-in-client + OIDCustomBrowserURLTransformation transform = + [[self class] URLTransformationSchemeConcatPrefix:@"firefox://open-url?url="]; + NSURL *appStoreURL = + [NSURL URLWithString:@"https://itunes.apple.com/us/app/firefox-web-browser/id989804926"]; + return [[[self class] alloc] initWithURLTransformation:transform + canOpenURLScheme:@"firefox" + appStoreURL:appStoreURL]; +} + ++ (instancetype)CustomBrowserOpera { + OIDCustomBrowserURLTransformation transform = + [[self class] URLTransformationSchemeSubstitutionHTTPS:@"opera-https" HTTP:@"opera-http"]; + NSURL *appStoreURL = + [NSURL URLWithString:@"https://itunes.apple.com/us/app/opera-mini-web-browser/id363729560"]; + return [[[self class] alloc] initWithURLTransformation:transform + canOpenURLScheme:@"opera-https" + appStoreURL:appStoreURL]; +} + ++ (instancetype)CustomBrowserSafari { + OIDCustomBrowserURLTransformation transformNOP = ^NSURL *(NSURL *requestURL) { + return requestURL; + }; + OIDExternalUserAgentIOSCustomBrowser *transform = + [[[self class] alloc] initWithURLTransformation:transformNOP]; + return transform; +} + ++ (OIDCustomBrowserURLTransformation) + URLTransformationSchemeSubstitutionHTTPS:(NSString *)browserSchemeHTTPS + HTTP:(nullable NSString *)browserSchemeHTTP { + OIDCustomBrowserURLTransformation transform = ^NSURL *(NSURL *requestURL) { + // Replace the URL Scheme with the Chrome equivalent. + NSString *newScheme = nil; + if ([requestURL.scheme isEqualToString:@"https"]) { + newScheme = browserSchemeHTTPS; + } else if ([requestURL.scheme isEqualToString:@"http"]) { + if (!browserSchemeHTTP) { + NSAssert(false, @"No HTTP scheme registered for browser"); + return nil; + } + newScheme = browserSchemeHTTP; + } + + // Replaces the URI scheme with the custom scheme + NSURLComponents *components = [NSURLComponents componentsWithURL:requestURL + resolvingAgainstBaseURL:YES]; + components.scheme = newScheme; + return components.URL; + }; + return transform; +} + ++ (OIDCustomBrowserURLTransformation)URLTransformationSchemeConcatPrefix:(NSString *)URLprefix { + OIDCustomBrowserURLTransformation transform = ^NSURL *(NSURL *requestURL) { + NSString *requestURLString = [requestURL absoluteString]; + NSMutableCharacterSet *allowedParamCharacters = + [OIDURLQueryComponent URLParamValueAllowedCharacters]; + NSString *encodedUrl = [requestURLString stringByAddingPercentEncodingWithAllowedCharacters:allowedParamCharacters]; + NSString *newURL = [NSString stringWithFormat:@"%@%@", URLprefix, encodedUrl]; + return [NSURL URLWithString:newURL]; + }; + return transform; +} + +- (nullable instancetype)initWithURLTransformation: + (OIDCustomBrowserURLTransformation)URLTransformation { + return [self initWithURLTransformation:URLTransformation canOpenURLScheme:nil appStoreURL:nil]; +} + +- (nullable instancetype) + initWithURLTransformation:(OIDCustomBrowserURLTransformation)URLTransformation + canOpenURLScheme:(nullable NSString *)canOpenURLScheme + appStoreURL:(nullable NSURL *)appStoreURL { + self = [super init]; + if (self) { + _URLTransformation = URLTransformation; + _canOpenURLScheme = canOpenURLScheme; + _appStoreURL = appStoreURL; + } + return self; +} + +- (BOOL)presentExternalUserAgentRequest:(nonnull id)request + session:(nonnull id)session { + // If the app store URL is set, checks if the app is installed and if not opens the app store. + if (_appStoreURL && _canOpenURLScheme) { + // Verifies existence of LSApplicationQueriesSchemes Info.plist key. + NSArray __unused* canOpenURLs = + [[NSBundle mainBundle] objectForInfoDictionaryKey:@"LSApplicationQueriesSchemes"]; + NSAssert(canOpenURLs, @"plist missing LSApplicationQueriesSchemes key"); + NSAssert1([canOpenURLs containsObject:_canOpenURLScheme], + @"plist missing LSApplicationQueriesSchemes entry for '%@'", _canOpenURLScheme); + + // Opens AppStore if app isn't installed + NSString *testURLString = [NSString stringWithFormat:@"%@://example.com", _canOpenURLScheme]; + NSURL *testURL = [NSURL URLWithString:testURLString]; + if (![[UIApplication sharedApplication] canOpenURL:testURL]) { + [[UIApplication sharedApplication] openURL:_appStoreURL options:@{} completionHandler:nil]; + return NO; + } + } + + // Transforms the request URL and opens it. + NSURL *requestURL = [request externalUserAgentRequestURL]; + requestURL = _URLTransformation(requestURL); + BOOL willOpen = [[UIApplication sharedApplication] canOpenURL:requestURL]; + [[UIApplication sharedApplication] openURL:requestURL options:@{} completionHandler:nil]; + return willOpen; +} + +- (void)dismissExternalUserAgentAnimated:(BOOL)animated + completion:(nonnull void (^)(void))completion { + completion(); +} + +@end + +NS_ASSUME_NONNULL_END + +#endif // !TARGET_OS_MACCATALYST + +#endif // TARGET_OS_IOS || TARGET_OS_MACCATALYST diff --git a/Source/macOS/LoopbackHTTPServer/OIDLoopbackHTTPServer.h b/Sources/AppAuth/macOS/LoopbackHTTPServer/OIDLoopbackHTTPServer.h similarity index 97% rename from Source/macOS/LoopbackHTTPServer/OIDLoopbackHTTPServer.h rename to Sources/AppAuth/macOS/LoopbackHTTPServer/OIDLoopbackHTTPServer.h index f60803c11..ed2ac8794 100644 --- a/Source/macOS/LoopbackHTTPServer/OIDLoopbackHTTPServer.h +++ b/Sources/AppAuth/macOS/LoopbackHTTPServer/OIDLoopbackHTTPServer.h @@ -20,6 +20,10 @@ // https://developer.apple.com/library/mac/samplecode/MiniSOAP/Introduction/Intro.html // Modified to limit connections to the loopback interface only. +#import + +#if TARGET_OS_OSX + #import #import @@ -112,7 +116,7 @@ typedef enum { __weak id delegate; NSData *peerAddress; __weak HTTPServer *server; - NSMutableArray *requests; + NSMutableArray *requests; NSInputStream *istream; NSOutputStream *ostream; NSMutableData *ibuffer; @@ -185,4 +189,4 @@ typedef enum { @end - +#endif // TARGET_OS_OSX diff --git a/Source/macOS/LoopbackHTTPServer/OIDLoopbackHTTPServer.m b/Sources/AppAuth/macOS/LoopbackHTTPServer/OIDLoopbackHTTPServer.m similarity index 97% rename from Source/macOS/LoopbackHTTPServer/OIDLoopbackHTTPServer.m rename to Sources/AppAuth/macOS/LoopbackHTTPServer/OIDLoopbackHTTPServer.m index bf66f0177..dca381c04 100644 --- a/Source/macOS/LoopbackHTTPServer/OIDLoopbackHTTPServer.m +++ b/Sources/AppAuth/macOS/LoopbackHTTPServer/OIDLoopbackHTTPServer.m @@ -16,11 +16,19 @@ limitations under the License. */ +#import + +#if TARGET_OS_OSX + #import "OIDLoopbackHTTPServer.h" #include #include #include +// We'll ignore the pointer arithmetic warnings for now. +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wpointer-arith" + @implementation HTTPServer - (id)init { @@ -99,14 +107,12 @@ - (HTTPServer *)server { } - (HTTPServerRequest *)nextRequest { - NSUInteger idx, cnt = requests ? [requests count] : 0; - for (idx = 0; idx < cnt; idx++) { - id obj = [requests objectAtIndex:idx]; - if ([obj response] == nil) { - return obj; - } + for (HTTPServerRequest *request in requests) { + if (![request response]) { + return request; } - return nil; + } + return nil; } - (BOOL)isValid { @@ -173,8 +179,9 @@ - (BOOL)processIncomingBytes { // directly as this method is called in a loop in order to process multiple messages, and // the delegate may choose to stop and dealloc the listener – so we need queue the messages // and process them separately. + id myDelegate = delegate; dispatch_async(dispatch_get_main_queue(), ^() { - [delegate HTTPConnection:self didReceiveRequest:request]; + [myDelegate HTTPConnection:self didReceiveRequest:request]; }); } else { [self performDefaultRequestHandling:request]; @@ -605,3 +612,7 @@ - (BOOL)hasIPv6Socket { } @end + +#pragma GCC diagnostic pop + +#endif // TARGET_OS_OSX diff --git a/Sources/AppAuth/macOS/OIDAuthState+Mac.h b/Sources/AppAuth/macOS/OIDAuthState+Mac.h new file mode 100644 index 000000000..71e56f22a --- /dev/null +++ b/Sources/AppAuth/macOS/OIDAuthState+Mac.h @@ -0,0 +1,92 @@ +/*! @file OIDAuthState+Mac.h + @brief AppAuth iOS SDK + @copyright + Copyright 2016 Google Inc. All Rights Reserved. + @copydetails + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#import + +#if TARGET_OS_OSX + +#import +#import "OIDAuthState.h" + +NS_ASSUME_NONNULL_BEGIN + +/*! @brief macOS specific convenience methods for @c OIDAuthState. + */ +@interface OIDAuthState (Mac) + +/*! @brief Convenience method to create a @c OIDAuthState by presenting an authorization request + and performing the authorization code exchange in the case of code flow requests. For + the hybrid flow, the caller should validate the id_token and c_hash, then perform the token + request (@c OIDAuthorizationService.performTokenRequest:callback:) + and update the OIDAuthState with the results (@c + OIDAuthState.updateWithTokenResponse:error:). + @param authorizationRequest The authorization request to present. + @param presentingWindow The window to present the authentication flow. + @param callback The method called when the request has completed or failed. + @return A @c OIDExternalUserAgentSession instance which will terminate when it + receives a @c OIDExternalUserAgentSession.cancel message, or after processing a + @c OIDExternalUserAgentSession.resumeExternalUserAgentFlowWithURL: message. + @discussion This method adopts @c ASWebAuthenticationSession for macOS 10.15 and above or the + default browser otherwise. + */ ++ (id) + authStateByPresentingAuthorizationRequest:(OIDAuthorizationRequest *)authorizationRequest + presentingWindow:(NSWindow *)presentingWindow + callback:(OIDAuthStateAuthorizationCallback)callback; + +/*! @brief Convenience method to create a @c OIDAuthState by presenting an authorization request + (optionally using an emphemeral browser session that shares no cookies or data with the + normal browser session) and performing the authorization code exchange in the case of code + flow requests. For the hybrid flow, the caller should validate the id_token and c_hash, then + perform the token request (@c OIDAuthorizationService.performTokenRequest:callback:) + and update the OIDAuthState with the results using + @c OIDAuthState.updateWithTokenResponse:error:. + @param authorizationRequest The authorization request to present. + @param presentingWindow The window to present the @c ASWebAuthenticationSession UI. + @param prefersEphemeralSession Whether the caller prefers to use a private authentication + session. See @c ASWebAuthenticationSession.prefersEphemeralWebBrowserSession for more. + @param callback The method called when the request has completed or failed. + @return A @c OIDExternalUserAgentSession instance which will terminate when it + receives a @c OIDExternalUserAgentSession.cancel message, or after processing a + @c OIDExternalUserAgentSession.resumeExternalUserAgentFlowWithURL: message. + */ ++ (id) + authStateByPresentingAuthorizationRequest:(OIDAuthorizationRequest *)authorizationRequest + presentingWindow:(NSWindow *)presentingWindow + prefersEphemeralSession:(BOOL)prefersEphemeralSession + callback:(OIDAuthStateAuthorizationCallback)callback + API_AVAILABLE(macos(10.15)); + +/*! @param authorizationRequest The authorization request to present. + @param callback The method called when the request has completed or failed. + @return A @c OIDExternalUserAgentSession instance which will terminate when it + receives a @c OIDExternalUserAgentSession.cancel message, or after processing a + @c OIDExternalUserAgentSession.resumeExternalUserAgentFlowWithURL: message. + @discussion This method uses the default browser to present the authentication flow. + */ ++ (id) + authStateByPresentingAuthorizationRequest:(OIDAuthorizationRequest *)authorizationRequest + callback:(OIDAuthStateAuthorizationCallback)callback + __deprecated_msg("For macOS 10.15 and above please use " + "authStateByPresentingAuthorizationRequest:presentingWindow:callback:"); + +@end + +NS_ASSUME_NONNULL_END + +#endif // TARGET_OS_OSX diff --git a/Sources/AppAuth/macOS/OIDAuthState+Mac.m b/Sources/AppAuth/macOS/OIDAuthState+Mac.m new file mode 100644 index 000000000..f2894daaf --- /dev/null +++ b/Sources/AppAuth/macOS/OIDAuthState+Mac.m @@ -0,0 +1,62 @@ +/*! @file OIDAuthState+Mac.m + @brief AppAuth iOS SDK + @copyright + Copyright 2016 Google Inc. All Rights Reserved. + @copydetails + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#import + +#if TARGET_OS_OSX + +#import "OIDAuthState+Mac.h" + +#import "OIDExternalUserAgentMac.h" + +@implementation OIDAuthState (Mac) + ++ (id) + authStateByPresentingAuthorizationRequest:(OIDAuthorizationRequest *)authorizationRequest + presentingWindow:(NSWindow *)presentingWindow + callback:(OIDAuthStateAuthorizationCallback)callback { + OIDExternalUserAgentMac *externalUserAgent = [[OIDExternalUserAgentMac alloc] initWithPresentingWindow:presentingWindow]; + return [self authStateByPresentingAuthorizationRequest:authorizationRequest + externalUserAgent:externalUserAgent + callback:callback]; +} ++ (id) + authStateByPresentingAuthorizationRequest:(OIDAuthorizationRequest *)authorizationRequest + presentingWindow:(NSWindow *)presentingWindow + prefersEphemeralSession:(BOOL)prefersEphemeralSession + callback:(OIDAuthStateAuthorizationCallback)callback { + OIDExternalUserAgentMac *externalUserAgent = + [[OIDExternalUserAgentMac alloc] initWithPresentingWindow:presentingWindow + prefersEphemeralSession:prefersEphemeralSession]; + return [self authStateByPresentingAuthorizationRequest:authorizationRequest + externalUserAgent:externalUserAgent + callback:callback]; +} + ++ (id) + authStateByPresentingAuthorizationRequest:(OIDAuthorizationRequest *)authorizationRequest + callback:(OIDAuthStateAuthorizationCallback)callback { + OIDExternalUserAgentMac *externalUserAgent = [[OIDExternalUserAgentMac alloc] init]; + return [self authStateByPresentingAuthorizationRequest:authorizationRequest + externalUserAgent:externalUserAgent + callback:callback]; +} + +@end + +#endif // TARGET_OS_OSX diff --git a/Sources/AppAuth/macOS/OIDAuthorizationService+Mac.h b/Sources/AppAuth/macOS/OIDAuthorizationService+Mac.h new file mode 100644 index 000000000..0a34faf28 --- /dev/null +++ b/Sources/AppAuth/macOS/OIDAuthorizationService+Mac.h @@ -0,0 +1,78 @@ +/*! @file OIDAuthorizationService+Mac.h + @brief AppAuth iOS SDK + @copyright + Copyright 2016 Google Inc. All Rights Reserved. + @copydetails + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#import + +#if TARGET_OS_OSX + +#import +#import "OIDAuthorizationService.h" + +NS_ASSUME_NONNULL_BEGIN + +/*! @brief Provides macOS specific authorization request handling. + */ +@interface OIDAuthorizationService (Mac) + +/*! @brief Perform an authorization flow. + @param request The authorization request. + @param presentingWindow The window to present the authentication flow. + @param callback The method called when the request has completed or failed. + @return A @c OIDExternalUserAgentSession instance which will terminate when it + receives a @c OIDExternalUserAgentSession.cancel message, or after processing a + @c OIDExternalUserAgentSession.resumeExternalUserAgentFlowWithURL: message. + @discussion This method adopts @c ASWebAuthenticationSession for macOS 10.15 and above or the + default browser otherwise. + */ ++ (id) presentAuthorizationRequest:(OIDAuthorizationRequest *)request + presentingWindow:(NSWindow *)presentingWindow + callback:(OIDAuthorizationCallback)callback; + +/*! @brief Perform an authorization flow using the @c ASWebAuthenticationSession optionally using an + emphemeral browser session that shares no cookies or data with the normal browser session. + @param request The authorization request. + @param presentingWindow The window to present the authentication flow. + @param prefersEphemeralSession Whether the caller prefers to use a private authentication + session. See @c ASWebAuthenticationSession.prefersEphemeralWebBrowserSession for more. + @param callback The method called when the request has completed or failed. + @return A @c OIDExternalUserAgentSession instance which will terminate when it + receives a @c OIDExternalUserAgentSession.cancel message, or after processing a + @c OIDExternalUserAgentSession.resumeExternalUserAgentFlowWithURL: message. + */ ++ (id) presentAuthorizationRequest:(OIDAuthorizationRequest *)request + presentingWindow:(NSWindow *)presentingWindow + prefersEphemeralSession:(BOOL)prefersEphemeralSession + callback:(OIDAuthorizationCallback)callback + API_AVAILABLE(macos(10.15)); + +/*! @brief Perform an authorization flow using the default browser. + @param request The authorization request. + @param callback The method called when the request has completed or failed. + @return A @c OIDExternalUserAgentSession instance which will terminate when it + receives a @c OIDExternalUserAgentSession.cancel message, or after processing a + @c OIDExternalUserAgentSession.resumeExternalUserAgentFlowWithURL: message. + */ ++ (id)presentAuthorizationRequest:(OIDAuthorizationRequest *)request + callback:(OIDAuthorizationCallback)callback + __deprecated_msg("For macOS 10.15 and above please use presentAuthorizationRequest:presentingWindow:callback:"); + +@end + +NS_ASSUME_NONNULL_END + +#endif // TARGET_OS_OSX diff --git a/Sources/AppAuth/macOS/OIDAuthorizationService+Mac.m b/Sources/AppAuth/macOS/OIDAuthorizationService+Mac.m new file mode 100644 index 000000000..4d3d9a038 --- /dev/null +++ b/Sources/AppAuth/macOS/OIDAuthorizationService+Mac.m @@ -0,0 +1,64 @@ +/*! @file OIDAuthorizationService+Mac.m + @brief AppAuth iOS SDK + @copyright + Copyright 2016 Google Inc. All Rights Reserved. + @copydetails + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#import + +#if TARGET_OS_OSX + +#import "OIDAuthorizationService+Mac.h" + +#import "OIDExternalUserAgentMac.h" + +NS_ASSUME_NONNULL_BEGIN + +@implementation OIDAuthorizationService (Mac) + ++ (id) presentAuthorizationRequest:(OIDAuthorizationRequest *)request + presentingWindow:(NSWindow *)presentingWindow + callback:(OIDAuthorizationCallback)callback { + OIDExternalUserAgentMac *externalUserAgent = [[OIDExternalUserAgentMac alloc] initWithPresentingWindow:presentingWindow]; + return [self presentAuthorizationRequest:request + externalUserAgent:externalUserAgent + callback:callback]; +} + ++ (id) presentAuthorizationRequest:(OIDAuthorizationRequest *)request + presentingWindow:(NSWindow *)presentingWindow + prefersEphemeralSession:(BOOL)prefersEphemeralSession + callback:(OIDAuthorizationCallback)callback { + OIDExternalUserAgentMac *externalUserAgent = + [[OIDExternalUserAgentMac alloc] initWithPresentingWindow:presentingWindow + prefersEphemeralSession:prefersEphemeralSession]; + return [self presentAuthorizationRequest:request + externalUserAgent:externalUserAgent + callback:callback]; +} + ++ (id) presentAuthorizationRequest:(OIDAuthorizationRequest *)request + callback:(OIDAuthorizationCallback)callback { + OIDExternalUserAgentMac *externalUserAgent = [[OIDExternalUserAgentMac alloc] init]; + return [self presentAuthorizationRequest:request + externalUserAgent:externalUserAgent + callback:callback]; +} + +@end + +NS_ASSUME_NONNULL_END + +#endif // TARGET_OS_OSX diff --git a/Sources/AppAuth/macOS/OIDExternalUserAgentMac.h b/Sources/AppAuth/macOS/OIDExternalUserAgentMac.h new file mode 100644 index 000000000..360c44ad6 --- /dev/null +++ b/Sources/AppAuth/macOS/OIDExternalUserAgentMac.h @@ -0,0 +1,54 @@ +/*! @file OIDExternalUserAgentMac.h + @brief AppAuth iOS SDK + @copyright + Copyright 2016 Google Inc. All Rights Reserved. + @copydetails + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#import + +#if TARGET_OS_OSX + +#import +#import "OIDExternalUserAgent.h" + +NS_ASSUME_NONNULL_BEGIN + +/*! @brief A Mac-specific external user-agent UI Coordinator that uses the default browser to + present an external user-agent request. + */ +@interface OIDExternalUserAgentMac : NSObject + +/*! @brief The designated initializer. + @param presentingWindow The window from which to present the @c ASWebAuthenticationSession on + macOS 10.15 and above. Older macOS versions use the system browser. + */ +- (instancetype)initWithPresentingWindow:(NSWindow *)presentingWindow NS_DESIGNATED_INITIALIZER; + +/*! @brief Create an external user-agent which optionally uses a private authentication session. + @param presentingWindow The window from which to present the @c ASWebAuthenticationSession. + @param prefersEphemeralSession Whether the caller prefers to use a private authentication + session. See @c ASWebAuthenticationSession.prefersEphemeralWebBrowserSession for more. + */ +- (nullable instancetype)initWithPresentingWindow:(NSWindow *)presentingWindow + prefersEphemeralSession:(BOOL)prefersEphemeralSession + API_AVAILABLE(macos(10.15)); + +- (instancetype)init __deprecated_msg("Use initWithPresentingWindow for macOS 10.15 and above."); + +@end + +NS_ASSUME_NONNULL_END + +#endif // TARGET_OS_OSX diff --git a/Sources/AppAuth/macOS/OIDExternalUserAgentMac.m b/Sources/AppAuth/macOS/OIDExternalUserAgentMac.m new file mode 100644 index 000000000..d1e08f902 --- /dev/null +++ b/Sources/AppAuth/macOS/OIDExternalUserAgentMac.m @@ -0,0 +1,170 @@ +/*! @file OIDExternalUserAgentMac.m + @brief AppAuth iOS SDK + @copyright + Copyright 2016 Google Inc. All Rights Reserved. + @copydetails + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#import + +#if TARGET_OS_OSX + +#import "OIDExternalUserAgentMac.h" + +#import + +#import "OIDErrorUtilities.h" +#import "OIDExternalUserAgentSession.h" +#import "OIDExternalUserAgentRequest.h" +#import + + +NS_ASSUME_NONNULL_BEGIN + +@interface OIDExternalUserAgentMac () +@end + +@implementation OIDExternalUserAgentMac { + BOOL _externalUserAgentFlowInProgress; + __weak id _session; + BOOL _prefersEphemeralSession; + + NSWindow *_presentingWindow; +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wpartial-availability" + ASWebAuthenticationSession *_webAuthenticationSession; +#pragma clang diagnostic pop +} + +- (instancetype)initWithPresentingWindow:(NSWindow *)presentingWindow { + self = [super init]; + if (self) { + _presentingWindow = presentingWindow; + } + return self; +} + +- (nullable instancetype)initWithPresentingWindow:(NSWindow *)presentingWindow + prefersEphemeralSession:(BOOL)prefersEphemeralSession { + self = [self initWithPresentingWindow:presentingWindow]; + if (self) { + _prefersEphemeralSession = prefersEphemeralSession; + } + return self; +} + +- (instancetype)init { +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wnonnull" + return [self initWithPresentingWindow:nil]; +#pragma clang diagnostic pop +} + +- (BOOL)presentExternalUserAgentRequest:(id)request + session:(id)session { + if (_externalUserAgentFlowInProgress) { + // TODO: Handle errors as authorization is already in progress. + return NO; + } + + _externalUserAgentFlowInProgress = YES; + _session = session; + NSURL *requestURL = [request externalUserAgentRequestURL]; + + if (@available(macOS 10.15, *)) { + if (_presentingWindow) { + __weak OIDExternalUserAgentMac *weakSelf = self; + NSString *redirectScheme = request.redirectScheme; + ASWebAuthenticationSession *authenticationSession = + [[ASWebAuthenticationSession alloc] initWithURL:requestURL + callbackURLScheme:redirectScheme + completionHandler:^(NSURL * _Nullable callbackURL, + NSError * _Nullable error) { + __strong OIDExternalUserAgentMac *strongSelf = weakSelf; + if (!strongSelf) { + return; + } + strongSelf->_webAuthenticationSession = nil; + if (callbackURL) { + [strongSelf->_session resumeExternalUserAgentFlowWithURL:callbackURL]; + } else { + NSError *safariError = + [OIDErrorUtilities errorWithCode:OIDErrorCodeUserCanceledAuthorizationFlow + underlyingError:error + description:nil]; + [strongSelf->_session failExternalUserAgentFlowWithError:safariError]; + } + }]; + + authenticationSession.presentationContextProvider = self; + + _webAuthenticationSession = authenticationSession; + _webAuthenticationSession.prefersEphemeralWebBrowserSession = _prefersEphemeralSession; + return [authenticationSession start]; + } + } + + BOOL openedBrowser = [[NSWorkspace sharedWorkspace] openURL:requestURL]; + if (!openedBrowser) { + [self cleanUp]; + NSError *safariError = [OIDErrorUtilities errorWithCode:OIDErrorCodeBrowserOpenError + underlyingError:nil + description:@"Unable to open the browser."]; + [session failExternalUserAgentFlowWithError:safariError]; + } + return openedBrowser; +} + +- (void)dismissExternalUserAgentAnimated:(BOOL)animated completion:(void (^)(void))completion { + if (!_externalUserAgentFlowInProgress) { + // Ignore this call if there is no authorization flow in progress. + if (completion) completion(); + return; + } + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wpartial-availability" + ASWebAuthenticationSession *webAuthenticationSession = _webAuthenticationSession; +#pragma clang diagnostic pop + + // Ideally the browser tab with the URL should be closed here, but the AppAuth library does not + // control the browser. + [self cleanUp]; + if (webAuthenticationSession) { + // dismiss the ASWebAuthenticationSession + [webAuthenticationSession cancel]; + if (completion) completion(); + } else if (completion) { + completion(); + } +} + +- (void)cleanUp { + _session = nil; + _externalUserAgentFlowInProgress = NO; + _webAuthenticationSession = nil; +} + +#pragma mark - ASWebAuthenticationPresentationContextProviding + +- (ASPresentationAnchor)presentationAnchorForWebAuthenticationSession:(ASWebAuthenticationSession *)session + API_AVAILABLE(macosx(10.15)) { + return _presentingWindow; +} + +@end + +NS_ASSUME_NONNULL_END + +#endif // TARGET_OS_OSX diff --git a/Source/macOS/OIDRedirectHTTPHandler.h b/Sources/AppAuth/macOS/OIDRedirectHTTPHandler.h similarity index 50% rename from Source/macOS/OIDRedirectHTTPHandler.h rename to Sources/AppAuth/macOS/OIDRedirectHTTPHandler.h index f445ebbea..831eb0bb3 100644 --- a/Source/macOS/OIDRedirectHTTPHandler.h +++ b/Sources/AppAuth/macOS/OIDRedirectHTTPHandler.h @@ -16,62 +16,75 @@ limitations under the License. */ +#import + +#if TARGET_OS_OSX + #import NS_ASSUME_NONNULL_BEGIN -@class HTTPServer; -@protocol OIDAuthorizationFlowSession; +@protocol OIDExternalUserAgentSession; -/*! @brief Start a HTTP server on the loopback interface (i.e. @c 127.0.0.1) to receive OAuth - authorization response redirects on macOS. +/*! @brief Start a HTTP server on the loopback interface (i.e. @c 127.0.0.1) to receive the OAuth + response redirects on macOS. */ -@interface OIDRedirectHTTPHandler : NSObject { - // private variables - HTTPServer *_httpServ; - NSURL *_successURL; - // property variables - NSObject *_currentAuthorizationFlow; -} - -/*! @brief The authorization flow session which receives the return URL from the browser. +@interface OIDRedirectHTTPHandler : NSObject + +/*! @brief The external user-agent request flow session which receives the return URL from the + browser. @discussion The loopback HTTP server will try sending incoming request URLs to the OAuth - redirect handler to continue the flow. This should be set while an authorization flow is - in progress. + redirect handler to continue the flow. This should be set while an external user-agent + request flow is in progress. */ -@property(nonatomic, strong, nullable) id currentAuthorizationFlow; +@property(nonatomic, strong, nullable) id currentAuthorizationFlow; /*! @brief Creates an a loopback HTTP redirect URI handler with the given success URL. - @param successURL The URL that the user is redirected to after the authorization flow completes + @param successURL The URL that the user is redirected to after the external user-agent request flow completes either with a result of success or error. The contents of this page should instruct the user to return to the app. - @discussion Once you have initiated the authorization request, be sure to set - @c currentAuthorizationFlow on this object so that any authorization responses received by - this listener will be routed accordingly. + @discussion Once you have initiated the external user-agent request, be sure to set + @c currentAuthorizationFlow on this object so that any responses received by this listener will + be routed accordingly. */ - (instancetype)initWithSuccessURL:(nullable NSURL *)successURL; +/*! @brief Starts listening on the loopback interface on a specified port, and returns a URL + with the base address. Use the returned redirect URI to build a @c OIDExternalUserAgentRequest, + and once you initiate the request, set the resulting @c OIDExternalUserAgentSession to + @c currentAuthorizationFlow so the response can be handled. + @param returnError The error if an error occurred while starting the local HTTP server. + @param port The manually specified port, or 0 for a random available port. + @return The URL containing the address of the server with the specified port, or nil if there was an error. + @discussion Each instance of @c OIDRedirectHTTPHandler can only listen for a single response. + Calling this more than once will result in the previous listener being cancelled (equivalent + of @c cancelHTTPListener being called). + */ +- (NSURL *)startHTTPListener:(NSError **)returnError withPort:(uint16_t)port; + /*! @brief Starts listening on the loopback interface on a random available port, and returns a URL - with the base address. Use the returned redirect URI to build an @c OIDAuthorizationRequest, - and once you initiate the request, set the resulting @c OIDAuthorizationFlowSession to + with the base address. Use the returned redirect URI to build a @c OIDExternalUserAgentRequest, + and once you initiate the request, set the resulting @c OIDExternalUserAgentSession to @c currentAuthorizationFlow so the response can be handled. - @param error The error if an error occurred while starting the local HTTP server. + @param returnError The error if an error occurred while starting the local HTTP server. @return The URL containing the address of the server with the randomly assigned available port. - @discussion Each instance of @c OIDRedirectHTTPHandler can only listen for a single - authorization response. Calling this more than once will result in the previous listener - being cancelled (equivalent of @c cancelHTTPListener being called). + @discussion Each instance of @c OIDRedirectHTTPHandler can only listen for a single response. + Calling this more than once will result in the previous listener being cancelled (equivalent + of @c cancelHTTPListener being called). */ -- (NSURL *)startHTTPListener:(NSError **)error; +- (NSURL *)startHTTPListener:(NSError **)returnError; /*! @brief Stops listening the loopback interface and sends an cancellation error (in the domain ::OIDGeneralErrorDomain, with the code ::OIDErrorCodeProgramCanceledAuthorizationFlow) to the @c currentAuthorizationFlow. Has no effect if called when no requests are pending. - @discussion The HTTP listener is stopped automatically on receiving a valid authorization - response (regardless of whether the authorization succeeded or not), this method should not - be called except when abandoning the authorization request. + @discussion The HTTP listener is stopped automatically on receiving a valid response (regardless + of whether the request succeeded or not), this method should not be called except when + abandoning the external user-agent request. */ - (void)cancelHTTPListener; @end NS_ASSUME_NONNULL_END + +#endif // TARGET_OS_OSX diff --git a/Source/macOS/OIDRedirectHTTPHandler.m b/Sources/AppAuth/macOS/OIDRedirectHTTPHandler.m similarity index 89% rename from Source/macOS/OIDRedirectHTTPHandler.m rename to Sources/AppAuth/macOS/OIDRedirectHTTPHandler.m index 3003de02f..3d0d765d8 100644 --- a/Source/macOS/OIDRedirectHTTPHandler.m +++ b/Sources/AppAuth/macOS/OIDRedirectHTTPHandler.m @@ -16,10 +16,15 @@ limitations under the License. */ +#import + +#if TARGET_OS_OSX + #import "OIDRedirectHTTPHandler.h" #import "OIDAuthorizationService.h" #import "OIDErrorUtilities.h" +#import "OIDExternalUserAgentSession.h" #import "OIDLoopbackHTTPServer.h" /*! @brief Page that is returned following a completed authorization. Show your own page instead by @@ -31,7 +36,7 @@ /*! @brief Error warning that the @c currentAuthorizationFlow is not set on this object (likely a developer error, unless the user stumbled upon the loopback server before the authorization had started completely). - @description An object conforming to @c OIDAuthorizationFlowSession is returned when the + @description An object conforming to @c OIDExternalUserAgentSession is returned when the authorization is presented with @c OIDAuthorizationService::presentAuthorizationRequest:callback:. It should be set to @c currentAuthorization when using a loopback redirect. @@ -46,9 +51,10 @@ static NSString *const kHTMLErrorRedirectNotValid = @"AppAuth Error: Not a valid redirect."; -@implementation OIDRedirectHTTPHandler - -@synthesize currentAuthorizationFlow = _currentAuthorizationFlow; +@implementation OIDRedirectHTTPHandler { + HTTPServer *_httpServ; + NSURL *_successURL; +} - (instancetype)init { return [self initWithSuccessURL:nil]; @@ -62,13 +68,14 @@ - (instancetype)initWithSuccessURL:(nullable NSURL *)successURL { return self; } -- (NSURL *)startHTTPListener:(NSError **)returnError { +- (NSURL *)startHTTPListener:(NSError **)returnError withPort:(uint16_t)port { // Cancels any pending requests. [self cancelHTTPListener]; // Starts a HTTP server on the loopback interface. // By not specifying a port, a random available one will be assigned. _httpServ = [[HTTPServer alloc] init]; + [_httpServ setPort:port]; [_httpServ setDelegate:self]; NSError *error = nil; if (![_httpServ start:&error]) { @@ -89,6 +96,11 @@ - (NSURL *)startHTTPListener:(NSError **)returnError { return nil; } +- (NSURL *)startHTTPListener:(NSError **)returnError { + // A port of 0 requests a random available port + return [self startHTTPListener:returnError withPort:0]; +} + - (void)cancelHTTPListener { [self stopHTTPListener]; @@ -97,7 +109,7 @@ - (void)cancelHTTPListener { [OIDErrorUtilities errorWithCode:OIDErrorCodeProgramCanceledAuthorizationFlow underlyingError:nil description:@"The HTTP listener was cancelled programmatically."]; - [_currentAuthorizationFlow failAuthorizationFlowWithError:cancelledError]; + [_currentAuthorizationFlow failExternalUserAgentFlowWithError:cancelledError]; _currentAuthorizationFlow = nil; } @@ -114,7 +126,7 @@ - (void)stopHTTPListener { - (void)HTTPConnection:(HTTPConnection *)conn didReceiveRequest:(HTTPServerRequest *)mess { // Sends URL to AppAuth. CFURLRef url = CFHTTPMessageCopyRequestURL(mess.request); - BOOL handled = [_currentAuthorizationFlow resumeAuthorizationFlowWithURL:(__bridge NSURL *)url]; + BOOL handled = [_currentAuthorizationFlow resumeExternalUserAgentFlowWithURL:(__bridge NSURL *)url]; // Stops listening to further requests after the first valid authorization response. if (handled) { @@ -161,3 +173,5 @@ - (void)dealloc { } @end + +#endif // TARGET_OS_OSX diff --git a/Sources/AppAuthCore.h b/Sources/AppAuthCore.h new file mode 100644 index 000000000..c30af4648 --- /dev/null +++ b/Sources/AppAuthCore.h @@ -0,0 +1,44 @@ +/*! @file AppAuthCore.h + @brief AppAuth iOS SDK + @copyright + Copyright 2015 Google Inc. All Rights Reserved. + @copydetails + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#import "OIDAuthState.h" +#import "OIDAuthStateChangeDelegate.h" +#import "OIDAuthStateErrorDelegate.h" +#import "OIDAuthorizationRequest.h" +#import "OIDAuthorizationResponse.h" +#import "OIDAuthorizationService.h" +#import "OIDError.h" +#import "OIDErrorUtilities.h" +#import "OIDExternalUserAgent.h" +#import "OIDExternalUserAgentRequest.h" +#import "OIDExternalUserAgentSession.h" +#import "OIDGrantTypes.h" +#import "OIDIDToken.h" +#import "OIDRegistrationRequest.h" +#import "OIDRegistrationResponse.h" +#import "OIDResponseTypes.h" +#import "OIDScopes.h" +#import "OIDScopeUtilities.h" +#import "OIDServiceConfiguration.h" +#import "OIDServiceDiscovery.h" +#import "OIDTokenRequest.h" +#import "OIDTokenResponse.h" +#import "OIDTokenUtilities.h" +#import "OIDURLSessionProvider.h" +#import "OIDEndSessionRequest.h" +#import "OIDEndSessionResponse.h" diff --git a/Source/OIDAuthState.h b/Sources/AppAuthCore/OIDAuthState.h similarity index 79% rename from Source/OIDAuthState.h rename to Sources/AppAuthCore/OIDAuthState.h index 8c5e11b5f..46c78a831 100644 --- a/Source/OIDAuthState.h +++ b/Sources/AppAuthCore/OIDAuthState.h @@ -23,10 +23,10 @@ @class OIDRegistrationResponse; @class OIDTokenResponse; @class OIDTokenRequest; -@protocol OIDAuthorizationFlowSession; -@protocol OIDAuthorizationUICoordinator; @protocol OIDAuthStateChangeDelegate; @protocol OIDAuthStateErrorDelegate; +@protocol OIDExternalUserAgent; +@protocol OIDExternalUserAgentSession; NS_ASSUME_NONNULL_BEGIN @@ -48,33 +48,16 @@ typedef void (^OIDAuthStateAction)(NSString *_Nullable accessToken, typedef void (^OIDAuthStateAuthorizationCallback)(OIDAuthState *_Nullable authState, NSError *_Nullable error); +/*! @brief The exception thrown when a developer tries to create a refresh request from an + authorization request with no authorization code. + */ +static NSString *const kRefreshTokenRequestException = + @"Attempted to create a token refresh request from a token response with no refresh token."; + /*! @brief A convenience class that retains the auth state between @c OIDAuthorizationResponse%s and @c OIDTokenResponse%s. */ -@interface OIDAuthState : NSObject { - // private variables - /*! @brief Array of pending actions (use @c _pendingActionsSyncObject to synchronize access). - */ - NSMutableArray *_pendingActions; - - /*! @brief Object for synchronizing access to @c pendingActions. - */ - id _pendingActionsSyncObject; - - /*! @brief If YES, tokens will be refreshed on the next API call regardless of expiry. - */ - BOOL _needsTokenRefresh; - - // property variables - NSString *_refreshToken; - NSString *_scope; - OIDAuthorizationResponse *_lastAuthorizationResponse; - OIDTokenResponse *_lastTokenResponse; - OIDRegistrationResponse *_lastRegistrationResponse; - NSError *_authorizationError; - __weak id _stateChangeDelegate; - __weak id _errorDelegate; -} +@interface OIDAuthState : NSObject /*! @brief The most recent refresh token received from the server. @discussion Rather than using this property directly, you should call @@ -140,18 +123,21 @@ typedef void (^OIDAuthStateAuthorizationCallback)(OIDAuthState *_Nullable authSt @property(nonatomic, weak, nullable) id errorDelegate; /*! @brief Convenience method to create a @c OIDAuthState by presenting an authorization request - and performing the authorization code exchange in the case of code flow requests. + and performing the authorization code exchange in the case of code flow requests. For + the hybrid flow, the caller should validate the id_token and c_hash, then perform the token + request (@c OIDAuthorizationService.performTokenRequest:callback:) + and update the OIDAuthState with the results (@c + OIDAuthState.updateWithTokenResponse:error:). @param authorizationRequest The authorization request to present. - @param UICoordinator Generic authorization UI coordinator that can present an authorization - request. + @param externalUserAgent A external user agent that can present an external user-agent request. @param callback The method called when the request has completed or failed. - @return A @c OIDAuthorizationFlowSession instance which will terminate when it - receives a @c OIDAuthorizationFlowSession.cancel message, or after processing a - @c OIDAuthorizationFlowSession.resumeAuthorizationFlowWithURL: message. + @return A @c OIDExternalUserAgentSession instance which will terminate when it + receives a @c OIDExternalUserAgentSession.cancel message, or after processing a + @c OIDExternalUserAgentSession.resumeExternalUserAgentFlowWithURL: message. */ -+ (id) ++ (id) authStateByPresentingAuthorizationRequest:(OIDAuthorizationRequest *)authorizationRequest - UICoordinator:(id)UICoordinator + externalUserAgent:(id)externalUserAgent callback:(OIDAuthStateAuthorizationCallback)callback; /*! @internal @@ -249,6 +235,19 @@ typedef void (^OIDAuthStateAuthorizationCallback)(OIDAuthState *_Nullable authSt additionalRefreshParameters: (nullable NSDictionary *)additionalParameters; +/*! @brief Calls the block with a valid access token (refreshing it first, if needed), or if a + refresh was needed and failed, with the error that caused it to fail. + @param action The block to execute with a fresh token. This block will be executed on the main + thread. + @param additionalParameters Additional parameters for the token request if token is + refreshed. + @param dispatchQueue The dispatchQueue on which to dispatch the action block. + */ +- (void)performActionWithFreshTokens:(OIDAuthStateAction)action + additionalRefreshParameters: + (nullable NSDictionary *)additionalParameters + dispatchQueue:(dispatch_queue_t)dispatchQueue; + /*! @brief Forces a token refresh the next time @c OIDAuthState.performActionWithFreshTokens: is called, even if the current tokens are considered valid. */ @@ -274,15 +273,30 @@ typedef void (^OIDAuthStateAuthorizationCallback)(OIDAuthState *_Nullable authSt - (nullable OIDTokenRequest *)tokenRefreshRequestWithAdditionalParameters: (nullable NSDictionary *)additionalParameters; -/*! @brief Deprecated, use @c OIDAuthState.performActionWithFreshTokens:. - @discussion Calls the block with a valid access token (refreshing it first, if needed), or if a - refresh was needed and failed, with the error that caused it to fail. - @param action The block to execute with a fresh token. This block will be executed on the main - thread. - @deprecated Use @c OIDAuthState.performActionWithFreshTokens: which is equivalent. +/*! @brief Creates a token request suitable for refreshing an access token. + @param additionalParameters Additional parameters for the token request. + @param additionalHeaders Additional headers for the token request. + @return A @c OIDTokenRequest suitable for using a refresh token to obtain a new access token. + @discussion After performing the refresh, call @c OIDAuthState.updateWithTokenResponse:error: + to update the authorization state based on the response. Rather than doing the token refresh + yourself, you should use @c OIDAuthState.performActionWithFreshTokens:. + @see https://tools.ietf.org/html/rfc6749#section-1.5 + */ +- (nullable OIDTokenRequest *)tokenRefreshRequestWithAdditionalParameters: + (nullable NSDictionary *)additionalParameters + additionalHeaders: + (nullable NSDictionary *)additionalHeaders; + +/*! @brief Creates a token request suitable for refreshing an access token. + @param additionalHeaders Additional parameters for the token request. + @return A @c OIDTokenRequest suitable for using a refresh token to obtain a new access token. + @discussion After performing the refresh, call @c OIDAuthState.updateWithTokenResponse:error: + to update the authorization state based on the response. Rather than doing the token refresh + yourself, you should use @c OIDAuthState.performActionWithFreshTokens:. + @see https://tools.ietf.org/html/rfc6749#section-1.5 */ -- (void)withFreshTokensPerformAction:(OIDAuthStateAction)action - __deprecated_msg("Use OIDAuthState.performActionWithFreshTokens:"); +- (nullable OIDTokenRequest *)tokenRefreshRequestWithAdditionalHeaders: + (nullable NSDictionary *)additionalHeaders; @end diff --git a/Source/OIDAuthState.m b/Sources/AppAuthCore/OIDAuthState.m similarity index 74% rename from Source/OIDAuthState.m rename to Sources/AppAuthCore/OIDAuthState.m index 57a47aa91..cb5a22a1e 100644 --- a/Source/OIDAuthState.m +++ b/Sources/AppAuthCore/OIDAuthState.m @@ -29,6 +29,7 @@ #import "OIDRegistrationResponse.h" #import "OIDTokenRequest.h" #import "OIDTokenResponse.h" +#import "OIDTokenUtilities.h" /*! @brief Key used to encode the @c refreshToken property for @c NSSecureCoding. */ @@ -54,16 +55,27 @@ */ static NSString *const kAuthorizationErrorKey = @"authorizationError"; -/*! @brief The exception thrown when a developer tries to create a refresh request from an - authorization request with no authorization code. - */ -static NSString *const kRefreshTokenRequestException = - @"Attempted to create a token refresh request from a token response with no refresh token."; - /*! @brief Number of seconds the access token is refreshed before it actually expires. */ static const NSUInteger kExpiryTimeTolerance = 60; +/*! @brief Object to hold OIDAuthState pending actions. + */ +@interface OIDAuthStatePendingAction : NSObject +@property(nonatomic, readonly, nullable) OIDAuthStateAction action; +@property(nonatomic, readonly, nullable) dispatch_queue_t dispatchQueue; +@end +@implementation OIDAuthStatePendingAction +- (id)initWithAction:(OIDAuthStateAction)action andDispatchQueue:(dispatch_queue_t)dispatchQueue { + self = [super init]; + if (self) { + _action = action; + _dispatchQueue = dispatchQueue; + } + return self; +} +@end + @interface OIDAuthState () /*! @brief The access token generated by the authorization server. @@ -91,27 +103,30 @@ - (void)didChangeState; @end -@implementation OIDAuthState +@implementation OIDAuthState { + /*! @brief Array of pending actions (use @c _pendingActionsSyncObject to synchronize access). + */ + NSMutableArray *_pendingActions; -@synthesize refreshToken = _refreshToken; -@synthesize scope = _scope; -@synthesize lastAuthorizationResponse = _lastAuthorizationResponse; -@synthesize lastTokenResponse = _lastTokenResponse; -@synthesize lastRegistrationResponse = _lastRegistrationResponse; -@synthesize authorizationError = _authorizationError; -@synthesize stateChangeDelegate = _stateChangeDelegate; -@synthesize errorDelegate = _errorDelegate; + /*! @brief Object for synchronizing access to @c pendingActions. + */ + id _pendingActionsSyncObject; + + /*! @brief If YES, tokens will be refreshed on the next API call regardless of expiry. + */ + BOOL _needsTokenRefresh; +} #pragma mark - Convenience initializers -+ (id) ++ (id) authStateByPresentingAuthorizationRequest:(OIDAuthorizationRequest *)authorizationRequest - UICoordinator:(id)UICoordinator + externalUserAgent:(id)externalUserAgent callback:(OIDAuthStateAuthorizationCallback)callback { // presents the authorization request - id authFlowSession = [OIDAuthorizationService + id authFlowSession = [OIDAuthorizationService presentAuthorizationRequest:authorizationRequest - UICoordinator:UICoordinator + externalUserAgent:externalUserAgent callback:^(OIDAuthorizationResponse *_Nullable authorizationResponse, NSError *_Nullable authorizationError) { // inspects response and processes further if needed (e.g. authorization @@ -124,9 +139,9 @@ @implementation OIDAuthState // code exchange OIDTokenRequest *tokenExchangeRequest = [authorizationResponse tokenExchangeRequest]; - [OIDAuthorizationService - performTokenRequest:tokenExchangeRequest - callback:^(OIDTokenResponse *_Nullable tokenResponse, + [OIDAuthorizationService performTokenRequest:tokenExchangeRequest + originalAuthorizationResponse:authorizationResponse + callback:^(OIDTokenResponse *_Nullable tokenResponse, NSError *_Nullable tokenError) { OIDAuthState *authState; if (tokenResponse) { @@ -136,10 +151,15 @@ @implementation OIDAuthState tokenResponse:tokenResponse]; } callback(authState, tokenError); - }]; + }]; } else { - // implicit or hybrid flow (hybrid flow assumes code is not for this - // client) + // hybrid flow (code id_token). Two possible cases: + // 1. The code is not for this client, ie. will be sent to a + // webservice that performs the id token verification and token + // exchange + // 2. The code is for this client and, for security reasons, the + // application developer must verify the id_token signature and + // c_hash before calling the token endpoint OIDAuthState *authState = [[OIDAuthState alloc] initWithAuthorizationResponse:authorizationResponse]; callback(authState, authorizationError); @@ -154,10 +174,10 @@ @implementation OIDAuthState #pragma mark - Initializers - (nonnull instancetype)init - OID_UNAVAILABLE_USE_INITIALIZER(@selector(initWithAuthorizationResponse:tokenResponse:)); + OID_UNAVAILABLE_USE_INITIALIZER(@selector(initWithAuthorizationResponse:tokenResponse:)) /*! @brief Creates an auth state from an authorization response. - @param response The authorization response. + @param authorizationResponse The authorization response. */ - (instancetype)initWithAuthorizationResponse:(OIDAuthorizationResponse *)authorizationResponse { return [self initWithAuthorizationResponse:authorizationResponse tokenResponse:nil]; @@ -165,7 +185,7 @@ - (instancetype)initWithAuthorizationResponse:(OIDAuthorizationResponse *)author /*! @brief Designated initializer. - @param response The authorization response. + @param authorizationResponse The authorization response. @discussion Creates an auth state from an authorization response and token response. */ - (instancetype)initWithAuthorizationResponse:(OIDAuthorizationResponse *)authorizationResponse @@ -216,13 +236,13 @@ - (NSString *)description { "lastAuthorizationResponse: %@, lastTokenResponse: %@, " "lastRegistrationResponse: %@, authorizationError: %@>", NSStringFromClass([self class]), - self, + (void *)self, (self.isAuthorized) ? @"YES" : @"NO", - _refreshToken, + [OIDTokenUtilities redact:_refreshToken], _scope, - self.accessToken, + [OIDTokenUtilities redact:self.accessToken], self.accessTokenExpirationDate, - self.idToken, + [OIDTokenUtilities redact:self.idToken], _lastAuthorizationResponse, _lastTokenResponse, _lastRegistrationResponse, @@ -401,7 +421,47 @@ - (OIDTokenRequest *)tokenRefreshRequest { - (OIDTokenRequest *)tokenRefreshRequestWithAdditionalParameters: (NSDictionary *)additionalParameters { - // TODO: Add unit test to confirm exception is thrown when expected + if (!_refreshToken) { + [OIDErrorUtilities raiseException:kRefreshTokenRequestException]; + } + return [[OIDTokenRequest alloc] + initWithConfiguration:_lastAuthorizationResponse.request.configuration + grantType:OIDGrantTypeRefreshToken + authorizationCode:nil + redirectURL:nil + clientID:_lastAuthorizationResponse.request.clientID + clientSecret:_lastAuthorizationResponse.request.clientSecret + scope:nil + refreshToken:_refreshToken + codeVerifier:nil + additionalParameters:additionalParameters + additionalHeaders:nil]; +} + +- (OIDTokenRequest *)tokenRefreshRequestWithAdditionalParameters: + (NSDictionary *)additionalParameters + additionalHeaders: + (NSDictionary *)additionalHeaders { + + if (!_refreshToken) { + [OIDErrorUtilities raiseException:kRefreshTokenRequestException]; + } + return [[OIDTokenRequest alloc] + initWithConfiguration:_lastAuthorizationResponse.request.configuration + grantType:OIDGrantTypeRefreshToken + authorizationCode:nil + redirectURL:nil + clientID:_lastAuthorizationResponse.request.clientID + clientSecret:_lastAuthorizationResponse.request.clientSecret + scope:nil + refreshToken:_refreshToken + codeVerifier:nil + additionalParameters:additionalParameters + additionalHeaders:additionalHeaders]; +} + +- (OIDTokenRequest *)tokenRefreshRequestWithAdditionalHeaders: + (NSDictionary *)additionalHeaders { if (!_refreshToken) { [OIDErrorUtilities raiseException:kRefreshTokenRequestException]; @@ -410,13 +470,14 @@ - (OIDTokenRequest *)tokenRefreshRequestWithAdditionalParameters: initWithConfiguration:_lastAuthorizationResponse.request.configuration grantType:OIDGrantTypeRefreshToken authorizationCode:nil - redirectURL:_lastAuthorizationResponse.request.redirectURL + redirectURL:nil clientID:_lastAuthorizationResponse.request.clientID clientSecret:_lastAuthorizationResponse.request.clientSecret - scope:_lastAuthorizationResponse.request.scope + scope:nil refreshToken:_refreshToken codeVerifier:nil - additionalParameters:additionalParameters]; + additionalParameters:nil + additionalHeaders:additionalHeaders]; } #pragma mark - Stateful Actions @@ -436,9 +497,19 @@ - (void)performActionWithFreshTokens:(OIDAuthStateAction)action { - (void)performActionWithFreshTokens:(OIDAuthStateAction)action additionalRefreshParameters: (nullable NSDictionary *)additionalParameters { + [self performActionWithFreshTokens:action + additionalRefreshParameters:additionalParameters + dispatchQueue:dispatch_get_main_queue()]; +} + +- (void)performActionWithFreshTokens:(OIDAuthStateAction)action + additionalRefreshParameters: + (nullable NSDictionary *)additionalParameters + dispatchQueue:(dispatch_queue_t)dispatchQueue { + if ([self isTokenFresh]) { // access token is valid within tolerance levels, perform action - dispatch_async(dispatch_get_main_queue(), ^() { + dispatch_async(dispatchQueue, ^{ action(self.accessToken, self.idToken, nil); }); return; @@ -450,67 +521,64 @@ - (void)performActionWithFreshTokens:(OIDAuthStateAction)action OIDErrorUtilities errorWithCode:OIDErrorCodeTokenRefreshError underlyingError:nil description:@"Unable to refresh expired token without a refresh token."]; - dispatch_async(dispatch_get_main_queue(), ^() { + dispatch_async(dispatchQueue, ^{ action(nil, nil, tokenRefreshError); }); return; } // access token is expired, first refresh the token, then perform action - NSAssert(_pendingActionsSyncObject, @"_pendingActionsSyncObject cannot be nil"); + NSAssert(_pendingActionsSyncObject, @"_pendingActionsSyncObject cannot be nil", @""); + OIDAuthStatePendingAction* pendingAction = + [[OIDAuthStatePendingAction alloc] initWithAction:action andDispatchQueue:dispatchQueue]; @synchronized(_pendingActionsSyncObject) { // if a token is already in the process of being refreshed, adds to pending actions if (_pendingActions) { - [_pendingActions addObject:action]; + [_pendingActions addObject:pendingAction]; return; } // creates a list of pending actions, starting with this one - _pendingActions = [NSMutableArray arrayWithObject:action]; + _pendingActions = [NSMutableArray arrayWithObject:pendingAction]; } // refresh the tokens OIDTokenRequest *tokenRefreshRequest = [self tokenRefreshRequestWithAdditionalParameters:additionalParameters]; [OIDAuthorizationService performTokenRequest:tokenRefreshRequest + originalAuthorizationResponse:_lastAuthorizationResponse callback:^(OIDTokenResponse *_Nullable response, NSError *_Nullable error) { - dispatch_async(dispatch_get_main_queue(), ^() { - // update OIDAuthState based on response - if (response) { - _needsTokenRefresh = NO; - [self updateWithTokenResponse:response error:nil]; + // update OIDAuthState based on response + if (response) { + self->_needsTokenRefresh = NO; + [self updateWithTokenResponse:response error:nil]; + } else { + if (error.domain == OIDOAuthTokenErrorDomain) { + self->_needsTokenRefresh = NO; + [self updateWithAuthorizationError:error]; } else { - if (error.domain == OIDOAuthTokenErrorDomain) { - _needsTokenRefresh = NO; - [self updateWithAuthorizationError:error]; - } else { - if ([_errorDelegate respondsToSelector: - @selector(authState:didEncounterTransientError:)]) { - [_errorDelegate authState:self didEncounterTransientError:error]; - } + if ([self->_errorDelegate respondsToSelector: + @selector(authState:didEncounterTransientError:)]) { + [self->_errorDelegate authState:self didEncounterTransientError:error]; } } + } - // nil the pending queue and process everything that was queued up - NSArray *actionsToProcess; - @synchronized(_pendingActionsSyncObject) { - actionsToProcess = _pendingActions; - _pendingActions = nil; - } - for (OIDAuthStateAction actionToProcess in actionsToProcess) { - actionToProcess(self.accessToken, self.idToken, error); - } - }); + // nil the pending queue and process everything that was queued up + NSArray *actionsToProcess; + @synchronized(self->_pendingActionsSyncObject) { + actionsToProcess = self->_pendingActions; + self->_pendingActions = nil; + } + for (OIDAuthStatePendingAction* actionToProcess in actionsToProcess) { + dispatch_async(actionToProcess.dispatchQueue, ^{ + actionToProcess.action(self.accessToken, self.idToken, error); + }); + } }]; } -#pragma mark - Deprecated - -- (void)withFreshTokensPerformAction:(OIDAuthStateAction)action { - [self performActionWithFreshTokens:action additionalRefreshParameters:nil]; -} - #pragma mark - /*! @fn isTokenFresh diff --git a/Source/OIDAuthStateChangeDelegate.h b/Sources/AppAuthCore/OIDAuthStateChangeDelegate.h similarity index 100% rename from Source/OIDAuthStateChangeDelegate.h rename to Sources/AppAuthCore/OIDAuthStateChangeDelegate.h diff --git a/Source/OIDAuthStateErrorDelegate.h b/Sources/AppAuthCore/OIDAuthStateErrorDelegate.h similarity index 100% rename from Source/OIDAuthStateErrorDelegate.h rename to Sources/AppAuthCore/OIDAuthStateErrorDelegate.h diff --git a/Source/OIDAuthorizationRequest.h b/Sources/AppAuthCore/OIDAuthorizationRequest.h similarity index 80% rename from Source/OIDAuthorizationRequest.h rename to Sources/AppAuthCore/OIDAuthorizationRequest.h index 971557ed9..c3a0cc0d8 100644 --- a/Source/OIDAuthorizationRequest.h +++ b/Sources/AppAuthCore/OIDAuthorizationRequest.h @@ -20,6 +20,7 @@ // These files only declare string constants useful for constructing a @c OIDAuthorizationRequest, // so they are imported here for convenience. +#import "OIDExternalUserAgentRequest.h" #import "OIDResponseTypes.h" #import "OIDScopes.h" @@ -37,20 +38,8 @@ extern NSString *const OIDOAuthorizationRequestCodeChallengeMethodS256; @see https://tools.ietf.org/html/rfc6749#section-4 @see https://tools.ietf.org/html/rfc6749#section-4.1.1 */ -@interface OIDAuthorizationRequest : NSObject { - // property variables - OIDServiceConfiguration *_configuration; - NSString *_responseType; - NSString *_clientID; - NSString *_clientSecret; - NSString *_scope; - NSURL *_redirectURL; - NSString *_state; - NSString *_codeVerifier; - NSString *_codeChallenge; - NSString *_codeChallengeMethod; - NSDictionary *_additionalParameters; -} +@interface OIDAuthorizationRequest : + NSObject /*! @brief The service's configuration. @remarks This configuration specifies how to connect to a particular OAuth provider. @@ -107,6 +96,17 @@ extern NSString *const OIDOAuthorizationRequestCodeChallengeMethodS256; */ @property(nonatomic, readonly, nullable) NSString *state; +/*! @brief String value used to associate a Client session with an ID Token, and to mitigate replay + attacks. The value is passed through unmodified from the Authentication Request to the ID + Token. Sufficient entropy MUST be present in the nonce values used to prevent attackers from + guessing values. + @remarks nonce + @discussion If this value is not explicitly set, this library will automatically add nonce and + perform appropriate validation of the nonce in the ID Token. + @see https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest + */ +@property(nonatomic, readonly, nullable) NSString *nonce; + /*! @brief The PKCE code verifier. @remarks code_verifier @discussion The code verifier itself is not included in the authorization request that is sent @@ -136,7 +136,7 @@ extern NSString *const OIDOAuthorizationRequestCodeChallengeMethodS256; /*! @internal @brief Unavailable. Please use - @c initWithConfiguration:clientId:scopes:redirectURL:additionalParameters:. + @c initWithConfiguration:clientId:scopes:redirectURL:responseType:additionalParameters:. */ - (instancetype)init NS_UNAVAILABLE; @@ -159,8 +159,31 @@ extern NSString *const OIDOAuthorizationRequestCodeChallengeMethodS256; responseType:(NSString *)responseType additionalParameters:(nullable NSDictionary *)additionalParameters; -/*! @brief Creates an authorization request with opinionated defaults (a secure @c state, and - PKCE with S256 as the @c code_challenge_method). +/*! @brief Creates an authorization request with custom nonce, a secure @c state, + and PKCE with S256 as the @c code_challenge_method. + @param configuration The service's configuration. + @param clientID The client identifier. + @param scopes An array of scopes to combine into a single scope string per the OAuth2 spec. + @param redirectURL The client's redirect URI. + @param responseType The expected response type. + @param nonce String value used to associate a Client session with an ID Token. Can be set to nil + if not using OpenID Connect, although pure OAuth servers should ignore params they don't + understand anyway. + @param additionalParameters The client's additional authorization parameters. + @remarks This convenience initializer generates a state parameter and PKCE challenges + automatically. + */ +- (instancetype) + initWithConfiguration:(OIDServiceConfiguration *)configuration + clientId:(NSString *)clientID + scopes:(nullable NSArray *)scopes + redirectURL:(NSURL *)redirectURL + responseType:(NSString *)responseType + nonce:(nullable NSString *)nonce + additionalParameters:(nullable NSDictionary *)additionalParameters; + +/*! @brief Creates an authorization request with opinionated defaults (a secure @c state, @c nonce, + and PKCE with S256 as the @c code_challenge_method). @param configuration The service's configuration. @param clientID The client identifier. @param clientSecret The client secret. @@ -188,6 +211,9 @@ extern NSString *const OIDOAuthorizationRequestCodeChallengeMethodS256; @param responseType The expected response type. @param state An opaque value used by the client to maintain state between the request and callback. + @param nonce String value used to associate a Client session with an ID Token. Can be set to nil + if not using OpenID Connect, although pure OAuth servers should ignore params they don't + understand anyway. @param codeVerifier The PKCE code verifier. See @c OIDAuthorizationRequest.generateCodeVerifier. @param codeChallenge The PKCE code challenge, calculated from the code verifier such as with @c OIDAuthorizationRequest.codeChallengeS256ForVerifier:. @@ -205,6 +231,7 @@ extern NSString *const OIDOAuthorizationRequestCodeChallengeMethodS256; redirectURL:(nullable NSURL *)redirectURL responseType:(NSString *)responseType state:(nullable NSString *)state + nonce:(nullable NSString *)nonce codeVerifier:(nullable NSString *)codeVerifier codeChallenge:(nullable NSString *)codeChallenge codeChallengeMethod:(nullable NSString *)codeChallengeMethod diff --git a/Source/OIDAuthorizationRequest.m b/Sources/AppAuthCore/OIDAuthorizationRequest.m similarity index 79% rename from Source/OIDAuthorizationRequest.m rename to Sources/AppAuthCore/OIDAuthorizationRequest.m index 3c00ecb78..1be1fdfde 100644 --- a/Source/OIDAuthorizationRequest.m +++ b/Sources/AppAuthCore/OIDAuthorizationRequest.m @@ -55,6 +55,10 @@ */ static NSString *const kStateKey = @"state"; +/*! @brief Key used to encode the @c nonce property for @c NSSecureCoding, and on the URL request. + */ +static NSString *const kNonceKey = @"nonce"; + /*! @brief Key used to encode the @c codeVerifier property for @c NSSecureCoding. */ static NSString *const kCodeVerifierKey = @"code_verifier"; @@ -83,7 +87,7 @@ /*! @brief Assertion text for unsupported response types. */ static NSString *const OIDOAuthUnsupportedResponseTypeMessage = - @"The response_type \"%@\" isn't supported. AppAuth only supports the \"code\" response_type."; + @"The response_type \"%@\" isn't supported. AppAuth only supports the \"code\" or \"code id_token\" response_type."; /*! @brief Code challenge request method. */ @@ -91,18 +95,6 @@ @implementation OIDAuthorizationRequest -@synthesize configuration = _configuration; -@synthesize responseType = _responseType; -@synthesize clientID = _clientID; -@synthesize clientSecret = _clientSecret; -@synthesize scope = _scope; -@synthesize redirectURL = _redirectURL; -@synthesize state = _state; -@synthesize codeVerifier = _codeVerifier; -@synthesize codeChallenge = _codeChallenge; -@synthesize codeChallengeMethod = _codeChallengeMethod; -@synthesize additionalParameters = _additionalParameters; - - (instancetype)init OID_UNAVAILABLE_USE_INITIALIZER( @selector(initWithConfiguration: @@ -111,7 +103,24 @@ - (instancetype)init redirectURL: responseType: additionalParameters:) - ); + ) + +/*! @brief Check if the response type is one AppAuth supports + @remarks AppAuth only supports the `code` and `code id_token` response types. + @see https://github.com/openid/AppAuth-iOS/issues/98 + @see https://github.com/openid/AppAuth-iOS/issues/292 + */ ++ (BOOL)isSupportedResponseType:(NSString *)responseType +{ + NSString *codeIdToken = [@[OIDResponseTypeCode, OIDResponseTypeIDToken] + componentsJoinedByString:@" "]; + NSString *idTokenCode = [@[OIDResponseTypeIDToken, OIDResponseTypeCode] + componentsJoinedByString:@" "]; + + return [responseType isEqualToString:OIDResponseTypeCode] + || [responseType isEqualToString:codeIdToken] + || [responseType isEqualToString:idTokenCode]; +} - (instancetype)initWithConfiguration:(OIDServiceConfiguration *)configuration clientId:(NSString *)clientID @@ -120,6 +129,7 @@ - (instancetype)initWithConfiguration:(OIDServiceConfiguration *)configuration redirectURL:(NSURL *)redirectURL responseType:(NSString *)responseType state:(nullable NSString *)state + nonce:(nullable NSString *)nonce codeVerifier:(nullable NSString *)codeVerifier codeChallenge:(nullable NSString *)codeChallenge codeChallengeMethod:(nullable NSString *)codeChallengeMethod @@ -133,15 +143,12 @@ - (instancetype)initWithConfiguration:(OIDServiceConfiguration *)configuration _scope = [scope copy]; _redirectURL = [redirectURL copy]; _responseType = [responseType copy]; - // Attention: Please refer to https://github.com/openid/AppAuth-iOS/issues/105 - // If you change the restriction on response type here, you must also update initWithCoder: - if (![_responseType isEqualToString:OIDResponseTypeCode]) { - // AppAuth only supports the `code` response type. - // Discussion: https://github.com/openid/AppAuth-iOS/issues/98 + if (![[self class] isSupportedResponseType:_responseType]) { NSAssert(NO, OIDOAuthUnsupportedResponseTypeMessage, _responseType); return nil; } _state = [state copy]; + _nonce = [nonce copy]; _codeVerifier = [codeVerifier copy]; _codeChallenge = [codeChallenge copy]; _codeChallengeMethod = [codeChallengeMethod copy]; @@ -172,6 +179,7 @@ - (instancetype)initWithConfiguration:(OIDServiceConfiguration *)configuration redirectURL:redirectURL responseType:responseType state:[[self class] generateState] + nonce:[[self class] generateState] codeVerifier:codeVerifier codeChallenge:codeChallenge codeChallengeMethod:OIDOAuthorizationRequestCodeChallengeMethodS256 @@ -194,6 +202,32 @@ - (instancetype)initWithConfiguration:(OIDServiceConfiguration *)configuration additionalParameters:additionalParameters]; } +- (instancetype) + initWithConfiguration:(OIDServiceConfiguration *)configuration + clientId:(NSString *)clientID + scopes:(nullable NSArray *)scopes + redirectURL:(NSURL *)redirectURL + responseType:(NSString *)responseType + nonce:(nullable NSString *)nonce + additionalParameters:(nullable NSDictionary *)additionalParameters { + // generates PKCE code verifier and challenge + NSString *codeVerifier = [[self class] generateCodeVerifier]; + NSString *codeChallenge = [[self class] codeChallengeS256ForVerifier:codeVerifier]; + + return [self initWithConfiguration:configuration + clientId:clientID + clientSecret:nil + scope:[OIDScopeUtilities scopesWithArray:scopes] + redirectURL:redirectURL + responseType:responseType + state:[[self class] generateState] + nonce:nonce + codeVerifier:codeVerifier + codeChallenge:codeChallenge + codeChallengeMethod:OIDOAuthorizationRequestCodeChallengeMethodS256 + additionalParameters:additionalParameters]; +} + #pragma mark - NSCopying - (instancetype)copyWithZone:(nullable NSZone *)zone { @@ -214,17 +248,13 @@ - (instancetype)initWithCoder:(NSCoder *)aDecoder { OIDServiceConfiguration *configuration = [aDecoder decodeObjectOfClass:[OIDServiceConfiguration class] forKey:kConfigurationKey]; - // Attention: Please refer to https://github.com/openid/AppAuth-iOS/issues/105 - // If the initializer relaxes it's restriction on the response type field, this code must also - // be updated to re-enable use of the serialized responseType value. The value of 'code' here - // is only a valid assumption for that reason. - // [aDecoder decodeObjectOfClass:[NSString class] forKey:kResponseTypeKey]; - NSString *responseType = OIDResponseTypeCode; + NSString *responseType = [aDecoder decodeObjectOfClass:[NSString class] forKey:kResponseTypeKey]; NSString *clientID = [aDecoder decodeObjectOfClass:[NSString class] forKey:kClientIDKey]; NSString *clientSecret = [aDecoder decodeObjectOfClass:[NSString class] forKey:kClientSecretKey]; NSString *scope = [aDecoder decodeObjectOfClass:[NSString class] forKey:kScopeKey]; NSURL *redirectURL = [aDecoder decodeObjectOfClass:[NSURL class] forKey:kRedirectURLKey]; NSString *state = [aDecoder decodeObjectOfClass:[NSString class] forKey:kStateKey]; + NSString *nonce = [aDecoder decodeObjectOfClass:[NSString class] forKey:kNonceKey]; NSString *codeVerifier = [aDecoder decodeObjectOfClass:[NSString class] forKey:kCodeVerifierKey]; NSString *codeChallenge = [aDecoder decodeObjectOfClass:[NSString class] forKey:kCodeChallengeKey]; @@ -245,6 +275,7 @@ - (instancetype)initWithCoder:(NSCoder *)aDecoder { redirectURL:redirectURL responseType:responseType state:state + nonce:nonce codeVerifier:codeVerifier codeChallenge:codeChallenge codeChallengeMethod:codeChallengeMethod @@ -260,6 +291,7 @@ - (void)encodeWithCoder:(NSCoder *)aCoder { [aCoder encodeObject:_scope forKey:kScopeKey]; [aCoder encodeObject:_redirectURL forKey:kRedirectURLKey]; [aCoder encodeObject:_state forKey:kStateKey]; + [aCoder encodeObject:_nonce forKey:kNonceKey]; [aCoder encodeObject:_codeVerifier forKey:kCodeVerifierKey]; [aCoder encodeObject:_codeChallenge forKey:kCodeChallengeKey]; [aCoder encodeObject:_codeChallengeMethod forKey:kCodeChallengeMethodKey]; @@ -271,7 +303,7 @@ - (void)encodeWithCoder:(NSCoder *)aCoder { - (NSString *)description { return [NSString stringWithFormat:@"<%@: %p, request: %@>", NSStringFromClass([self class]), - self, + (void *)self, self.authorizationRequestURL]; } @@ -292,7 +324,7 @@ + (nullable NSString *)codeChallengeS256ForVerifier:(NSString *)codeVerifier { // generates the code_challenge per spec https://tools.ietf.org/html/rfc7636#section-4.2 // code_challenge = BASE64URL-ENCODE(SHA256(ASCII(code_verifier))) // NB. the ASCII conversion on the code_verifier entropy was done at time of generation. - NSData *sha256Verifier = [OIDTokenUtilities sha265:codeVerifier]; + NSData *sha256Verifier = [OIDTokenUtilities sha256:codeVerifier]; return [OIDTokenUtilities encodeBase64urlNoPadding:sha256Verifier]; } @@ -318,6 +350,9 @@ - (NSURL *)authorizationRequestURL { if (_state) { [query addParameter:kStateKey value:_state]; } + if (_nonce) { + [query addParameter:kNonceKey value:_nonce]; + } if (_codeChallenge) { [query addParameter:kCodeChallengeKey value:_codeChallenge]; } @@ -329,4 +364,14 @@ - (NSURL *)authorizationRequestURL { return [query URLByReplacingQueryInURL:_configuration.authorizationEndpoint]; } +#pragma mark - OIDExternalUserAgentRequest + +- (NSURL *)externalUserAgentRequestURL { + return [self authorizationRequestURL]; +} + +- (NSString *)redirectScheme { + return [[self redirectURL] scheme]; +} + @end diff --git a/Source/OIDAuthorizationResponse.h b/Sources/AppAuthCore/OIDAuthorizationResponse.h similarity index 86% rename from Source/OIDAuthorizationResponse.h rename to Sources/AppAuthCore/OIDAuthorizationResponse.h index 57b3af7b2..a9af1864b 100644 --- a/Source/OIDAuthorizationResponse.h +++ b/Sources/AppAuthCore/OIDAuthorizationResponse.h @@ -28,18 +28,7 @@ NS_ASSUME_NONNULL_BEGIN @see https://tools.ietf.org/html/rfc6749#section-5.1 @see http://openid.net/specs/openid-connect-core-1_0.html#ImplicitAuthResponse */ -@interface OIDAuthorizationResponse : NSObject { - // property variables - OIDAuthorizationRequest *_request; - NSString *_authorizationCode; - NSString *_state; - NSString *_accessToken; - NSDate *_accessTokenExpirationDate; - NSString *_tokenType; - NSString *_idToken; - NSString *_scope; - NSDictionary *> *_additionalParameters; -} +@interface OIDAuthorizationResponse : NSObject /*! @brief The request which was serviced. */ @@ -101,7 +90,7 @@ NS_ASSUME_NONNULL_BEGIN NSDictionary *> *additionalParameters; /*! @internal - @brief Unavailable. Please use initWithParameters:. + @brief Unavailable. Please use initWithRequest:parameters:. */ - (instancetype)init NS_UNAVAILABLE; @@ -134,6 +123,19 @@ NS_ASSUME_NONNULL_BEGIN - (nullable OIDTokenRequest *)tokenExchangeRequestWithAdditionalParameters: (nullable NSDictionary *)additionalParameters; +/*! @brief Creates a token request suitable for exchanging an authorization code for an access + token. + @param additionalParameters Additional parameters for the token request. + @param additionalHeaders Additional headers for the token request. + @return A @c OIDTokenRequest suitable for exchanging an authorization code for an access + token. + @see https://tools.ietf.org/html/rfc6749#section-4.1.3 + */ +- (nullable OIDTokenRequest *)tokenExchangeRequestWithAdditionalParameters: + (nullable NSDictionary *)additionalParameters + additionalHeaders: + (nullable NSDictionary *)additionalHeaders; + @end NS_ASSUME_NONNULL_END diff --git a/Source/OIDAuthorizationResponse.m b/Sources/AppAuthCore/OIDAuthorizationResponse.m similarity index 91% rename from Source/OIDAuthorizationResponse.m rename to Sources/AppAuthCore/OIDAuthorizationResponse.m index 4376f2cc4..957f81d3a 100644 --- a/Source/OIDAuthorizationResponse.m +++ b/Sources/AppAuthCore/OIDAuthorizationResponse.m @@ -23,6 +23,7 @@ #import "OIDError.h" #import "OIDFieldMapping.h" #import "OIDTokenRequest.h" +#import "OIDTokenUtilities.h" /*! @brief The key for the @c authorizationCode property in the incoming parameters and for @c NSSecureCoding. @@ -73,16 +74,6 @@ @implementation OIDAuthorizationResponse -@synthesize request = _request; -@synthesize authorizationCode = _authorizationCode; -@synthesize state = _state; -@synthesize accessToken = _accessToken; -@synthesize accessTokenExpirationDate = _accessTokenExpirationDate; -@synthesize tokenType = _tokenType; -@synthesize idToken = _idToken; -@synthesize scope = _scope; -@synthesize additionalParameters = _additionalParameters; - /*! @brief Returns a mapping of incoming parameters to instance variables. @return A mapping of incoming parameters to instance variables. */ @@ -120,7 +111,7 @@ @implementation OIDAuthorizationResponse #pragma mark - Initializers - (instancetype)init - OID_UNAVAILABLE_USE_INITIALIZER(@selector(initWithRequest:parameters:)); + OID_UNAVAILABLE_USE_INITIALIZER(@selector(initWithRequest:parameters:)) - (instancetype)initWithRequest:(OIDAuthorizationRequest *)request parameters:(NSDictionary *> *)parameters { @@ -178,13 +169,13 @@ - (NSString *)description { "idToken: \"%@\", scope: \"%@\", additionalParameters: %@, " "request: %@>", NSStringFromClass([self class]), - self, + (void *)self, _authorizationCode, _state, - _accessToken, + [OIDTokenUtilities redact:_accessToken], _accessTokenExpirationDate, _tokenType, - _idToken, + [OIDTokenUtilities redact:_idToken], _scope, _additionalParameters, _request]; @@ -193,11 +184,19 @@ - (NSString *)description { #pragma mark - - (OIDTokenRequest *)tokenExchangeRequest { - return [self tokenExchangeRequestWithAdditionalParameters:nil]; + return [self tokenExchangeRequestWithAdditionalParameters:nil additionalHeaders:nil]; } - (OIDTokenRequest *)tokenExchangeRequestWithAdditionalParameters: (NSDictionary *)additionalParameters { + return [self tokenExchangeRequestWithAdditionalParameters:additionalParameters + additionalHeaders:nil]; +} + +- (OIDTokenRequest *)tokenExchangeRequestWithAdditionalParameters: + (NSDictionary *)additionalParameters + additionalHeaders: + (NSDictionary *)additionalHeaders { // TODO: add a unit test to confirm exception is thrown when expected and the request is created // with the correct parameters. if (!_authorizationCode) { @@ -213,7 +212,8 @@ - (OIDTokenRequest *)tokenExchangeRequestWithAdditionalParameters: scope:nil refreshToken:nil codeVerifier:_request.codeVerifier - additionalParameters:additionalParameters]; + additionalParameters:additionalParameters + additionalHeaders:additionalHeaders]; } @end diff --git a/Source/OIDAuthorizationService.h b/Sources/AppAuthCore/OIDAuthorizationService.h similarity index 68% rename from Source/OIDAuthorizationService.h rename to Sources/AppAuthCore/OIDAuthorizationService.h index fe80c0b41..c8fee5358 100644 --- a/Source/OIDAuthorizationService.h +++ b/Sources/AppAuthCore/OIDAuthorizationService.h @@ -21,13 +21,15 @@ @class OIDAuthorization; @class OIDAuthorizationRequest; @class OIDAuthorizationResponse; +@class OIDEndSessionRequest; +@class OIDEndSessionResponse; @class OIDRegistrationRequest; @class OIDRegistrationResponse; @class OIDServiceConfiguration; @class OIDTokenRequest; @class OIDTokenResponse; -@protocol OIDAuthorizationFlowSession; -@protocol OIDAuthorizationUICoordinator; +@protocol OIDExternalUserAgent; +@protocol OIDExternalUserAgentSession; NS_ASSUME_NONNULL_BEGIN @@ -47,6 +49,13 @@ typedef void (^OIDDiscoveryCallback)(OIDServiceConfiguration *_Nullable configur typedef void (^OIDAuthorizationCallback)(OIDAuthorizationResponse *_Nullable authorizationResponse, NSError *_Nullable error); +/*! @brief Block used as a callback for the end-session request of @c OIDAuthorizationService. + @param endSessionResponse The end-session response, if available. + @param error The error if an error occurred. + */ +typedef void (^OIDEndSessionCallback)(OIDEndSessionResponse *_Nullable endSessionResponse, + NSError *_Nullable error); + /*! @brief Represents the type of block used as a callback for various methods of @c OIDAuthorizationService. @param tokenResponse The token response, if available. @@ -71,10 +80,7 @@ typedef void (^OIDRegistrationCompletion)(OIDRegistrationResponse *_Nullable reg /*! @brief Performs various OAuth and OpenID Connect related calls via the user agent or \NSURLSession. */ -@interface OIDAuthorizationService : NSObject { - // property variables - OIDServiceConfiguration *_configuration; -} +@interface OIDAuthorizationService : NSObject /*! @brief The service's configuration. @remarks Each authorization service is initialized with a configuration. This configuration @@ -112,17 +118,30 @@ typedef void (^OIDRegistrationCompletion)(OIDRegistrationResponse *_Nullable reg /*! @brief Perform an authorization flow using a generic flow shim. @param request The authorization request. - @param UICoordinator Generic authorization UI coordinator that can present an authorization + @param externalUserAgent Generic external user-agent that can present an authorization request. @param callback The method called when the request has completed or failed. - @return A @c OIDAuthorizationFlowSession instance which will terminate when it - receives a @c OIDAuthorizationFlowSession.cancel message, or after processing a - @c OIDAuthorizationFlowSession.resumeAuthorizationFlowWithURL: message. + @return A @c OIDExternalUserAgentSession instance which will terminate when it + receives a @c OIDExternalUserAgentSession.cancel message, or after processing a + @c OIDExternalUserAgentSession.resumeExternalUserAgentFlowWithURL: message. + */ ++ (id) presentAuthorizationRequest:(OIDAuthorizationRequest *)request + externalUserAgent:(id)externalUserAgent + callback:(OIDAuthorizationCallback)callback; + +/*! @brief Perform a logout request. + @param request The end-session logout request. + @param externalUserAgent Generic external user-agent that can present user-agent requests. + @param callback The method called when the request has completed or failed. + @return A @c OIDExternalUserAgentSession instance which will terminate when it + receives a @c OIDExternalUserAgentSession.cancel message, or after processing a + @c OIDExternalUserAgentSession.resumeExternalUserAgentFlowWithURL: message. + @see http://openid.net/specs/openid-connect-session-1_0.html#RPLogout */ -+ (id) - presentAuthorizationRequest:(OIDAuthorizationRequest *)request - UICoordinator:(id)UICoordinator - callback:(OIDAuthorizationCallback)callback; ++ (id) + presentEndSessionRequest:(OIDEndSessionRequest *)request + externalUserAgent:(id)externalUserAgent + callback:(OIDEndSessionCallback)callback; /*! @brief Performs a token request. @param request The token request. @@ -130,6 +149,15 @@ typedef void (^OIDRegistrationCompletion)(OIDRegistrationResponse *_Nullable reg */ + (void)performTokenRequest:(OIDTokenRequest *)request callback:(OIDTokenCallback)callback; +/*! @brief Performs a token request. + @param request The token request. + @param authorizationResponse The original authorization response related to this token request. + @param callback The method called when the request has completed or failed. + */ ++ (void)performTokenRequest:(OIDTokenRequest *)request + originalAuthorizationResponse:(OIDAuthorizationResponse *_Nullable)authorizationResponse + callback:(OIDTokenCallback)callback; + /*! @brief Performs a registration request. @param request The registration request. @param completion The method called when the request has completed or failed. @@ -139,38 +167,4 @@ typedef void (^OIDRegistrationCompletion)(OIDRegistrationResponse *_Nullable reg @end -/*! @brief Represents an in-flight authorization flow session. - */ -@protocol OIDAuthorizationFlowSession - -/*! @brief Cancels the code flow session, invoking the request's callback with a cancelled error. - @remarks Has no effect if called more than once, or after a - @c OIDAuthorizationFlowSession.resumeAuthorizationFlowWithURL: message was received. Will - cause an error with code: @c ::OIDErrorCodeProgramCanceledAuthorizationFlow to be passed to - the @c callback block passed to - @c OIDAuthorizationService.presentAuthorizationRequest:presentingViewController:callback: - */ -- (void)cancel; - -/*! @brief Clients should call this method with the result of the authorization code flow if it - becomes available. - @param URL The redirect URL invoked by the authorization server. - @discussion When the URL represented a valid authorization response, implementations - should clean up any left-over UI state from the authorization, for example by - closing the \SFSafariViewController or looback HTTP listener if those were used. - The completion block of the pending authorization request should then be invoked. - @remarks Has no effect if called more than once, or after a @c cancel message was received. - @return YES if the passed URL matches the expected redirect URL and was consumed, NO otherwise. - */ -- (BOOL)resumeAuthorizationFlowWithURL:(NSURL *)URL; - -/*! @brief @c OIDAuthorizationUICoordinator or clients should call this method when the - authorization flow failed with a non-OAuth error. - @param error The error that is the reason for the failure of this authorization flow. - @remarks Has no effect if called more than once, or after a @c cancel message was received. - */ -- (void)failAuthorizationFlowWithError:(NSError *)error; - -@end - NS_ASSUME_NONNULL_END diff --git a/Sources/AppAuthCore/OIDAuthorizationService.m b/Sources/AppAuthCore/OIDAuthorizationService.m new file mode 100644 index 000000000..cc749a3f9 --- /dev/null +++ b/Sources/AppAuthCore/OIDAuthorizationService.m @@ -0,0 +1,790 @@ +/*! @file OIDAuthorizationService.m + @brief AppAuth iOS SDK + @copyright + Copyright 2015 Google Inc. All Rights Reserved. + @copydetails + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#import "OIDAuthorizationService.h" + +#import "OIDAuthorizationRequest.h" +#import "OIDAuthorizationResponse.h" +#import "OIDDefines.h" +#import "OIDEndSessionRequest.h" +#import "OIDEndSessionResponse.h" +#import "OIDErrorUtilities.h" +#import "OIDExternalUserAgent.h" +#import "OIDExternalUserAgentSession.h" +#import "OIDIDToken.h" +#import "OIDRegistrationRequest.h" +#import "OIDRegistrationResponse.h" +#import "OIDServiceConfiguration.h" +#import "OIDServiceDiscovery.h" +#import "OIDTokenRequest.h" +#import "OIDTokenResponse.h" +#import "OIDURLQueryComponent.h" +#import "OIDURLSessionProvider.h" + +/*! @brief Path appended to an OpenID Connect issuer for discovery + @see https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfig + */ +static NSString *const kOpenIDConfigurationWellKnownPath = @".well-known/openid-configuration"; + +/*! @brief Max allowable iat (Issued At) time skew + @see https://openid.net/specs/openid-connect-core-1_0.html#IDTokenValidation + */ +static int const kOIDAuthorizationSessionIATMaxSkew = 600; + +NS_ASSUME_NONNULL_BEGIN + +@interface OIDAuthorizationSession : NSObject + +- (instancetype)init NS_UNAVAILABLE; + +- (instancetype)initWithRequest:(OIDAuthorizationRequest *)request + NS_DESIGNATED_INITIALIZER; + +@end + +@implementation OIDAuthorizationSession { + OIDAuthorizationRequest *_request; + id _externalUserAgent; + OIDAuthorizationCallback _pendingauthorizationFlowCallback; +} + +- (instancetype)initWithRequest:(OIDAuthorizationRequest *)request { + self = [super init]; + if (self) { + _request = [request copy]; + } + return self; +} + +- (void)presentAuthorizationWithExternalUserAgent:(id)externalUserAgent + callback:(OIDAuthorizationCallback)authorizationFlowCallback { + _externalUserAgent = externalUserAgent; + _pendingauthorizationFlowCallback = authorizationFlowCallback; + BOOL authorizationFlowStarted = + [_externalUserAgent presentExternalUserAgentRequest:_request session:self]; + if (!authorizationFlowStarted) { + NSError *safariError = [OIDErrorUtilities errorWithCode:OIDErrorCodeSafariOpenError + underlyingError:nil + description:@"Unable to open Safari."]; + [self didFinishWithResponse:nil error:safariError]; + } +} + +- (void)cancel { + [self cancelWithCompletion:nil]; +} + +- (void)cancelWithCompletion:(nullable void (^)(void))completion { + [_externalUserAgent dismissExternalUserAgentAnimated:YES completion:^{ + NSError *error = [OIDErrorUtilities errorWithCode:OIDErrorCodeUserCanceledAuthorizationFlow + underlyingError:nil + description:@"Authorization flow was cancelled."]; + [self didFinishWithResponse:nil error:error]; + if (completion) completion(); + }]; +} + +/*! @brief Does the redirection URL equal another URL down to the path component? + @param URL The first redirect URI to compare. + @param redirectionURL The second redirect URI to compare. + @return YES if the URLs match down to the path level (query params are ignored). + */ ++ (BOOL)URL:(NSURL *)URL matchesRedirectionURL:(NSURL *)redirectionURL { + NSURL *standardizedURL = [URL standardizedURL]; + NSURL *standardizedRedirectURL = [redirectionURL standardizedURL]; + + return [standardizedURL.scheme caseInsensitiveCompare:standardizedRedirectURL.scheme] == NSOrderedSame + && OIDIsEqualIncludingNil(standardizedURL.user, standardizedRedirectURL.user) + && OIDIsEqualIncludingNil(standardizedURL.password, standardizedRedirectURL.password) + && OIDIsEqualIncludingNil(standardizedURL.host, standardizedRedirectURL.host) + && OIDIsEqualIncludingNil(standardizedURL.port, standardizedRedirectURL.port) + && OIDIsEqualIncludingNil(standardizedURL.path, standardizedRedirectURL.path); +} + +- (BOOL)shouldHandleURL:(NSURL *)URL { + return [[self class] URL:URL matchesRedirectionURL:_request.redirectURL]; +} + +- (BOOL)resumeExternalUserAgentFlowWithURL:(NSURL *)URL { + // rejects URLs that don't match redirect (these may be completely unrelated to the authorization) + if (![self shouldHandleURL:URL]) { + return NO; + } + + AppAuthRequestTrace(@"Authorization Response: %@", URL); + + // checks for an invalid state + if (!_pendingauthorizationFlowCallback) { + [NSException raise:OIDOAuthExceptionInvalidAuthorizationFlow + format:@"%@", OIDOAuthExceptionInvalidAuthorizationFlow, nil]; + } + + OIDURLQueryComponent *query = [[OIDURLQueryComponent alloc] initWithURL:URL]; + + NSError *error; + OIDAuthorizationResponse *response = nil; + + // checks for an OAuth error response as per RFC6749 Section 4.1.2.1 + if (query.dictionaryValue[OIDOAuthErrorFieldError]) { + error = [OIDErrorUtilities OAuthErrorWithDomain:OIDOAuthAuthorizationErrorDomain + OAuthResponse:query.dictionaryValue + underlyingError:nil]; + } + + // no error, should be a valid OAuth 2.0 response + if (!error) { + response = [[OIDAuthorizationResponse alloc] initWithRequest:_request + parameters:query.dictionaryValue]; + + // verifies that the state in the response matches the state in the request, or both are nil + if (!OIDIsEqualIncludingNil(_request.state, response.state)) { + NSMutableDictionary *userInfo = [query.dictionaryValue mutableCopy]; + userInfo[NSLocalizedDescriptionKey] = + [NSString stringWithFormat:@"State mismatch, expecting %@ but got %@ in authorization " + "response %@", + _request.state, + response.state, + response]; + response = nil; + error = [NSError errorWithDomain:OIDOAuthAuthorizationErrorDomain + code:OIDErrorCodeOAuthAuthorizationClientError + userInfo:userInfo]; + } + } + + [_externalUserAgent dismissExternalUserAgentAnimated:YES completion:^{ + [self didFinishWithResponse:response error:error]; + }]; + + return YES; +} + +- (void)failExternalUserAgentFlowWithError:(NSError *)error { + [self didFinishWithResponse:nil error:error]; +} + +/*! @brief Invokes the pending callback and performs cleanup. + @param response The authorization response, if any to return to the callback. + @param error The error, if any, to return to the callback. + */ +- (void)didFinishWithResponse:(nullable OIDAuthorizationResponse *)response + error:(nullable NSError *)error { + OIDAuthorizationCallback callback = _pendingauthorizationFlowCallback; + _pendingauthorizationFlowCallback = nil; + _externalUserAgent = nil; + if (callback) { + callback(response, error); + } +} + +@end + +@interface OIDEndSessionImplementation : NSObject { + // private variables + OIDEndSessionRequest *_request; + id _externalUserAgent; + OIDEndSessionCallback _pendingEndSessionCallback; +} +- (instancetype)init NS_UNAVAILABLE; + +- (instancetype)initWithRequest:(OIDEndSessionRequest *)request + NS_DESIGNATED_INITIALIZER; +@end + + +@implementation OIDEndSessionImplementation + +- (instancetype)initWithRequest:(OIDEndSessionRequest *)request { + self = [super init]; + if (self) { + _request = [request copy]; + } + return self; +} + +- (void)presentAuthorizationWithExternalUserAgent:(id)externalUserAgent + callback:(OIDEndSessionCallback)authorizationFlowCallback { + _externalUserAgent = externalUserAgent; + _pendingEndSessionCallback = authorizationFlowCallback; + BOOL authorizationFlowStarted = + [_externalUserAgent presentExternalUserAgentRequest:_request session:self]; + if (!authorizationFlowStarted) { + NSError *safariError = [OIDErrorUtilities errorWithCode:OIDErrorCodeSafariOpenError + underlyingError:nil + description:@"Unable to open Safari."]; + [self didFinishWithResponse:nil error:safariError]; + } +} + +- (void)cancel { + [self cancelWithCompletion:nil]; +} + +- (void)cancelWithCompletion:(nullable void (^)(void))completion { + [_externalUserAgent dismissExternalUserAgentAnimated:YES completion:^{ + NSError *error = [OIDErrorUtilities + errorWithCode:OIDErrorCodeUserCanceledAuthorizationFlow + underlyingError:nil + description:nil]; + [self didFinishWithResponse:nil error:error]; + if (completion) completion(); + }]; +} + +- (BOOL)shouldHandleURL:(NSURL *)URL { + // The logic of when to handle the URL is the same as for authorization requests: should match + // down to the path component. + return [[OIDAuthorizationSession class] URL:URL + matchesRedirectionURL:_request.postLogoutRedirectURL]; +} + +- (BOOL)resumeExternalUserAgentFlowWithURL:(NSURL *)URL { + // rejects URLs that don't match redirect (these may be completely unrelated to the authorization) + if (![self shouldHandleURL:URL]) { + return NO; + } + // checks for an invalid state + if (!_pendingEndSessionCallback) { + [NSException raise:OIDOAuthExceptionInvalidAuthorizationFlow + format:@"%@", OIDOAuthExceptionInvalidAuthorizationFlow, nil]; + } + + + NSError *error; + OIDEndSessionResponse *response = nil; + + OIDURLQueryComponent *query = [[OIDURLQueryComponent alloc] initWithURL:URL]; + response = [[OIDEndSessionResponse alloc] initWithRequest:_request + parameters:query.dictionaryValue]; + + // verifies that the state in the response matches the state in the request, or both are nil + if (!OIDIsEqualIncludingNil(_request.state, response.state)) { + NSMutableDictionary *userInfo = [query.dictionaryValue mutableCopy]; + userInfo[NSLocalizedDescriptionKey] = + [NSString stringWithFormat:@"State mismatch, expecting %@ but got %@ in authorization " + "response %@", + _request.state, + response.state, + response]; + response = nil; + error = [NSError errorWithDomain:OIDOAuthAuthorizationErrorDomain + code:OIDErrorCodeOAuthAuthorizationClientError + userInfo:userInfo]; + } + + [_externalUserAgent dismissExternalUserAgentAnimated:YES completion:^{ + [self didFinishWithResponse:response error:error]; + }]; + + return YES; +} + +- (void)failExternalUserAgentFlowWithError:(NSError *)error { + [self didFinishWithResponse:nil error:error]; +} + +/*! @brief Invokes the pending callback and performs cleanup. + @param response The authorization response, if any to return to the callback. + @param error The error, if any, to return to the callback. + */ +- (void)didFinishWithResponse:(nullable OIDEndSessionResponse *)response + error:(nullable NSError *)error { + OIDEndSessionCallback callback = _pendingEndSessionCallback; + _pendingEndSessionCallback = nil; + _externalUserAgent = nil; + if (callback) { + callback(response, error); + } +} + +@end + +@implementation OIDAuthorizationService + ++ (void)discoverServiceConfigurationForIssuer:(NSURL *)issuerURL + completion:(OIDDiscoveryCallback)completion { + NSURL *fullDiscoveryURL = + [issuerURL URLByAppendingPathComponent:kOpenIDConfigurationWellKnownPath]; + + [[self class] discoverServiceConfigurationForDiscoveryURL:fullDiscoveryURL + completion:completion]; +} + ++ (void)discoverServiceConfigurationForDiscoveryURL:(NSURL *)discoveryURL + completion:(OIDDiscoveryCallback)completion { + + NSURLSession *session = [OIDURLSessionProvider session]; + NSURLSessionDataTask *task = + [session dataTaskWithURL:discoveryURL + completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) { + // If we got any sort of error, just report it. + if (error || !data) { + NSString *errorDescription = + [NSString stringWithFormat:@"Connection error fetching discovery document '%@': %@.", + discoveryURL, + error.localizedDescription]; + error = [OIDErrorUtilities errorWithCode:OIDErrorCodeNetworkError + underlyingError:error + description:errorDescription]; + dispatch_async(dispatch_get_main_queue(), ^{ + completion(nil, error); + }); + return; + } + + NSHTTPURLResponse *urlResponse = (NSHTTPURLResponse *)response; + + // Check for non-200 status codes. + // https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfigurationResponse + if (urlResponse.statusCode != 200) { + NSError *URLResponseError = [OIDErrorUtilities HTTPErrorWithHTTPResponse:urlResponse + data:data]; + NSString *errorDescription = + [NSString stringWithFormat:@"Non-200 HTTP response (%d) fetching discovery document " + "'%@'.", + (int)urlResponse.statusCode, + discoveryURL]; + error = [OIDErrorUtilities errorWithCode:OIDErrorCodeNetworkError + underlyingError:URLResponseError + description:errorDescription]; + dispatch_async(dispatch_get_main_queue(), ^{ + completion(nil, error); + }); + return; + } + + // Construct an OIDServiceDiscovery with the received JSON. + OIDServiceDiscovery *discovery = + [[OIDServiceDiscovery alloc] initWithJSONData:data error:&error]; + if (error || !discovery) { + NSString *errorDescription = + [NSString stringWithFormat:@"JSON error parsing document at '%@': %@", + discoveryURL, + error.localizedDescription]; + error = [OIDErrorUtilities errorWithCode:OIDErrorCodeNetworkError + underlyingError:error + description:errorDescription]; + dispatch_async(dispatch_get_main_queue(), ^{ + completion(nil, error); + }); + return; + } + + // Create our service configuration with the discovery document and return it. + OIDServiceConfiguration *configuration = + [[OIDServiceConfiguration alloc] initWithDiscoveryDocument:discovery]; + dispatch_async(dispatch_get_main_queue(), ^{ + completion(configuration, nil); + }); + }]; + [task resume]; +} + +#pragma mark - Authorization Endpoint + ++ (id) presentAuthorizationRequest:(OIDAuthorizationRequest *)request + externalUserAgent:(id)externalUserAgent + callback:(OIDAuthorizationCallback)callback { + + AppAuthRequestTrace(@"Authorization Request: %@", request); + + OIDAuthorizationSession *flowSession = [[OIDAuthorizationSession alloc] initWithRequest:request]; + [flowSession presentAuthorizationWithExternalUserAgent:externalUserAgent callback:callback]; + return flowSession; +} + ++ (id) + presentEndSessionRequest:(OIDEndSessionRequest *)request + externalUserAgent:(id)externalUserAgent + callback:(OIDEndSessionCallback)callback { + OIDEndSessionImplementation *flowSession = + [[OIDEndSessionImplementation alloc] initWithRequest:request]; + [flowSession presentAuthorizationWithExternalUserAgent:externalUserAgent callback:callback]; + return flowSession; +} + +#pragma mark - Token Endpoint + ++ (void)performTokenRequest:(OIDTokenRequest *)request callback:(OIDTokenCallback)callback { + [[self class] performTokenRequest:request + originalAuthorizationResponse:nil + callback:callback]; +} + ++ (void)performTokenRequest:(OIDTokenRequest *)request + originalAuthorizationResponse:(OIDAuthorizationResponse *_Nullable)authorizationResponse + callback:(OIDTokenCallback)callback { + + NSURLRequest *URLRequest = [request URLRequest]; + + AppAuthRequestTrace(@"Token Request: %@\nHeaders:%@\nHTTPBody: %@", + URLRequest.URL, + URLRequest.allHTTPHeaderFields, + [[NSString alloc] initWithData:URLRequest.HTTPBody + encoding:NSUTF8StringEncoding]); + + NSURLSession *session = [OIDURLSessionProvider session]; + [[session dataTaskWithRequest:URLRequest + completionHandler:^(NSData *_Nullable data, + NSURLResponse *_Nullable response, + NSError *_Nullable error) { + if (error) { + // A network error or server error occurred. + NSString *errorDescription = + [NSString stringWithFormat:@"Connection error making token request to '%@': %@.", + URLRequest.URL, + error.localizedDescription]; + NSError *returnedError = + [OIDErrorUtilities errorWithCode:OIDErrorCodeNetworkError + underlyingError:error + description:errorDescription]; + dispatch_async(dispatch_get_main_queue(), ^{ + callback(nil, returnedError); + }); + return; + } + + NSHTTPURLResponse *HTTPURLResponse = (NSHTTPURLResponse *)response; + NSInteger statusCode = HTTPURLResponse.statusCode; + AppAuthRequestTrace(@"Token Response: HTTP Status %d\nHTTPBody: %@", + (int)statusCode, + [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]); + if (statusCode != 200) { + // A server error occurred. + NSError *serverError = + [OIDErrorUtilities HTTPErrorWithHTTPResponse:HTTPURLResponse data:data]; + + // HTTP 4xx may indicate an RFC6749 Section 5.2 error response, attempts to parse as such. + if (statusCode >= 400 && statusCode < 500) { + NSError *jsonDeserializationError; + NSDictionary *> *json = + [NSJSONSerialization JSONObjectWithData:data options:0 error:&jsonDeserializationError]; + + // If the HTTP 4xx response parses as JSON and has an 'error' key, it's an OAuth error. + // These errors are special as they indicate a problem with the authorization grant. + if (json[OIDOAuthErrorFieldError]) { + NSError *oauthError = + [OIDErrorUtilities OAuthErrorWithDomain:OIDOAuthTokenErrorDomain + OAuthResponse:json + underlyingError:serverError]; + dispatch_async(dispatch_get_main_queue(), ^{ + callback(nil, oauthError); + }); + return; + } + } + + // Status code indicates this is an error, but not an RFC6749 Section 5.2 error. + NSString *errorDescription = + [NSString stringWithFormat:@"Non-200 HTTP response (%d) making token request to '%@'.", + (int)statusCode, + URLRequest.URL]; + NSError *returnedError = + [OIDErrorUtilities errorWithCode:OIDErrorCodeServerError + underlyingError:serverError + description:errorDescription]; + dispatch_async(dispatch_get_main_queue(), ^{ + callback(nil, returnedError); + }); + return; + } + + NSError *jsonDeserializationError; + NSDictionary *> *json = + [NSJSONSerialization JSONObjectWithData:data options:0 error:&jsonDeserializationError]; + if (jsonDeserializationError) { + // A problem occurred deserializing the response/JSON. + NSString *errorDescription = + [NSString stringWithFormat:@"JSON error parsing token response: %@", + jsonDeserializationError.localizedDescription]; + NSError *returnedError = + [OIDErrorUtilities errorWithCode:OIDErrorCodeJSONDeserializationError + underlyingError:jsonDeserializationError + description:errorDescription]; + dispatch_async(dispatch_get_main_queue(), ^{ + callback(nil, returnedError); + }); + return; + } + + OIDTokenResponse *tokenResponse = + [[OIDTokenResponse alloc] initWithRequest:request parameters:json]; + if (!tokenResponse) { + // A problem occurred constructing the token response from the JSON. + NSError *returnedError = + [OIDErrorUtilities errorWithCode:OIDErrorCodeTokenResponseConstructionError + underlyingError:jsonDeserializationError + description:@"Token response invalid."]; + dispatch_async(dispatch_get_main_queue(), ^{ + callback(nil, returnedError); + }); + return; + } + + // If an ID Token is included in the response, validates the ID Token following the rules + // in OpenID Connect Core Section 3.1.3.7 for features that AppAuth directly supports + // (which excludes rules #1, #4, #5, #7, #8, #12, and #13). Regarding rule #6, ID Tokens + // received by this class are received via direct communication between the Client and the Token + // Endpoint, thus we are exercising the option to rely only on the TLS validation. AppAuth + // has a zero dependencies policy, and verifying the JWT signature would add a dependency. + // Users of the library are welcome to perform the JWT signature verification themselves should + // they wish. + if (tokenResponse.idToken) { + OIDIDToken *idToken = [[OIDIDToken alloc] initWithIDTokenString:tokenResponse.idToken]; + if (!idToken) { + NSError *invalidIDToken = + [OIDErrorUtilities errorWithCode:OIDErrorCodeIDTokenParsingError + underlyingError:nil + description:@"ID Token parsing failed"]; + dispatch_async(dispatch_get_main_queue(), ^{ + callback(nil, invalidIDToken); + }); + return; + } + + // OpenID Connect Core Section 3.1.3.7. rule #1 + // Not supported: AppAuth does not support JWT encryption. + + // OpenID Connect Core Section 3.1.3.7. rule #2 + // Validates that the issuer in the ID Token matches that of the discovery document. + NSURL *issuer = tokenResponse.request.configuration.issuer; + if (issuer && ![idToken.issuer isEqual:issuer]) { + NSError *invalidIDToken = + [OIDErrorUtilities errorWithCode:OIDErrorCodeIDTokenFailedValidationError + underlyingError:nil + description:@"Issuer mismatch"]; + dispatch_async(dispatch_get_main_queue(), ^{ + callback(nil, invalidIDToken); + }); + return; + } + + // OpenID Connect Core Section 3.1.3.7. rule #3 & Section 2 azp Claim + // Validates that the aud (audience) Claim contains the client ID, or that the azp + // (authorized party) Claim matches the client ID. + NSString *clientID = tokenResponse.request.clientID; + if (![idToken.audience containsObject:clientID] && + ![idToken.claims[@"azp"] isEqualToString:clientID]) { + NSError *invalidIDToken = + [OIDErrorUtilities errorWithCode:OIDErrorCodeIDTokenFailedValidationError + underlyingError:nil + description:@"Audience mismatch"]; + dispatch_async(dispatch_get_main_queue(), ^{ + callback(nil, invalidIDToken); + }); + return; + } + + // OpenID Connect Core Section 3.1.3.7. rules #4 & #5 + // Not supported. + + // OpenID Connect Core Section 3.1.3.7. rule #6 + // As noted above, AppAuth only supports the code flow which results in direct communication + // of the ID Token from the Token Endpoint to the Client, and we are exercising the option to + // use TSL server validation instead of checking the token signature. Users may additionally + // check the token signature should they wish. + + // OpenID Connect Core Section 3.1.3.7. rules #7 & #8 + // Not applicable. See rule #6. + + // OpenID Connect Core Section 3.1.3.7. rule #9 + // Validates that the current time is before the expiry time. + NSTimeInterval expiresAtDifference = [idToken.expiresAt timeIntervalSinceNow]; + if (expiresAtDifference < 0) { + NSError *invalidIDToken = + [OIDErrorUtilities errorWithCode:OIDErrorCodeIDTokenFailedValidationError + underlyingError:nil + description:@"ID Token expired"]; + dispatch_async(dispatch_get_main_queue(), ^{ + callback(nil, invalidIDToken); + }); + return; + } + + // OpenID Connect Core Section 3.1.3.7. rule #10 + // Validates that the issued at time is not more than +/- 10 minutes on the current time. + NSTimeInterval issuedAtDifference = [idToken.issuedAt timeIntervalSinceNow]; + if (fabs(issuedAtDifference) > kOIDAuthorizationSessionIATMaxSkew) { + NSString *message = + [NSString stringWithFormat:@"Issued at time is more than %d seconds before or after " + "the current time", + kOIDAuthorizationSessionIATMaxSkew]; + NSError *invalidIDToken = + [OIDErrorUtilities errorWithCode:OIDErrorCodeIDTokenFailedValidationError + underlyingError:nil + description:message]; + dispatch_async(dispatch_get_main_queue(), ^{ + callback(nil, invalidIDToken); + }); + return; + } + + // Only relevant for the authorization_code response type + if ([tokenResponse.request.grantType isEqual:OIDGrantTypeAuthorizationCode]) { + // OpenID Connect Core Section 3.1.3.7. rule #11 + // Validates the nonce. + NSString *nonce = authorizationResponse.request.nonce; + if (nonce && ![idToken.nonce isEqual:nonce]) { + NSError *invalidIDToken = + [OIDErrorUtilities errorWithCode:OIDErrorCodeIDTokenFailedValidationError + underlyingError:nil + description:@"Nonce mismatch"]; + dispatch_async(dispatch_get_main_queue(), ^{ + callback(nil, invalidIDToken); + }); + return; + } + } + + // OpenID Connect Core Section 3.1.3.7. rules #12 + // ACR is not directly supported by AppAuth. + + // OpenID Connect Core Section 3.1.3.7. rules #12 + // max_age is not directly supported by AppAuth. + } + + // Success + dispatch_async(dispatch_get_main_queue(), ^{ + callback(tokenResponse, nil); + }); + }] resume]; +} + + +#pragma mark - Registration Endpoint + ++ (void)performRegistrationRequest:(OIDRegistrationRequest *)request + completion:(OIDRegistrationCompletion)completion { + NSURLRequest *URLRequest = [request URLRequest]; + if (!URLRequest) { + // A problem occurred deserializing the response/JSON. + NSError *returnedError = [OIDErrorUtilities errorWithCode:OIDErrorCodeJSONSerializationError + underlyingError:nil + description:@"The registration request could not " + "be serialized as JSON."]; + dispatch_async(dispatch_get_main_queue(), ^{ + completion(nil, returnedError); + }); + return; + } + + NSURLSession *session = [OIDURLSessionProvider session]; + [[session dataTaskWithRequest:URLRequest + completionHandler:^(NSData *_Nullable data, + NSURLResponse *_Nullable response, + NSError *_Nullable error) { + if (error) { + // A network error or server error occurred. + NSString *errorDescription = + [NSString stringWithFormat:@"Connection error making registration request to '%@': %@.", + URLRequest.URL, + error.localizedDescription]; + NSError *returnedError = [OIDErrorUtilities errorWithCode:OIDErrorCodeNetworkError + underlyingError:error + description:errorDescription]; + dispatch_async(dispatch_get_main_queue(), ^{ + completion(nil, returnedError); + }); + return; + } + + NSHTTPURLResponse *HTTPURLResponse = (NSHTTPURLResponse *) response; + + if (HTTPURLResponse.statusCode != 201 && HTTPURLResponse.statusCode != 200) { + // A server error occurred. + NSError *serverError = [OIDErrorUtilities HTTPErrorWithHTTPResponse:HTTPURLResponse + data:data]; + + // HTTP 400 may indicate an OpenID Connect Dynamic Client Registration 1.0 Section 3.3 error + // response, checks for that + if (HTTPURLResponse.statusCode == 400) { + NSError *jsonDeserializationError; + NSDictionary *> *json = + [NSJSONSerialization JSONObjectWithData:data options:0 error:&jsonDeserializationError]; + + // if the HTTP 400 response parses as JSON and has an 'error' key, it's an OAuth error + // these errors are special as they indicate a problem with the authorization grant + if (json[OIDOAuthErrorFieldError]) { + NSError *oauthError = + [OIDErrorUtilities OAuthErrorWithDomain:OIDOAuthRegistrationErrorDomain + OAuthResponse:json + underlyingError:serverError]; + dispatch_async(dispatch_get_main_queue(), ^{ + completion(nil, oauthError); + }); + return; + } + } + + // not an OAuth error, just a generic server error + NSString *errorDescription = + [NSString stringWithFormat:@"Non-200/201 HTTP response (%d) making registration request " + "to '%@'.", + (int)HTTPURLResponse.statusCode, + URLRequest.URL]; + NSError *returnedError = [OIDErrorUtilities errorWithCode:OIDErrorCodeServerError + underlyingError:serverError + description:errorDescription]; + dispatch_async(dispatch_get_main_queue(), ^{ + completion(nil, returnedError); + }); + return; + } + + NSError *jsonDeserializationError; + NSDictionary *> *json = + [NSJSONSerialization JSONObjectWithData:data options:0 error:&jsonDeserializationError]; + if (jsonDeserializationError) { + // A problem occurred deserializing the response/JSON. + NSString *errorDescription = + [NSString stringWithFormat:@"JSON error parsing registration response: %@", + jsonDeserializationError.localizedDescription]; + NSError *returnedError = [OIDErrorUtilities errorWithCode:OIDErrorCodeJSONDeserializationError + underlyingError:jsonDeserializationError + description:errorDescription]; + dispatch_async(dispatch_get_main_queue(), ^{ + completion(nil, returnedError); + }); + return; + } + + OIDRegistrationResponse *registrationResponse = + [[OIDRegistrationResponse alloc] initWithRequest:request + parameters:json]; + if (!registrationResponse) { + // A problem occurred constructing the registration response from the JSON. + NSError *returnedError = + [OIDErrorUtilities errorWithCode:OIDErrorCodeRegistrationResponseConstructionError + underlyingError:nil + description:@"Registration response invalid."]; + dispatch_async(dispatch_get_main_queue(), ^{ + completion(nil, returnedError); + }); + return; + } + + // Success + dispatch_async(dispatch_get_main_queue(), ^{ + completion(registrationResponse, nil); + }); + }] resume]; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/Source/OIDClientMetadataParameters.h b/Sources/AppAuthCore/OIDClientMetadataParameters.h similarity index 100% rename from Source/OIDClientMetadataParameters.h rename to Sources/AppAuthCore/OIDClientMetadataParameters.h diff --git a/Source/OIDClientMetadataParameters.m b/Sources/AppAuthCore/OIDClientMetadataParameters.m similarity index 100% rename from Source/OIDClientMetadataParameters.m rename to Sources/AppAuthCore/OIDClientMetadataParameters.m diff --git a/Source/OIDDefines.h b/Sources/AppAuthCore/OIDDefines.h similarity index 92% rename from Source/OIDDefines.h rename to Sources/AppAuthCore/OIDDefines.h index 0dccf842a..8ff4f19ba 100644 --- a/Source/OIDDefines.h +++ b/Sources/AppAuthCore/OIDDefines.h @@ -42,3 +42,10 @@ reason:reason \ userInfo:nil]; \ } + +#ifdef _APPAUTHTRACE +# define AppAuthRequestTrace(fmt, ...) NSLog(fmt, ##__VA_ARGS__); +#else // _APPAUTHTRACE +# define AppAuthRequestTrace(...) +#endif // _APPAUTHTRACE + diff --git a/Sources/AppAuthCore/OIDEndSessionRequest.h b/Sources/AppAuthCore/OIDEndSessionRequest.h new file mode 100644 index 000000000..4087e9fa9 --- /dev/null +++ b/Sources/AppAuthCore/OIDEndSessionRequest.h @@ -0,0 +1,107 @@ +/*! @file OIDEndSessionRequest.h + @brief AppAuth iOS SDK + @copyright + Copyright 2017 The AppAuth Authors. All Rights Reserved. + @copydetails + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#import + +#import "OIDExternalUserAgentRequest.h" + +@class OIDServiceConfiguration; + +NS_ASSUME_NONNULL_BEGIN + +@interface OIDEndSessionRequest : NSObject + + +/*! @brief The service's configuration. + @remarks This configuration specifies how to connect to a particular OAuth provider. + Configurations may be created manually, or via an OpenID Connect Discovery Document. + */ +@property(nonatomic, readonly) OIDServiceConfiguration *configuration; + +/*! @brief The client's redirect URI. + @remarks post_logout_redirect_uri + @see http://openid.net/specs/openid-connect-session-1_0.html#RPLogout + */ +@property(nonatomic, readonly, nullable) NSURL *postLogoutRedirectURL; + +/*! @brief Previously issued ID Token passed to the end session endpoint as a hint about the End-User's current authenticated + session with the Client + @remarks id_token_hint + @see http://openid.net/specs/openid-connect-session-1_0.html#RPLogout + */ +@property(nonatomic, readonly, nullable) NSString *idTokenHint; + +/*! @brief An opaque value used by the client to maintain state between the request and callback. + @remarks state + @discussion If this value is not explicitly set, this library will automatically add state and + perform appropriate validation of the state in the authorization response. It is recommended + that the default implementation of this parameter be used wherever possible. Typically used + to prevent CSRF attacks, as recommended in RFC6819 Section 5.3.5. + @see http://openid.net/specs/openid-connect-session-1_0.html#RPLogout + */ +@property(nonatomic, readonly, nullable) NSString *state; + +/*! @brief The client's additional authorization parameters. + @see https://tools.ietf.org/html/rfc6749#section-3.1 + */ +@property(nonatomic, readonly, nullable) NSDictionary *additionalParameters; + +/*! @internal + @brief Unavailable. Please use @c initWithConfiguration:clientId:scopes:redirectURL:additionalParameters:. + */ +- (instancetype)init NS_UNAVAILABLE; + +/*! @brief Creates an authorization request with opinionated defaults (a secure @c state). + @param configuration The service's configuration. + @param idTokenHint The previously issued ID Token + @param postLogoutRedirectURL The client's post-logout redirect URI. + callback. + @param additionalParameters The client's additional authorization parameters. +*/ +- (instancetype) + initWithConfiguration:(OIDServiceConfiguration *)configuration + idTokenHint:(NSString *)idTokenHint + postLogoutRedirectURL:(NSURL *)postLogoutRedirectURL + additionalParameters:(nullable NSDictionary *)additionalParameters; + +/*! @brief Designated initializer. + @param configuration The service's configuration. + @param idTokenHint The previously issued ID Token + @param postLogoutRedirectURL The client's post-logout redirect URI. + @param state An opaque value used by the client to maintain state between the request and + callback. + @param additionalParameters The client's additional authorization parameters. + */ +- (instancetype) + initWithConfiguration:(OIDServiceConfiguration *)configuration + idTokenHint:(NSString *)idTokenHint + postLogoutRedirectURL:(NSURL *)postLogoutRedirectURL + state:(NSString *)state + additionalParameters:(nullable NSDictionary *)additionalParameters + NS_DESIGNATED_INITIALIZER; + +/*! @brief Constructs the request URI by adding the request parameters to the query component of the + authorization endpoint URI using the "application/x-www-form-urlencoded" format. + @return A URL representing the authorization request. + @see http://openid.net/specs/openid-connect-session-1_0.html#RPLogout + */ +- (NSURL *)endSessionRequestURL; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Sources/AppAuthCore/OIDEndSessionRequest.m b/Sources/AppAuthCore/OIDEndSessionRequest.m new file mode 100644 index 000000000..1e9eb0e22 --- /dev/null +++ b/Sources/AppAuthCore/OIDEndSessionRequest.m @@ -0,0 +1,190 @@ +/*! @file OIDEndSessionRequest.m + @brief AppAuth iOS SDK + @copyright + Copyright 2017 The AppAuth Authors. All Rights Reserved. + @copydetails + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#import "OIDEndSessionRequest.h" + +#import "OIDDefines.h" +#import "OIDTokenUtilities.h" +#import "OIDServiceConfiguration.h" +#import "OIDServiceDiscovery.h" +#import "OIDURLQueryComponent.h" + +/*! @brief The key for the @c configuration property for @c NSSecureCoding + */ +static NSString *const kConfigurationKey = @"configuration"; + +/*! @brief Key used to encode the @c state property for @c NSSecureCoding, and on the URL request. + */ +static NSString *const kStateKey = @"state"; + +/*! @brief Key used to encode the @c postLogoutRedirectURL property for @c NSSecureCoding, and on the URL request. + */ +static NSString *const kPostLogoutRedirectURLKey = @"post_logout_redirect_uri"; + +/*! @brief Key used to encode the @c idTokenHint property for @c NSSecureCoding, and on the URL request. + */ +static NSString *const kIdTokenHintKey = @"id_token_hint"; + +/*! @brief Key used to encode the @c additionalParameters property for @c NSSecureCoding + */ +static NSString *const kAdditionalParametersKey = @"additionalParameters"; + +/*! @brief Number of random bytes generated for the @state. + */ +static NSUInteger const kStateSizeBytes = 32; + +/*! @brief Assertion text for missing end_session_endpoint. + */ +static NSString *const OIDMissingEndSessionEndpointMessage = +@"The service configuration is missing an end_session_endpoint."; + +@implementation OIDEndSessionRequest + +- (instancetype)init + OID_UNAVAILABLE_USE_INITIALIZER( + @selector(initWithConfiguration: + idTokenHint: + postLogoutRedirectURL: + additionalParameters:) + ) + +- (instancetype)initWithConfiguration:(OIDServiceConfiguration *)configuration + idTokenHint:(NSString *)idTokenHint + postLogoutRedirectURL:(NSURL *)postLogoutRedirectURL + state:(NSString *)state + additionalParameters:(NSDictionary *)additionalParameters +{ + self = [super init]; + if (self) { + _configuration = [configuration copy]; + _idTokenHint = [idTokenHint copy]; + _postLogoutRedirectURL = [postLogoutRedirectURL copy]; + _state = [state copy]; + _additionalParameters = + [[NSDictionary alloc] initWithDictionary:additionalParameters copyItems:YES]; + } + return self; +} + +- (instancetype)initWithConfiguration:(OIDServiceConfiguration *)configuration + idTokenHint:(NSString *)idTokenHint + postLogoutRedirectURL:(NSURL *)postLogoutRedirectURL + additionalParameters:(NSDictionary *)additionalParameters +{ + return [self initWithConfiguration:configuration + idTokenHint:idTokenHint + postLogoutRedirectURL:postLogoutRedirectURL + state:[[self class] generateState] + additionalParameters:additionalParameters]; +} +#pragma mark - NSCopying + +- (instancetype)copyWithZone:(nullable NSZone *)zone { + // The documentation for NSCopying specifically advises us to return a reference to the original + // instance in the case where instances are immutable (as ours is): + // "Implement NSCopying by retaining the original instead of creating a new copy when the class + // and its contents are immutable." + return self; +} + +#pragma mark - NSSecureCoding + ++ (BOOL)supportsSecureCoding { + return YES; +} + +- (instancetype)initWithCoder:(NSCoder *)aDecoder { + OIDServiceConfiguration *configuration = [aDecoder decodeObjectOfClass:[OIDServiceConfiguration class] forKey:kConfigurationKey]; + + NSString *idTokenHint = [aDecoder decodeObjectOfClass:[NSString class] forKey:kIdTokenHintKey]; + NSURL *postLogoutRedirectURL = [aDecoder decodeObjectOfClass:[NSURL class] forKey:kPostLogoutRedirectURLKey]; + NSString *state = [aDecoder decodeObjectOfClass:[NSString class] forKey:kStateKey]; + NSSet *additionalParameterCodingClasses = [NSSet setWithArray:@[ + [NSDictionary class], + [NSString class] + ]]; + NSDictionary *additionalParameters = [aDecoder decodeObjectOfClasses:additionalParameterCodingClasses + forKey:kAdditionalParametersKey]; + + self = [self initWithConfiguration:configuration + idTokenHint:idTokenHint + postLogoutRedirectURL:postLogoutRedirectURL + state:state + additionalParameters:additionalParameters]; + return self; +} + +- (void)encodeWithCoder:(NSCoder *)aCoder { + [aCoder encodeObject:_configuration forKey:kConfigurationKey]; + [aCoder encodeObject:_idTokenHint forKey:kIdTokenHintKey]; + [aCoder encodeObject:_postLogoutRedirectURL forKey:kPostLogoutRedirectURLKey]; + [aCoder encodeObject:_state forKey:kStateKey]; + [aCoder encodeObject:_additionalParameters forKey:kAdditionalParametersKey]; +} + +#pragma mark - NSObject overrides + +- (NSString *)description { + return [NSString stringWithFormat:@"<%@: %p, request: %@>", + NSStringFromClass([self class]), + (void *)self, + self.endSessionRequestURL]; +} + ++ (nullable NSString *)generateState { + return [OIDTokenUtilities randomURLSafeStringWithSize:kStateSizeBytes]; +} + +#pragma mark - OIDExternalUserAgentRequest + +- (NSURL*)externalUserAgentRequestURL { + return [self endSessionRequestURL]; +} + +- (NSString *)redirectScheme { + return [_postLogoutRedirectURL scheme]; +} + +#pragma mark - + +- (NSURL *)endSessionRequestURL { + OIDURLQueryComponent *query = [[OIDURLQueryComponent alloc] init]; + + // Add any additional parameters the client has specified. + [query addParameters:_additionalParameters]; + + // Add optional parameters, as applicable. + if (_idTokenHint) { + [query addParameter:kIdTokenHintKey value:_idTokenHint]; + } + + if (_postLogoutRedirectURL) { + [query addParameter:kPostLogoutRedirectURLKey value:_postLogoutRedirectURL.absoluteString]; + } + + if (_state) { + [query addParameter:kStateKey value:_state]; + } + + NSAssert(_configuration.endSessionEndpoint, OIDMissingEndSessionEndpointMessage); + + // Construct the URL + return [query URLByReplacingQueryInURL:_configuration.endSessionEndpoint]; +} + +@end diff --git a/Sources/AppAuthCore/OIDEndSessionResponse.h b/Sources/AppAuthCore/OIDEndSessionResponse.h new file mode 100644 index 000000000..ab69b9305 --- /dev/null +++ b/Sources/AppAuthCore/OIDEndSessionResponse.h @@ -0,0 +1,64 @@ +/*! @file OIDEndSessionResponse.h + @brief AppAuth iOS SDK + @copyright + Copyright 2017 The AppAuth Authors. All Rights Reserved. + @copydetails + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#import + +@class OIDEndSessionRequest; + +NS_ASSUME_NONNULL_BEGIN + +/*! @brief Represents the response to an End Session request. + @see http://openid.net/specs/openid-connect-session-1_0.html#RPLogout + */ + +@interface OIDEndSessionResponse : NSObject + +/*! @brief The request which was serviced. + */ +@property(nonatomic, readonly) OIDEndSessionRequest *request; + +/*! @brief REQUIRED if the "state" parameter was present in the client end-session request. The + exact value received from the client. + @remarks state + */ +@property(nonatomic, readonly, nullable) NSString *state; + +/*! @brief Additional parameters returned from the end session endpoint. + */ +@property(nonatomic, readonly, nullable) + NSDictionary *> *additionalParameters; + +/*! @internal + @brief Unavailable. Please use initWithParameters:. + */ +- (instancetype)init NS_UNAVAILABLE; + +/*! @brief Designated initializer. + @param request The serviced request. + @param parameters The decoded parameters returned from the End Session Endpoint. + @remarks Known parameters are extracted from the @c parameters parameter and the normative + properties are populated. Non-normative parameters are placed in the + @c #additionalParameters dictionary. + */ +- (instancetype)initWithRequest:(OIDEndSessionRequest *)request + parameters:(NSDictionary *> *)parameters + NS_DESIGNATED_INITIALIZER; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Sources/AppAuthCore/OIDEndSessionResponse.m b/Sources/AppAuthCore/OIDEndSessionResponse.m new file mode 100644 index 000000000..bedf0cd93 --- /dev/null +++ b/Sources/AppAuthCore/OIDEndSessionResponse.m @@ -0,0 +1,118 @@ +/*! @file OIDEndSessionResponse.m + @brief AppAuth iOS SDK + @copyright + Copyright 2017 The AppAuth Authors. All Rights Reserved. + @copydetails + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#import "OIDEndSessionResponse.h" + +#import "OIDDefines.h" +#import "OIDEndSessionRequest.h" +#import "OIDFieldMapping.h" + +/*! @brief The key for the @c state property in the incoming parameters and for @c NSSecureCoding. + */ +static NSString *const kStateKey = @"state"; + +/*! @brief Key used to encode the @c request property for @c NSSecureCoding + */ +static NSString *const kRequestKey = @"request"; + +/*! @brief Key used to encode the @c additionalParameters property for + @c NSSecureCoding + */ +static NSString *const kAdditionalParametersKey = @"additionalParameters"; + +@implementation OIDEndSessionResponse + +#pragma mark - Initializers + +- (instancetype)init + OID_UNAVAILABLE_USE_INITIALIZER(@selector(initWithRequest:parameters:)) + +- (instancetype)initWithRequest:(OIDEndSessionRequest *)request + parameters:(NSDictionary *> *)parameters { + self = [super init]; + if (self) { + _request = [request copy]; + NSDictionary *> *additionalParameters = + [OIDFieldMapping remainingParametersWithMap:[[self class] fieldMap] + parameters:parameters + instance:self]; + _additionalParameters = additionalParameters; + } + return self; +} + +/*! @brief Returns a mapping of incoming parameters to instance variables. + @return A mapping of incoming parameters to instance variables. + */ ++ (NSDictionary *)fieldMap { + static NSMutableDictionary *fieldMap; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + fieldMap = [NSMutableDictionary dictionary]; + fieldMap[kStateKey] = + [[OIDFieldMapping alloc] initWithName:@"_state" type:[NSString class]]; + }); + return fieldMap; +} + +#pragma mark - NSCopying + +- (instancetype)copyWithZone:(nullable NSZone *)zone { + // The documentation for NSCopying specifically advises us to return a reference to the original + // instance in the case where instances are immutable (as ours is): + // "Implement NSCopying by retaining the original instead of creating a new copy when the class + // and its contents are immutable." + return self; +} + +#pragma mark - NSSecureCoding + ++ (BOOL)supportsSecureCoding { + return YES; +} + +- (instancetype)initWithCoder:(NSCoder *)aDecoder { + OIDEndSessionRequest *request = + [aDecoder decodeObjectOfClass:[OIDEndSessionRequest class] forKey:kRequestKey]; + self = [self initWithRequest:request parameters:@{ }]; + if (self) { + [OIDFieldMapping decodeWithCoder:aDecoder map:[[self class] fieldMap] instance:self]; + _additionalParameters = [aDecoder decodeObjectOfClasses:[OIDFieldMapping JSONTypes] + forKey:kAdditionalParametersKey]; + } + return self; +} + +- (void)encodeWithCoder:(NSCoder *)aCoder { + [aCoder encodeObject:_request forKey:kRequestKey]; + [OIDFieldMapping encodeWithCoder:aCoder map:[[self class] fieldMap] instance:self]; + [aCoder encodeObject:_additionalParameters forKey:kAdditionalParametersKey]; +} + +#pragma mark - NSObject overrides + +- (NSString *)description { + return [NSString stringWithFormat:@"<%@: %p, state: \"%@\", " + "additionalParameters: %@, request: %@>", + NSStringFromClass([self class]), + (void *)self, + _state, + _additionalParameters, + _request]; +} +@end diff --git a/Source/OIDError.h b/Sources/AppAuthCore/OIDError.h similarity index 96% rename from Source/OIDError.h rename to Sources/AppAuthCore/OIDError.h index 229802cc0..5131f0ad4 100644 --- a/Source/OIDError.h +++ b/Sources/AppAuthCore/OIDError.h @@ -144,6 +144,13 @@ typedef NS_ENUM(NSInteger, OIDErrorCode) { */ OIDErrorCodeJSONSerializationError = -13, + /*! @brief The ID Token did not parse. + */ + OIDErrorCodeIDTokenParsingError = -14, + + /*! @brief The ID Token did not pass validation (e.g. issuer, audience checks). + */ + OIDErrorCodeIDTokenFailedValidationError = -15, }; /*! @brief Enum of all possible OAuth error codes as defined by RFC6749 @@ -374,8 +381,13 @@ typedef NS_ENUM(NSInteger, OIDErrorCodeOAuthRegistration) { /*! @brief The exception text for the exception which occurs when a - @c OIDAuthorizationFlowSession receives a message after it has already completed. + @c OIDExternalUserAgentSession receives a message after it has already completed. */ extern NSString *const OIDOAuthExceptionInvalidAuthorizationFlow; +/*! @brief The text for the exception which occurs when a Token Request is constructed + with a null redirectURL for a grant_type that requires a nonnull Redirect + */ +extern NSString *const OIDOAuthExceptionInvalidTokenRequestNullRedirectURL; + NS_ASSUME_NONNULL_END diff --git a/Source/OIDError.m b/Sources/AppAuthCore/OIDError.m similarity index 86% rename from Source/OIDError.m rename to Sources/AppAuthCore/OIDError.m index 697e63ac9..87c8623ee 100644 --- a/Source/OIDError.m +++ b/Sources/AppAuthCore/OIDError.m @@ -31,7 +31,10 @@ NSString *const OIDHTTPErrorDomain = @"org.openid.appauth.remote-http"; NSString *const OIDOAuthExceptionInvalidAuthorizationFlow = @"An OAuth redirect was sent to a " - "OIDAuthorizationFlowSession after it already completed."; + "OIDExternalUserAgentSession after it already completed."; + +NSString *const OIDOAuthExceptionInvalidTokenRequestNullRedirectURL = @"A OIDTokenRequest was " + "created with a grant_type that requires a redirectURL, but a null redirectURL was given"; NSString *const OIDOAuthErrorResponseErrorKey = @"OIDOAuthErrorResponseErrorKey"; diff --git a/Source/OIDErrorUtilities.h b/Sources/AppAuthCore/OIDErrorUtilities.h similarity index 100% rename from Source/OIDErrorUtilities.h rename to Sources/AppAuthCore/OIDErrorUtilities.h diff --git a/Source/OIDErrorUtilities.m b/Sources/AppAuthCore/OIDErrorUtilities.m similarity index 89% rename from Source/OIDErrorUtilities.m rename to Sources/AppAuthCore/OIDErrorUtilities.m index 3e4e305b4..3b3c06075 100644 --- a/Source/OIDErrorUtilities.m +++ b/Sources/AppAuthCore/OIDErrorUtilities.m @@ -66,7 +66,8 @@ + (NSError *)OAuthErrorWithDomain:(NSString *)oAuthErrorDomain // not a valid OAuth error if (![self isOAuthErrorDomain:oAuthErrorDomain] || !errorResponse - || !errorResponse[OIDOAuthErrorFieldError]) { + || !errorResponse[OIDOAuthErrorFieldError] + || ![errorResponse[OIDOAuthErrorFieldError] isKindOfClass:[NSString class]]) { return [[self class] errorWithCode:OIDErrorCodeNetworkError underlyingError:underlyingError description:underlyingError.localizedDescription]; @@ -80,8 +81,18 @@ + (NSError *)OAuthErrorWithDomain:(NSString *)oAuthErrorDomain } NSString *oauthErrorCodeString = errorResponse[OIDOAuthErrorFieldError]; - NSString *oauthErrorMessage = errorResponse[OIDOAuthErrorFieldErrorDescription]; - NSString *oauthErrorURI = errorResponse[OIDOAuthErrorFieldErrorURI]; + NSString *oauthErrorMessage = nil; + if ([errorResponse[OIDOAuthErrorFieldErrorDescription] isKindOfClass:[NSString class]]) { + oauthErrorMessage = errorResponse[OIDOAuthErrorFieldErrorDescription]; + } else { + oauthErrorMessage = [errorResponse[OIDOAuthErrorFieldErrorDescription] description]; + } + NSString *oauthErrorURI = nil; + if ([errorResponse[OIDOAuthErrorFieldErrorURI] isKindOfClass:[NSString class]]) { + oauthErrorURI = errorResponse[OIDOAuthErrorFieldErrorURI]; + } else { + oauthErrorURI = [errorResponse[OIDOAuthErrorFieldErrorURI] description]; + } // builds the error description, using the information supplied by the server if possible NSMutableString *description = [NSMutableString string]; diff --git a/Sources/AppAuthCore/OIDExternalUserAgent.h b/Sources/AppAuthCore/OIDExternalUserAgent.h new file mode 100644 index 000000000..c4eb0a908 --- /dev/null +++ b/Sources/AppAuthCore/OIDExternalUserAgent.h @@ -0,0 +1,53 @@ +/*! @file OIDExternalUserAgent.h + @brief AppAuth iOS SDK + @copyright + Copyright 2016 Google Inc. All Rights Reserved. + @copydetails + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#import + +@protocol OIDExternalUserAgentSession; +@protocol OIDExternalUserAgentRequest; + +NS_ASSUME_NONNULL_BEGIN + +/*! @protocol OIDExternalUserAgent + @brief An external user-agent UI that presents displays the request to the user. Clients may + provide custom implementations of an external user-agent to customize the way the requests + are presented to the end user. + */ +@protocol OIDExternalUserAgent + +/*! @brief Presents the request in the external user-agent. + @param request The request to be presented in the external user-agent. + @param session The @c OIDExternalUserAgentSession instance that initiates presenting the UI. + Concrete implementations of a @c OIDExternalUserAgent may call + resumeExternalUserAgentFlowWithURL or failExternalUserAgentFlowWithError on session to either + resume or fail the request. + @return YES If the request UI was successfully presented to the user. + */ +- (BOOL)presentExternalUserAgentRequest:(id )request + session:(id)session; + +/*! @brief Dimisses the external user-agent and calls completion when the dismiss operation ends. + @param animated Whether or not the dismiss operation should be animated. + @remarks Has no effect if no UI is presented. + @param completion The block to be called when the dismiss operations ends + */ +- (void)dismissExternalUserAgentAnimated:(BOOL)animated completion:(void (^)(void))completion; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Sources/AppAuthCore/OIDExternalUserAgentRequest.h b/Sources/AppAuthCore/OIDExternalUserAgentRequest.h new file mode 100644 index 000000000..8ea40cb69 --- /dev/null +++ b/Sources/AppAuthCore/OIDExternalUserAgentRequest.h @@ -0,0 +1,37 @@ +/*! @file OIDExternalUserAgent.h + @brief AppAuth iOS SDK + @copyright + Copyright 2017 The AppAuth Authors. All Rights Reserved. + @copydetails + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +/*! @protocol OIDExternalUserAgent + @brief An interface that any external user-agent request may implement to use the + @c OIDExternalUserAgent flow. + */ +@protocol OIDExternalUserAgentRequest + +/*! @brief Method to create and return the complete request URL instance. + @return A @c NSURL instance which contains the URL to be opened in an external UI (i.e. browser) + */ +- (NSURL*)externalUserAgentRequestURL; + +/*! @brief If this external user-agent request has a redirect URL, this should return its scheme. + Since some external requests have optional callbacks (such as the end session endpoint), the + return value of this method is nullable. + @return A @c NSString instance that contains the scheme of a callback url, or nil if there is + no callback url for this request. + */ +- (NSString*)redirectScheme; +@end diff --git a/Sources/AppAuthCore/OIDExternalUserAgentSession.h b/Sources/AppAuthCore/OIDExternalUserAgentSession.h new file mode 100644 index 000000000..3b886a6c3 --- /dev/null +++ b/Sources/AppAuthCore/OIDExternalUserAgentSession.h @@ -0,0 +1,65 @@ +/*! @file OIDExternalUserAgentSession.h + @brief AppAuth iOS SDK + @copyright + Copyright 2017 The AppAuth Authors. All Rights Reserved. + @copydetails + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +NS_ASSUME_NONNULL_BEGIN + +/*! @brief Represents an in-flight external user-agent session. + */ +@protocol OIDExternalUserAgentSession + +/*! @brief Cancels the code flow session, invoking the request's callback with a cancelled error. + @remarks Has no effect if called more than once, or after a + @c OIDExternalUserAgentSession.resumeExternalUserAgentFlowWithURL: message was received. + Will cause an error with code: @c ::OIDErrorCodeProgramCanceledAuthorizationFlow to be + passed to the @c callback block passed to + @c OIDAuthorizationService.presentAuthorizationRequest:presentingViewController:callback: + */ +- (void)cancel; + +/*! @brief Cancels the code flow session, invoking the request's callback with a cancelled error. + @remarks Has no effect if called more than once, or after a + @c OIDExternalUserAgentSession.resumeExternalUserAgentFlowWithURL: message was received. + Will cause an error with code: @c ::OIDErrorCodeProgramCanceledAuthorizationFlow to be + passed to the @c callback block passed to + @c OIDAuthorizationService.presentAuthorizationRequest:presentingViewController:callback: + @param completion The block to be called when the cancel operation ends + */ +- (void)cancelWithCompletion:(nullable void (^)(void))completion; + +/*! @brief Clients should call this method with the result of the external user-agent code flow if + it becomes available. + @param URL The redirect URL invoked by the server. + @discussion When the URL represented a valid response, implementations should clean up any + left-over UI state from the request, for example by closing the + \SFSafariViewController or loopback HTTP listener if those were used. The completion block + of the pending request should then be invoked. + @remarks Has no effect if called more than once, or after a @c cancel message was received. + @return YES if the passed URL matches the expected redirect URL and was consumed, NO otherwise. + */ +- (BOOL)resumeExternalUserAgentFlowWithURL:(NSURL *)URL; + +/*! @brief @c OIDExternalUserAgent or clients should call this method when the + external user-agent flow failed with a non-OAuth error. + @param error The error that is the reason for the failure of this external flow. + @remarks Has no effect if called more than once, or after a @c cancel message was received. + */ +- (void)failExternalUserAgentFlowWithError:(NSError *)error; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Source/OIDFieldMapping.h b/Sources/AppAuthCore/OIDFieldMapping.h similarity index 97% rename from Source/OIDFieldMapping.h rename to Sources/AppAuthCore/OIDFieldMapping.h index 22d915924..f0a56fef3 100644 --- a/Source/OIDFieldMapping.h +++ b/Sources/AppAuthCore/OIDFieldMapping.h @@ -28,12 +28,7 @@ typedef _Nullable id(^OIDFieldMappingConversionFunction)(NSObject *_Nullable val /*! @brief Describes the mapping of a key/value pair to an iVar with an optional conversion function. */ -@interface OIDFieldMapping : NSObject { - // property variables - NSString *_name; - Class _expectedType; - OIDFieldMappingConversionFunction _conversion; -} +@interface OIDFieldMapping : NSObject /*! @brief The name of the instance variable the field should be mapped to. */ diff --git a/Source/OIDFieldMapping.m b/Sources/AppAuthCore/OIDFieldMapping.m similarity index 97% rename from Source/OIDFieldMapping.m rename to Sources/AppAuthCore/OIDFieldMapping.m index bb1c81824..f84365602 100644 --- a/Source/OIDFieldMapping.m +++ b/Sources/AppAuthCore/OIDFieldMapping.m @@ -22,12 +22,8 @@ @implementation OIDFieldMapping -@synthesize name = _name; -@synthesize expectedType = _expectedType; -@synthesize conversion = _conversion; - - (nonnull instancetype)init - OID_UNAVAILABLE_USE_INITIALIZER(@selector(initWithName:type:conversion:)); + OID_UNAVAILABLE_USE_INITIALIZER(@selector(initWithName:type:conversion:)) - (instancetype)initWithName:(NSString *)name type:(Class)type { diff --git a/Source/OIDGrantTypes.h b/Sources/AppAuthCore/OIDGrantTypes.h similarity index 100% rename from Source/OIDGrantTypes.h rename to Sources/AppAuthCore/OIDGrantTypes.h diff --git a/Source/OIDGrantTypes.m b/Sources/AppAuthCore/OIDGrantTypes.m similarity index 100% rename from Source/OIDGrantTypes.m rename to Sources/AppAuthCore/OIDGrantTypes.m diff --git a/Sources/AppAuthCore/OIDIDToken.h b/Sources/AppAuthCore/OIDIDToken.h new file mode 100644 index 000000000..6fe84d7fe --- /dev/null +++ b/Sources/AppAuthCore/OIDIDToken.h @@ -0,0 +1,91 @@ +/*! @file OIDIDToken.h + @brief AppAuth iOS SDK + @copyright + Copyright 2017 Google Inc. All Rights Reserved. + @copydetails + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ +#import + +NS_ASSUME_NONNULL_BEGIN + +/*! @brief A convenience class that parses an ID Token and extracts the claims _but does not_ + verify its signature. AppAuth only supports the OpenID Code flow, meaning ID Tokens + received by AppAuth are sent from the token endpoint on a TLS protected channel, + offering some assurances as to the origin of the token. You may wish to additionally + verify the ID Token signature using a JWT signature verification library of your + choosing. + @see http://openid.net/specs/openid-connect-core-1_0.html#IDToken + @see https://tools.ietf.org/html/rfc7519 + @see https://jwt.io/ + */ +@interface OIDIDToken : NSObject + +/*! @internal + @brief Unavailable. Please use @c initWithAuthorizationResponse:. + */ +- (instancetype)init NS_UNAVAILABLE; + +/*! @brief Parses the given ID Token string. + @param idToken The ID Token spring. + */ +- (nullable instancetype)initWithIDTokenString:(NSString *)idToken; + +/*! @brief The header JWT values. + */ +@property(nonatomic, readonly) NSDictionary *header; + +/*! @brief All ID Token claims. + */ +@property(nonatomic, readonly) NSDictionary *claims; + +/*! @brief Issuer Identifier for the Issuer of the response. + @remarks iss + @see http://openid.net/specs/openid-connect-core-1_0.html#IDToken + */ +@property(nonatomic, readonly) NSURL *issuer; + +/*! @brief Subject Identifier. + @remarks sub + @see http://openid.net/specs/openid-connect-core-1_0.html#IDToken + */ +@property(nonatomic, readonly) NSString *subject; + +/*! @brief Audience(s) that this ID Token is intended for. + @remarks aud + @see http://openid.net/specs/openid-connect-core-1_0.html#IDToken + */ +@property(nonatomic, readonly) NSArray *audience; + +/*! @brief Expiration time on or after which the ID Token MUST NOT be accepted for processing. + @remarks exp + @see http://openid.net/specs/openid-connect-core-1_0.html#IDToken + */ +@property(nonatomic, readonly) NSDate *expiresAt; + +/*! @brief Time at which the JWT was issued. + @remarks iat + @see http://openid.net/specs/openid-connect-core-1_0.html#IDToken + */ +@property(nonatomic, readonly) NSDate *issuedAt; + +/*! @brief String value used to associate a Client session with an ID Token, and to mitigate replay + attacks. + @remarks nonce + @see http://openid.net/specs/openid-connect-core-1_0.html#IDToken + */ +@property(nonatomic, readonly, nullable) NSString *nonce; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Sources/AppAuthCore/OIDIDToken.m b/Sources/AppAuthCore/OIDIDToken.m new file mode 100644 index 000000000..57a7324e8 --- /dev/null +++ b/Sources/AppAuthCore/OIDIDToken.m @@ -0,0 +1,149 @@ +/*! @file OIDIDToken.m + @brief AppAuth iOS SDK + @copyright + Copyright 2017 Google Inc. All Rights Reserved. + @copydetails + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#import "OIDIDToken.h" + +/*! Field keys associated with an ID Token. */ +static NSString *const kIssKey = @"iss"; +static NSString *const kSubKey = @"sub"; +static NSString *const kAudKey = @"aud"; +static NSString *const kExpKey = @"exp"; +static NSString *const kIatKey = @"iat"; +static NSString *const kNonceKey = @"nonce"; + +#import "OIDFieldMapping.h" + +@implementation OIDIDToken + +- (instancetype)initWithIDTokenString:(NSString *)idToken { + self = [super init]; + NSArray *sections = [idToken componentsSeparatedByString:@"."]; + + // The header and claims sections are required. + if (sections.count <= 1) { + return nil; + } + + _header = [[self class] parseJWTSection:sections[0]]; + _claims = [[self class] parseJWTSection:sections[1]]; + if (!_header || !_claims) { + return nil; + } + + [OIDFieldMapping remainingParametersWithMap:[[self class] fieldMap] + parameters:_claims + instance:self]; + + // Required fields. + if (!_issuer || !_audience || !_subject || !_expiresAt || !_issuedAt) { + return nil; + } + + return self; +} + +/*! @brief Returns a mapping of incoming parameters to instance variables. + @return A mapping of incoming parameters to instance variables. + */ ++ (NSDictionary *)fieldMap { + static NSMutableDictionary *fieldMap; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + fieldMap = [NSMutableDictionary dictionary]; + + fieldMap[kIssKey] = + [[OIDFieldMapping alloc] initWithName:@"_issuer" + type:[NSURL class] + conversion:[OIDFieldMapping URLConversion]]; + fieldMap[kSubKey] = + [[OIDFieldMapping alloc] initWithName:@"_subject" type:[NSString class]]; + fieldMap[kAudKey] = + [[OIDFieldMapping alloc] initWithName:@"_audience" + type:[NSArray class] + conversion:^id _Nullable(NSObject *_Nullable value) { + if ([value isKindOfClass:[NSArray class]]) { + return value; + } + if ([value isKindOfClass:[NSString class]]) { + return @[value]; + } + return nil; + }]; + fieldMap[kExpKey] = + [[OIDFieldMapping alloc] initWithName:@"_expiresAt" + type:[NSDate class] + conversion:^id _Nullable(NSObject *_Nullable value) { + if (![value isKindOfClass:[NSNumber class]]) { + return value; + } + NSNumber *valueAsNumber = (NSNumber *)value; + return [NSDate dateWithTimeIntervalSince1970:valueAsNumber.longLongValue]; + }]; + fieldMap[kIatKey] = + [[OIDFieldMapping alloc] initWithName:@"_issuedAt" + type:[NSDate class] + conversion:^id _Nullable(NSObject *_Nullable value) { + if (![value isKindOfClass:[NSNumber class]]) { + return value; + } + NSNumber *valueAsNumber = (NSNumber *)value; + return [NSDate dateWithTimeIntervalSince1970:valueAsNumber.longLongValue]; + }]; + fieldMap[kNonceKey] = + [[OIDFieldMapping alloc] initWithName:@"_nonce" type:[NSString class]]; + }); + return fieldMap; +} + ++ (NSDictionary *)parseJWTSection:(NSString *)sectionString { + NSData *decodedData = [[self class] base64urlNoPaddingDecode:sectionString]; + + // Parses JSON. + NSError *error; + id object = [NSJSONSerialization JSONObjectWithData:decodedData options:0 error:&error]; + if (error) { + NSLog(@"Error %@ parsing token payload %@", error, sectionString); + } + if ([object isKindOfClass:[NSDictionary class]]) { + return (NSDictionary *)object; + } + + return nil; +} + ++ (NSData *)base64urlNoPaddingDecode:(NSString *)base64urlNoPaddingString { + NSMutableString *body = [base64urlNoPaddingString mutableCopy]; + + // Converts base64url to base64. + NSRange range = NSMakeRange(0, base64urlNoPaddingString.length); + [body replaceOccurrencesOfString:@"-" withString:@"+" options:NSLiteralSearch range:range]; + [body replaceOccurrencesOfString:@"_" withString:@"/" options:NSLiteralSearch range:range]; + + // Converts base64 no padding to base64 with padding + while (body.length % 4 != 0) { + [body appendString:@"="]; + } + + // Decodes base64 string. + NSData *decodedData = [[NSData alloc] initWithBase64EncodedString:body options:0]; + return decodedData; +} + +@end + + diff --git a/Source/OIDRegistrationRequest.h b/Sources/AppAuthCore/OIDRegistrationRequest.h similarity index 68% rename from Source/OIDRegistrationRequest.h rename to Sources/AppAuthCore/OIDRegistrationRequest.h index 47cf06357..e509c60a2 100644 --- a/Source/OIDRegistrationRequest.h +++ b/Sources/AppAuthCore/OIDRegistrationRequest.h @@ -26,17 +26,7 @@ NS_ASSUME_NONNULL_BEGIN /*! @brief Represents a registration request. @see https://openid.net/specs/openid-connect-registration-1_0.html#RegistrationRequest */ -@interface OIDRegistrationRequest : NSObject { - // property variables - OIDServiceConfiguration *_configuration; - NSString *_applicationType; - NSArray *_redirectURIs; - NSArray *_responseTypes; - NSArray *_grantTypes; - NSString *_subjectType; - NSString *_tokenEndpointAuthenticationMethod; - NSDictionary *_additionalParameters; -} +@interface OIDRegistrationRequest : NSObject /*! @brief The service's configuration. @remarks This configuration specifies how to connect to a particular OAuth provider. @@ -44,6 +34,16 @@ NS_ASSUME_NONNULL_BEGIN */ @property(nonatomic, readonly) OIDServiceConfiguration *configuration; +/*! @brief The initial access token to access the Client Registration Endpoint + (if required by the OpenID Provider). + @remarks OAuth 2.0 Access Token optionally issued by an Authorization Server granting + access to its Client Registration Endpoint. This token (if required) is + provisioned out of band. + @see Section 3 of OpenID Connect Dynamic Client Registration 1.0 + https://openid.net/specs/openid-connect-registration-1_0.html#ClientRegistration + */ +@property(nonatomic, readonly) NSString *initialAccessToken; + /*! @brief The application type to register, will always be 'native'. @remarks application_type @see https://openid.net/specs/openid-connect-registration-1_0.html#ClientMetadata @@ -89,6 +89,25 @@ NS_ASSUME_NONNULL_BEGIN */ - (instancetype)init NS_UNAVAILABLE; +/*! @brief Create a Client Registration Request to an OpenID Provider that supports open Dynamic + Registration. + @param configuration The service's configuration. + @param redirectURIs The redirect URIs to register for the client. + @param responseTypes The response types to register for the client. + @param grantTypes The grant types to register for the client. + @param subjectType The subject type to register for the client. + @param tokenEndpointAuthMethod The token endpoint authentication method to register for the + client. + @param additionalParameters The client's additional registration request parameters. + */ +- (instancetype)initWithConfiguration:(OIDServiceConfiguration *)configuration + redirectURIs:(NSArray *)redirectURIs + responseTypes:(nullable NSArray *)responseTypes + grantTypes:(nullable NSArray *)grantTypes + subjectType:(nullable NSString *)subjectType + tokenEndpointAuthMethod:(nullable NSString *)tokenEndpointAuthMethod + additionalParameters:(nullable NSDictionary *)additionalParameters; + /*! @brief Designated initializer. @param configuration The service's configuration. @param redirectURIs The redirect URIs to register for the client. @@ -97,7 +116,10 @@ NS_ASSUME_NONNULL_BEGIN @param subjectType The subject type to register for the client. @param tokenEndpointAuthMethod The token endpoint authentication method to register for the client. + @param initialAccessToken The initial access token to access the Client Registration Endpoint + (if required by the OpenID Provider). @param additionalParameters The client's additional registration request parameters. + @see https://openid.net/specs/openid-connect-registration-1_0.html#ClientRegistration */ - (instancetype)initWithConfiguration:(OIDServiceConfiguration *)configuration redirectURIs:(NSArray *)redirectURIs @@ -105,6 +127,7 @@ NS_ASSUME_NONNULL_BEGIN grantTypes:(nullable NSArray *)grantTypes subjectType:(nullable NSString *)subjectType tokenEndpointAuthMethod:(nullable NSString *)tokenEndpointAuthMethod + initialAccessToken:(nullable NSString *)initialAccessToken additionalParameters:(nullable NSDictionary *)additionalParameters NS_DESIGNATED_INITIALIZER; diff --git a/Source/OIDRegistrationRequest.m b/Sources/AppAuthCore/OIDRegistrationRequest.m similarity index 76% rename from Source/OIDRegistrationRequest.m rename to Sources/AppAuthCore/OIDRegistrationRequest.m index e6018af37..2b3435f85 100644 --- a/Source/OIDRegistrationRequest.m +++ b/Sources/AppAuthCore/OIDRegistrationRequest.m @@ -26,6 +26,10 @@ */ static NSString *const kConfigurationKey = @"configuration"; +/*! @brief The key for the @c initialAccessToken property for @c NSSecureCoding + */ +static NSString *const kInitialAccessToken = @"initial_access_token"; + /*! @brief Key used to encode the @c redirectURIs property for @c NSSecureCoding */ static NSString *const kRedirectURIsKey = @"redirect_uris"; @@ -49,15 +53,6 @@ @implementation OIDRegistrationRequest -@synthesize configuration = _configuration; -@synthesize applicationType = _applicationType; -@synthesize redirectURIs = _redirectURIs; -@synthesize responseTypes = _responseTypes; -@synthesize grantTypes = _grantTypes; -@synthesize subjectType = _subjectType; -@synthesize tokenEndpointAuthenticationMethod = _tokenEndpointAuthenticationMethod; -@synthesize additionalParameters = _additionalParameters; - #pragma mark - Initializers - (instancetype)init @@ -69,7 +64,7 @@ - (instancetype)init subjectType: tokenEndpointAuthMethod: additionalParameters:) - ); + ) - (instancetype)initWithConfiguration:(OIDServiceConfiguration *)configuration redirectURIs:(NSArray *)redirectURIs @@ -78,9 +73,28 @@ - (instancetype)initWithConfiguration:(OIDServiceConfiguration *)configuration subjectType:(nullable NSString *)subjectType tokenEndpointAuthMethod:(nullable NSString *)tokenEndpointAuthenticationMethod additionalParameters:(nullable NSDictionary *)additionalParameters { + return [self initWithConfiguration:configuration + redirectURIs:redirectURIs + responseTypes:responseTypes + grantTypes:grantTypes + subjectType:subjectType + tokenEndpointAuthMethod:tokenEndpointAuthenticationMethod + initialAccessToken:nil + additionalParameters:additionalParameters]; +} + +- (instancetype)initWithConfiguration:(OIDServiceConfiguration *)configuration + redirectURIs:(NSArray *)redirectURIs + responseTypes:(nullable NSArray *)responseTypes + grantTypes:(nullable NSArray *)grantTypes + subjectType:(nullable NSString *)subjectType + tokenEndpointAuthMethod:(nullable NSString *)tokenEndpointAuthenticationMethod + initialAccessToken:(nullable NSString *)initialAccessToken + additionalParameters:(nullable NSDictionary *)additionalParameters { self = [super init]; if (self) { _configuration = [configuration copy]; + _initialAccessToken = [initialAccessToken copy]; _redirectURIs = [redirectURIs copy]; _responseTypes = [responseTypes copy]; _grantTypes = [grantTypes copy]; @@ -114,12 +128,17 @@ - (instancetype)initWithCoder:(NSCoder *)aDecoder { OIDServiceConfiguration *configuration = [aDecoder decodeObjectOfClass:[OIDServiceConfiguration class] forKey:kConfigurationKey]; - NSArray *redirectURIs = [aDecoder decodeObjectOfClass:[NSArray class] - forKey:kRedirectURIsKey]; - NSArray *responseTypes = [aDecoder decodeObjectOfClass:[NSArray class] - forKey:kResponseTypesKey]; - NSArray *grantTypes = [aDecoder decodeObjectOfClass:[NSArray class] - forKey:kGrantTypesKey]; + NSString *initialAccessToken = [aDecoder decodeObjectOfClass:[NSString class] + forKey:kInitialAccessToken]; + NSArray *redirectURIs = + [aDecoder decodeObjectOfClasses:[NSSet setWithArray:@[[NSArray class], [NSURL class]]] + forKey:kRedirectURIsKey]; + NSArray *responseTypes = + [aDecoder decodeObjectOfClasses:[NSSet setWithArray:@[[NSArray class], [NSString class]]] + forKey:kResponseTypesKey]; + NSArray *grantTypes = + [aDecoder decodeObjectOfClasses:[NSSet setWithArray:@[[NSArray class], [NSString class]]] + forKey:kGrantTypesKey]; NSString *subjectType = [aDecoder decodeObjectOfClass:[NSString class] forKey:kSubjectTypeKey]; NSString *tokenEndpointAuthenticationMethod = @@ -136,12 +155,14 @@ - (instancetype)initWithCoder:(NSCoder *)aDecoder { grantTypes:grantTypes subjectType:subjectType tokenEndpointAuthMethod:tokenEndpointAuthenticationMethod + initialAccessToken:initialAccessToken additionalParameters:additionalParameters]; return self; } - (void)encodeWithCoder:(NSCoder *)aCoder { [aCoder encodeObject:_configuration forKey:kConfigurationKey]; + [aCoder encodeObject:_initialAccessToken forKey:kInitialAccessToken]; [aCoder encodeObject:_redirectURIs forKey:kRedirectURIsKey]; [aCoder encodeObject:_responseTypes forKey:kResponseTypesKey]; [aCoder encodeObject:_grantTypes forKey:kGrantTypesKey]; @@ -159,15 +180,17 @@ - (NSString *)description { encoding:NSUTF8StringEncoding]; return [NSString stringWithFormat:@"<%@: %p, request: >", NSStringFromClass([self class]), - self, + (void *)self, request.URL, requestBody]; } - (NSURLRequest *)URLRequest { static NSString *const kHTTPPost = @"POST"; + static NSString *const kBearer = @"Bearer"; static NSString *const kHTTPContentTypeHeaderKey = @"Content-Type"; static NSString *const kHTTPContentTypeHeaderValue = @"application/json"; + static NSString *const kHTTPAuthorizationHeaderKey = @"Authorization"; NSData *postBody = [self JSONString]; if (!postBody) { @@ -179,6 +202,10 @@ - (NSURLRequest *)URLRequest { [[NSURLRequest requestWithURL:registrationRequestURL] mutableCopy]; URLRequest.HTTPMethod = kHTTPPost; [URLRequest setValue:kHTTPContentTypeHeaderValue forHTTPHeaderField:kHTTPContentTypeHeaderKey]; + if (_initialAccessToken) { + NSString *value = [NSString stringWithFormat:@"%@ %@", kBearer, _initialAccessToken]; + [URLRequest setValue:value forHTTPHeaderField:kHTTPAuthorizationHeaderKey]; + } URLRequest.HTTPBody = postBody; return URLRequest; } diff --git a/Source/OIDRegistrationResponse.h b/Sources/AppAuthCore/OIDRegistrationResponse.h similarity index 92% rename from Source/OIDRegistrationResponse.h rename to Sources/AppAuthCore/OIDRegistrationResponse.h index f7fec7716..df6239064 100644 --- a/Source/OIDRegistrationResponse.h +++ b/Sources/AppAuthCore/OIDRegistrationResponse.h @@ -50,18 +50,7 @@ extern NSString *const OIDRegistrationClientURIParam; /*! @brief Represents a registration response. @see https://openid.net/specs/openid-connect-registration-1_0.html#RegistrationResponse */ -@interface OIDRegistrationResponse : NSObject { - // property variables - OIDRegistrationRequest *_request; - NSString *_clientID; - NSDate *_clientIDIssuedAt; - NSString *_clientSecret; - NSDate *_clientSecretExpiresAt; - NSString *_registrationAccessToken; - NSURL *_registrationClientURI; - NSString *_tokenEndpointAuthenticationMethod; - NSDictionary *> *_additionalParameters; -} +@interface OIDRegistrationResponse : NSObject /*! @brief The request which was serviced. */ diff --git a/Source/OIDRegistrationResponse.m b/Sources/AppAuthCore/OIDRegistrationResponse.m similarity index 92% rename from Source/OIDRegistrationResponse.m rename to Sources/AppAuthCore/OIDRegistrationResponse.m index 251a99188..ec0411b79 100644 --- a/Source/OIDRegistrationResponse.m +++ b/Sources/AppAuthCore/OIDRegistrationResponse.m @@ -22,6 +22,7 @@ #import "OIDDefines.h" #import "OIDFieldMapping.h" #import "OIDRegistrationRequest.h" +#import "OIDTokenUtilities.h" NSString *const OIDClientIDParam = @"client_id"; NSString *const OIDClientIDIssuedAtParam = @"client_id_issued_at"; @@ -40,16 +41,6 @@ @implementation OIDRegistrationResponse -@synthesize request = _request; -@synthesize clientID = _clientID; -@synthesize clientIDIssuedAt = _clientIDIssuedAt; -@synthesize clientSecret = _clientSecret; -@synthesize clientSecretExpiresAt = _clientSecretExpiresAt; -@synthesize registrationAccessToken = _registrationAccessToken; -@synthesize registrationClientURI = _registrationClientURI; -@synthesize tokenEndpointAuthenticationMethod = _tokenEndpointAuthenticationMethod; -@synthesize additionalParameters = _additionalParameters; - /*! @brief Returns a mapping of incoming parameters to instance variables. @return A mapping of incoming parameters to instance variables. */ @@ -89,7 +80,7 @@ @implementation OIDRegistrationResponse #pragma mark - Initializers - (nonnull instancetype)init - OID_UNAVAILABLE_USE_INITIALIZER(@selector(initWithRequest:parameters:)); + OID_UNAVAILABLE_USE_INITIALIZER(@selector(initWithRequest:parameters:)) - (instancetype)initWithRequest:(OIDRegistrationRequest *)request parameters:(NSDictionary *> *)parameters { @@ -159,12 +150,12 @@ - (NSString *)description { "registrationClientURI: \"%@\", " "additionalParameters: %@, request: %@>", NSStringFromClass([self class]), - self, + (void *)self, _clientID, _clientIDIssuedAt, - _clientSecret, + [OIDTokenUtilities redact:_clientSecret], _clientSecretExpiresAt, - _registrationAccessToken, + [OIDTokenUtilities redact:_registrationAccessToken], _registrationClientURI, _additionalParameters, _request]; diff --git a/Source/OIDResponseTypes.h b/Sources/AppAuthCore/OIDResponseTypes.h similarity index 100% rename from Source/OIDResponseTypes.h rename to Sources/AppAuthCore/OIDResponseTypes.h diff --git a/Source/OIDResponseTypes.m b/Sources/AppAuthCore/OIDResponseTypes.m similarity index 100% rename from Source/OIDResponseTypes.m rename to Sources/AppAuthCore/OIDResponseTypes.m diff --git a/Source/OIDScopeUtilities.h b/Sources/AppAuthCore/OIDScopeUtilities.h similarity index 100% rename from Source/OIDScopeUtilities.h rename to Sources/AppAuthCore/OIDScopeUtilities.h diff --git a/Source/OIDScopeUtilities.m b/Sources/AppAuthCore/OIDScopeUtilities.m similarity index 100% rename from Source/OIDScopeUtilities.m rename to Sources/AppAuthCore/OIDScopeUtilities.m diff --git a/Source/OIDScopes.h b/Sources/AppAuthCore/OIDScopes.h similarity index 100% rename from Source/OIDScopes.h rename to Sources/AppAuthCore/OIDScopes.h diff --git a/Source/OIDScopes.m b/Sources/AppAuthCore/OIDScopes.m similarity index 100% rename from Source/OIDScopes.m rename to Sources/AppAuthCore/OIDScopes.m diff --git a/Source/OIDServiceConfiguration.h b/Sources/AppAuthCore/OIDServiceConfiguration.h similarity index 60% rename from Source/OIDServiceConfiguration.h rename to Sources/AppAuthCore/OIDServiceConfiguration.h index 513e5d970..a072a478f 100644 --- a/Source/OIDServiceConfiguration.h +++ b/Sources/AppAuthCore/OIDServiceConfiguration.h @@ -32,13 +32,7 @@ typedef void (^OIDServiceConfigurationCreated) /*! @brief Represents the information needed to construct a @c OIDAuthorizationService. */ -@interface OIDServiceConfiguration : NSObject { - // property variables - NSURL *_authorizationEndpoint; - NSURL *_tokenEndpoint; - NSURL *_registrationEndpoint; - OIDServiceDiscovery *_discoveryDocument; -} +@interface OIDServiceConfiguration : NSObject /*! @brief The authorization endpoint URI. */ @@ -48,10 +42,18 @@ typedef void (^OIDServiceConfigurationCreated) */ @property(nonatomic, readonly) NSURL *tokenEndpoint; +/*! @brief The OpenID Connect issuer. + */ +@property(nonatomic, readonly, nullable) NSURL *issuer; + /*! @brief The dynamic client registration endpoint URI. */ @property(nonatomic, readonly, nullable) NSURL *registrationEndpoint; +/*! @brief The end session logout endpoint URI. + */ +@property(nonatomic, readonly, nullable) NSURL *endSessionEndpoint; + /*! @brief The discovery document. */ @property(nonatomic, readonly, nullable) OIDServiceDiscovery *discoveryDocument; @@ -76,6 +78,36 @@ typedef void (^OIDServiceConfigurationCreated) tokenEndpoint:(NSURL *)tokenEndpoint registrationEndpoint:(nullable NSURL *)registrationEndpoint; +/*! @param authorizationEndpoint The authorization endpoint URI. + @param tokenEndpoint The token exchange and refresh endpoint URI. + @param issuer The OpenID Connect issuer. + */ +- (instancetype)initWithAuthorizationEndpoint:(NSURL *)authorizationEndpoint + tokenEndpoint:(NSURL *)tokenEndpoint + issuer:(nullable NSURL *)issuer; + +/*! @param authorizationEndpoint The authorization endpoint URI. + @param tokenEndpoint The token exchange and refresh endpoint URI. + @param issuer The OpenID Connect issuer. + @param registrationEndpoint The dynamic client registration endpoint URI. + */ +- (instancetype)initWithAuthorizationEndpoint:(NSURL *)authorizationEndpoint + tokenEndpoint:(NSURL *)tokenEndpoint + issuer:(nullable NSURL *)issuer + registrationEndpoint:(nullable NSURL *)registrationEndpoint; + +/*! @param authorizationEndpoint The authorization endpoint URI. + @param tokenEndpoint The token exchange and refresh endpoint URI. + @param issuer The OpenID Connect issuer. + @param registrationEndpoint The dynamic client registration endpoint URI. + @param endSessionEndpoint The end session endpoint (logout) URI. + */ +- (instancetype)initWithAuthorizationEndpoint:(NSURL *)authorizationEndpoint + tokenEndpoint:(NSURL *)tokenEndpoint + issuer:(nullable NSURL *)issuer + registrationEndpoint:(nullable NSURL *)registrationEndpoint + endSessionEndpoint:(nullable NSURL *)endSessionEndpoint; + /*! @param discoveryDocument The discovery document from which to extract the required OAuth configuration. */ diff --git a/Source/OIDServiceConfiguration.m b/Sources/AppAuthCore/OIDServiceConfiguration.m similarity index 57% rename from Source/OIDServiceConfiguration.m rename to Sources/AppAuthCore/OIDServiceConfiguration.m index d3121c28b..0e5c1197a 100644 --- a/Source/OIDServiceConfiguration.m +++ b/Sources/AppAuthCore/OIDServiceConfiguration.m @@ -30,10 +30,18 @@ */ static NSString *const kTokenEndpointKey = @"tokenEndpoint"; +/*! @brief The key for the @c issuer property. + */ +static NSString *const kIssuerKey = @"issuer"; + /*! @brief The key for the @c registrationEndpoint property. */ static NSString *const kRegistrationEndpointKey = @"registrationEndpoint"; +/*! @brief The key for the @c endSessionEndpoint property. + */ +static NSString *const kEndSessionEndpointKey = @"endSessionEndpoint"; + /*! @brief The key for the @c discoveryDocument property. */ static NSString *const kDiscoveryDocumentKey = @"discoveryDocument"; @@ -44,7 +52,9 @@ @interface OIDServiceConfiguration () - (instancetype)initWithAuthorizationEndpoint:(NSURL *)authorizationEndpoint tokenEndpoint:(NSURL *)tokenEndpoint + issuer:(nullable NSURL *)issuer registrationEndpoint:(nullable NSURL *)registrationEndpoint + endSessionEndpoint:(nullable NSURL *)endSessionEndpoint discoveryDocument:(nullable OIDServiceDiscovery *)discoveryDocument NS_DESIGNATED_INITIALIZER; @@ -52,28 +62,26 @@ - (instancetype)initWithAuthorizationEndpoint:(NSURL *)authorizationEndpoint @implementation OIDServiceConfiguration -@synthesize authorizationEndpoint = _authorizationEndpoint; -@synthesize tokenEndpoint = _tokenEndpoint; -@synthesize registrationEndpoint = _registrationEndpoint; -@synthesize discoveryDocument = _discoveryDocument; - - (instancetype)init OID_UNAVAILABLE_USE_INITIALIZER(@selector( initWithAuthorizationEndpoint: - tokenEndpoint: - registrationEndpoint:) - ); + tokenEndpoint:) + ) - (instancetype)initWithAuthorizationEndpoint:(NSURL *)authorizationEndpoint tokenEndpoint:(NSURL *)tokenEndpoint + issuer:(nullable NSURL *)issuer registrationEndpoint:(nullable NSURL *)registrationEndpoint + endSessionEndpoint:(nullable NSURL *)endSessionEndpoint discoveryDocument:(nullable OIDServiceDiscovery *)discoveryDocument { self = [super init]; if (self) { _authorizationEndpoint = [authorizationEndpoint copy]; _tokenEndpoint = [tokenEndpoint copy]; + _issuer = [issuer copy]; _registrationEndpoint = [registrationEndpoint copy]; + _endSessionEndpoint = [endSessionEndpoint copy]; _discoveryDocument = [discoveryDocument copy]; } return self; @@ -83,7 +91,9 @@ - (instancetype)initWithAuthorizationEndpoint:(NSURL *)authorizationEndpoint tokenEndpoint:(NSURL *)tokenEndpoint { return [self initWithAuthorizationEndpoint:authorizationEndpoint tokenEndpoint:tokenEndpoint + issuer:nil registrationEndpoint:nil + endSessionEndpoint:nil discoveryDocument:nil]; } @@ -92,14 +102,54 @@ - (instancetype)initWithAuthorizationEndpoint:(NSURL *)authorizationEndpoint registrationEndpoint:(nullable NSURL *)registrationEndpoint { return [self initWithAuthorizationEndpoint:authorizationEndpoint tokenEndpoint:tokenEndpoint + issuer:nil + registrationEndpoint:registrationEndpoint + endSessionEndpoint:nil + discoveryDocument:nil]; +} + +- (instancetype)initWithAuthorizationEndpoint:(NSURL *)authorizationEndpoint + tokenEndpoint:(NSURL *)tokenEndpoint + issuer:(nullable NSURL *)issuer { + return [self initWithAuthorizationEndpoint:authorizationEndpoint + tokenEndpoint:tokenEndpoint + issuer:issuer + registrationEndpoint:nil + endSessionEndpoint:nil + discoveryDocument:nil]; +} + +- (instancetype)initWithAuthorizationEndpoint:(NSURL *)authorizationEndpoint + tokenEndpoint:(NSURL *)tokenEndpoint + issuer:(nullable NSURL *)issuer + registrationEndpoint:(nullable NSURL *)registrationEndpoint { + return [self initWithAuthorizationEndpoint:authorizationEndpoint + tokenEndpoint:tokenEndpoint + issuer:issuer + registrationEndpoint:registrationEndpoint + endSessionEndpoint:nil + discoveryDocument:nil]; +} + +- (instancetype)initWithAuthorizationEndpoint:(NSURL *)authorizationEndpoint + tokenEndpoint:(NSURL *)tokenEndpoint + issuer:(nullable NSURL *)issuer + registrationEndpoint:(nullable NSURL *)registrationEndpoint + endSessionEndpoint:(nullable NSURL *)endSessionEndpoint { + return [self initWithAuthorizationEndpoint:authorizationEndpoint + tokenEndpoint:tokenEndpoint + issuer:issuer registrationEndpoint:registrationEndpoint + endSessionEndpoint:endSessionEndpoint discoveryDocument:nil]; } - (instancetype)initWithDiscoveryDocument:(OIDServiceDiscovery *) discoveryDocument { return [self initWithAuthorizationEndpoint:discoveryDocument.authorizationEndpoint tokenEndpoint:discoveryDocument.tokenEndpoint + issuer:discoveryDocument.issuer registrationEndpoint:discoveryDocument.registrationEndpoint + endSessionEndpoint:discoveryDocument.endSessionEndpoint discoveryDocument:discoveryDocument]; } @@ -124,27 +174,44 @@ - (nullable instancetype)initWithCoder:(NSCoder *)aDecoder { forKey:kAuthorizationEndpointKey]; NSURL *tokenEndpoint = [aDecoder decodeObjectOfClass:[NSURL class] forKey:kTokenEndpointKey]; + NSURL *issuer = [aDecoder decodeObjectOfClass:[NSURL class] + forKey:kIssuerKey]; NSURL *registrationEndpoint = [aDecoder decodeObjectOfClass:[NSURL class] forKey:kRegistrationEndpointKey]; + NSURL *endSessionEndpoint = [aDecoder decodeObjectOfClass:[NSURL class] + forKey:kEndSessionEndpointKey]; // We don't accept nil authorizationEndpoints or tokenEndpoints. if (!authorizationEndpoint || !tokenEndpoint) { return nil; } - OIDServiceDiscovery *discoveryDocument = [aDecoder decodeObjectOfClass:[OIDServiceDiscovery class] - forKey:kDiscoveryDocumentKey]; + NSSet *allowedClasses = [NSSet setWithArray:@[[OIDServiceDiscovery class], + // The following classes are required in + // order to support secure decoding of the + // old OIDServiceDiscovery encoding. + [NSDictionary class], + [NSArray class], + [NSString class], + [NSNumber class], + [NSNull class]]]; + OIDServiceDiscovery *discoveryDocument = [aDecoder decodeObjectOfClasses:allowedClasses + forKey:kDiscoveryDocumentKey]; return [self initWithAuthorizationEndpoint:authorizationEndpoint tokenEndpoint:tokenEndpoint + issuer:issuer registrationEndpoint:registrationEndpoint + endSessionEndpoint:endSessionEndpoint discoveryDocument:discoveryDocument]; } - (void)encodeWithCoder:(NSCoder *)aCoder { [aCoder encodeObject:_authorizationEndpoint forKey:kAuthorizationEndpointKey]; [aCoder encodeObject:_tokenEndpoint forKey:kTokenEndpointKey]; + [aCoder encodeObject:_issuer forKey:kIssuerKey]; [aCoder encodeObject:_registrationEndpoint forKey:kRegistrationEndpointKey]; [aCoder encodeObject:_discoveryDocument forKey:kDiscoveryDocumentKey]; + [aCoder encodeObject:_endSessionEndpoint forKey:kEndSessionEndpointKey]; } #pragma mark - description @@ -152,10 +219,11 @@ - (void)encodeWithCoder:(NSCoder *)aCoder { - (NSString *)description { return [NSString stringWithFormat: @"OIDServiceConfiguration authorizationEndpoint: %@, tokenEndpoint: %@, " - "registrationEndpoint: %@, discoveryDocument: [%@]", + "registrationEndpoint: %@, endSessionEndpoint: %@, discoveryDocument: [%@]", _authorizationEndpoint, _tokenEndpoint, _registrationEndpoint, + _endSessionEndpoint, _discoveryDocument]; } diff --git a/Source/OIDServiceDiscovery.h b/Sources/AppAuthCore/OIDServiceDiscovery.h similarity index 96% rename from Source/OIDServiceDiscovery.h rename to Sources/AppAuthCore/OIDServiceDiscovery.h index e3c8eda90..1a4929c53 100644 --- a/Source/OIDServiceDiscovery.h +++ b/Sources/AppAuthCore/OIDServiceDiscovery.h @@ -23,10 +23,7 @@ NS_ASSUME_NONNULL_BEGIN /*! @brief Represents an OpenID Connect 1.0 Discovery Document @see https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderMetadata */ -@interface OIDServiceDiscovery : NSObject { - // private variables - NSDictionary *_discoveryDictionary; -} +@interface OIDServiceDiscovery : NSObject /*! @brief The decoded OpenID Connect 1.0 Discovery Document as a dictionary. */ @@ -47,6 +44,12 @@ NS_ASSUME_NONNULL_BEGIN */ @property(nonatomic, readonly) NSURL *authorizationEndpoint; +/*! @brief OPTIONAL. URL of the OP's OAuth 2.0 Device Authorization Endpoint. + @remarks device_authorization_endpoint + @seealso https://tools.ietf.org/html/rfc8628#section-4 + */ +@property(nonatomic, readonly, nullable) NSURL *deviceAuthorizationEndpoint; + /*! @brief URL of the OP's OAuth 2.0 Token Endpoint. This is REQUIRED unless only the Implicit Flow is used. @remarks token_endpoint @@ -81,6 +84,12 @@ NS_ASSUME_NONNULL_BEGIN */ @property(nonatomic, readonly, nullable) NSURL *registrationEndpoint; +/* @brief OPTIONAL. URL of the OP's RP-Initiated Logout endpoint. + @remarks end_session_endpoint + @seealso http://openid.net/specs/openid-connect-session-1_0.html#OPMetadata + */ +@property(nonatomic, readonly, nullable) NSURL *endSessionEndpoint; + /*! @brief RECOMMENDED. JSON array containing a list of the OAuth 2.0 [RFC6749] scope values that this server supports. The server MUST support the openid scope value. Servers MAY choose not to advertise some supported scope values even when this parameter is used, although those @@ -317,7 +326,7 @@ NS_ASSUME_NONNULL_BEGIN /*! @internal @brief Unavailable. Please use @c initWithDictionary:error:, @c initWithJSON:error, or the - @c serviceDiscoveryWithURL:callback: factory method. + @c discoverServiceConfigurationForDiscoveryURL:callback: from @c OIDAuthorizationService. */ - (nonnull instancetype)init NS_UNAVAILABLE; diff --git a/Source/OIDServiceDiscovery.m b/Sources/AppAuthCore/OIDServiceDiscovery.m similarity index 84% rename from Source/OIDServiceDiscovery.m rename to Sources/AppAuthCore/OIDServiceDiscovery.m index 565010577..4d96f9db3 100644 --- a/Source/OIDServiceDiscovery.m +++ b/Sources/AppAuthCore/OIDServiceDiscovery.m @@ -23,13 +23,19 @@ NS_ASSUME_NONNULL_BEGIN +/*! @brief The key for the @c discoveryDictionary property. + */ +static NSString *const kDiscoveryDictionaryKey = @"discoveryDictionary"; + /*! Field keys associated with an OpenID Connect Discovery Document. */ static NSString *const kIssuerKey = @"issuer"; static NSString *const kAuthorizationEndpointKey = @"authorization_endpoint"; +static NSString *const kDeviceAuthorizationEndpointKey = @"device_authorization_endpoint"; static NSString *const kTokenEndpointKey = @"token_endpoint"; static NSString *const kUserinfoEndpointKey = @"userinfo_endpoint"; static NSString *const kJWKSURLKey = @"jwks_uri"; static NSString *const kRegistrationEndpointKey = @"registration_endpoint"; +static NSString *const kEndSessionEndpointKey = @"end_session_endpoint"; static NSString *const kScopesSupportedKey = @"scopes_supported"; static NSString *const kResponseTypesSupportedKey = @"response_types_supported"; static NSString *const kResponseModesSupportedKey = @"response_modes_supported"; @@ -71,9 +77,11 @@ static NSString *const kOPPolicyURIKey = @"op_policy_uri"; static NSString *const kOPTosURIKey = @"op_tos_uri"; -@implementation OIDServiceDiscovery +@implementation OIDServiceDiscovery { + NSDictionary *_discoveryDictionary; +} -- (nonnull instancetype)init OID_UNAVAILABLE_USE_INITIALIZER(@selector(initWithDictionary:error:)); +- (nonnull instancetype)init OID_UNAVAILABLE_USE_INITIALIZER(@selector(initWithDictionary:error:)) - (nullable instancetype)initWithJSON:(NSString *)serviceDiscoveryJSON error:(NSError **)error { NSData *jsonData = [serviceDiscoveryJSON dataUsingEncoding:NSUTF8StringEncoding]; @@ -88,9 +96,16 @@ - (nullable instancetype)initWithJSONData:(NSData *)serviceDiscoveryJSONData if (!json || jsonError) { *error = [OIDErrorUtilities errorWithCode:OIDErrorCodeJSONDeserializationError underlyingError:jsonError - description:nil]; + description:jsonError.localizedDescription]; return nil; } + if (![json isKindOfClass:[NSDictionary class]]) { + *error = [OIDErrorUtilities errorWithCode:OIDErrorCodeInvalidDiscoveryDocument + underlyingError:nil + description:@"Discovery document isn't a dictionary"]; + return nil; + } + return [self initWithDictionary:json error:error]; } @@ -181,7 +196,27 @@ + (BOOL)supportsSecureCoding { - (nullable instancetype)initWithCoder:(NSCoder *)aDecoder { NSError *error; - NSDictionary *dictionary = [[NSDictionary alloc] initWithCoder:aDecoder]; + NSDictionary *dictionary; + if ([aDecoder containsValueForKey:kDiscoveryDictionaryKey]) { + // We're decoding a collection type (NSDictionary) from NSJSONSerialization's + // +JSONObjectWithData, so we need to include all classes that could potentially be contained + // within. + NSSet *allowedClasses = [NSSet setWithArray:@[[NSDictionary class], + [NSArray class], + [NSString class], + [NSNumber class], + [NSNull class]]]; + dictionary = [aDecoder decodeObjectOfClasses:allowedClasses + forKey:kDiscoveryDictionaryKey]; + } else { + // Decode using the old encoding which delegated to NSDictionary's encodeWithCoder: + // implementation: + // + // - (void)encodeWithCoder:(NSCoder *)aCoder { + // [_discoveryDictionary encodeWithCoder:aCoder]; + // } + dictionary = [[NSDictionary alloc] initWithCoder:aDecoder]; + } self = [self initWithDictionary:dictionary error:&error]; if (error) { return nil; @@ -190,6 +225,8 @@ - (nullable instancetype)initWithCoder:(NSCoder *)aDecoder { } - (void)encodeWithCoder:(NSCoder *)aCoder { + [aCoder encodeObject:_discoveryDictionary forKey:kDiscoveryDictionaryKey]; + // Provide forward compatibilty by continuing to add the old encoding. [_discoveryDictionary encodeWithCoder:aCoder]; } @@ -207,6 +244,10 @@ - (NSURL *)authorizationEndpoint { return [NSURL URLWithString:_discoveryDictionary[kAuthorizationEndpointKey]]; } +- (nullable NSURL *)deviceAuthorizationEndpoint { + return [NSURL URLWithString:_discoveryDictionary[kDeviceAuthorizationEndpointKey]]; +} + - (NSURL *)tokenEndpoint { return [NSURL URLWithString:_discoveryDictionary[kTokenEndpointKey]]; } @@ -223,6 +264,10 @@ - (nullable NSURL *)registrationEndpoint { return [NSURL URLWithString:_discoveryDictionary[kRegistrationEndpointKey]]; } +- (nullable NSURL *)endSessionEndpoint { + return [NSURL URLWithString:_discoveryDictionary[kEndSessionEndpointKey]]; +} + - (nullable NSArray *)scopesSupported { return _discoveryDictionary[kScopesSupportedKey]; } diff --git a/Source/OIDTokenRequest.h b/Sources/AppAuthCore/OIDTokenRequest.h similarity index 62% rename from Source/OIDTokenRequest.h rename to Sources/AppAuthCore/OIDTokenRequest.h index c0261382f..53a007378 100644 --- a/Source/OIDTokenRequest.h +++ b/Sources/AppAuthCore/OIDTokenRequest.h @@ -31,19 +31,7 @@ NS_ASSUME_NONNULL_BEGIN @see https://tools.ietf.org/html/rfc6749#section-3.2 @see https://tools.ietf.org/html/rfc6749#section-4.1.3 */ -@interface OIDTokenRequest : NSObject { - // property variables - OIDServiceConfiguration *_configuration; - NSString *_grantType; - NSString *_authorizationCode; - NSURL *_redirectURL; - NSString *_clientID; - NSString *_clientSecret; - NSString *_scope; - NSString *_refreshToken; - NSString *_codeVerifier; - NSDictionary *_additionalParameters; -} +@interface OIDTokenRequest : NSObject /*! @brief The service's configuration. @remarks This configuration specifies how to connect to a particular OAuth provider. @@ -69,7 +57,7 @@ NS_ASSUME_NONNULL_BEGIN @remarks redirect_uri @see https://tools.ietf.org/html/rfc6749#section-4.1.3 */ -@property(nonatomic, readonly) NSURL *redirectURL; +@property(nonatomic, readonly, nullable) NSURL *redirectURL; /*! @brief The client identifier. @remarks client_id @@ -107,9 +95,13 @@ NS_ASSUME_NONNULL_BEGIN */ @property(nonatomic, readonly, nullable) NSDictionary *additionalParameters; +/*! @brief The client's additional token request headers. + */ +@property(nonatomic, readonly, nullable) NSDictionary *additionalHeaders; + /*! @internal @brief Unavailable. Please use - initWithConfiguration:grantType:code:redirectURL:clientID:additionalParameters:. + initWithConfiguration:grantType:code:redirectURL:clientID:additionalParameters:additionalHeaders:. */ - (instancetype)init NS_UNAVAILABLE; @@ -120,14 +112,16 @@ NS_ASSUME_NONNULL_BEGIN @param code The authorization code received from the authorization server. @param redirectURL The client's redirect URI. @param clientID The client identifier. + @param clientSecret The client secret. @param scopes An array of scopes to combine into a single scope string per the OAuth2 spec. @param refreshToken The refresh token. + @param codeVerifier The PKCE code verifier. @param additionalParameters The client's additional token request parameters. */ - (instancetype)initWithConfiguration:(OIDServiceConfiguration *)configuration grantType:(NSString *)grantType authorizationCode:(nullable NSString *)code - redirectURL:(NSURL *)redirectURL + redirectURL:(nullable NSURL *)redirectURL clientID:(NSString *)clientID clientSecret:(nullable NSString *)clientSecret scopes:(nullable NSArray *)scopes @@ -135,6 +129,57 @@ NS_ASSUME_NONNULL_BEGIN codeVerifier:(nullable NSString *)codeVerifier additionalParameters:(nullable NSDictionary *)additionalParameters; +/*! @param configuration The service's configuration. + @param grantType the type of token being sent to the token endpoint, i.e. "authorization_code" + for the authorization code exchange, or "refresh_token" for an access token refresh request. + @see OIDGrantTypes.h + @param code The authorization code received from the authorization server. + @param redirectURL The client's redirect URI. + @param clientID The client identifier. + @param clientSecret The client secret. + @param scope The value of the scope parameter is expressed as a list of space-delimited, + case-sensitive strings. + @param refreshToken The refresh token. + @param codeVerifier The PKCE code verifier. + @param additionalParameters The client's additional token request parameters. + */ +- (instancetype)initWithConfiguration:(OIDServiceConfiguration *)configuration + grantType:(NSString *)grantType + authorizationCode:(nullable NSString *)code + redirectURL:(nullable NSURL *)redirectURL + clientID:(NSString *)clientID + clientSecret:(nullable NSString *)clientSecret + scope:(nullable NSString *)scope + refreshToken:(nullable NSString *)refreshToken + codeVerifier:(nullable NSString *)codeVerifier + additionalParameters:(nullable NSDictionary *)additionalParameters; + +/*! @param configuration The service's configuration. + @param grantType the type of token being sent to the token endpoint, i.e. "authorization_code" + for the authorization code exchange, or "refresh_token" for an access token refresh request. + @see OIDGrantTypes.h + @param code The authorization code received from the authorization server. + @param redirectURL The client's redirect URI. + @param clientID The client identifier. + @param clientSecret The client secret. + @param scopes An array of scopes to combine into a single scope string per the OAuth2 spec. + @param refreshToken The refresh token. + @param codeVerifier The PKCE code verifier. + @param additionalParameters The client's additional token request parameters. + @param additionalHeaders The client's additional token request headers. + */ +- (instancetype)initWithConfiguration:(OIDServiceConfiguration *)configuration + grantType:(NSString *)grantType + authorizationCode:(nullable NSString *)code + redirectURL:(nullable NSURL *)redirectURL + clientID:(NSString *)clientID + clientSecret:(nullable NSString *)clientSecret + scopes:(nullable NSArray *)scopes + refreshToken:(nullable NSString *)refreshToken + codeVerifier:(nullable NSString *)codeVerifier + additionalParameters:(nullable NSDictionary *)additionalParameters + additionalHeaders:(nullable NSDictionary *)additionalHeaders; + /*! @brief Designated initializer. @param configuration The service's configuration. @param grantType the type of token being sent to the token endpoint, i.e. "authorization_code" @@ -143,23 +188,32 @@ NS_ASSUME_NONNULL_BEGIN @param code The authorization code received from the authorization server. @param redirectURL The client's redirect URI. @param clientID The client identifier. + @param clientSecret The client secret. @param scope The value of the scope parameter is expressed as a list of space-delimited, case-sensitive strings. @param refreshToken The refresh token. + @param codeVerifier The PKCE code verifier. @param additionalParameters The client's additional token request parameters. + @param additionalHeaders The client's additional token request headers. */ - (instancetype)initWithConfiguration:(OIDServiceConfiguration *)configuration grantType:(NSString *)grantType authorizationCode:(nullable NSString *)code - redirectURL:(NSURL *)redirectURL + redirectURL:(nullable NSURL *)redirectURL clientID:(NSString *)clientID clientSecret:(nullable NSString *)clientSecret scope:(nullable NSString *)scope refreshToken:(nullable NSString *)refreshToken codeVerifier:(nullable NSString *)codeVerifier additionalParameters:(nullable NSDictionary *)additionalParameters + additionalHeaders:(nullable NSDictionary *)additionalHeaders NS_DESIGNATED_INITIALIZER; +/*! @brief Designated initializer for NSSecureCoding. + @param aDecoder Unarchiver object to decode + */ +- (instancetype)initWithCoder:(NSCoder *)aDecoder NS_DESIGNATED_INITIALIZER; + /*! @brief Constructs an @c NSURLRequest representing the token request. @return An @c NSURLRequest representing the token request. */ diff --git a/Source/OIDTokenRequest.m b/Sources/AppAuthCore/OIDTokenRequest.m similarity index 68% rename from Source/OIDTokenRequest.m rename to Sources/AppAuthCore/OIDTokenRequest.m index d76d87a76..6d30b6d6b 100644 --- a/Source/OIDTokenRequest.m +++ b/Sources/AppAuthCore/OIDTokenRequest.m @@ -19,9 +19,11 @@ #import "OIDTokenRequest.h" #import "OIDDefines.h" +#import "OIDError.h" #import "OIDScopeUtilities.h" #import "OIDServiceConfiguration.h" #import "OIDURLQueryComponent.h" +#import "OIDTokenUtilities.h" /*! @brief The key for the @c configuration property for @c NSSecureCoding */ @@ -65,18 +67,12 @@ */ static NSString *const kAdditionalParametersKey = @"additionalParameters"; -@implementation OIDTokenRequest +/*! @brief Key used to encode the @c additionalHeaders property for + @c NSSecureCoding + */ +static NSString *const kAdditionalHeadersKey = @"additionalHeaders"; -@synthesize configuration = _configuration; -@synthesize grantType = _grantType; -@synthesize authorizationCode = _authorizationCode; -@synthesize redirectURL = _redirectURL; -@synthesize clientID = _clientID; -@synthesize clientSecret = _clientSecret; -@synthesize scope = _scope; -@synthesize refreshToken = _refreshToken; -@synthesize codeVerifier = _codeVerifier; -@synthesize additionalParameters = _additionalParameters; +@implementation OIDTokenRequest - (instancetype)init OID_UNAVAILABLE_USE_INITIALIZER( @@ -89,19 +85,67 @@ - (instancetype)init scope: refreshToken: codeVerifier: - additionalParameters:) - ); + additionalParameters: + additionalHeaders:) + ) + +- (instancetype)initWithConfiguration:(nonnull OIDServiceConfiguration *)configuration + grantType:(nonnull NSString *)grantType + authorizationCode:(nullable NSString *)code + redirectURL:(nullable NSURL *)redirectURL + clientID:(nonnull NSString *)clientID + clientSecret:(nullable NSString *)clientSecret + scopes:(nullable NSArray *)scopes + refreshToken:(nullable NSString *)refreshToken + codeVerifier:(nullable NSString *)codeVerifier + additionalParameters:(nullable NSDictionary *)additionalParameters { + return [self initWithConfiguration:configuration + grantType:grantType + authorizationCode:code + redirectURL:redirectURL + clientID:clientID + clientSecret:clientSecret + scopes:scopes + refreshToken:refreshToken + codeVerifier:codeVerifier + additionalParameters:additionalParameters + additionalHeaders:_additionalHeaders]; +} + +- (instancetype)initWithConfiguration:(nonnull OIDServiceConfiguration *)configuration + grantType:(nonnull NSString *)grantType + authorizationCode:(nullable NSString *)code + redirectURL:(nullable NSURL *)redirectURL + clientID:(nonnull NSString *)clientID + clientSecret:(nullable NSString *)clientSecret + scope:(nullable NSString *)scope + refreshToken:(nullable NSString *)refreshToken + codeVerifier:(nullable NSString *)codeVerifier + additionalParameters:(nullable NSDictionary *)additionalParameters { + return [self initWithConfiguration:configuration + grantType:grantType + authorizationCode:code + redirectURL:redirectURL + clientID:clientID + clientSecret:clientSecret + scope:scope + refreshToken:refreshToken + codeVerifier:codeVerifier + additionalParameters:additionalParameters + additionalHeaders:_additionalHeaders]; +} - (instancetype)initWithConfiguration:(OIDServiceConfiguration *)configuration grantType:(NSString *)grantType authorizationCode:(nullable NSString *)code - redirectURL:(NSURL *)redirectURL + redirectURL:(nullable NSURL *)redirectURL clientID:(NSString *)clientID clientSecret:(nullable NSString *)clientSecret scopes:(nullable NSArray *)scopes refreshToken:(nullable NSString *)refreshToken codeVerifier:(nullable NSString *)codeVerifier - additionalParameters:(nullable NSDictionary *)additionalParameters { + additionalParameters:(nullable NSDictionary *)additionalParameters + additionalHeaders:(nullable NSDictionary *)additionalHeaders { return [self initWithConfiguration:configuration grantType:grantType authorizationCode:code @@ -111,19 +155,21 @@ - (instancetype)initWithConfiguration:(OIDServiceConfiguration *)configuration scope:[OIDScopeUtilities scopesWithArray:scopes] refreshToken:refreshToken codeVerifier:(NSString *)codeVerifier - additionalParameters:additionalParameters]; + additionalParameters:additionalParameters + additionalHeaders:additionalHeaders]; } - (instancetype)initWithConfiguration:(OIDServiceConfiguration *)configuration grantType:(NSString *)grantType authorizationCode:(nullable NSString *)code - redirectURL:(NSURL *)redirectURL + redirectURL:(nullable NSURL *)redirectURL clientID:(NSString *)clientID clientSecret:(nullable NSString *)clientSecret scope:(nullable NSString *)scope refreshToken:(nullable NSString *)refreshToken codeVerifier:(nullable NSString *)codeVerifier - additionalParameters:(nullable NSDictionary *)additionalParameters { + additionalParameters:(nullable NSDictionary *)additionalParameters + additionalHeaders:(nullable NSDictionary *)additionalHeaders { self = [super init]; if (self) { _configuration = [configuration copy]; @@ -137,6 +183,18 @@ - (instancetype)initWithConfiguration:(OIDServiceConfiguration *)configuration _codeVerifier = [codeVerifier copy]; _additionalParameters = [[NSDictionary alloc] initWithDictionary:additionalParameters copyItems:YES]; + _additionalHeaders = + [[NSDictionary alloc] initWithDictionary:additionalHeaders copyItems:YES]; + + // Additional validation for the authorization_code grant type + if ([_grantType isEqual:OIDGrantTypeAuthorizationCode]) { + // redirect URI must not be nil + if (!_redirectURL) { + [NSException raise:OIDOAuthExceptionInvalidTokenRequestNullRedirectURL + format:@"%@", OIDOAuthExceptionInvalidTokenRequestNullRedirectURL, nil]; + + } + } } return self; } @@ -173,19 +231,35 @@ - (instancetype)initWithCoder:(NSCoder *)aDecoder { [NSDictionary class], [NSString class] ]]; + NSDictionary *additionalParameters = - [aDecoder decodeObjectOfClasses:additionalParameterCodingClasses - forKey:kAdditionalParametersKey]; - self = [self initWithConfiguration:configuration - grantType:grantType - authorizationCode:code - redirectURL:redirectURL - clientID:clientID - clientSecret:clientSecret - scope:scope - refreshToken:refreshToken - codeVerifier:codeVerifier - additionalParameters:additionalParameters]; + [aDecoder decodeObjectOfClasses:additionalParameterCodingClasses forKey:kAdditionalParametersKey]; + + + NSSet *additionalHeaderCodingClasses = [NSSet setWithArray:@[ + [NSDictionary class], + [NSString class] + ]]; + + NSDictionary *additionalHeaders = + [aDecoder decodeObjectOfClasses:additionalHeaderCodingClasses forKey:kAdditionalHeadersKey]; + + self = [super init]; + if (self) { + _configuration = [configuration copy]; + _grantType = [grantType copy]; + _authorizationCode = [code copy]; + _redirectURL = [redirectURL copy]; + _clientID = [clientID copy]; + _clientSecret = [clientSecret copy]; + _scope = [scope copy]; + _refreshToken = [refreshToken copy]; + _codeVerifier = [codeVerifier copy]; + _additionalParameters = + [[NSDictionary alloc] initWithDictionary:additionalParameters copyItems:YES]; + _additionalHeaders = + [[NSDictionary alloc] initWithDictionary:additionalHeaders copyItems:YES]; + } return self; } @@ -200,6 +274,7 @@ - (void)encodeWithCoder:(NSCoder *)aCoder { [aCoder encodeObject:_refreshToken forKey:kRefreshTokenKey]; [aCoder encodeObject:_codeVerifier forKey:kCodeVerifierKey]; [aCoder encodeObject:_additionalParameters forKey:kAdditionalParametersKey]; + [aCoder encodeObject:_additionalHeaders forKey:kAdditionalHeadersKey]; } #pragma mark - NSObject overrides @@ -210,7 +285,7 @@ - (NSString *)description { [[NSString alloc] initWithData:request.HTTPBody encoding:NSUTF8StringEncoding]; return [NSString stringWithFormat:@"<%@: %p, request: >", NSStringFromClass([self class]), - self, + (void *)self, request.URL, requestBody]; } @@ -274,7 +349,14 @@ - (NSURLRequest *)URLRequest { NSMutableDictionary *httpHeaders = [[NSMutableDictionary alloc] init]; if (_clientSecret) { - NSString *credentials = [NSString stringWithFormat:@"%@:%@", _clientID, _clientSecret]; + // The client id and secret are encoded using the "application/x-www-form-urlencoded" + // encoding algorithm per RFC 6749 Section 2.3.1. + // https://tools.ietf.org/html/rfc6749#section-2.3.1 + NSString *encodedClientID = [OIDTokenUtilities formUrlEncode:_clientID]; + NSString *encodedClientSecret = [OIDTokenUtilities formUrlEncode:_clientSecret]; + + NSString *credentials = + [NSString stringWithFormat:@"%@:%@", encodedClientID, encodedClientSecret]; NSData *plainData = [credentials dataUsingEncoding:NSUTF8StringEncoding]; NSString *basicAuth = [plainData base64EncodedStringWithOptions:kNilOptions]; @@ -292,6 +374,10 @@ - (NSURLRequest *)URLRequest { for (id header in httpHeaders) { [URLRequest setValue:httpHeaders[header] forHTTPHeaderField:header]; } + + for (id header in _additionalHeaders) { + [URLRequest setValue:_additionalHeaders[header] forHTTPHeaderField:header]; + } return URLRequest; } diff --git a/Source/OIDTokenResponse.h b/Sources/AppAuthCore/OIDTokenResponse.h similarity index 91% rename from Source/OIDTokenResponse.h rename to Sources/AppAuthCore/OIDTokenResponse.h index dc5d9766c..b446e944a 100644 --- a/Source/OIDTokenResponse.h +++ b/Sources/AppAuthCore/OIDTokenResponse.h @@ -26,17 +26,7 @@ NS_ASSUME_NONNULL_BEGIN @see https://tools.ietf.org/html/rfc6749#section-3.2 @see https://tools.ietf.org/html/rfc6749#section-4.1.3 */ -@interface OIDTokenResponse : NSObject { - // property variables - OIDTokenRequest *_request; - NSString *_accessToken; - NSDate *_accessTokenExpirationDate; - NSString *_tokenType; - NSString *_idToken; - NSString *_refreshToken; - NSString *_scope; - NSDictionary *> *_additionalParameters; -} +@interface OIDTokenResponse : NSObject /*! @brief The request which was serviced. */ @@ -67,12 +57,17 @@ NS_ASSUME_NONNULL_BEGIN /*! @brief ID Token value associated with the authenticated session. Always present for the authorization code grant exchange when OpenID Connect is used, optional for responses to - access token refresh requests. + access token refresh requests. Note that AppAuth does NOT verify the JWT signature. Users + of AppAuth are encouraged to verifying the JWT signature using the validation library of + their choosing. @remarks id_token @see http://openid.net/specs/openid-connect-core-1_0.html#TokenResponse @see http://openid.net/specs/openid-connect-core-1_0.html#RefreshTokenResponse @see http://openid.net/specs/openid-connect-core-1_0.html#IDToken - */ + @see https://jwt.io + @discussion @c OIDIDToken can be used to parse the ID Token and extract the claims. As noted, + this class does not verify the JWT signature. +*/ @property(nonatomic, readonly, nullable) NSString *idToken; /*! @brief The refresh token, which can be used to obtain new access tokens using the same diff --git a/Source/OIDTokenResponse.m b/Sources/AppAuthCore/OIDTokenResponse.m similarity index 91% rename from Source/OIDTokenResponse.m rename to Sources/AppAuthCore/OIDTokenResponse.m index 537d3878d..6995fb914 100644 --- a/Source/OIDTokenResponse.m +++ b/Sources/AppAuthCore/OIDTokenResponse.m @@ -21,6 +21,7 @@ #import "OIDDefines.h" #import "OIDFieldMapping.h" #import "OIDTokenRequest.h" +#import "OIDTokenUtilities.h" /*! @brief Key used to encode the @c request property for @c NSSecureCoding */ @@ -60,15 +61,6 @@ @implementation OIDTokenResponse -@synthesize request = _request; -@synthesize accessToken = _accessToken; -@synthesize accessTokenExpirationDate = _accessTokenExpirationDate; -@synthesize tokenType = _tokenType; -@synthesize idToken = _idToken; -@synthesize refreshToken = _refreshToken; -@synthesize scope = _scope; -@synthesize additionalParameters = _additionalParameters; - /*! @brief Returns a mapping of incoming parameters to instance variables. @return A mapping of incoming parameters to instance variables. */ @@ -98,7 +90,7 @@ @implementation OIDTokenResponse #pragma mark - Initializers - (instancetype)init - OID_UNAVAILABLE_USE_INITIALIZER(@selector(initWithRequest:parameters:)); + OID_UNAVAILABLE_USE_INITIALIZER(@selector(initWithRequest:parameters:)) - (instancetype)initWithRequest:(OIDTokenRequest *)request parameters:(NSDictionary *> *)parameters { @@ -155,12 +147,12 @@ - (NSString *)description { "tokenType: %@, idToken: \"%@\", refreshToken: \"%@\", " "scope: \"%@\", additionalParameters: %@, request: %@>", NSStringFromClass([self class]), - self, - _accessToken, + (void *)self, + [OIDTokenUtilities redact:_accessToken], _accessTokenExpirationDate, _tokenType, - _idToken, - _refreshToken, + [OIDTokenUtilities redact:_idToken], + [OIDTokenUtilities redact:_refreshToken], _scope, _additionalParameters, _request]; diff --git a/Source/OIDTokenUtilities.h b/Sources/AppAuthCore/OIDTokenUtilities.h similarity index 79% rename from Source/OIDTokenUtilities.h rename to Sources/AppAuthCore/OIDTokenUtilities.h index f13d937b0..fda898546 100644 --- a/Source/OIDTokenUtilities.h +++ b/Sources/AppAuthCore/OIDTokenUtilities.h @@ -48,7 +48,19 @@ NS_ASSUME_NONNULL_BEGIN @param inputString The input string. @return The SHA256 data. */ -+ (NSData *)sha265:(NSString *)inputString; ++ (NSData *)sha256:(NSString *)inputString; + +/*! @brief Truncated intput string after first 6 characters followed by ellipses + @param inputString The input string. + @return Truncated string. + */ ++ (nullable NSString *)redact:(nullable NSString *)inputString; + +/*! @brief Form url encode the input string by applying application/x-www-form-urlencoded algorithm + @param inputString The input string. + @return The encoded string. + */ ++ (NSString*)formUrlEncode:(NSString*)inputString; @end diff --git a/Source/OIDTokenUtilities.m b/Sources/AppAuthCore/OIDTokenUtilities.m similarity index 56% rename from Source/OIDTokenUtilities.m rename to Sources/AppAuthCore/OIDTokenUtilities.m index fa08ddcb5..3280c856c 100644 --- a/Source/OIDTokenUtilities.m +++ b/Sources/AppAuthCore/OIDTokenUtilities.m @@ -20,6 +20,12 @@ #import +/*! @brief String representing the set of characters that are allowed as is for the + application/x-www-form-urlencoded encoding algorithm. + */ +static NSString *const kFormUrlEncodedAllowedCharacters = + @" *-._0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; + @implementation OIDTokenUtilities + (NSString *)encodeBase64urlNoPadding:(NSData *)data { @@ -41,11 +47,43 @@ + (nullable NSString *)randomURLSafeStringWithSize:(NSUInteger)size { return [[self class] encodeBase64urlNoPadding:randomData]; } -+ (NSData *)sha265:(NSString *)inputString { ++ (NSData *)sha256:(NSString *)inputString { NSData *verifierData = [inputString dataUsingEncoding:NSUTF8StringEncoding]; NSMutableData *sha256Verifier = [NSMutableData dataWithLength:CC_SHA256_DIGEST_LENGTH]; CC_SHA256(verifierData.bytes, (CC_LONG)verifierData.length, sha256Verifier.mutableBytes); return sha256Verifier; } ++ (NSString *)redact:(NSString *)inputString { + if (inputString == nil) { + return nil; + } + switch(inputString.length){ + case 0: + return @""; + case 1 ... 8: + return @"[redacted]"; + case 9: + default: + return [[inputString substringToIndex:6] stringByAppendingString:@"...[redacted]"]; + } +} + ++ (NSString*)formUrlEncode:(NSString*)inputString { + // https://www.w3.org/TR/html5/sec-forms.html#application-x-www-form-urlencoded-encoding-algorithm + // Following the spec from the above link, application/x-www-form-urlencoded percent encode all + // the characters except *-._A-Za-z0-9 + // Space character is replaced by + in the resulting bytes sequence + if (inputString.length == 0) { + return inputString; + } + NSCharacterSet *allowedCharacters = + [NSCharacterSet characterSetWithCharactersInString:kFormUrlEncodedAllowedCharacters]; + // Percent encode all characters not present in the provided set. + NSString *encodedString = + [inputString stringByAddingPercentEncodingWithAllowedCharacters:allowedCharacters]; + // Replace occurences of space by '+' character + return [encodedString stringByReplacingOccurrencesOfString:@" " withString:@"+"]; +} + @end diff --git a/Source/OIDURLQueryComponent.h b/Sources/AppAuthCore/OIDURLQueryComponent.h similarity index 75% rename from Source/OIDURLQueryComponent.h rename to Sources/AppAuthCore/OIDURLQueryComponent.h index 068dcc369..054b11ea1 100644 --- a/Source/OIDURLQueryComponent.h +++ b/Sources/AppAuthCore/OIDURLQueryComponent.h @@ -28,14 +28,17 @@ NS_ASSUME_NONNULL_BEGIN */ extern BOOL gOIDURLQueryComponentForceIOS7Handling; -/*! @brief A utility class for creating and parsing URL query components. +/*! @brief A utility class for creating and parsing URL query components encoded with the + application/x-www-form-urlencoded format. + @description Supports application/x-www-form-urlencoded encoding and decoding, specifically + '+' is replaced with space before percent decoding. For encoding, simply percent encodes + space, as this is valid application/x-www-form-urlencoded. + @see https://tools.ietf.org/html/rfc6749#section-4.1.2 + @see https://tools.ietf.org/html/rfc6749#section-4.1.3 + @see https://tools.ietf.org/html/rfc6749#appendix-B + @see https://url.spec.whatwg.org/#urlencoded-parsing */ -@interface OIDURLQueryComponent : NSObject { - // private variables - /*! @brief A dictionary of parameter names and values representing the contents of the query. - */ - NSMutableDictionary *> *_parameters; -} +@interface OIDURLQueryComponent : NSObject /*! @brief The parameter names in the query. */ @@ -79,6 +82,12 @@ extern BOOL gOIDURLQueryComponentForceIOS7Handling; */ - (NSString *)URLEncodedParameters; +/*! @brief A NSMutableCharacterSet containing allowed characters in URL parameter values (that is + the "value" part of "?key=value"). This has less allowed characters than + @c URLQueryAllowedCharacterSet, as the query component includes both the key & value. + */ ++ (NSMutableCharacterSet *)URLParamValueAllowedCharacters; + @end NS_ASSUME_NONNULL_END diff --git a/Source/OIDURLQueryComponent.m b/Sources/AppAuthCore/OIDURLQueryComponent.m similarity index 85% rename from Source/OIDURLQueryComponent.m rename to Sources/AppAuthCore/OIDURLQueryComponent.m index 6a70d91ad..07050c905 100644 --- a/Source/OIDURLQueryComponent.m +++ b/Sources/AppAuthCore/OIDURLQueryComponent.m @@ -26,7 +26,11 @@ */ static NSString *const kQueryStringParamAdditionalDisallowedCharacters = @"=&+"; -@implementation OIDURLQueryComponent +@implementation OIDURLQueryComponent { + /*! @brief A dictionary of parameter names and values representing the contents of the query. + */ + NSMutableDictionary *> *_parameters; +} - (nullable instancetype)init { self = [super init]; @@ -44,6 +48,12 @@ - (nullable instancetype)initWithURL:(NSURL *)URL { if (!gOIDURLQueryComponentForceIOS7Handling) { NSURLComponents *components = [NSURLComponents componentsWithURL:URL resolvingAgainstBaseURL:NO]; + // As OAuth uses application/x-www-form-urlencoded encoding, interprets '+' as a space + // in addition to regular percent decoding. https://url.spec.whatwg.org/#urlencoded-parsing + components.percentEncodedQuery = + [components.percentEncodedQuery stringByReplacingOccurrencesOfString:@"+" + withString:@"%20"]; + // NB. @c queryItems are already percent decoded NSArray *queryItems = components.queryItems; for (NSURLQueryItem *queryItem in queryItems) { [self addParameter:queryItem.name value:queryItem.value]; @@ -54,6 +64,10 @@ - (nullable instancetype)initWithURL:(NSURL *)URL { // Fallback for iOS 7 NSString *query = URL.query; + // As OAuth uses application/x-www-form-urlencoded encoding, interprets '+' as a space + // in addition to regular percent decoding. https://url.spec.whatwg.org/#urlencoded-parsing + query = [query stringByReplacingOccurrencesOfString:@"+" withString:@"%20"]; + NSArray *queryParts = [query componentsSeparatedByString:@"&"]; for (NSString *queryPart in queryParts) { NSRange equalsRange = [queryPart rangeOfString:@"="]; @@ -124,6 +138,15 @@ - (void)addParameters:(NSDictionary *)parameters { return queryParameters; } ++ (NSMutableCharacterSet *)URLParamValueAllowedCharacters { + // Starts with the standard URL-allowed character set. + NSMutableCharacterSet *allowedParamCharacters = + [[NSCharacterSet URLQueryAllowedCharacterSet] mutableCopy]; + // Removes additional characters we don't want to see in the query component. + [allowedParamCharacters removeCharactersInString:kQueryStringParamAdditionalDisallowedCharacters]; + return allowedParamCharacters; +} + /*! @brief Builds a query string that can be set to @c NSURLComponents.percentEncodedQuery @discussion This string is percent encoded, and shouldn't be used with @c NSURLComponents.query. @@ -133,10 +156,7 @@ - (NSString *)percentEncodedQueryString { NSMutableArray *parameterizedValues = [NSMutableArray array]; // Starts with the standard URL-allowed character set. - NSMutableCharacterSet *allowedParamCharacters = - [[NSCharacterSet URLQueryAllowedCharacterSet] mutableCopy]; - // Removes additional characters we don't want to see in the query component. - [allowedParamCharacters removeCharactersInString:kQueryStringParamAdditionalDisallowedCharacters]; + NSMutableCharacterSet *allowedParamCharacters = [[self class] URLParamValueAllowedCharacters]; for (NSString *parameterName in _parameters.allKeys) { NSString *encodedParameterName = @@ -192,7 +212,7 @@ - (NSURL *)URLByReplacingQueryInURL:(NSURL *)URL { - (NSString *)description { return [NSString stringWithFormat:@"<%@: %p, parameters: %@>", NSStringFromClass([self class]), - self, + (void *)self, _parameters]; } diff --git a/Source/OIDURLSessionProvider.h b/Sources/AppAuthCore/OIDURLSessionProvider.h similarity index 79% rename from Source/OIDURLSessionProvider.h rename to Sources/AppAuthCore/OIDURLSessionProvider.h index 814c669f9..28e911696 100644 --- a/Source/OIDURLSessionProvider.h +++ b/Sources/AppAuthCore/OIDURLSessionProvider.h @@ -21,19 +21,19 @@ NS_ASSUME_NONNULL_BEGIN /*! @brief A NSURLSession provider that allows clients to provide custom implementation - for NSURLSession + for NSURLSession */ @interface OIDURLSessionProvider : NSObject /*! @brief Obtains the current @c NSURLSession; using the +[NSURLSession sharedSession] if - no custom implementation provided - @return NSURLSession object to be used for making network requests + no custom implementation is provided. + @return NSURLSession object to be used for making network requests. */ + (NSURLSession *)session; -/*! @brief Allows library consumers to change the @c NSURLSession used to create make - network requests - @param session The @c NSURLSession instance that should be used for making network requests +/*! @brief Allows library consumers to change the @c NSURLSession instance used to make + network requests. + @param session The @c NSURLSession instance that should be used for making network requests. */ + (void)setSession:(NSURLSession *)session; @end diff --git a/Source/OIDURLSessionProvider.m b/Sources/AppAuthCore/OIDURLSessionProvider.m similarity index 100% rename from Source/OIDURLSessionProvider.m rename to Sources/AppAuthCore/OIDURLSessionProvider.m diff --git a/Sources/AppAuthCore/Resources/PrivacyInfo.xcprivacy b/Sources/AppAuthCore/Resources/PrivacyInfo.xcprivacy new file mode 100644 index 000000000..cc6746dcb --- /dev/null +++ b/Sources/AppAuthCore/Resources/PrivacyInfo.xcprivacy @@ -0,0 +1,16 @@ + + + + + NSPrivacyAccessedAPITypes + + + NSPrivacyCollectedDataTypes + + + NSPrivacyTrackingDomains + + NSPrivacyTracking + + + diff --git a/Sources/AppAuthTV.h b/Sources/AppAuthTV.h new file mode 100644 index 000000000..3768c6c14 --- /dev/null +++ b/Sources/AppAuthTV.h @@ -0,0 +1,22 @@ +/*! @file AppAuthTV.h + @brief AppAuthTV SDK + @copyright + Copyright 2020 Google Inc. + @copydetails + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#import "OIDTVAuthorizationRequest.h" +#import "OIDTVAuthorizationResponse.h" +#import "OIDTVAuthorizationService.h" +#import "OIDTVServiceConfiguration.h" diff --git a/Sources/AppAuthTV/OIDTVAuthorizationRequest.h b/Sources/AppAuthTV/OIDTVAuthorizationRequest.h new file mode 100644 index 000000000..2496948f1 --- /dev/null +++ b/Sources/AppAuthTV/OIDTVAuthorizationRequest.h @@ -0,0 +1,53 @@ +/*! @file OIDTVAuthorizationRequest.h + @brief AppAuth iOS SDK + @copyright + Copyright 2016 Google Inc. + @copydetails + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#import + +#import "OIDAuthorizationRequest.h" + +@class OIDTVServiceConfiguration; + +NS_ASSUME_NONNULL_BEGIN + +/*! @brief Represents a TV and limited input device authorization request. + @see https://tools.ietf.org/html/rfc8628#section-3.1 + */ +@interface OIDTVAuthorizationRequest : OIDAuthorizationRequest + +/*! @brief Creates a TV authorization request with opinionated defaults + @param configuration The service's configuration. + @param clientID The client identifier. + @param clientSecret The client secret. + @param scopes An array of scopes to combine into a single scope string per the OAuth2 spec. + @param additionalParameters The client's additional authorization parameters. + */ +- (instancetype) + initWithConfiguration:(OIDTVServiceConfiguration *)configuration + clientId:(NSString *)clientID + clientSecret:(NSString *)clientSecret + scopes:(nullable NSArray *)scopes + additionalParameters:(nullable NSDictionary *)additionalParameters; + +/*! @brief Constructs an @c NSURLRequest representing the TV authorization request. + @return An @c NSURLRequest representing the TV authorization request. + */ +- (NSURLRequest *)URLRequest; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Sources/AppAuthTV/OIDTVAuthorizationRequest.m b/Sources/AppAuthTV/OIDTVAuthorizationRequest.m new file mode 100644 index 000000000..da524d388 --- /dev/null +++ b/Sources/AppAuthTV/OIDTVAuthorizationRequest.m @@ -0,0 +1,117 @@ +/*! @file OIDTVAuthorizationRequest.m + @brief AppAuth iOS SDK + @copyright + Copyright 2016 Google Inc. + @copydetails + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ +#import "OIDTVAuthorizationRequest.h" +#import "OIDTVServiceConfiguration.h" +#import "OIDURLQueryComponent.h" + +@implementation OIDTVAuthorizationRequest + +- (instancetype) + initWithConfiguration:(OIDTVServiceConfiguration *)configuration + clientId:(NSString *)clientID + clientSecret:(nullable NSString *)clientSecret + scope:(nullable NSString *)scope + redirectURL:(NSURL *)redirectURL + responseType:(NSString *)responseType + state:(nullable NSString *)state + nonce:(nullable NSString *)nonce + codeVerifier:(nullable NSString *)codeVerifier + codeChallenge:(nullable NSString *)codeChallenge + codeChallengeMethod:(nullable NSString *)codeChallengeMethod + additionalParameters:(nullable NSDictionary *)additionalParameters { + + if (![configuration isKindOfClass:[OIDTVServiceConfiguration class]]) { + NSAssert([configuration isKindOfClass:[OIDTVServiceConfiguration class]], + @"configuration parameter must be of type OIDTVServiceConfiguration, encountered %@", + NSStringFromClass([configuration class])); + return nil; + } + + return [super initWithConfiguration:configuration + clientId:clientID + clientSecret:clientSecret + scope:scope + redirectURL:redirectURL + responseType:responseType + state:state + nonce:nonce + codeVerifier:codeVerifier + codeChallenge:codeChallenge + codeChallengeMethod:codeChallengeMethod + additionalParameters:additionalParameters]; +} + +- (instancetype) + initWithConfiguration:(OIDTVServiceConfiguration *)configuration + clientId:(NSString *)clientID + clientSecret:(NSString *)clientSecret + scopes:(nullable NSArray *)scopes + additionalParameters:(nullable NSDictionary *)additionalParameters { + return [self initWithConfiguration:configuration + clientId:clientID + clientSecret:clientSecret + scopes:scopes + redirectURL:[[NSURL alloc] initWithString:@""] + responseType:OIDResponseTypeCode + additionalParameters:additionalParameters]; +} + +#pragma mark - NSObject overrides + +- (NSString *)description { + return [NSString stringWithFormat:@"<%@: %p, request: %@>", + NSStringFromClass([self class]), + (void *)self, + self.authorizationRequestURL]; +} + +#pragma mark - + +- (NSURLRequest *)URLRequest { + OIDURLQueryComponent *query = [[OIDURLQueryComponent alloc] init]; + + // Required parameters. + [query addParameter:@"client_id" value:self.clientID]; + + if (self.additionalParameters) { + // Add any additional parameters the client has specified. + [query addParameters:(NSDictionary *)self.additionalParameters]; + } + + if (self.scope) { + [query addParameter:@"scope" value:(NSString *)self.scope]; + } + + static NSString *const kHTTPPost = @"POST"; + static NSString *const kHTTPContentTypeHeaderKey = @"Content-Type"; + static NSString *const kHTTPContentTypeHeaderValue = + @"application/x-www-form-urlencoded; charset=UTF-8"; + + OIDTVServiceConfiguration *tvConfiguration = (OIDTVServiceConfiguration *)self.configuration; + + NSMutableURLRequest *URLRequest = + [[NSURLRequest requestWithURL:tvConfiguration.deviceAuthorizationEndpoint] mutableCopy]; + URLRequest.HTTPMethod = kHTTPPost; + [URLRequest setValue:kHTTPContentTypeHeaderValue forHTTPHeaderField:kHTTPContentTypeHeaderKey]; + NSString *bodyString = [query URLEncodedParameters]; + NSData *body = [bodyString dataUsingEncoding:NSUTF8StringEncoding]; + URLRequest.HTTPBody = body; + return URLRequest; +} + +@end diff --git a/Sources/AppAuthTV/OIDTVAuthorizationResponse.h b/Sources/AppAuthTV/OIDTVAuthorizationResponse.h new file mode 100644 index 000000000..c57847c6e --- /dev/null +++ b/Sources/AppAuthTV/OIDTVAuthorizationResponse.h @@ -0,0 +1,111 @@ +/*! @file OIDTVAuthorizationResponse.h + @brief AppAuth iOS SDK + @copyright + Copyright 2016 Google Inc. + @copydetails + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#import + +#import "OIDAuthorizationResponse.h" + +@class OIDTVAuthorizationRequest; +@class OIDTVTokenRequest; + +NS_ASSUME_NONNULL_BEGIN + +/*! @brief Represents the response to a TV authorization request. + @see https://tools.ietf.org/html/rfc8628#section-3.5 + */ +@interface OIDTVAuthorizationResponse : OIDAuthorizationResponse + +/*! @brief The verification URI that should be displayed to the user instructing them to visit the + URI and enter the code. + @remarks verification_uri + */ +@property(nonatomic, readonly, nullable) NSString *verificationURI; + +/*! @brief A complete verification URI to allow for verification without entering the user code. + @remarks verification_uri + */ +@property(nonatomic, readonly, nullable) NSString *verificationURIComplete; + +/*! @brief The code that should be displayed to the user which they enter at the @c verificationURI. + @remarks user_code + */ +@property(nonatomic, readonly, nullable) NSString *userCode; + +/*! @brief The device code grant used to poll the token endpoint. Rather than using this directly, + use the provided @c tokenPollRequest method to create the token request. + @remarks device_code + */ +@property(nonatomic, readonly, nullable) NSString *deviceCode; + +/*! @brief The interval at which the token endpoint should be polled with the @c deviceCode. + @remarks interval + */ +@property(nonatomic, readonly, nullable) NSNumber *interval; + +/*! @brief The date at which the user can no longer authorize this request. + @remarks expires_in + */ +@property(nonatomic, readonly, nullable) NSDate *expirationDate; + +/*! @brief Designated initializer. + @param request The serviced request. + @param parameters The decoded parameters returned from the Authorization Server. + @remarks Known parameters are extracted from the @c parameters parameter and the normative + properties are populated. Non-normative parameters are placed in the + @c #additionalParameters dictionary. + */ +- (instancetype)initWithRequest:(OIDTVAuthorizationRequest *)request + parameters:(NSDictionary *> *)parameters + NS_DESIGNATED_INITIALIZER; + +/*! @brief Creates a token request suitable for polling the token endpoint with the @c deviceCode. + @return A @c OIDTVTokenRequest suitable for polling the token endpoint. + @see https://tools.ietf.org/html/rfc8628#section-3.4 + */ +- (nullable OIDTVTokenRequest *)tokenPollRequest; + +/*! @brief Creates a token request suitable for polling the token endpoint with the @c deviceCode. + @param additionalParameters Additional parameters for the token request. + @return A @c OIDTVTokenRequest suitable for polling the token endpoint. + @see https://tools.ietf.org/html/rfc8628#section-3.4 + */ +- (nullable OIDTVTokenRequest *)tokenPollRequestWithAdditionalParameters: + (nullable NSDictionary *)additionalParameters; + +/*! @brief Creates a token request suitable for polling the token endpoint with the @c deviceCode. + @param additionalHeaders Additional headers for the token request. + @return A @c OIDTVTokenRequest suitable for polling the token endpoint. + @see https://tools.ietf.org/html/rfc8628#section-3.4 + */ +- (nullable OIDTVTokenRequest *)tokenPollRequestWithAdditionalHeaders: + (nullable NSDictionary *)additionalHeaders; + +/*! @brief Creates a token request suitable for polling the token endpoint with the @c deviceCode. + @param additionalParameters Additional parameters for the token request. + @param additionalHeaders Additional headers for the token request. + @return A @c OIDTVTokenRequest suitable for polling the token endpoint. + @see https://tools.ietf.org/html/rfc8628#section-3.4 + */ +- (nullable OIDTVTokenRequest *)tokenPollRequestWithAdditionalParameters: + (nullable NSDictionary *)additionalParameters + additionalHeaders: + (nullable NSDictionary *)additionalHeaders; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Sources/AppAuthTV/OIDTVAuthorizationResponse.m b/Sources/AppAuthTV/OIDTVAuthorizationResponse.m new file mode 100644 index 000000000..b45fc85f3 --- /dev/null +++ b/Sources/AppAuthTV/OIDTVAuthorizationResponse.m @@ -0,0 +1,190 @@ +/*! @file OIDTVAuthorizationResponse.m + @brief AppAuth iOS SDK + @copyright + Copyright 2016 Google Inc. + @copydetails + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#import "OIDTVAuthorizationResponse.h" + +#import "OIDDefines.h" +#import "OIDFieldMapping.h" + +#import "OIDTVTokenRequest.h" +#import "OIDTVAuthorizationRequest.h" + +/*! @brief The key for the @c verificationURI property in the incoming parameters and for + @c NSSecureCoding. + */ +static NSString *const kVerificationURIKey = @"verification_uri"; + +/*! @brief An alternative key for the @c verificationURI property in the incoming parameters and for + @c NSSecureCoding. If "verification_uri" is not found in the response, a "verification_url" + key is considered equivalent. This is included for compatibility with legacy implementations + and should ideally be removed in the future. + */ +static NSString *const kVerificationURIAlternativeKey = @"verification_url"; + +/*! @brief The key for the @c verificationURIComplete property in the incoming parameters and for + @c NSSecureCoding. + */ +static NSString *const kVerificationURICompleteKey = @"verification_uri_complete"; + +/*! @brief The key for the @c userCode property in the incoming parameters and for + @c NSSecureCoding. + */ +static NSString *const kUserCodeKey = @"user_code"; + +/*! @brief The key for the @c deviceCode property in the incoming parameters and for + @c NSSecureCoding. + */ +static NSString *const kDeviceCodeKey = @"device_code"; + +/*! @brief The key for the @c expirationDate property in the incoming parameters and for + @c NSSecureCoding. + */ +static NSString *const kExpiresInKey = @"expires_in"; + +/*! @brief The key for the @c interval property in the incoming parameters and for + @c NSSecureCoding. + */ +static NSString *const kIntervalKey = @"interval"; + +/*! @brief Key used to encode the @c additionalParameters property for @c NSSecureCoding + */ +static NSString *const kAdditionalParametersKey = @"additionalParameters"; + +/*! @brief Key used to encode the @c request property for @c NSSecureCoding + */ +static NSString *const kRequestKey = @"request"; + +@implementation OIDTVAuthorizationResponse + +/*! @brief Returns a mapping of incoming parameters to instance variables. + @return A mapping of incoming parameters to instance variables. + */ ++ (NSDictionary *)fieldMap { + static NSMutableDictionary *fieldMap; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + fieldMap = [NSMutableDictionary dictionary]; + fieldMap[kVerificationURIKey] = + [[OIDFieldMapping alloc] initWithName:@"_verificationURI" type:[NSString class]]; + fieldMap[kVerificationURICompleteKey] = + [[OIDFieldMapping alloc] initWithName:@"_verificationURIComplete" type:[NSString class]]; + fieldMap[kUserCodeKey] = + [[OIDFieldMapping alloc] initWithName:@"_userCode" type:[NSString class]]; + fieldMap[kDeviceCodeKey] = + [[OIDFieldMapping alloc] initWithName:@"_deviceCode" type:[NSString class]]; + fieldMap[kExpiresInKey] = + [[OIDFieldMapping alloc] initWithName:@"_expirationDate" + type:[NSDate class] + conversion:^id _Nullable(NSObject *_Nullable value) { + if (![value isKindOfClass:[NSNumber class]]) { + return value; + } + NSNumber *valueAsNumber = (NSNumber *)value; + return [NSDate dateWithTimeIntervalSinceNow:[valueAsNumber longLongValue]]; + }]; + fieldMap[kIntervalKey] = + [[OIDFieldMapping alloc] initWithName:@"_interval" type:[NSNumber class]]; + + // Map the alternative verification URI key to "_verificationURI" to support legacy + // implementations using the alternative key + fieldMap[kVerificationURIAlternativeKey] = + [[OIDFieldMapping alloc] initWithName:@"_verificationURI" type:[NSString class]]; + }); + return fieldMap; +} + +#pragma mark - Initializers + +- (instancetype)initWithRequest:(OIDTVAuthorizationRequest *)request + parameters:(NSDictionary *> *)parameters { + self = [super initWithRequest:request parameters:parameters]; + return self; +} + +#pragma mark - NSCopying + +- (instancetype)copyWithZone:(nullable NSZone *)zone { + // The documentation for NSCopying specifically advises us to return a reference to the original + // instance in the case where instances are immutable (as ours is): + // "Implement NSCopying by retaining the original instead of creating a new copy when the class + // and its contents are immutable." + return self; +} + +#pragma mark - NSObject overrides + +- (NSString *)description { + return [NSString stringWithFormat:@"<%@: %p, verificationURI: %@, verificationURIComplete: %@, " + "userCode: \"%@\", deviceCode: " + "\"%@\", interval: %@, expirationDate: %@, " + "additionalParameters: %@, " + "request: %@>", + NSStringFromClass([self class]), + (void *)self, + _verificationURI, + _verificationURIComplete, + _userCode, + _deviceCode, + _interval, + _expirationDate, + self.additionalParameters, + self.request]; +} + +#pragma mark - + +- (OIDTVTokenRequest *)tokenPollRequest { + return [self tokenPollRequestWithAdditionalParameters:nil additionalHeaders:nil]; +} + +- (OIDTVTokenRequest *)tokenPollRequestWithAdditionalParameters: + (NSDictionary *)additionalParameters { + return [[OIDTVTokenRequest alloc] + initWithConfiguration:(OIDTVServiceConfiguration *)self.request.configuration + deviceCode:_deviceCode + clientID:self.request.clientID + clientSecret:self.request.clientSecret + additionalParameters:additionalParameters + additionalHeaders:nil]; +} + +- (OIDTVTokenRequest *)tokenPollRequestWithAdditionalHeaders: + (NSDictionary *)additionalHeaders { + return [[OIDTVTokenRequest alloc] + initWithConfiguration:(OIDTVServiceConfiguration *)self.request.configuration + deviceCode:_deviceCode + clientID:self.request.clientID + clientSecret:self.request.clientSecret + additionalParameters:nil + additionalHeaders:additionalHeaders]; +} + +- (OIDTVTokenRequest *)tokenPollRequestWithAdditionalParameters: + (NSDictionary *)additionalParameters + additionalHeaders: + (NSDictionary *)additionalHeaders { + return [[OIDTVTokenRequest alloc] + initWithConfiguration:(OIDTVServiceConfiguration *)self.request.configuration + deviceCode:_deviceCode + clientID:self.request.clientID + clientSecret:self.request.clientSecret + additionalParameters:additionalParameters + additionalHeaders:additionalHeaders]; +} + +@end diff --git a/Sources/AppAuthTV/OIDTVAuthorizationService.h b/Sources/AppAuthTV/OIDTVAuthorizationService.h new file mode 100644 index 000000000..93158579c --- /dev/null +++ b/Sources/AppAuthTV/OIDTVAuthorizationService.h @@ -0,0 +1,111 @@ +/*! @file OIDTVAuthorizationService.h + @brief AppAuth iOS SDK + @copyright + Copyright 2016 Google Inc. + @copydetails + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#import + +NS_ASSUME_NONNULL_BEGIN + +@class OIDAuthState; +@class OIDTVAuthorizationRequest; +@class OIDTVAuthorizationResponse; +@class OIDTVServiceConfiguration; + +/*! @brief Represents the type of block used as a callback for creating a TV service configuration from + a remote OpenID Connect Discovery document. + @param configuration The TV service configuration, if available. + @param error The error if an error occurred. + */ +typedef void (^OIDTVDiscoveryCallback)(OIDTVServiceConfiguration *_Nullable configuration, + NSError *_Nullable error); + +/*! @brief The block that is called when the TV authorization has initialized. + @param response The authorization response, or nil if there was an error. Display + @c OIDTVAuthorizationResponse.userCode and @c OIDTVAuthorizationResponse.verificationURI to + the user so they can action the request. + @param error The error if an error occurred. + */ +typedef void (^OIDTVAuthorizationInitialization)(OIDTVAuthorizationResponse *_Nullable response, + NSError *_Nullable error); + +/*! @brief The block that is called when the TV authorization has completed. + @param authorization The @c OIDAuthState which you can use to authorize + API calls, or nil if there was an error. + @param error The error if an error occurred. + */ +typedef void (^OIDTVAuthorizationCompletion) + (OIDAuthState *_Nullable authorization, + NSError *_Nullable error); + +/*! @brief Block returned when authorization is initialized that will cancel the pending + authorization when executed. Has no effect if called twice or after the authorization + concluded. + */ +typedef void (^OIDTVAuthorizationCancelBlock)(void); + +/*! @brief Performs authorization flows designed for TVs and other limited input devices. + */ +@interface OIDTVAuthorizationService : NSObject +/*! @internal + @brief Unavailable. This class should not be initialized. + */ +- (instancetype)init NS_UNAVAILABLE; + +/*! @brief Convenience method for creating a TV authorization service configuration from an OpenID + Connect compliant issuer URL. This method validates the presence of a device authorization + endpoint in the retrieved discovery document and instantiates an + @c OIDTVServiceConfiguration. + @param issuerURL The service provider's OpenID Connect issuer. + @param completion A block which will be invoked when the authorization service configuration has + been created, or when an error has occurred. + @see https://openid.net/specs/openid-connect-discovery-1_0.html + */ ++ (void)discoverServiceConfigurationForIssuer:(NSURL *)issuerURL + completion:(OIDTVDiscoveryCallback)completion; + +/*! @brief Convenience method for creating a TV authorization service configuration from an OpenID + Connect compliant identity provider's discovery document. This method validates the presence + of a device authorization endpoint in the retrieved discovery document and instantiates an + @c OIDTVServiceConfiguration. + @param discoveryURL The URL of the service provider's OpenID Connect discovery document. + @param completion A block which will be invoked when the authorization service configuration has + been created, or when an error has occurred. + @see https://openid.net/specs/openid-connect-discovery-1_0.html + */ ++ (void)discoverServiceConfigurationForDiscoveryURL:(NSURL *)discoveryURL + completion:(OIDTVDiscoveryCallback)completion; + +/*! @brief Starts a TV authorization flow with the given request and polls for a response. + @param request The TV authorization request to initiate. + @param initialization Block that is called with the initial authorization response. Unlike other + OAuth authorization responses, the TV authorization response doesn't contain the + authorization as the user has yet to grant it. Rather, it contains the information that you + show to the user in order for them to authorize the request on another device. + @param completion Block that is called on the success or failure of the authorization. If the + user approves the request, you will get a @c OIDAuthState that you can use + to authenticate API calls, otherwis eyou will get an error. + @return A block which you can execute if you need to cancel the ongoing authorization. Has no + effect if called twice, or called after the authorization concludes. + @see https://tools.ietf.org/html/rfc8628 + */ ++ (OIDTVAuthorizationCancelBlock)authorizeTVRequest:(OIDTVAuthorizationRequest *)request + initialization:(OIDTVAuthorizationInitialization)initialization + completion:(OIDTVAuthorizationCompletion)completion; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Sources/AppAuthTV/OIDTVAuthorizationService.m b/Sources/AppAuthTV/OIDTVAuthorizationService.m new file mode 100644 index 000000000..eaa4d6bfc --- /dev/null +++ b/Sources/AppAuthTV/OIDTVAuthorizationService.m @@ -0,0 +1,285 @@ +/*! @file OIDTVAuthorizationService.m + @brief AppAuth iOS SDK + @copyright + Copyright 2016 Google Inc. + @copydetails + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#import "OIDTVAuthorizationService.h" + +#import "OIDAuthorizationService.h" +#import "OIDAuthState.h" +#import "OIDDefines.h" +#import "OIDErrorUtilities.h" +#import "OIDServiceDiscovery.h" +#import "OIDURLQueryComponent.h" +#import "OIDURLSessionProvider.h" + +#import "OIDTVAuthorizationRequest.h" +#import "OIDTVAuthorizationResponse.h" +#import "OIDTVServiceConfiguration.h" +#import "OIDTVTokenRequest.h" + +/*! @brief The authorization pending error code. + @see https://tools.ietf.org/html/rfc8628#section-3.5 + */ +NSString *const kErrorCodeAuthorizationPending = @"authorization_pending"; + +/*! @brief The slow down error code. + @see https://tools.ietf.org/html/rfc8628#section-3.5 + */ +NSString *const kErrorCodeSlowDown = @"slow_down"; + +/*! @brief Path appended to an OpenID Connect issuer for discovery + @see https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfig + */ +static NSString *const kOpenIDConfigurationWellKnownPath = @".well-known/openid-configuration"; + +@implementation OIDTVAuthorizationService + +#pragma mark OIDC Discovery + ++ (void)discoverServiceConfigurationForIssuer:(NSURL *)issuerURL + completion:(OIDTVDiscoveryCallback)completion { + NSURL *fullDiscoveryURL = + [issuerURL URLByAppendingPathComponent:kOpenIDConfigurationWellKnownPath]; + + [[self class] discoverServiceConfigurationForDiscoveryURL:fullDiscoveryURL + completion:completion]; +} + ++ (void)discoverServiceConfigurationForDiscoveryURL:(NSURL *)discoveryURL + completion:(OIDTVDiscoveryCallback)completion { + // Call the corresponding discovery method in OIDAuthorizationService + [OIDAuthorizationService discoverServiceConfigurationForDiscoveryURL:discoveryURL + completion:^(OIDServiceConfiguration * _Nullable configuration, NSError * _Nullable error) { + if (configuration == nil) { + completion(nil, error); + return; + } + + if (configuration.discoveryDocument.deviceAuthorizationEndpoint == nil) { + NSError *missingEndpointError = [OIDErrorUtilities + errorWithCode:OIDErrorCodeInvalidDiscoveryDocument + underlyingError:nil + description:@"Discovery document does not contain device authorization endpoint."]; + + completion(nil, missingEndpointError); + return; + } + + // Create an OIDTVServiceConfiguration from the discovery document of the configuration + OIDTVServiceConfiguration *TVConfiguration = [[OIDTVServiceConfiguration alloc] + initWithDiscoveryDocument:configuration.discoveryDocument]; + + completion(TVConfiguration, nil); + }]; +} + +#pragma mark - Initializers + ++ (OIDTVAuthorizationCancelBlock)authorizeTVRequest:(OIDTVAuthorizationRequest *)request + initialization:(OIDTVAuthorizationInitialization)initialization + completion:(OIDTVAuthorizationCompletion)completion { + // Block level variable that can be used to cancel the polling. + __block BOOL pollRunning = YES; + + // Block that will be returned allowign the caller to cancel the polling. + OIDTVAuthorizationCancelBlock cancelBlock = ^{ + if (pollRunning) { + dispatch_async(dispatch_get_main_queue(), ^{ + NSError *cancelError = + [OIDErrorUtilities errorWithCode:OIDErrorCodeProgramCanceledAuthorizationFlow + underlyingError:nil + description:@"Authorization cancelled"]; + completion(nil, cancelError); + }); + } + pollRunning = NO; + }; + + // Performs the initial authorization reqeust. + NSURLRequest *URLRequest = [request URLRequest]; + NSURLSession *session = [NSURLSession sharedSession]; + [[session dataTaskWithRequest:URLRequest + completionHandler:^(NSData *_Nullable data, + NSURLResponse *_Nullable response, + NSError *_Nullable error) { + if (error) { + // A network error or server error occurred. + NSError *returnedError = + [OIDErrorUtilities errorWithCode:OIDErrorCodeNetworkError + underlyingError:error + description:nil]; + dispatch_async(dispatch_get_main_queue(), ^{ + initialization(nil, returnedError); + }); + return; + } + + NSHTTPURLResponse *HTTPURLResponse = (NSHTTPURLResponse *)response; + + if (HTTPURLResponse.statusCode != 200) { + // A server error occurred. + NSError *serverError = + [OIDErrorUtilities HTTPErrorWithHTTPResponse:HTTPURLResponse data:data]; + + // HTTP 400 may indicate an RFC6749 Section 5.2 error response, checks for that + if (HTTPURLResponse.statusCode == 400) { + NSError *jsonDeserializationError; + NSDictionary *> *json = + [NSJSONSerialization JSONObjectWithData:(NSData *)data + options:0 + error:&jsonDeserializationError]; + + // if the HTTP 400 response parses as JSON and has an 'error' key, it's an OAuth error + // these errors are special as they indicate a problem with the authorization grant + if (json[OIDOAuthErrorFieldError]) { + NSError *oauthError = + [OIDErrorUtilities OAuthErrorWithDomain:OIDOAuthTokenErrorDomain + OAuthResponse:json + underlyingError:serverError]; + dispatch_async(dispatch_get_main_queue(), ^{ + initialization(nil, oauthError); + }); + return; + } + } + + // not an OAuth error, just a generic server error + NSError *returnedError = + [OIDErrorUtilities errorWithCode:OIDErrorCodeServerError + underlyingError:serverError + description:nil]; + dispatch_async(dispatch_get_main_queue(), ^{ + initialization(nil, returnedError); + }); + return; + } + + NSError *jsonDeserializationError; + NSDictionary *> *json = + [NSJSONSerialization JSONObjectWithData:(NSData *)data + options:0 + error:&jsonDeserializationError]; + if (jsonDeserializationError) { + // A problem occurred deserializing the response/JSON. + NSError *returnedError = + [OIDErrorUtilities errorWithCode:OIDErrorCodeJSONDeserializationError + underlyingError:jsonDeserializationError + description:nil]; + dispatch_async(dispatch_get_main_queue(), ^{ + initialization(nil, returnedError); + }); + return; + } + + // Parses the authorization response. + OIDTVAuthorizationResponse *TVAuthorizationResponse = + [[OIDTVAuthorizationResponse alloc] initWithRequest:request parameters:json]; + if (!TVAuthorizationResponse) { + // A problem occurred constructing the token response from the JSON. + NSError *returnedError = + [OIDErrorUtilities errorWithCode:OIDErrorCodeTokenResponseConstructionError + underlyingError:jsonDeserializationError + description:nil]; + dispatch_async(dispatch_get_main_queue(), ^{ + initialization(nil, returnedError); + }); + return; + } + + // Calls the initialization block to signal that we received a TV authorization response. + dispatch_async(dispatch_get_main_queue(), ^() { + initialization(TVAuthorizationResponse, nil); + }); + + // Creates the token request that will be used to poll the token endpoint. + OIDTVTokenRequest *pollRequest = [TVAuthorizationResponse tokenPollRequest]; + + // Starting polling interval (may be increased if a slow down message is received). + __block NSTimeInterval interval = [TVAuthorizationResponse.interval doubleValue]; + + // If no interval is set, use default value of 5 as per RFC. + // If interval is set to 0, use value of 1 to prevent infinite polling. + if (TVAuthorizationResponse.interval == nil) { + interval = 5.0; + } else if (interval == 0.0) { + interval = 1.0; + } + + // Polls the token endpoint until the authorization completes or expires. + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + do { + // Sleeps for polling interval. + [NSThread sleepForTimeInterval:interval]; + + if (!pollRunning) { + break; + } + + // Polls token endpoint. + [OIDAuthorizationService performTokenRequest:pollRequest + callback:^(OIDTokenResponse *_Nullable tokenResponse, + NSError *_Nullable tokenError) { + if (!pollRunning) { + return; + } + dispatch_async(dispatch_get_main_queue(), ^() { + if (tokenResponse) { + // Success response. + pollRunning = NO; + dispatch_async(dispatch_get_main_queue(), ^{ + OIDAuthState *authState = + [[OIDAuthState alloc] initWithAuthorizationResponse:TVAuthorizationResponse + tokenResponse:tokenResponse]; + completion(authState, nil); + }); + } else { + if (tokenError.domain == OIDOAuthTokenErrorDomain) { + // OAuth token errors inspected for device flow specific errors. + NSString *errorCode = + tokenError.userInfo[OIDOAuthErrorResponseErrorKey][OIDOAuthErrorFieldError]; + if ([errorCode isEqual:kErrorCodeAuthorizationPending]) { + // authorization_pending is an expected response. + return; + } else if ([errorCode isEqual:kErrorCodeSlowDown]) { + // Increase interval by 20%, enforce a lower bound of 5s. + interval *= 1.20; + interval = MAX(5.0, interval); + } else { + // Unhandled token error, considered fatal. + pollRunning = NO; + dispatch_async(dispatch_get_main_queue(), ^{ + completion(nil, tokenError); + }); + } + } else { + // All other errors considered fatal. + pollRunning = NO; + dispatch_async(dispatch_get_main_queue(), ^{ + completion(nil, tokenError); + }); + } + } + }); + }]; + } while ([TVAuthorizationResponse.expirationDate timeIntervalSinceNow] > 0 && pollRunning); + }); + }] resume]; + + return cancelBlock; +} + +@end diff --git a/Sources/AppAuthTV/OIDTVServiceConfiguration.h b/Sources/AppAuthTV/OIDTVServiceConfiguration.h new file mode 100644 index 000000000..2afea6cac --- /dev/null +++ b/Sources/AppAuthTV/OIDTVServiceConfiguration.h @@ -0,0 +1,61 @@ +/*! @file OIDTVServiceConfiguration.h + @brief AppAuth iOS SDK + @copyright + Copyright 2016 Google Inc. + @copydetails + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#import "OIDServiceConfiguration.h" + +NS_ASSUME_NONNULL_BEGIN + +/*! @brief Configuration for authorizing the user with the @c OIDTVAuthorizationService. + */ +@interface OIDTVServiceConfiguration : OIDServiceConfiguration + +/*! @brief The device authorization endpoint URI. + */ +@property(nonatomic, readonly) NSURL *deviceAuthorizationEndpoint; + +/*! @internal + @brief Unavailable. Please use + @c initWithDeviceAuthorizationEndpoint:tokenEndpoint: + */ +- (instancetype)init NS_UNAVAILABLE; + +/*! @internal + @brief Unavailable. Please use + @c initWithDeviceAuthorizationEndpoint:tokenEndpoint: + */ +- (instancetype)initWithAuthorizationEndpoint:(NSURL *)authorizationEndpoint + tokenEndpoint:(NSURL *)tokenEndpoint NS_UNAVAILABLE; + +/*! @brief Designated initializer. + @param discoveryDocument The discovery document from which to extract the required OAuth + configuration. +*/ +- (instancetype)initWithDiscoveryDocument:(OIDServiceDiscovery *)discoveryDocument + NS_DESIGNATED_INITIALIZER; + +/*! @brief Designated initializer. + @param deviceAuthorizationEndpoint The device authorization endpoint URI. + @param tokenEndpoint The token exchange and refresh endpoint URI. + */ +- (instancetype)initWithDeviceAuthorizationEndpoint:(NSURL *)deviceAuthorizationEndpoint + tokenEndpoint:(NSURL *)tokenEndpoint + NS_DESIGNATED_INITIALIZER; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Sources/AppAuthTV/OIDTVServiceConfiguration.m b/Sources/AppAuthTV/OIDTVServiceConfiguration.m new file mode 100644 index 000000000..202aa9104 --- /dev/null +++ b/Sources/AppAuthTV/OIDTVServiceConfiguration.m @@ -0,0 +1,105 @@ +/*! @file OIDTVServiceConfiguration.m + @brief AppAuth iOS SDK + @copyright + Copyright 2016 Google Inc. + @copydetails + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#import "OIDTVServiceConfiguration.h" + +#import "OIDDefines.h" +#import "OIDServiceDiscovery.h" + +/*! @brief The key for the @c deviceAuthorizationEndpoint property. + */ +static NSString *const kDeviceAuthorizationEndpointKey = @"deviceAuthorizationEndpoint"; + +NS_ASSUME_NONNULL_BEGIN + +@interface OIDTVServiceConfiguration () + +/*! @brief Designated initializer. + @param aDecoder NSCoder to unserialize the object from. + */ +- (nullable instancetype)initWithCoder:(NSCoder *)aDecoder NS_DESIGNATED_INITIALIZER; + +@end + +@implementation OIDTVServiceConfiguration + +- (instancetype)init + OID_UNAVAILABLE_USE_INITIALIZER(@selector(initWithDeviceAuthorizationEndpoint:tokenEndpoint:)) + +- (instancetype)initWithAuthorizationEndpoint:(NSURL *)authorizationEndpoint + tokenEndpoint:(NSURL *)tokenEndpoint + OID_UNAVAILABLE_USE_INITIALIZER(@selector(initWithDeviceAuthorizationEndpoint:tokenEndpoint:)) + +- (instancetype)initWithDiscoveryDocument:(OIDServiceDiscovery *)discoveryDocument { + self = [super initWithDiscoveryDocument:discoveryDocument]; + + if (self) { + if (discoveryDocument.deviceAuthorizationEndpoint == nil) { + NSLog(@"Warning: Discovery document used to initialize %@ " + @"does not contain device authorization endpoint.", self); + } else { + _deviceAuthorizationEndpoint = [discoveryDocument.deviceAuthorizationEndpoint copy]; + } + } + return self; +} + +- (instancetype)initWithDeviceAuthorizationEndpoint:(NSURL *)deviceAuthorizationEndpoint + tokenEndpoint:(NSURL *)tokenEndpoint { + self = [super initWithAuthorizationEndpoint:[[NSURL alloc] initWithString:@""] + tokenEndpoint:tokenEndpoint]; + if (self) { + _deviceAuthorizationEndpoint = [deviceAuthorizationEndpoint copy]; + } + return self; +} + +#pragma mark - NSSecureCoding + ++ (BOOL)supportsSecureCoding { + return YES; +} + +- (nullable instancetype)initWithCoder:(NSCoder *)aDecoder { + self = [super initWithCoder:aDecoder]; + if (self) { + NSURL *deviceAuthorizationEndpoint = + [aDecoder decodeObjectOfClass:[NSURL class] forKey:kDeviceAuthorizationEndpointKey]; + _deviceAuthorizationEndpoint = deviceAuthorizationEndpoint; + } + return self; +} + +- (void)encodeWithCoder:(NSCoder *)aCoder { + [super encodeWithCoder:aCoder]; + [aCoder encodeObject:_deviceAuthorizationEndpoint forKey:kDeviceAuthorizationEndpointKey]; +} + +#pragma mark - description + +- (NSString *)description { + return [NSString stringWithFormat:@"<%@: %p, deviceAuthorizationEndpoint: %@ tokenEndpoint: %@>", + NSStringFromClass([self class]), + (void *)self, + _deviceAuthorizationEndpoint, + self.tokenEndpoint]; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/Sources/AppAuthTV/OIDTVTokenRequest.h b/Sources/AppAuthTV/OIDTVTokenRequest.h new file mode 100644 index 000000000..8643b4cc5 --- /dev/null +++ b/Sources/AppAuthTV/OIDTVTokenRequest.h @@ -0,0 +1,122 @@ +/*! @file OIDTVTokenRequest.h + @brief AppAuth iOS SDK + @copyright + Copyright 2020 Google Inc. + @copydetails + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +#import "OIDTokenRequest.h" + +#import + +@class OIDServiceConfiguration; +@class OIDTVServiceConfiguration; + +NS_ASSUME_NONNULL_BEGIN + +@interface OIDTVTokenRequest : OIDTokenRequest + +/*! @brief The device code received from the authorization server. + @remarks device_code + @see https://tools.ietf.org/html/rfc8628#section-3.4 + */ +@property(nonatomic, readonly) NSString *deviceCode; + +/*! @internal + @brief Unavailable. Please use + @c initWithConfiguration:deviceCode:clientID:clientSecret:additionalParameters:additionalHeaders: + or @c initWithCoder:. +*/ +- (instancetype)init NS_UNAVAILABLE; + +/*! @internal + @brief Unavailable. Please use + @c initWithConfiguration:deviceCode:clientID:clientSecret:additionalParameters:additionalHeaders: + or @c initWithCoder:. +*/ +- (instancetype)initWithConfiguration:(OIDServiceConfiguration *)configuration + grantType:(NSString *)grantType + authorizationCode:(nullable NSString *)code + redirectURL:(nullable NSURL *)redirectURL + clientID:(NSString *)clientID + clientSecret:(nullable NSString *)clientSecret + scopes:(nullable NSArray *)scopes + refreshToken:(nullable NSString *)refreshToken + codeVerifier:(nullable NSString *)codeVerifier + additionalParameters: + (nullable NSDictionary *)additionalParameters + additionalHeaders: + (nullable NSDictionary *)additionalHeaders + NS_UNAVAILABLE; + +/*! @internal + @brief Unavailable. Please use + @c initWithConfiguration:deviceCode:clientID:clientSecret:additionalParameters:additionalHeaders: + or @c initWithCoder:. +*/ +- (instancetype)initWithConfiguration:(OIDServiceConfiguration *)configuration + grantType:(NSString *)grantType + authorizationCode:(nullable NSString *)code + redirectURL:(nullable NSURL *)redirectURL + clientID:(NSString *)clientID + clientSecret:(nullable NSString *)clientSecret + scope:(nullable NSString *)scope + refreshToken:(nullable NSString *)refreshToken + codeVerifier:(nullable NSString *)codeVerifier + additionalParameters: + (nullable NSDictionary *)additionalParameters + additionalHeaders: + (nullable NSDictionary *)additionalHeaders + NS_UNAVAILABLE; + +/*! @brief Designated initializer. + @param configuration The service's configuration. + @param deviceCode The device verification code received from the authorization server. + @param clientID The client identifier. + @param clientSecret The client secret (nullable). + @param additionalParameters The client's additional token request parameters. +*/ +- (instancetype)initWithConfiguration:(OIDTVServiceConfiguration *)configuration + deviceCode:(NSString *)deviceCode + clientID:(NSString *)clientID + clientSecret:(nullable NSString *)clientSecret + additionalParameters: + (nullable NSDictionary *)additionalParameters; + +/*! @brief Designated initializer. + @param configuration The service's configuration. + @param deviceCode The device verification code received from the authorization server. + @param clientID The client identifier. + @param clientSecret The client secret (nullable). + @param additionalParameters The client's additional token request parameters. + @param additionalHeaders The client's additional token request headers. +*/ +- (instancetype)initWithConfiguration:(OIDTVServiceConfiguration *)configuration + deviceCode:(NSString *)deviceCode + clientID:(NSString *)clientID + clientSecret:(nullable NSString *)clientSecret + additionalParameters: + (nullable NSDictionary *)additionalParameters + additionalHeaders: + (nullable NSDictionary *)additionalHeaders + NS_DESIGNATED_INITIALIZER; + +/*! @brief Designated initializer for NSSecureCoding. + @param aDecoder Unarchiver object to decode + */ +- (instancetype)initWithCoder:(NSCoder *)aDecoder NS_DESIGNATED_INITIALIZER; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Sources/AppAuthTV/OIDTVTokenRequest.m b/Sources/AppAuthTV/OIDTVTokenRequest.m new file mode 100644 index 000000000..0878c7934 --- /dev/null +++ b/Sources/AppAuthTV/OIDTVTokenRequest.m @@ -0,0 +1,174 @@ +/*! @file OIDTVTokenRequest.m + @brief AppAuth iOS SDK + @copyright + Copyright 2020 Google Inc. + @copydetails + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +#import "OIDTVTokenRequest.h" + +#import "OIDDefines.h" +#import "OIDTVServiceConfiguration.h" +#import "OIDURLQueryComponent.h" + +/*! @brief The key for the @c deviceCode property for @c NSSecureCoding and request body. + */ +static NSString *const kDeviceCodeKey = @"device_code"; + +/*! @brief Key used to encode the @c grantType property for @c NSSecureCoding and request body. + */ +static NSString *const kGrantTypeKey = @"grant_type"; + +/*! @brief Value for @c grant_type key in the request body + @see https://tools.ietf.org/html/rfc8628#section-3.4 + */ +static NSString *const kOIDTVDeviceTokenGrantType = @"urn:ietf:params:oauth:grant-type:device_code"; + +@implementation OIDTVTokenRequest + +- (instancetype)init OID_UNAVAILABLE_USE_INITIALIZER(@selector + (initWithConfiguration: + deviceCode: + clientID: + clientSecret: + additionalParameters: + additionalHeaders: + )) + +- (instancetype)initWithConfiguration:(OIDServiceConfiguration *)configuration + grantType:(NSString *)grantType + authorizationCode:(nullable NSString *)code + redirectURL:(nullable NSURL *)redirectURL + clientID:(NSString *)clientID + clientSecret:(nullable NSString *)clientSecret + scopes:(nullable NSArray *)scopes + refreshToken:(nullable NSString *)refreshToken + codeVerifier:(nullable NSString *)codeVerifier + additionalParameters: + (nullable NSDictionary *)additionalParameters + additionalHeaders: + (nullable NSDictionary *)additionalHeaders + OID_UNAVAILABLE_USE_INITIALIZER(@selector + (initWithConfiguration: + deviceCode: + clientID: + clientSecret: + additionalParameters: + additionalHeaders: + )) + +- (instancetype)initWithConfiguration:(OIDServiceConfiguration *)configuration + grantType:(NSString *)grantType + authorizationCode:(nullable NSString *)code + redirectURL:(nullable NSURL *)redirectURL + clientID:(NSString *)clientID + clientSecret:(nullable NSString *)clientSecret + scope:(nullable NSString *)scope + refreshToken:(nullable NSString *)refreshToken + codeVerifier:(nullable NSString *)codeVerifier + additionalParameters: + (nullable NSDictionary *)additionalParameters + additionalHeaders: + (nullable NSDictionary *)additionalHeaders + OID_UNAVAILABLE_USE_INITIALIZER(@selector + (initWithConfiguration: + deviceCode: + clientID: + clientSecret: + additionalParameters: + additionalHeaders: + )) + +- (instancetype)initWithConfiguration:(OIDTVServiceConfiguration *)configuration + deviceCode:(NSString *)deviceCode + clientID:(NSString *)clientID + clientSecret:(NSString *)clientSecret + additionalParameters:(NSDictionary *)additionalParameters { + return [self initWithConfiguration:configuration + deviceCode:deviceCode + clientID:clientID + clientSecret:clientSecret + additionalParameters:additionalParameters + additionalHeaders:nil]; +} + +- (instancetype)initWithConfiguration:(OIDTVServiceConfiguration *)configuration + deviceCode:(NSString *)deviceCode + clientID:(NSString *)clientID + clientSecret:(NSString *)clientSecret + additionalParameters:(NSDictionary *)additionalParameters + additionalHeaders:(NSDictionary *)additionalHeaders { + self = [super initWithConfiguration:configuration + grantType:kOIDTVDeviceTokenGrantType + authorizationCode:nil + redirectURL:[[NSURL alloc] initWithString:@""] + clientID:clientID + clientSecret:clientSecret + scope:nil + refreshToken:nil + codeVerifier:nil + additionalParameters:additionalParameters + additionalHeaders:additionalHeaders]; + + if (self) { + _deviceCode = [deviceCode copy]; + } + return self; +} + +#pragma mark - NSCopying + +- (instancetype)copyWithZone:(nullable NSZone *)zone { + // The documentation for NSCopying specifically advises us to return a reference to the original + // instance in the case where instances are immutable (as ours is): + // "Implement NSCopying by retaining the original instead of creating a new copy when the class + // and its contents are immutable." + return self; +} + +#pragma mark - NSSecureCoding + ++ (BOOL)supportsSecureCoding { + return YES; +} + +- (instancetype)initWithCoder:(NSCoder *)aDecoder { + self = [super initWithCoder:aDecoder]; + if (self) { + NSString *deviceCode = [aDecoder decodeObjectOfClass:[NSString class] forKey:kDeviceCodeKey]; + _deviceCode = deviceCode; + } + return self; +} + +- (void)encodeWithCoder:(NSCoder *)aCoder { + [super encodeWithCoder:aCoder]; + [aCoder encodeObject:_deviceCode forKey:kDeviceCodeKey]; +} + +- (OIDURLQueryComponent *)tokenRequestBody { + OIDURLQueryComponent *query = [[OIDURLQueryComponent alloc] init]; + + if (self.grantType) { + [query addParameter:kGrantTypeKey value:self.grantType]; + } + + [query addParameter:kDeviceCodeKey value:self.deviceCode]; + + [query addParameters:self.additionalParameters]; + + return query; +} + +@end diff --git a/Sources/AppAuthTV/Resources/PrivacyInfo.xcprivacy b/Sources/AppAuthTV/Resources/PrivacyInfo.xcprivacy new file mode 100644 index 000000000..cc6746dcb --- /dev/null +++ b/Sources/AppAuthTV/Resources/PrivacyInfo.xcprivacy @@ -0,0 +1,16 @@ + + + + + NSPrivacyAccessedAPITypes + + + NSPrivacyCollectedDataTypes + + + NSPrivacyTrackingDomains + + NSPrivacyTracking + + + diff --git a/Sources/CoreFramework/AppAuthCore.h b/Sources/CoreFramework/AppAuthCore.h new file mode 100644 index 000000000..9db3f3c69 --- /dev/null +++ b/Sources/CoreFramework/AppAuthCore.h @@ -0,0 +1,53 @@ +/*! @file AppAuthCore.h + @brief AppAuth iOS SDK + @copyright + Copyright 2018 The AppAuth Authors. All Rights Reserved. + @copydetails + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#import + +//! Project version number for AppAuthFramework-iOS. +FOUNDATION_EXPORT double AppAuthCoreVersionNumber; + +//! Project version string for AppAuthCoreFramework. +FOUNDATION_EXPORT const unsigned char AppAuthCoreVersionString[]; + +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import + diff --git a/Source/Framework/Info.plist b/Sources/CoreFramework/Info.plist similarity index 100% rename from Source/Framework/Info.plist rename to Sources/CoreFramework/Info.plist diff --git a/Source/Framework/AppAuth.h b/Sources/Framework/AppAuth.h similarity index 51% rename from Source/Framework/AppAuth.h rename to Sources/Framework/AppAuth.h index 1275e36f2..f1916de77 100644 --- a/Source/Framework/AppAuth.h +++ b/Sources/Framework/AppAuth.h @@ -1,19 +1,19 @@ /*! @file AppAuth.h - @brief AppAuth iOS SDK - @copyright - Copyright 2015 Google Inc. All Rights Reserved. - @copydetails - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. + @brief AppAuth iOS SDK + @copyright + Copyright 2015 Google Inc. All Rights Reserved. + @copydetails + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. */ #import @@ -30,10 +30,13 @@ FOUNDATION_EXPORT const unsigned char AppAuthVersionString[]; #import #import #import -#import #import #import +#import +#import +#import #import +#import #import #import #import @@ -44,17 +47,22 @@ FOUNDATION_EXPORT const unsigned char AppAuthVersionString[]; #import #import #import +#import +#import +#import #if TARGET_OS_TV #elif TARGET_OS_WATCH -#elif TARGET_OS_IOS +#elif TARGET_OS_IOS || TARGET_OS_MACCATALYST #import #import -#import -#elif TARGET_OS_MAC +#import +#import +#import "AppAuth/OIDExternalUserAgentCatalyst.h" +#elif TARGET_OS_OSX #import #import -#import +#import #import #else #error "Platform Undefined" diff --git a/Sources/Framework/Info.plist b/Sources/Framework/Info.plist new file mode 100644 index 000000000..4c93b0d31 --- /dev/null +++ b/Sources/Framework/Info.plist @@ -0,0 +1,26 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleVersion + $(CURRENT_PROJECT_VERSION) + NSHumanReadableCopyright + Copyright © 2017 The AppAuth Authors + NSPrincipalClass + + + diff --git a/Sources/TVFramework/AppAuthTV.h b/Sources/TVFramework/AppAuthTV.h new file mode 100644 index 000000000..ab3c1e604 --- /dev/null +++ b/Sources/TVFramework/AppAuthTV.h @@ -0,0 +1,57 @@ +/*! @file AppAuthTV.h + @brief AppAuthTV SDK + @copyright + Copyright 2020 Google Inc. + @copydetails + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +#import + +//! Project version number for AppAuthTV. +FOUNDATION_EXPORT double AppAuthTVVersionNumber; + +//! Project version string for AppAuthTV. +FOUNDATION_EXPORT const unsigned char AppAuthTVVersionString[]; + +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import + +#import +#import +#import +#import diff --git a/Sources/TVFramework/Info.plist b/Sources/TVFramework/Info.plist new file mode 100644 index 000000000..9bcb24442 --- /dev/null +++ b/Sources/TVFramework/Info.plist @@ -0,0 +1,22 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + $(PRODUCT_BUNDLE_PACKAGE_TYPE) + CFBundleShortVersionString + 1.0 + CFBundleVersion + $(CURRENT_PROJECT_VERSION) + + diff --git a/UnitTests/AppAuthTV/OIDTVAuthorizationRequestTests.h b/UnitTests/AppAuthTV/OIDTVAuthorizationRequestTests.h new file mode 100644 index 000000000..d4198a448 --- /dev/null +++ b/UnitTests/AppAuthTV/OIDTVAuthorizationRequestTests.h @@ -0,0 +1,60 @@ +/*! @file OIDTVAuthorizationRequestTests.h + @brief AppAuth iOS SDK + @copyright + Copyright 2020 Google Inc. All Rights Reserved. + @copydetails + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#import + +@class OIDTVServiceConfiguration; + +NS_ASSUME_NONNULL_BEGIN + +/*! @brief Unit tests for @c OIDTVAuthorizationRequest. + */ +@interface OIDTVAuthorizationRequestTests : XCTestCase +- (OIDTVServiceConfiguration *)testServiceConfiguration; +- (NSDictionary *)bodyParametersFromURLRequest:(NSURLRequest *)urlRequest; + +/*! @brief Tests the initializer + */ +- (void)testInitializer; + +/*! @brief Tests the @c NSCopying implementation by round-tripping an instance through the copying + * process and checking to make sure the source and destination both contain the + * @c deviceAuthorizationEndpoint + */ +- (void)testCopying; + +/*! @brief Tests the @c NSSecureCoding implementation by round-tripping an instance through the + * coding process and checking to make sure the source and destination both contain the + * @c deviceAuthorizationEndpoint + */ +- (void)testSecureCoding; + +/*! @brief Tests the @c URLRequest method on a request with no scopes or additional parameters + */ +- (void)testURLRequestBasicClientAuth; + +/*! @brief Tests the @c URLRequest method on a request with two scopes and no additional parameters + */ +- (void)testURLRequestScopes; + +/*! @brief Tests the @c URLRequest method on a request with two scopes and one additional parameter + */ +- (void)testURLRequestAdditionalParams; +@end + +NS_ASSUME_NONNULL_END diff --git a/UnitTests/AppAuthTV/OIDTVAuthorizationRequestTests.m b/UnitTests/AppAuthTV/OIDTVAuthorizationRequestTests.m new file mode 100644 index 000000000..a5ccd6e1d --- /dev/null +++ b/UnitTests/AppAuthTV/OIDTVAuthorizationRequestTests.m @@ -0,0 +1,287 @@ +/*! @file OIDTVAuthorizationRequestTests.m + @brief AppAuth iOS SDK + @copyright + Copyright 2020 Google Inc. All Rights Reserved. + @copydetails + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#import "OIDTVAuthorizationRequestTests.h" + +#if SWIFT_PACKAGE +@import AppAuthTV; +#else +#import "Sources/AppAuthCore/OIDScopeUtilities.h" +#import "Sources/AppAuthCore/OIDURLQueryComponent.h" +#import "Sources/AppAuthTV/OIDTVAuthorizationRequest.h" +#import "Sources/AppAuthTV/OIDTVServiceConfiguration.h" +#endif + +// Ignore warnings about "Use of GNU statement expression extension" which is raised by our use of +// the XCTAssert___ macros. +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wgnu" + +/*! @brief Test value for the @c deviceAuthorizationEndpoint property. + */ +static NSString *const kTestDeviceAuthorizationEndpoint = @"https://www.example.com/device/code"; + +/*! @brief Test value for the @c tokenEndpoint property. + */ +static NSString *const kTestTokenEndpoint = @"https://www.example.com/token"; + +/*! @brief Test key for the @c additionalParameters property. + */ +static NSString *const kTestAdditionalParameterKey = @"A"; + +/*! @brief Test value for the @c additionalParameters property. + */ +static NSString *const kTestAdditionalParameterValue = @"1"; + +/*! @brief Test key for the @c clientID parameter in the HTTP request. + */ +static NSString *const kTestClientIDKey = @"client_id"; + +/*! @brief Test value for the @c clientID property. + */ +static NSString *const kTestClientID = @"ClientID"; + +/*! @brief Test value for the @c clientSecret property. + */ +static NSString *const kTestClientSecret = @"ClientSecret"; + +/*! @brief Test key for the @c scope parameter in the HTTP request. + */ +static NSString *const kTestScopeKey = @"scope"; + +/*! @brief Test value for the @c scope property. + */ +static NSString *const kTestScope = @"Scope"; + +/*! @brief Test value for the @c scope property. + */ +static NSString *const kTestScopeA = @"ScopeA"; + +/*! @brief Expected HTTP Method for the authorization @c URLRequest + */ +static NSString *const kHTTPPost = @"POST"; + +/*! @brief Expected @c ContentType header key for the authorization @c URLRequest + */ +static NSString *const kHTTPContentTypeHeaderKey = @"Content-Type"; + +/*! @brief Expected @c ContentType header value for the authorization @c URLRequest + */ +static NSString *const kHTTPContentTypeHeaderValue = + @"application/x-www-form-urlencoded; charset=UTF-8"; + +@implementation OIDTVAuthorizationRequestTests + +- (OIDTVServiceConfiguration *)testServiceConfiguration { + NSURL *tokenEndpoint = [NSURL URLWithString:kTestTokenEndpoint]; + NSURL *deviceAuthorizationEndpoint = [NSURL URLWithString:kTestDeviceAuthorizationEndpoint]; + + OIDTVServiceConfiguration *configuration = + [[OIDTVServiceConfiguration alloc] initWithDeviceAuthorizationEndpoint:deviceAuthorizationEndpoint + tokenEndpoint:tokenEndpoint]; + return configuration; +} + +- (NSDictionary *)bodyParametersFromURLRequest:(NSURLRequest *)URLRequest { + NSString *bodyString = [[NSString alloc] initWithData:URLRequest.HTTPBody + encoding:NSUTF8StringEncoding]; + NSArray *bodyParameterStrings = [bodyString componentsSeparatedByString:@"&"]; + + NSMutableDictionary *bodyParameters = [[NSMutableDictionary alloc] init]; + + for (NSString *paramString in bodyParameterStrings) { + NSArray *components = [paramString componentsSeparatedByString:@"="]; + + if (components.count == 2) { + bodyParameters[components[0]] = components[1]; + } + } + + return bodyParameters; +} + +/*! @brief Tests the initializer + */ +- (void)testInitializer { + OIDTVServiceConfiguration *serviceConfiguration = [self testServiceConfiguration]; + NSArray *testScopes = @[ kTestScope, kTestScopeA ]; + NSString *testScopeString = [OIDScopeUtilities scopesWithArray:testScopes]; + NSDictionary *testAdditionalParameters = + @{kTestAdditionalParameterKey : kTestAdditionalParameterValue}; + + OIDTVAuthorizationRequest *authRequest = + [[OIDTVAuthorizationRequest alloc] initWithConfiguration:serviceConfiguration + clientId:kTestClientID + clientSecret:kTestClientSecret + scopes:testScopes + additionalParameters:testAdditionalParameters]; + + NSURL *authRequestDeviceAuthorizationEndpoint = + ((OIDTVServiceConfiguration *)authRequest.configuration).deviceAuthorizationEndpoint; + + XCTAssertEqualObjects(authRequest.clientID, kTestClientID); + XCTAssertEqualObjects(authRequest.clientSecret, kTestClientSecret); + XCTAssertEqualObjects(authRequest.scope, testScopeString); + XCTAssertEqualObjects(authRequest.additionalParameters, testAdditionalParameters); + XCTAssertEqualObjects(authRequest.responseType, OIDResponseTypeCode); + XCTAssertEqualObjects(authRequest.redirectURL, [[NSURL alloc] initWithString:@""]); + XCTAssertEqualObjects(authRequestDeviceAuthorizationEndpoint, + serviceConfiguration.deviceAuthorizationEndpoint); +} + +/*! @brief Tests the @c NSCopying implementation by round-tripping an instance through the copying + * process and checking to make sure the source and destination both contain the + * @c deviceAuthorizationEndpoint + */ +- (void)testCopying { + OIDTVServiceConfiguration *serviceConfiguration = [self testServiceConfiguration]; + + OIDTVAuthorizationRequest *authRequest = + [[OIDTVAuthorizationRequest alloc] initWithConfiguration:serviceConfiguration + clientId:kTestClientID + clientSecret:kTestClientSecret + scopes:nil + additionalParameters:nil]; + + OIDTVAuthorizationRequest *authRequestCopy = [authRequest copy]; + NSURL *authRequestCopyDeviceAuthorizationEndpoint = + ((OIDTVServiceConfiguration *)authRequestCopy.configuration).deviceAuthorizationEndpoint; + + XCTAssertEqualObjects(authRequestCopyDeviceAuthorizationEndpoint, + serviceConfiguration.deviceAuthorizationEndpoint); +} + +/*! @brief Tests the @c NSSecureCoding implementation by round-tripping an instance through the + * coding process and checking to make sure the source and destination both contain the + * @c deviceAuthorizationEndpoint + */ +- (void)testSecureCoding { + OIDTVServiceConfiguration *serviceConfiguration = [self testServiceConfiguration]; + + OIDTVAuthorizationRequest *authRequest = + [[OIDTVAuthorizationRequest alloc] initWithConfiguration:serviceConfiguration + clientId:kTestClientID + clientSecret:kTestClientSecret + scopes:nil + additionalParameters:nil]; + + NSData *data = [NSKeyedArchiver archivedDataWithRootObject:authRequest]; + OIDTVAuthorizationRequest *authRequestCopy = [NSKeyedUnarchiver unarchiveObjectWithData:data]; + + NSURL *authRequestCopyDeviceAuthorizationEndpoint = + ((OIDTVServiceConfiguration *)authRequestCopy.configuration).deviceAuthorizationEndpoint; + + XCTAssertEqualObjects(authRequestCopyDeviceAuthorizationEndpoint, + serviceConfiguration.deviceAuthorizationEndpoint); +} + +/*! @brief Tests the @c URLRequest method on a request with no scopes or additional parameters + */ +- (void)testURLRequestBasicClientAuth { + OIDTVServiceConfiguration *serviceConfiguration = [self testServiceConfiguration]; + + OIDTVAuthorizationRequest *authRequest = + [[OIDTVAuthorizationRequest alloc] initWithConfiguration:serviceConfiguration + clientId:kTestClientID + clientSecret:kTestClientSecret + scopes:nil + additionalParameters:nil]; + + NSURLRequest *URLRequest = [authRequest URLRequest]; + + XCTAssertEqualObjects(URLRequest.HTTPMethod, kHTTPPost); + XCTAssertEqualObjects([URLRequest valueForHTTPHeaderField:kHTTPContentTypeHeaderKey], + kHTTPContentTypeHeaderValue); + XCTAssertEqualObjects(URLRequest.URL, serviceConfiguration.deviceAuthorizationEndpoint); + + NSDictionary *bodyParameters = + [self bodyParametersFromURLRequest:URLRequest]; + NSDictionary *expectedParameters = @{@"client_id" : kTestClientID}; + + XCTAssertEqualObjects(bodyParameters, expectedParameters); +} + +/*! @brief Tests the @c URLRequest method on a request with two scopes and no additional parameters + */ +- (void)testURLRequestScopes { + OIDTVServiceConfiguration *serviceConfiguration = [self testServiceConfiguration]; + NSArray *testScopes = @[ kTestScope, kTestScopeA ]; + NSString *testScopeString = [OIDScopeUtilities scopesWithArray:testScopes]; + NSString *testScopeStringPercentEncoded = [testScopeString + stringByAddingPercentEncodingWithAllowedCharacters:[OIDURLQueryComponent + URLParamValueAllowedCharacters]]; + + OIDTVAuthorizationRequest *authRequest = + [[OIDTVAuthorizationRequest alloc] initWithConfiguration:serviceConfiguration + clientId:kTestClientID + clientSecret:kTestClientSecret + scopes:@[ kTestScope, kTestScopeA ] + additionalParameters:nil]; + + NSURLRequest *URLRequest = [authRequest URLRequest]; + + XCTAssertEqualObjects([URLRequest HTTPMethod], kHTTPPost); + XCTAssertEqualObjects([URLRequest valueForHTTPHeaderField:kHTTPContentTypeHeaderKey], + kHTTPContentTypeHeaderValue); + XCTAssertEqualObjects(URLRequest.URL, serviceConfiguration.deviceAuthorizationEndpoint); + + NSDictionary *bodyParameters = + [self bodyParametersFromURLRequest:URLRequest]; + NSDictionary *expectedParameters = + @{kTestClientIDKey : kTestClientID, kTestScopeKey : testScopeStringPercentEncoded}; + + XCTAssertEqualObjects(bodyParameters, expectedParameters); +} + +/*! @brief Tests the @c URLRequest method on a request with two scopes and one additional parameter + */ +- (void)testURLRequestAdditionalParams { + OIDTVServiceConfiguration *serviceConfiguration = [self testServiceConfiguration]; + NSArray *testScopes = @[ kTestScope, kTestScopeA ]; + NSString *testScopeString = [OIDScopeUtilities scopesWithArray:testScopes]; + NSString *testScopeStringPercentEncoded = [testScopeString + stringByAddingPercentEncodingWithAllowedCharacters:[OIDURLQueryComponent + URLParamValueAllowedCharacters]]; + OIDTVAuthorizationRequest *authRequest = [[OIDTVAuthorizationRequest alloc] + initWithConfiguration:serviceConfiguration + clientId:kTestClientID + clientSecret:kTestClientSecret + scopes:@[ kTestScope, kTestScopeA ] + additionalParameters:@{kTestAdditionalParameterKey : kTestAdditionalParameterValue}]; + + NSURLRequest *URLRequest = [authRequest URLRequest]; + + XCTAssertEqualObjects([URLRequest HTTPMethod], kHTTPPost); + XCTAssertEqualObjects([URLRequest valueForHTTPHeaderField:kHTTPContentTypeHeaderKey], + kHTTPContentTypeHeaderValue); + XCTAssertEqualObjects(URLRequest.URL, serviceConfiguration.deviceAuthorizationEndpoint); + + NSDictionary *bodyParameters = + [self bodyParametersFromURLRequest:URLRequest]; + NSDictionary *expectedParameters = @{ + kTestClientIDKey : kTestClientID, + kTestScopeKey : testScopeStringPercentEncoded, + kTestAdditionalParameterKey : kTestAdditionalParameterValue + }; + + XCTAssertEqualObjects(bodyParameters, expectedParameters); +} + +@end + +#pragma GCC diagnostic pop diff --git a/UnitTests/AppAuthTV/OIDTVAuthorizationResponseTests.h b/UnitTests/AppAuthTV/OIDTVAuthorizationResponseTests.h new file mode 100644 index 000000000..2ffbc5775 --- /dev/null +++ b/UnitTests/AppAuthTV/OIDTVAuthorizationResponseTests.h @@ -0,0 +1,59 @@ +/*! @file OIDTVAuthorizationResponseTests.h + @brief AppAuth iOS SDK + @copyright + Copyright 2020 Google Inc. All Rights Reserved. + @copydetails + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#import + +@class OIDTVAuthorizationResponse; + +NS_ASSUME_NONNULL_BEGIN + +/*! @brief Unit tests for @c OIDTVAuthorizationResponse. + */ +@interface OIDTVAuthorizationResponseTests : XCTestCase + +/*! @brief Tests the initializer using the standard key for @c verificationURI. + */ +- (void)testInitializer; + +/*! @brief Tests the initializer using the alternative key for @c verificationURI. + */ +- (void)testInitializerAlternativeKey; + +/*! @brief Tests the @c NSCopying implementation by round-tripping an instance through the copying + * process and checking to make sure the source and destination are equivalent. + */ +- (void)testCopying; + +/*! @brief Tests the @c NSSecureCoding implementation by round-tripping an instance through the + * coding process and checking to make sure the source and destination are equivalent. + */ +- (void)testSecureCoding; + +/*! @brief Tests the @c tokenPollRequest method that takes no additional parameters. + */ +- (void)testTokenPollRequest; + +/*! @brief Tests the @c testTokenPollRequestWithAdditionalParametersAdditionalHeaders method with one additional + parameter and one additional header. + */ +- (void)testTokenPollRequestWithAdditionalParametersAdditionalHeaders; + +@end + +NS_ASSUME_NONNULL_END + diff --git a/UnitTests/AppAuthTV/OIDTVAuthorizationResponseTests.m b/UnitTests/AppAuthTV/OIDTVAuthorizationResponseTests.m new file mode 100644 index 000000000..436232322 --- /dev/null +++ b/UnitTests/AppAuthTV/OIDTVAuthorizationResponseTests.m @@ -0,0 +1,295 @@ +/*! @file OIDTVAuthorizationResponseTests.m + @brief AppAuth iOS SDK + @copyright + Copyright 2020 Google Inc. All Rights Reserved. + @copydetails + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#import "OIDTVAuthorizationResponseTests.h" + +#if SWIFT_PACKAGE +@import AppAuthTV; +#else +#import "Sources/AppAuthCore/OIDScopeUtilities.h" +#import "Sources/AppAuthCore/OIDURLQueryComponent.h" +#import "Sources/AppAuthTV/OIDTVAuthorizationRequest.h" +#import "Sources/AppAuthTV/OIDTVAuthorizationResponse.h" +#import "Sources/AppAuthTV/OIDTVServiceConfiguration.h" +#import "Sources/AppAuthTV/OIDTVTokenRequest.h" +#endif + +/*! @brief Test value for the @c deviceAuthorizationEndpoint property. + */ +static NSString *const kTestDeviceAuthorizationEndpoint = @"https://www.example.com/device/code"; + +/*! @brief Test value for the @c tokenEndpoint property. + */ +static NSString *const kTestTokenEndpoint = @"https://www.example.com/token"; + +/*! @brief Test key for the @c additionalParameters property. + */ +static NSString *const kTestAdditionalParameterKey = @"A"; + +/*! @brief Test value for the @c additionalParameters property. + */ +static NSString *const kTestAdditionalParameterValue = @"1"; + +/*! @brief Test key for the @c additionalHeaders property. + */ +static NSString *const kTestAdditionalHeaderKey = @"B"; + +/*! @brief Test value for the @c additionalHeaders property. + */ +static NSString *const kTestAdditionalHeaderValue = @"2"; + +/*! @brief Test value for the @c clientID property. + */ +static NSString *const kTestClientID = @"ClientID"; + +/*! @brief Test value for the @c clientSecret property. + */ +static NSString *const kTestClientSecret = @"ClientSecret"; + +/*! @brief Key for the @c verificationURI property. + */ +static NSString *const kVerificationURIKey = @"verification_uri"; + +/*! @brief Alternative key for the @c verificationURI property. If "verification_uri" is not found + in the response, a "verification_url" key is considered equivalent. + */ +static NSString *const kVerificationURIAlternativeKey = @"verification_url"; + +/*! @brief Test value for the @c verificationURI property. + */ +static NSString *const kTestVerificationURI = @"https://www.example.com/device"; + +/*! @brief Key for the @c verificationURIComplete property. + */ +static NSString *const kVerificationURICompleteKey = @"verification_uri_complete"; + +/*! @brief Test value for the @c verificationURIComplete property. + */ +static NSString *const kTestVerificationURIComplete = @"https://www.example.com/device/UserCode"; + +/*! @brief Key for the @c userCode property. + */ +static NSString *const kUserCodeKey = @"user_code"; + +/*! @brief Test value for the @c userCode property. + */ +static NSString *const kTestUserCode = @"UserCode"; + +/*! @brief Key for the @c deviceCode property. + */ +static NSString *const kDeviceCodeKey = @"device_code"; + +/*! @brief Test value for the @c deviceCode property. + */ +static NSString *const kTestDeviceCode = @"DeviceCode"; + +/*! @brief Key for the @c expirationDate property. + */ +static NSString *const kExpiresInKey = @"expires_in"; + +/*! @brief Test lifetime value used for the @c expirationDate property. + */ +static long long const kTestExpiresIn = 1800; + +/*! @brief Key for the @c interval property. + */ +static NSString *const kIntervalKey = @"interval"; + +/*! @brief Test value for the @c interval property. + */ +static int const kTestInterval = 5; + +@implementation OIDTVAuthorizationResponseTests + +- (OIDTVServiceConfiguration *)testServiceConfiguration { + NSURL *tokenEndpoint = [NSURL URLWithString:kTestTokenEndpoint]; + NSURL *deviceAuthorizationEndpoint = [NSURL URLWithString:kTestDeviceAuthorizationEndpoint]; + + OIDTVServiceConfiguration *configuration = + [[OIDTVServiceConfiguration alloc] initWithDeviceAuthorizationEndpoint:deviceAuthorizationEndpoint + tokenEndpoint:tokenEndpoint]; + return configuration; +} + +- (OIDTVAuthorizationRequest *)testAuthorizationRequest { + OIDTVAuthorizationRequest *request = + [[OIDTVAuthorizationRequest alloc] initWithConfiguration:[self testServiceConfiguration] + clientId:kTestClientID + clientSecret:kTestClientSecret + scopes:nil + additionalParameters:nil]; + return request; +} + +/*! @brief Returns an @c OIDTVAuthorizationResponse instance using the standard key for + @c verificationURI, with a @c verificationURIComplete value and additional parameter. + @returns an @c OIDTVAuthorizationResponse instance +*/ +- (OIDTVAuthorizationResponse *)testAuthorizationResponse { + OIDTVAuthorizationResponse *response = [[OIDTVAuthorizationResponse alloc] + initWithRequest:[self testAuthorizationRequest] + parameters:@{ + kVerificationURIKey : kTestVerificationURI, + kVerificationURICompleteKey : kTestVerificationURIComplete, + kUserCodeKey : kTestUserCode, + kDeviceCodeKey : kTestDeviceCode, + kExpiresInKey : @(kTestExpiresIn), + kIntervalKey : @(kTestInterval), + kTestAdditionalParameterKey : kTestAdditionalParameterValue + }]; + return response; +} + +/*! @brief Tests the initializer using the standard key for @c verificationURI. + */ +- (void)testInitializer { + OIDTVAuthorizationResponse *response = [self testAuthorizationResponse]; + + NSDictionary *testAdditionalParameters = + @{kTestAdditionalParameterKey : kTestAdditionalParameterValue}; + + XCTAssertEqualObjects(response.verificationURI, kTestVerificationURI); + XCTAssertEqualObjects(response.verificationURIComplete, kTestVerificationURIComplete); + XCTAssertEqualObjects(response.userCode, kTestUserCode); + XCTAssertEqualObjects(response.deviceCode, kTestDeviceCode); + XCTAssertEqualObjects(response.interval, @(kTestInterval)); + XCTAssertEqualObjects(response.additionalParameters, testAdditionalParameters); + + // Should be ~ kExpiresInValue seconds. Avoiding swizzling NSDate here for certainty + // to keep dependencies down, and simply making an assumption that this check will be executed + // relatively quickly after the initialization above (less than 5 seconds.) + NSTimeInterval expiration = [response.expirationDate timeIntervalSinceNow]; + XCTAssert(expiration > kTestExpiresIn - 5 && expiration <= kTestExpiresIn); +} + +/*! @brief Tests the initializer using the alternative key for @c verificationURI. + */ +- (void)testInitializerAlternativeKey { + OIDTVAuthorizationResponse *response = [[OIDTVAuthorizationResponse alloc] + initWithRequest:[self testAuthorizationRequest] + parameters:@{ + kVerificationURIAlternativeKey : kTestVerificationURI, + kVerificationURICompleteKey : kTestVerificationURIComplete, + kUserCodeKey : kTestUserCode, + kDeviceCodeKey : kTestDeviceCode, + kExpiresInKey : @(kTestExpiresIn), + kIntervalKey : @(kTestInterval), + kTestAdditionalParameterKey : kTestAdditionalParameterValue + }]; + + NSDictionary *testAdditionalParameters = + @{kTestAdditionalParameterKey : kTestAdditionalParameterValue}; + + // Tests that the alternative key used above maps to the verificationURI property, so + // subsequent tests can simply test using [self testAuthorizationResponse] which uses + // the standard key. + XCTAssertEqualObjects(response.verificationURI, kTestVerificationURI); + + XCTAssertEqualObjects(response.verificationURIComplete, kTestVerificationURIComplete); + XCTAssertEqualObjects(response.userCode, kTestUserCode); + XCTAssertEqualObjects(response.deviceCode, kTestDeviceCode); + XCTAssertEqualObjects(response.interval, @(kTestInterval)); + XCTAssertEqualObjects(response.additionalParameters, testAdditionalParameters); + + // Should be ~ kExpiresInValue seconds. Avoiding swizzling NSDate here for certainty + // to keep dependencies down, and simply making an assumption that this check will be executed + // relatively quickly after the initialization above (less than 5 seconds.) + NSTimeInterval expiration = [response.expirationDate timeIntervalSinceNow]; + XCTAssert(expiration > kTestExpiresIn - 5 && expiration <= kTestExpiresIn); +} + +/*! @brief Tests the @c NSCopying implementation by round-tripping an instance through the copying + * process and checking to make sure the source and destination are equivalent. + */ +- (void)testCopying { + OIDTVAuthorizationResponse *response = [self testAuthorizationResponse]; + OIDTVAuthorizationResponse *responseCopy = [response copy]; + + NSDictionary *testAdditionalParameters = + @{kTestAdditionalParameterKey : kTestAdditionalParameterValue}; + + XCTAssertEqualObjects(responseCopy.request, response.request); + XCTAssertEqualObjects(responseCopy.deviceCode, kTestDeviceCode); + XCTAssertEqualObjects(responseCopy.interval, @(kTestInterval)); + XCTAssertEqualObjects(responseCopy.userCode, kTestUserCode); + XCTAssertEqualObjects(responseCopy.verificationURIComplete, kTestVerificationURIComplete); + XCTAssertEqualObjects(responseCopy.verificationURI, kTestVerificationURI); + XCTAssertEqualObjects(responseCopy.additionalParameters, testAdditionalParameters); +} + +/*! @brief Tests the @c NSSecureCoding implementation by round-tripping an instance through the + * coding process and checking to make sure the source and destination are equivalent. + */ +- (void)testSecureCoding { + OIDTVAuthorizationResponse *response = [self testAuthorizationResponse]; + NSData *data = [NSKeyedArchiver archivedDataWithRootObject:response]; + OIDTVAuthorizationResponse *responseCopy = [NSKeyedUnarchiver unarchiveObjectWithData:data]; + + NSDictionary *testAdditionalParameters = + @{kTestAdditionalParameterKey : kTestAdditionalParameterValue}; + + // Not a full test of the request deserialization, but should be sufficient as a smoke test + // to make sure the request IS actually getting serialized and deserialized in the + // NSSecureCoding implementation. We'll leave it up to the OIDTVAuthorizationRequest tests to make + // sure the NSSecureCoding implementation of that class is correct. + XCTAssertNotNil(responseCopy.request); + + XCTAssertEqualObjects(responseCopy.deviceCode, kTestDeviceCode); + XCTAssertEqualObjects(responseCopy.interval, @(kTestInterval)); + XCTAssertEqualObjects(responseCopy.userCode, kTestUserCode); + XCTAssertEqualObjects(responseCopy.verificationURIComplete, kTestVerificationURIComplete); + XCTAssertEqualObjects(responseCopy.verificationURI, kTestVerificationURI); + XCTAssertEqualObjects(responseCopy.additionalParameters, testAdditionalParameters); +} + +/*! @brief Tests the @c tokenPollRequest method that takes no additional parameters. + */ +- (void)testTokenPollRequest { + OIDTVAuthorizationResponse *testResponse = [self testAuthorizationResponse]; + + OIDTVTokenRequest *pollRequest = [testResponse tokenPollRequest]; + + XCTAssertEqualObjects(pollRequest.deviceCode, kTestDeviceCode); + XCTAssertEqualObjects(pollRequest.clientID, kTestClientID); + XCTAssertEqualObjects(pollRequest.clientSecret, kTestClientSecret); + XCTAssertEqualObjects(pollRequest.additionalParameters, @{}); +} + +/*! @brief Tests the @c testTokenPollRequestWithAdditionalParametersAdditionalHeaders method with one additional + parameter and one additional header. + */ +- (void)testTokenPollRequestWithAdditionalParametersAdditionalHeaders { + OIDTVAuthorizationResponse *testResponse = [self testAuthorizationResponse]; + + NSDictionary *testAdditionalParameters = + @{kTestAdditionalParameterKey : kTestAdditionalParameterValue}; + + NSDictionary *testAdditionalHeaders = + @{kTestAdditionalHeaderKey : kTestAdditionalHeaderValue}; + + OIDTVTokenRequest *pollRequest = + [testResponse tokenPollRequestWithAdditionalParameters:testAdditionalParameters additionalHeaders:testAdditionalHeaders]; + + XCTAssertEqualObjects(pollRequest.deviceCode, kTestDeviceCode); + XCTAssertEqualObjects(pollRequest.clientID, kTestClientID); + XCTAssertEqualObjects(pollRequest.clientSecret, kTestClientSecret); + XCTAssertEqualObjects(pollRequest.additionalParameters, testAdditionalParameters); + XCTAssertEqualObjects(pollRequest.additionalHeaders, testAdditionalHeaders); +} + +@end diff --git a/UnitTests/AppAuthTV/OIDTVTokenRequestTests.h b/UnitTests/AppAuthTV/OIDTVTokenRequestTests.h new file mode 100644 index 000000000..1f6eecbdc --- /dev/null +++ b/UnitTests/AppAuthTV/OIDTVTokenRequestTests.h @@ -0,0 +1,51 @@ +/*! @file OIDTVTokenRequestTests.h + @brief AppAuth iOS SDK + @copyright + Copyright 2020 Google Inc. All Rights Reserved. + @copydetails + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#import + +@class OIDTVTokenRequest; + +NS_ASSUME_NONNULL_BEGIN + +/*! @brief Unit tests for @c OIDTVTokenRequest. + */ +@interface OIDTVTokenRequestTests : XCTestCase + +/*! @brief Tests the initializer +*/ +- (void)testInitializer; + +/*! @brief Tests the @c NSCopying implementation by round-tripping an instance through the copying + * process and checking to make sure the source and destination both contain the @c deviceCode. + */ +- (void)testCopying; + +/*! @brief Tests the @c NSSecureCoding implementation by round-tripping an instance through the + * coding process and checking to make sure the source and destination both contain the + * @c deviceCode + */ +- (void)testSecureCoding; + +/*! @brief Tests the @c URLRequest method to verify that the body parameters include the correct + * grant type, device code and additional parameters. + */ +- (void)testURLRequest; + +@end + +NS_ASSUME_NONNULL_END diff --git a/UnitTests/AppAuthTV/OIDTVTokenRequestTests.m b/UnitTests/AppAuthTV/OIDTVTokenRequestTests.m new file mode 100644 index 000000000..cd56344fd --- /dev/null +++ b/UnitTests/AppAuthTV/OIDTVTokenRequestTests.m @@ -0,0 +1,201 @@ +/*! @file OIDTVTokenRequestTests.m + @brief AppAuth iOS SDK + @copyright + Copyright 2020 Google Inc. All Rights Reserved. + @copydetails + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#import "OIDTVTokenRequestTests.h" + +#if SWIFT_PACKAGE +@import AppAuthTV; +#else +#import "Sources/AppAuthCore/OIDScopeUtilities.h" +#import "Sources/AppAuthTV/OIDTVAuthorizationRequest.h" +#import "Sources/AppAuthTV/OIDTVAuthorizationResponse.h" +#import "Sources/AppAuthTV/OIDTVServiceConfiguration.h" +#import "Sources/AppAuthTV/OIDTVTokenRequest.h" +#endif + +// Ignore warnings about "Use of GNU statement expression extension" which is +// raised by our use of the XCTAssert___ macros. +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wgnu" + +/*! @brief Test value for the @c deviceAuthorizationEndpoint property. + */ +static NSString *const kTestDeviceAuthorizationEndpoint = + @"https://www.example.com/device/code"; + +/*! @brief Test value for the @c tokenEndpoint property. + */ +static NSString *const kTestTokenEndpoint = @"https://www.example.com/token"; + +/*! @brief Test key for the @c additionalParameters property. + */ +static NSString *const kTestAdditionalParameterKey = @"A"; + +/*! @brief Test value for the @c additionalParameters property. + */ +static NSString *const kTestAdditionalParameterValue = @"1"; + +/*! @brief Test key for the @c additionalHeaders property. + */ +static NSString *const kTestAdditionalHeaderKey = @"B"; + +/*! @brief Test value for the @c additionalHeaders property. + */ +static NSString *const kTestAdditionalHeaderValue = @"2"; + +/*! @brief Test key for the @c clientID parameter in the HTTP request. + */ +static NSString *const kTestClientIDKey = @"client_id"; + +/*! @brief Test value for the @c clientID property. + */ +static NSString *const kTestClientID = @"ClientID"; + +/*! @brief Test value for the @c clientSecret property. + */ +static NSString *const kTestClientSecret = @"ClientSecret"; + +/*! @brief Key for the @c deviceCode property for @c NSSecureCoding and the HTTP request body. + */ +static NSString *const kDeviceCodeKey = @"device_code"; + +/*! @brief Value for the @c deviceCode key in the HTTP request body. + */ +static NSString *const kDeviceCodeValue = @"DeviceCode"; + +/*! @brief Key for the @c grantType property for @c NSSecureCoding and the HTTP request body. + */ +static NSString *const kGrantTypeKey = @"grant_type"; + +/*! @brief Value for the @c grant_type key in the HTTP request body + * @see https://tools.ietf.org/html/rfc8628#section-3.4 + */ +static NSString *const kOIDTVDeviceTokenGrantType = + @"urn:ietf:params:oauth:grant-type:device_code"; + +@implementation OIDTVTokenRequestTests + +- (NSDictionary *)bodyParametersFromURLRequest: + (NSURLRequest *)URLRequest { + NSString *bodyString = [[NSString alloc] initWithData:URLRequest.HTTPBody + encoding:NSUTF8StringEncoding]; + NSArray *bodyParameterStrings = + [bodyString componentsSeparatedByString:@"&"]; + + NSMutableDictionary *bodyParameters = + [[NSMutableDictionary alloc] init]; + + for (NSString *paramString in bodyParameterStrings) { + NSArray *components = + [paramString componentsSeparatedByString:@"="]; + + if (components.count == 2) { + bodyParameters[components[0]] = components[1]; + } + } + + return bodyParameters; +} + +- (OIDTVServiceConfiguration *)testServiceConfiguration { + NSURL *tokenEndpoint = [NSURL URLWithString:kTestTokenEndpoint]; + NSURL *deviceAuthorizationEndpoint = [NSURL URLWithString:kTestDeviceAuthorizationEndpoint]; + + OIDTVServiceConfiguration *configuration = [[OIDTVServiceConfiguration alloc] + initWithDeviceAuthorizationEndpoint:deviceAuthorizationEndpoint + tokenEndpoint:tokenEndpoint]; + return configuration; +} + +- (OIDTVTokenRequest *)testTokenRequest { + OIDTVServiceConfiguration *service = [self testServiceConfiguration]; + return [[OIDTVTokenRequest alloc] + initWithConfiguration:service + deviceCode:kDeviceCodeValue + clientID:kTestClientID + clientSecret:kTestClientSecret + additionalParameters:@{kTestAdditionalParameterKey : kTestAdditionalParameterValue} + additionalHeaders:@{kTestAdditionalHeaderKey : kTestAdditionalHeaderValue}]; +} + +/*! @brief Tests the initializer +*/ +- (void)testInitializer { + OIDTVTokenRequest *request = [self testTokenRequest]; + NSURL *requestDeviceAuthorizationEndpoint = + ((OIDTVServiceConfiguration *)request.configuration).deviceAuthorizationEndpoint; + + XCTAssertEqualObjects(requestDeviceAuthorizationEndpoint, + [self testServiceConfiguration].deviceAuthorizationEndpoint); + XCTAssertEqualObjects(request.deviceCode, kDeviceCodeValue); + XCTAssertEqualObjects(request.grantType, kOIDTVDeviceTokenGrantType); + XCTAssertEqualObjects(request.clientID, kTestClientID); + XCTAssertEqualObjects(request.clientSecret, kTestClientSecret); + XCTAssertEqualObjects(request.additionalParameters, + @{kTestAdditionalParameterKey:kTestAdditionalParameterValue}); + XCTAssertEqualObjects(request.additionalHeaders, + @{kTestAdditionalHeaderKey:kTestAdditionalHeaderValue}); +} + +/*! @brief Tests the @c NSCopying implementation by round-tripping an instance through the copying + * process and checking to make sure the source and destination both contain the @c deviceCode. + */ +- (void)testCopying { + OIDTVTokenRequest *request = [self testTokenRequest]; + OIDTVTokenRequest *requestCopy = [request copy]; + + XCTAssertEqualObjects(requestCopy.deviceCode, request.deviceCode); +} + +/*! @brief Tests the @c NSSecureCoding implementation by round-tripping an instance through the + * coding process and checking to make sure the source and destination both contain the + * @c deviceCode + */ +- (void)testSecureCoding { + OIDTVTokenRequest *request = [self testTokenRequest]; + NSData *data = [NSKeyedArchiver archivedDataWithRootObject:request]; + OIDTVTokenRequest *requestDecoded = [NSKeyedUnarchiver unarchiveObjectWithData:data]; + XCTAssertEqualObjects(requestDecoded.deviceCode, request.deviceCode); +} + +/*! @brief Tests the @c URLRequest method to verify that the body parameters include the correct + * grant type, device code and additional parameters. + */ +- (void)testURLRequest { + OIDTVTokenRequest *request = [self testTokenRequest]; + + NSURLRequest *URLRequest = [request URLRequest]; + + NSDictionary *bodyParameters = + [self bodyParametersFromURLRequest:URLRequest]; + + // Since clientSecret is present, we will not need to check for client_id + // as that will be passed in using HTTP Basic Authentication + + NSDictionary *expectedParameters = @{ + kGrantTypeKey : kOIDTVDeviceTokenGrantType, + kDeviceCodeKey : kDeviceCodeValue, + kTestAdditionalParameterKey : kTestAdditionalParameterValue + }; + + XCTAssertEqualObjects(bodyParameters, expectedParameters); +} + +@end + +#pragma GCC diagnostic pop diff --git a/UnitTests/OIDAuthStateTests.h b/UnitTests/OIDAuthStateTests.h index 66acd82da..3bd1eb6fb 100644 --- a/UnitTests/OIDAuthStateTests.h +++ b/UnitTests/OIDAuthStateTests.h @@ -18,8 +18,12 @@ #import -#import "Source/OIDAuthStateChangeDelegate.h" -#import "Source/OIDAuthStateErrorDelegate.h" +#if SWIFT_PACKAGE +@import AppAuthCore; +#else +#import "Sources/AppAuthCore/OIDAuthStateChangeDelegate.h" +#import "Sources/AppAuthCore/OIDAuthStateErrorDelegate.h" +#endif @class OIDAuthState; @@ -27,22 +31,7 @@ NS_ASSUME_NONNULL_BEGIN /*! @brief Unit tests for @c OIDAuthState. */ -@interface OIDAuthStateTests : XCTestCase { - // private variables - /*! @brief An expectation for tests waiting on OIDAuthStateChangeDelegate.didChangeState:. - */ - XCTestExpectation *_didChangeStateExpectation; - - /*! @brief An expectation for tests waiting on - OIDAuthStateErrorDelegate.didEncounterAuthorizationError:. - */ - XCTestExpectation *_didEncounterAuthorizationErrorExpectation; - - /*! @brief An expectation for tests waiting on - OIDAuthStateErrorDelegate.didEncounterTransientError:. - */ - XCTestExpectation *_didEncounterTransientErrorExpectation; -} +@interface OIDAuthStateTests : XCTestCase /*! @brief Creates a new @c OIDAuthState for testing. */ diff --git a/UnitTests/OIDAuthStateTests.m b/UnitTests/OIDAuthStateTests.m index 9b4c66a82..d18ab3405 100644 --- a/UnitTests/OIDAuthStateTests.m +++ b/UnitTests/OIDAuthStateTests.m @@ -21,13 +21,24 @@ #import "OIDAuthorizationResponseTests.h" #import "OIDRegistrationResponseTests.h" #import "OIDTokenResponseTests.h" -#import "Source/OIDAuthState.h" -#import "Source/OIDAuthorizationResponse.h" -#import "Source/OIDErrorUtilities.h" -#import "Source/OIDRegistrationResponse.h" -#import "Source/OIDTokenResponse.h" + +#if SWIFT_PACKAGE +@import AppAuthCore; +#else +#import "Sources/AppAuthCore/OIDAuthState.h" +#import "Sources/AppAuthCore/OIDAuthorizationResponse.h" +#import "Sources/AppAuthCore/OIDErrorUtilities.h" +#import "Sources/AppAuthCore/OIDRegistrationResponse.h" +#import "Sources/AppAuthCore/OIDTokenResponse.h" +#endif + #import "OIDTokenRequestTests.h" +// Ignore warnings about "Use of GNU statement expression extension" which is raised by our use of +// the XCTAssert___ macros. +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wgnu" + @interface OIDAuthState (Testing) // expose private method for simple testing - (BOOL)isTokenFresh; @@ -36,7 +47,21 @@ - (BOOL)isTokenFresh; @interface OIDAuthStateTests () @end -@implementation OIDAuthStateTests +@implementation OIDAuthStateTests { + /*! @brief An expectation for tests waiting on OIDAuthStateChangeDelegate.didChangeState:. + */ + XCTestExpectation *_didChangeStateExpectation; + + /*! @brief An expectation for tests waiting on + OIDAuthStateErrorDelegate.didEncounterAuthorizationError:. + */ + XCTestExpectation *_didEncounterAuthorizationErrorExpectation; + + /*! @brief An expectation for tests waiting on + OIDAuthStateErrorDelegate.didEncounterTransientError:. + */ + XCTestExpectation *_didEncounterTransientErrorExpectation; +} + (OIDAuthState *)testInstance { OIDAuthorizationResponse *authorizationResponse = @@ -83,7 +108,7 @@ + (NSError *)OAuthTokenInvalidClientError { - (void)didChangeState:(OIDAuthState *)state { // in this test, this method should only be called when we expect it - XCTAssertNotNil(_didChangeStateExpectation); + XCTAssertNotNil(_didChangeStateExpectation, @""); [_didChangeStateExpectation fulfill]; } @@ -92,7 +117,7 @@ - (void)didChangeState:(OIDAuthState *)state { - (void)authState:(OIDAuthState *)state didEncounterAuthorizationError:(NSError *)error { // in this test, this method should only be called when we expect it - XCTAssertNotNil(_didEncounterAuthorizationErrorExpectation); + XCTAssertNotNil(_didEncounterAuthorizationErrorExpectation, @""); [_didEncounterAuthorizationErrorExpectation fulfill]; } @@ -113,16 +138,16 @@ - (void)testErrorState { OIDAuthState *authstate = [[self class] testInstance]; // starting state should be authorized - XCTAssert([authstate isAuthorized]); - XCTAssertFalse([authstate authorizationError]); + XCTAssert([authstate isAuthorized], @""); + XCTAssertFalse([authstate authorizationError], @""); NSError *oauthError = [[self class] OAuthTokenInvalidGrantErrorWithUnderlyingError:nil]; [authstate updateWithAuthorizationError:oauthError]; // after updating with an error, should no longer be authorized - XCTAssertFalse([authstate isAuthorized]); - XCTAssert([authstate authorizationError]); + XCTAssertFalse([authstate isAuthorized], @""); + XCTAssert([authstate authorizationError], @""); } /*! @brief Tests that the didChangeState delegate is called. @@ -179,7 +204,18 @@ - (void)testNonCompliantNSCodingNSErrors { NSError *oauthError = [[self class] OAuthTokenInvalidGrantErrorWithUnderlyingError:nonCompliantError]; [authstate updateWithAuthorizationError:oauthError]; - XCTAssertNoThrow([NSKeyedArchiver archivedDataWithRootObject:authstate]); + NSError *error; + NSData *data; + if (@available(iOS 12.0, macOS 10.13, tvOS 11.0, watchOS 4.0, *)) { + data = [NSKeyedArchiver archivedDataWithRootObject:authstate + requiringSecureCoding:YES + error:&error]; + XCTAssertNoThrow(data, @""); + } else { +#if !TARGET_OS_IOS + XCTAssertNoThrow([NSKeyedArchiver archivedDataWithRootObject:authstate], @""); +#endif + } } /*! @brief Tests @c OIDAuthState.updateWithAuthorizationResponse:error: with a success response. @@ -189,8 +225,8 @@ - (void)testUpdateWithAuthorizationResponseSuccess { OIDAuthorizationResponse *authorizationResponse = [OIDAuthorizationResponseTests testInstanceCodeFlow]; [authState updateWithAuthorizationResponse:authorizationResponse error:nil]; - XCTAssertEqual(authState.lastAuthorizationResponse, authorizationResponse); - XCTAssertNil(authState.authorizationError); + XCTAssertEqual(authState.lastAuthorizationResponse, authorizationResponse, @""); + XCTAssertNil(authState.authorizationError, @""); } /*! @brief Tests @c OIDAuthState.updateWithAuthorizationResponse:error: with an authorization @@ -200,7 +236,7 @@ - (void)testUpdateWithAuthorizationResponseOAuthError { OIDAuthState *authState = [[self class] testInstance]; NSError *oauthError = [[self class] OAuthAuthorizationError]; [authState updateWithAuthorizationResponse:nil error:oauthError]; - XCTAssertNotNil(authState.authorizationError); + XCTAssertNotNil(authState.authorizationError, @""); } /*! @brief Tests @c OIDAuthState.updateWithAuthorizationResponse:error: with a transient @@ -210,7 +246,7 @@ - (void)testUpdateWithAuthorizationResponseTransientError { OIDAuthState *authState = [[self class] testInstance]; NSError *transientError = [[NSError alloc] init]; [authState updateWithAuthorizationResponse:nil error:transientError]; - XCTAssertNil(authState.authorizationError); + XCTAssertNil(authState.authorizationError, @""); } /*! @brief Tests @c OIDAuthState.updateWithAuthorizationResponse:error: with both a success @@ -222,7 +258,7 @@ - (void)testUpdateWithAuthorizationResponseBothSuccessAndError { [OIDAuthorizationResponseTests testInstanceCodeFlow]; NSError *oauthError = [[self class] OAuthAuthorizationError]; [authState updateWithAuthorizationResponse:authorizationResponse error:oauthError]; - XCTAssertNotNil(authState.authorizationError); + XCTAssertNotNil(authState.authorizationError, @""); } /*! @brief Tests @c OIDAuthState.updateWithRegistrationResponse: with a success response. @@ -245,10 +281,10 @@ - (void)testUpdateWithTokenResponseSuccess { OIDAuthState *authState = [[self class] testInstance]; OIDTokenResponse *tokenResponse = [OIDTokenResponseTests testInstanceRefresh]; [authState updateWithTokenResponse:tokenResponse error:nil]; - XCTAssertEqual(authState.lastTokenResponse, tokenResponse); - XCTAssertNotNil(authState.refreshToken); - XCTAssertTrue(authState.isAuthorized); - XCTAssertNil(authState.authorizationError); + XCTAssertEqual(authState.lastTokenResponse, tokenResponse, @""); + XCTAssertNotNil(authState.refreshToken, @""); + XCTAssertTrue(authState.isAuthorized, @""); + XCTAssertNil(authState.authorizationError, @""); } /*! @brief Tests @c OIDAuthState.updateWithTokenResponse:error: with an authorization error. @@ -257,8 +293,8 @@ - (void)testUpdateWithTokenResponseOAuthError { OIDAuthState *authState = [[self class] testInstance]; NSError *oauthError = [[self class] OAuthTokenInvalidGrantErrorWithUnderlyingError:nil]; [authState updateWithTokenResponse:nil error:oauthError]; - XCTAssertFalse(authState.isAuthorized); - XCTAssertNotNil(authState.authorizationError); + XCTAssertFalse(authState.isAuthorized, @""); + XCTAssertNotNil(authState.authorizationError, @""); } /*! @brief Tests @c OIDAuthState.updateWithTokenResponse:error: with a transient (non-OAuth) error. @@ -267,10 +303,10 @@ - (void)testUpdateWithTokenResponseTransientError { OIDAuthState *authState = [[self class] testInstance]; NSError *transientError = [[NSError alloc] init]; [authState updateWithTokenResponse:nil error:transientError]; - XCTAssertNotNil(authState.lastTokenResponse); - XCTAssertNotNil(authState.refreshToken); - XCTAssertTrue(authState.isAuthorized); - XCTAssertNil(authState.authorizationError); + XCTAssertNotNil(authState.lastTokenResponse, @""); + XCTAssertNotNil(authState.refreshToken, @""); + XCTAssertTrue(authState.isAuthorized, @""); + XCTAssertNil(authState.authorizationError, @""); } /*! @brief Tests @c OIDAuthState.updateWithTokenResponse:error: with both a success response @@ -281,8 +317,8 @@ - (void)testUpdateWithTokenResponseBothSuccessAndError { OIDTokenResponse *tokenResponse = [OIDTokenResponseTests testInstanceRefresh]; NSError *oauthError = [[self class] OAuthTokenInvalidGrantErrorWithUnderlyingError:nil]; [authState updateWithTokenResponse:tokenResponse error:oauthError]; - XCTAssertFalse(authState.isAuthorized); - XCTAssertNotNil(authState.authorizationError); + XCTAssertFalse(authState.isAuthorized, @""); + XCTAssertNotNil(authState.authorizationError, @""); } /*! @brief Full lifecycle test of the code flow from code exchange, refresh, error and re-auth. @@ -294,68 +330,99 @@ - (void)testCodeFlowLifecycle { // initializes from code flow authorization response OIDAuthState *authState = [[OIDAuthState alloc] initWithAuthorizationResponse:authorizationResponse]; - XCTAssertEqual(authState.lastAuthorizationResponse, authorizationResponse); + XCTAssertEqual(authState.lastAuthorizationResponse, authorizationResponse, @""); XCTAssertFalse(authState.isAuthorized, @"Shouldn't be authorized as the code needs to be exchanged"); // updates with result from token exchange OIDTokenResponse *tokenResponseCodeExchange = [OIDTokenResponseTests testInstanceCodeExchange]; [authState updateWithTokenResponse:tokenResponseCodeExchange error:nil]; - XCTAssertEqual(authState.lastTokenResponse, tokenResponseCodeExchange); - XCTAssertTrue(authState.isAuthorized); + XCTAssertEqual(authState.lastTokenResponse, tokenResponseCodeExchange, @""); + XCTAssertTrue(authState.isAuthorized, @""); // updates with code refresh OIDTokenResponse *tokenResponseRefresh = [OIDTokenResponseTests testInstanceRefresh]; [authState updateWithTokenResponse:tokenResponseRefresh error:nil]; - XCTAssertEqual(authState.lastTokenResponse, tokenResponseRefresh); - XCTAssertTrue(authState.isAuthorized); + XCTAssertEqual(authState.lastTokenResponse, tokenResponseRefresh, @""); + XCTAssertTrue(authState.isAuthorized, @""); // simulates token error (invalid_grant, token revoked) NSError *oauthError = [[self class] OAuthTokenInvalidGrantErrorWithUnderlyingError:nil]; [authState updateWithTokenResponse:nil error:oauthError]; - XCTAssertFalse(authState.isAuthorized); - XCTAssertNotNil(authState.authorizationError); + XCTAssertFalse(authState.isAuthorized, @""); + XCTAssertNotNil(authState.authorizationError, @""); // simulates successful re-auth response [authState updateWithAuthorizationResponse:authorizationResponse error:nil]; - XCTAssertEqual(authState.lastAuthorizationResponse, authorizationResponse); + XCTAssertEqual(authState.lastAuthorizationResponse, authorizationResponse, @""); XCTAssertNil(authState.authorizationError, @"Error should be nil now."); XCTAssertFalse(authState.isAuthorized, @"Since this is the code flow, AuthState should still not be isAuthorized."); // updates with result from token exchange [authState updateWithTokenResponse:tokenResponseCodeExchange error:nil]; - XCTAssertEqual(authState.lastTokenResponse, tokenResponseCodeExchange); + XCTAssertEqual(authState.lastTokenResponse, tokenResponseCodeExchange, @""); XCTAssertTrue(authState.isAuthorized, @"Should be in an authorized state now"); } - (void)testSecureCoding { - XCTAssert([OIDAuthState supportsSecureCoding]); + XCTAssert([OIDAuthState supportsSecureCoding], @""); OIDAuthState *authState = [[self class] testInstance]; - NSData *data = [NSKeyedArchiver archivedDataWithRootObject:authState]; - OIDAuthState *authStateCopy = [NSKeyedUnarchiver unarchiveObjectWithData:data]; - - XCTAssertEqualObjects(authStateCopy.refreshToken, authState.refreshToken); - XCTAssertEqualObjects(authStateCopy.scope, authState.scope); + OIDAuthState *authStateCopy; + NSError *error; + NSData *data; + if (@available(iOS 12.0, macOS 10.13, tvOS 11.0, watchOS 4.0, *)) { + data = [NSKeyedArchiver archivedDataWithRootObject:authState + requiringSecureCoding:YES + error:&error]; + authStateCopy = [NSKeyedUnarchiver unarchivedObjectOfClass:[OIDAuthState class] + fromData:data + error:&error]; + } else { +#if !TARGET_OS_IOS + data = [NSKeyedArchiver archivedDataWithRootObject:authState]; + authStateCopy = [NSKeyedUnarchiver unarchiveObjectWithData:data]; +#endif + } + + XCTAssertEqualObjects(authStateCopy.refreshToken, authState.refreshToken, @""); + XCTAssertEqualObjects(authStateCopy.scope, authState.scope, @""); XCTAssertEqualObjects(authStateCopy.lastAuthorizationResponse.authorizationCode, - authState.lastAuthorizationResponse.authorizationCode); + authState.lastAuthorizationResponse.authorizationCode, @""); XCTAssertEqualObjects(authStateCopy.lastTokenResponse.refreshToken, - authState.lastTokenResponse.refreshToken); + authState.lastTokenResponse.refreshToken, @""); XCTAssertEqualObjects(authStateCopy.authorizationError.domain, - authState.authorizationError.domain); - XCTAssertEqual(authStateCopy.authorizationError.code, authState.authorizationError.code); - XCTAssertEqual(authStateCopy.isAuthorized, authState.isAuthorized); + authState.authorizationError.domain, @""); + XCTAssertEqual(authStateCopy.authorizationError.code, authState.authorizationError.code, @""); + XCTAssertEqual(authStateCopy.isAuthorized, authState.isAuthorized, @""); // Verify the error object is indeed restored. NSError *oauthError = [[self class] OAuthTokenInvalidGrantErrorWithUnderlyingError:nil]; [authState updateWithTokenResponse:nil error:oauthError]; - data = [NSKeyedArchiver archivedDataWithRootObject:authState]; - XCTAssertNotNil(authState.authorizationError); - authStateCopy = [NSKeyedUnarchiver unarchiveObjectWithData:data]; + if (@available(iOS 12.0, macOS 10.13, tvOS 11.0, watchOS 4.0, *)) { + data = [NSKeyedArchiver archivedDataWithRootObject:authState + requiringSecureCoding:YES + error:&error]; + } else { +#if !TARGET_OS_IOS + data = [NSKeyedArchiver archivedDataWithRootObject:authState]; +#endif + } + XCTAssertNotNil(authState.authorizationError, @""); + + if (@available(iOS 12.0, macOS 10.13, tvOS 11.0, watchOS 4.0, *)) { + authStateCopy = [NSKeyedUnarchiver unarchivedObjectOfClass:[OIDAuthState class] + fromData:data + error:&error]; + } else { +#if !TARGET_OS_IOS + authStateCopy = [NSKeyedUnarchiver unarchiveObjectWithData:data]; +#endif + } XCTAssertEqualObjects(authStateCopy.authorizationError.domain, - authState.authorizationError.domain); - XCTAssertEqual(authStateCopy.authorizationError.code, authState.authorizationError.code); + authState.authorizationError.domain, @""); + XCTAssertEqual(authStateCopy.authorizationError.code, authState.authorizationError.code, @""); } - (void)testIsTokenFreshWithFreshToken { @@ -371,7 +438,7 @@ - (void)testIsTokenFreshWithFreshToken { OIDAuthState *authState = [[OIDAuthState alloc] initWithAuthorizationResponse:authorizationResponse tokenResponse:tokenResponse]; - XCTAssertEqual([authState isTokenFresh], YES); + XCTAssertEqual([authState isTokenFresh], YES, @""); } - (void)testIsTokenFreshWithExpiredToken { @@ -387,13 +454,13 @@ - (void)testIsTokenFreshWithExpiredToken { OIDAuthState *authState = [[OIDAuthState alloc] initWithAuthorizationResponse:authorizationResponse tokenResponse:tokenResponse]; - XCTAssertEqual([authState isTokenFresh], NO); + XCTAssertEqual([authState isTokenFresh], NO, @""); } - (void)testIsTokenFreshRespectsTokenRefreshOverride { OIDAuthState *authState = [[self class] testInstance]; [authState setNeedsTokenRefresh]; - XCTAssertEqual([authState isTokenFresh], NO); + XCTAssertEqual([authState isTokenFresh], NO, @""); } - (void)testIsTokenFreshHandlesTokenWithoutExpirationTime { @@ -407,8 +474,30 @@ - (void)testIsTokenFreshHandlesTokenWithoutExpirationTime { OIDAuthState *authState = [[OIDAuthState alloc] initWithAuthorizationResponse:authorizationResponse tokenResponse:tokenResponse]; - XCTAssertEqual([authState isTokenFresh], YES); + XCTAssertEqual([authState isTokenFresh], YES, @""); +} + +- (void)testThatRefreshTokenExceptionWillBeRaisedForTokenRequestWithAdditionalParameters { + OIDAuthState *authState = [[OIDAuthState alloc] initWithAuthorizationResponse:nil tokenResponse:nil registrationResponse:nil]; + XCTAssertThrowsSpecificNamed([authState tokenRefreshRequestWithAdditionalParameters:nil], + NSException, + kRefreshTokenRequestException); +} + +- (void)testThatRefreshTokenExceptionWillBeRaisedForTokenRequestWithAdditionalHeaders { + OIDAuthState *authState = [[OIDAuthState alloc] initWithAuthorizationResponse:nil tokenResponse:nil registrationResponse:nil]; + XCTAssertThrowsSpecificNamed([authState tokenRefreshRequestWithAdditionalHeaders:nil], + NSException, + kRefreshTokenRequestException); +} + +- (void)testThatRefreshTokenExceptionWillBeRaisedForTokenRequestWithAdditionalParametersAndHeaders { + OIDAuthState *authState = [[OIDAuthState alloc] initWithAuthorizationResponse:nil tokenResponse:nil registrationResponse:nil]; + XCTAssertThrowsSpecificNamed([authState tokenRefreshRequestWithAdditionalHeaders:nil], + NSException, + kRefreshTokenRequestException); } @end +#pragma GCC diagnostic pop diff --git a/UnitTests/OIDAuthorizationRequestTests.m b/UnitTests/OIDAuthorizationRequestTests.m index 4a4af75af..dcefc6ff2 100644 --- a/UnitTests/OIDAuthorizationRequestTests.m +++ b/UnitTests/OIDAuthorizationRequestTests.m @@ -19,9 +19,19 @@ #import "OIDAuthorizationRequestTests.h" #import "OIDServiceConfigurationTests.h" -#import "Source/OIDAuthorizationRequest.h" -#import "Source/OIDScopeUtilities.h" -#import "Source/OIDServiceConfiguration.h" + +#if SWIFT_PACKAGE +@import AppAuthCore; +#else +#import "Sources/AppAuthCore/OIDAuthorizationRequest.h" +#import "Sources/AppAuthCore/OIDScopeUtilities.h" +#import "Sources/AppAuthCore/OIDServiceConfiguration.h" +#endif + +// Ignore warnings about "Use of GNU statement expression extension" which is raised by our use of +// the XCTAssert___ macros. +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wgnu" /*! @brief Test value for the @c responseType property. */ @@ -63,6 +73,10 @@ */ static NSString *const kTestState = @"State"; +/*! @brief Test value for the @c nonce property. + */ +static NSString *const kTestNonce = @"Nonce"; + /*! @brief Test value for the @c codeVerifier property. */ static NSString *const kTestCodeVerifier = @"code verifier"; @@ -142,6 +156,7 @@ + (OIDAuthorizationRequest *)testInstance { redirectURL:[NSURL URLWithString:kTestRedirectURL] responseType:kTestResponseType state:kTestState + nonce:kTestNonce codeVerifier:kTestCodeVerifier codeChallenge:[[self class] codeChallenge] codeChallengeMethod:[[self class] codeChallengeMethod] @@ -159,6 +174,7 @@ + (OIDAuthorizationRequest *)testInstanceCodeFlow { redirectURL:[NSURL URLWithString:kTestRedirectURL] responseType:OIDResponseTypeCode state:kTestState + nonce:kTestNonce codeVerifier:kTestCodeVerifier codeChallenge:[[self class] codeChallenge] codeChallengeMethod:[[self class] codeChallengeMethod] @@ -176,6 +192,7 @@ + (OIDAuthorizationRequest *)testInstanceCodeFlowClientAuth { redirectURL:[NSURL URLWithString:kTestRedirectURL] responseType:OIDResponseTypeCode state:kTestState + nonce:kTestNonce codeVerifier:kTestCodeVerifier codeChallenge:[[self class] codeChallenge] codeChallengeMethod:[[self class] codeChallengeMethod] @@ -197,13 +214,36 @@ - (void)testScopeInitializerWithManyScopesAndNoClientSecret { responseType:OIDResponseTypeCode additionalParameters:additionalParameters]; + XCTAssertEqualObjects(request.responseType, @"code", @""); + XCTAssertEqualObjects(request.scope, kTestScopesMerged, @""); + XCTAssertEqualObjects(request.clientID, kTestClientID, @""); + XCTAssertEqualObjects(request.clientSecret, nil, @""); + XCTAssertEqualObjects(request.redirectURL, [NSURL URLWithString:kTestRedirectURL], @""); + XCTAssertEqualObjects(request.additionalParameters[kTestAdditionalParameterKey], + kTestAdditionalParameterValue, @""); +} + + +/*! @brief Tests the initializer which takes a nonce + */ +- (void)testNonceInitializer { + OIDServiceConfiguration *configuration = [OIDServiceConfigurationTests testInstance]; + OIDAuthorizationRequest *request = + [[OIDAuthorizationRequest alloc] initWithConfiguration:configuration + clientId:kTestClientID + scopes:@[] + redirectURL:[NSURL URLWithString:kTestRedirectURL] + responseType:OIDResponseTypeCode + nonce:kTestNonce + additionalParameters:nil]; + + XCTAssertEqualObjects(request.nonce, kTestNonce); XCTAssertEqualObjects(request.responseType, @"code"); - XCTAssertEqualObjects(request.scope, kTestScopesMerged); + XCTAssertEqualObjects(request.scope, @""); XCTAssertEqualObjects(request.clientID, kTestClientID); - XCTAssertEqualObjects(request.clientSecret, nil); + XCTAssertNil(request.clientSecret); XCTAssertEqualObjects(request.redirectURL, [NSURL URLWithString:kTestRedirectURL]); - XCTAssertEqualObjects(request.additionalParameters[kTestAdditionalParameterKey], - kTestAdditionalParameterValue); + XCTAssertEqualObjects(@(request.additionalParameters.count), @0); } - (void)testScopeInitializerWithManyScopesAndClientSecret { @@ -219,13 +259,13 @@ - (void)testScopeInitializerWithManyScopesAndClientSecret { responseType:OIDResponseTypeCode additionalParameters:additionalParameters]; - XCTAssertEqualObjects(request.responseType, @"code"); - XCTAssertEqualObjects(request.scope, kTestScopesMerged); - XCTAssertEqualObjects(request.clientID, kTestClientID); - XCTAssertEqualObjects(request.clientSecret, kTestClientSecret); - XCTAssertEqualObjects(request.redirectURL, [NSURL URLWithString:kTestRedirectURL]); + XCTAssertEqualObjects(request.responseType, @"code", @""); + XCTAssertEqualObjects(request.scope, kTestScopesMerged, @""); + XCTAssertEqualObjects(request.clientID, kTestClientID, @""); + XCTAssertEqualObjects(request.clientSecret, kTestClientSecret, @""); + XCTAssertEqualObjects(request.redirectURL, [NSURL URLWithString:kTestRedirectURL], @""); XCTAssertEqualObjects(request.additionalParameters[kTestAdditionalParameterKey], - kTestAdditionalParameterValue); + kTestAdditionalParameterValue, @""); } /*! @brief Tests the @c NSCopying implementation by round-tripping an instance through the copying @@ -234,33 +274,34 @@ - (void)testScopeInitializerWithManyScopesAndClientSecret { - (void)testCopying { OIDAuthorizationRequest *request = [[self class] testInstance]; - XCTAssertEqualObjects(request.responseType, kTestResponseType); - XCTAssertEqualObjects(request.scope, kTestScopesMerged); - XCTAssertEqualObjects(request.clientID, kTestClientID); - XCTAssertEqualObjects(request.clientSecret, kTestClientSecret); - XCTAssertEqualObjects(request.redirectURL, [NSURL URLWithString:kTestRedirectURL]); - XCTAssertEqualObjects(request.state, kTestState); - XCTAssertEqualObjects(request.codeVerifier, kTestCodeVerifier); - XCTAssertEqualObjects(request.codeChallenge, [[self class] codeChallenge]); - XCTAssertEqualObjects(request.codeChallengeMethod, [[self class] codeChallengeMethod]); + XCTAssertEqualObjects(request.responseType, kTestResponseType, @""); + XCTAssertEqualObjects(request.scope, kTestScopesMerged, @""); + XCTAssertEqualObjects(request.clientID, kTestClientID, @""); + XCTAssertEqualObjects(request.clientSecret, kTestClientSecret, @""); + XCTAssertEqualObjects(request.redirectURL, [NSURL URLWithString:kTestRedirectURL], @""); + XCTAssertEqualObjects(request.state, kTestState, @""); + XCTAssertEqualObjects(request.nonce, kTestNonce, @""); + XCTAssertEqualObjects(request.codeVerifier, kTestCodeVerifier, @""); + XCTAssertEqualObjects(request.codeChallenge, [[self class] codeChallenge], @""); + XCTAssertEqualObjects(request.codeChallengeMethod, [[self class] codeChallengeMethod], @""); XCTAssertEqualObjects(request.additionalParameters[kTestAdditionalParameterKey], - kTestAdditionalParameterValue); + kTestAdditionalParameterValue, @""); OIDAuthorizationRequest *requestCopy = [request copy]; - XCTAssertNotNil(requestCopy.configuration); - XCTAssertEqualObjects(requestCopy.configuration, request.configuration); - XCTAssertEqualObjects(requestCopy.responseType, request.responseType); - XCTAssertEqualObjects(requestCopy.scope, request.scope); - XCTAssertEqualObjects(requestCopy.clientID, request.clientID); - XCTAssertEqualObjects(requestCopy.clientSecret, request.clientSecret); - XCTAssertEqualObjects(requestCopy.redirectURL, request.redirectURL); - XCTAssertEqualObjects(requestCopy.state, request.state); - XCTAssertEqualObjects(requestCopy.codeVerifier, request.codeVerifier); - XCTAssertEqualObjects(requestCopy.codeChallenge, request.codeChallenge); - XCTAssertEqualObjects(requestCopy.codeChallengeMethod, request.codeChallengeMethod); + XCTAssertNotNil(requestCopy.configuration, @""); + XCTAssertEqualObjects(requestCopy.configuration, request.configuration, @""); + XCTAssertEqualObjects(requestCopy.responseType, request.responseType, @""); + XCTAssertEqualObjects(requestCopy.scope, request.scope, @""); + XCTAssertEqualObjects(requestCopy.clientID, request.clientID, @""); + XCTAssertEqualObjects(requestCopy.clientSecret, request.clientSecret, @""); + XCTAssertEqualObjects(requestCopy.redirectURL, request.redirectURL, @""); + XCTAssertEqualObjects(requestCopy.state, request.state, @""); + XCTAssertEqualObjects(requestCopy.codeVerifier, request.codeVerifier, @""); + XCTAssertEqualObjects(requestCopy.codeChallenge, request.codeChallenge, @""); + XCTAssertEqualObjects(requestCopy.codeChallengeMethod, request.codeChallengeMethod, @""); XCTAssertEqualObjects(requestCopy.additionalParameters, - request.additionalParameters); + request.additionalParameters, @""); } /*! @brief Tests the @c NSSecureCoding by round-tripping an instance through the coding process and @@ -269,39 +310,53 @@ - (void)testCopying { - (void)testSecureCoding { OIDAuthorizationRequest *request = [[self class] testInstance]; - XCTAssertEqualObjects(request.responseType, kTestResponseType); - XCTAssertEqualObjects(request.scope, kTestScopesMerged); - XCTAssertEqualObjects(request.clientID, kTestClientID); - XCTAssertEqualObjects(request.clientSecret, kTestClientSecret); - XCTAssertEqualObjects(request.redirectURL, [NSURL URLWithString:kTestRedirectURL]); - XCTAssertEqualObjects(request.state, kTestState); - XCTAssertEqualObjects(request.codeVerifier, kTestCodeVerifier); - XCTAssertEqualObjects(request.codeChallenge, [[self class] codeChallenge]); - XCTAssertEqualObjects(request.codeChallengeMethod, [[self class] codeChallengeMethod]); + XCTAssertEqualObjects(request.responseType, kTestResponseType, @""); + XCTAssertEqualObjects(request.scope, kTestScopesMerged, @""); + XCTAssertEqualObjects(request.clientID, kTestClientID, @""); + XCTAssertEqualObjects(request.clientSecret, kTestClientSecret, @""); + XCTAssertEqualObjects(request.redirectURL, [NSURL URLWithString:kTestRedirectURL], @""); + XCTAssertEqualObjects(request.state, kTestState, @""); + XCTAssertEqualObjects(request.codeVerifier, kTestCodeVerifier, @""); + XCTAssertEqualObjects(request.codeChallenge, [[self class] codeChallenge], @""); + XCTAssertEqualObjects(request.codeChallengeMethod, [[self class] codeChallengeMethod], @""); XCTAssertEqualObjects(request.additionalParameters[kTestAdditionalParameterKey], - kTestAdditionalParameterValue); - - NSData *data = [NSKeyedArchiver archivedDataWithRootObject:request]; - OIDAuthorizationRequest *requestCopy = [NSKeyedUnarchiver unarchiveObjectWithData:data]; + kTestAdditionalParameterValue, @""); + + OIDAuthorizationRequest *requestCopy; + NSError *error; + NSData *data; + if (@available(iOS 12.0, macOS 10.13, tvOS 11.0, watchOS 4.0, *)) { + data = [NSKeyedArchiver archivedDataWithRootObject:request + requiringSecureCoding:YES + error:&error]; + requestCopy = [NSKeyedUnarchiver unarchivedObjectOfClass:[OIDAuthorizationRequest class] + fromData:data + error:&error]; + } else { +#if !TARGET_OS_IOS + data = [NSKeyedArchiver archivedDataWithRootObject:request]; + requestCopy = [NSKeyedUnarchiver unarchiveObjectWithData:data]; +#endif + } // Not a full test of the configuration deserialization, but should be sufficient as a smoke test // to make sure the configuration IS actually getting serialized and deserialized in the // NSSecureCoding implementation. We'll leave it up to the OIDServiceConfiguration tests to make // sure the NSSecureCoding implementation of that class is correct. - XCTAssertNotNil(requestCopy.configuration); + XCTAssertNotNil(requestCopy.configuration, @""); XCTAssertEqualObjects(requestCopy.configuration.authorizationEndpoint, - request.configuration.authorizationEndpoint); - - XCTAssertEqualObjects(requestCopy.responseType, kTestResponseType); - XCTAssertEqualObjects(requestCopy.scope, kTestScopesMerged); - XCTAssertEqualObjects(requestCopy.clientID, kTestClientID); - XCTAssertEqualObjects(requestCopy.redirectURL, [NSURL URLWithString:kTestRedirectURL]); - XCTAssertEqualObjects(requestCopy.state, kTestState); - XCTAssertEqualObjects(requestCopy.codeVerifier, kTestCodeVerifier); - XCTAssertEqualObjects(requestCopy.codeChallenge, [[self class] codeChallenge]); - XCTAssertEqualObjects(requestCopy.codeChallengeMethod, [[self class] codeChallengeMethod]); + request.configuration.authorizationEndpoint, @""); + + XCTAssertEqualObjects(requestCopy.responseType, kTestResponseType, @""); + XCTAssertEqualObjects(requestCopy.scope, kTestScopesMerged, @""); + XCTAssertEqualObjects(requestCopy.clientID, kTestClientID, @""); + XCTAssertEqualObjects(requestCopy.redirectURL, [NSURL URLWithString:kTestRedirectURL], @""); + XCTAssertEqualObjects(requestCopy.state, kTestState, @""); + XCTAssertEqualObjects(requestCopy.codeVerifier, kTestCodeVerifier, @""); + XCTAssertEqualObjects(requestCopy.codeChallenge, [[self class] codeChallenge], @""); + XCTAssertEqualObjects(requestCopy.codeChallengeMethod, [[self class] codeChallengeMethod], @""); XCTAssertEqualObjects(requestCopy.additionalParameters[kTestAdditionalParameterKey], - kTestAdditionalParameterValue); + kTestAdditionalParameterValue, @""); } /*! @brief Tests the scope string logic to make sure the disallowed characters are properly @@ -316,63 +371,63 @@ - (void)testDisallowedCharactersInScopes { scopes:@[ kTestInvalidScope1 ] redirectURL:redirectURL responseType:OIDResponseTypeCode - additionalParameters:nil]); + additionalParameters:nil], @""); XCTAssertThrows( [[OIDAuthorizationRequest alloc] initWithConfiguration:configuration clientId:kTestClientID scopes:@[ kTestInvalidScope2 ] redirectURL:redirectURL responseType:OIDResponseTypeCode - additionalParameters:nil]); + additionalParameters:nil], @""); XCTAssertThrows( [[OIDAuthorizationRequest alloc] initWithConfiguration:configuration clientId:kTestClientID scopes:@[ kTestInvalidScope3 ] redirectURL:redirectURL responseType:OIDResponseTypeCode - additionalParameters:nil]); + additionalParameters:nil], @""); XCTAssertThrows( [[OIDAuthorizationRequest alloc] initWithConfiguration:configuration clientId:kTestClientID scopes:@[ kTestInvalidScope4 ] redirectURL:redirectURL responseType:OIDResponseTypeCode - additionalParameters:nil]); + additionalParameters:nil], @""); XCTAssertNoThrow( [[OIDAuthorizationRequest alloc] initWithConfiguration:configuration clientId:kTestClientID scopes:@[ kTestValidScope1 ] redirectURL:redirectURL responseType:OIDResponseTypeCode - additionalParameters:nil]); + additionalParameters:nil], @""); XCTAssertNoThrow( [[OIDAuthorizationRequest alloc] initWithConfiguration:configuration clientId:kTestClientID scopes:@[ kTestValidScope2 ] redirectURL:redirectURL responseType:OIDResponseTypeCode - additionalParameters:nil]); + additionalParameters:nil], @""); XCTAssertNoThrow( [[OIDAuthorizationRequest alloc] initWithConfiguration:configuration clientId:kTestClientID scopes:@[ kTestValidScope3 ] redirectURL:redirectURL responseType:OIDResponseTypeCode - additionalParameters:nil]); + additionalParameters:nil], @""); XCTAssertNoThrow( [[OIDAuthorizationRequest alloc] initWithConfiguration:configuration clientId:kTestClientID scopes:@[ kTestValidScope4 ] redirectURL:redirectURL responseType:OIDResponseTypeCode - additionalParameters:nil]); + additionalParameters:nil], @""); XCTAssertNoThrow( [[OIDAuthorizationRequest alloc] initWithConfiguration:configuration clientId:kTestClientID scopes:@[ kTestValidScope5 ] redirectURL:redirectURL responseType:OIDResponseTypeCode - additionalParameters:nil]); + additionalParameters:nil], @""); } /*! @brief Returns a character set with all legal PKCE characters for the codeVerifier. @return Character set representing all legal codeVerifier characters. @@ -395,11 +450,11 @@ - (void)testPKCEVerifierCompliance { // as this test involves random numbers, repeats multiple times for (int i = 0; i < 1000; i++) { NSString *codeVerifier = [OIDAuthorizationRequest generateCodeVerifier]; - XCTAssertNotNil(codeVerifier); + XCTAssertNotNil(codeVerifier, @""); // tests that the code verifier is within the specified size bounds - XCTAssertGreaterThanOrEqual(codeVerifier.length, kCodeVerifierMinLength); - XCTAssertLessThanOrEqual(codeVerifier.length, kCodeVerifierMaxLength); + XCTAssertGreaterThanOrEqual(codeVerifier.length, kCodeVerifierMinLength, @""); + XCTAssertLessThanOrEqual(codeVerifier.length, kCodeVerifierMaxLength, @""); // tests that the code verifier uses legal characters NSCharacterSet *legalChars = [[self class] legalPKCECharacters]; @@ -414,7 +469,7 @@ - (void)testPKCEVerifierCompliance { */ - (void)testPKCEVerifierRecommendations { NSString *codeVerifier = [OIDAuthorizationRequest generateCodeVerifier]; - XCTAssertNotNil(codeVerifier); + XCTAssertNotNil(codeVerifier, @""); XCTAssertEqual(codeVerifier.length, kCodeVerifierRecommendedLength, @"The spec RECOMMENDS a '43-octet URL safe string'"); @@ -427,7 +482,7 @@ - (void)testSupportedResponseTypes { NSString *scope = [OIDScopeUtilities scopesWithArray:@[ kTestScope, kTestScopeA ]]; - XCTAssertThrows( + XCTAssertNoThrow( [[OIDAuthorizationRequest alloc] initWithConfiguration:configuration clientId:kTestClientID clientSecret:kTestClientSecret @@ -435,6 +490,23 @@ - (void)testSupportedResponseTypes { redirectURL:[NSURL URLWithString:kTestRedirectURL] responseType:@"code id_token" state:kTestState + nonce:kTestNonce + codeVerifier:kTestCodeVerifier + codeChallenge:[[self class] codeChallenge] + codeChallengeMethod:[[self class] codeChallengeMethod] + additionalParameters:additionalParameters] + ); + + // https://tools.ietf.org/html/rfc6749#section-3.1.1 says the order of values does not matter + XCTAssertNoThrow( + [[OIDAuthorizationRequest alloc] initWithConfiguration:configuration + clientId:kTestClientID + clientSecret:kTestClientSecret + scope:scope + redirectURL:[NSURL URLWithString:kTestRedirectURL] + responseType:@"id_token code" + state:kTestState + nonce:kTestNonce codeVerifier:kTestCodeVerifier codeChallenge:[[self class] codeChallenge] codeChallengeMethod:[[self class] codeChallengeMethod] @@ -449,6 +521,22 @@ - (void)testSupportedResponseTypes { redirectURL:[NSURL URLWithString:kTestRedirectURL] responseType:@"code token id_token" state:kTestState + nonce:kTestNonce + codeVerifier:kTestCodeVerifier + codeChallenge:[[self class] codeChallenge] + codeChallengeMethod:[[self class] codeChallengeMethod] + additionalParameters:additionalParameters] + ); + + XCTAssertThrows( + [[OIDAuthorizationRequest alloc] initWithConfiguration:configuration + clientId:kTestClientID + clientSecret:kTestClientSecret + scope:scope + redirectURL:[NSURL URLWithString:kTestRedirectURL] + responseType:@"token" + state:kTestState + nonce:kTestNonce codeVerifier:kTestCodeVerifier codeChallenge:[[self class] codeChallenge] codeChallengeMethod:[[self class] codeChallengeMethod] @@ -463,6 +551,7 @@ - (void)testSupportedResponseTypes { redirectURL:[NSURL URLWithString:kTestRedirectURL] responseType:@"code" state:kTestState + nonce:kTestNonce codeVerifier:kTestCodeVerifier codeChallenge:[[self class] codeChallenge] codeChallengeMethod:[[self class] codeChallengeMethod] @@ -471,4 +560,12 @@ - (void)testSupportedResponseTypes { } +- (void)testExternalUserAgentMethods { + OIDAuthorizationRequest *request = [[self class] testInstance]; + XCTAssertEqualObjects([request externalUserAgentRequestURL], [request authorizationRequestURL]); + XCTAssert([[request redirectScheme] isEqualToString:request.redirectURL.scheme]); +} + @end + +#pragma GCC diagnostic pop diff --git a/UnitTests/OIDAuthorizationResponseTests.m b/UnitTests/OIDAuthorizationResponseTests.m index f0a437555..6bfb5fce2 100644 --- a/UnitTests/OIDAuthorizationResponseTests.m +++ b/UnitTests/OIDAuthorizationResponseTests.m @@ -19,9 +19,19 @@ #import "OIDAuthorizationResponseTests.h" #import "OIDAuthorizationRequestTests.h" -#import "Source/OIDAuthorizationRequest.h" -#import "Source/OIDAuthorizationResponse.h" -#import "Source/OIDGrantTypes.h" + +#if SWIFT_PACKAGE +@import AppAuthCore; +#else +#import "Sources/AppAuthCore/OIDAuthorizationRequest.h" +#import "Sources/AppAuthCore/OIDAuthorizationResponse.h" +#import "Sources/AppAuthCore/OIDGrantTypes.h" +#endif + +// Ignore warnings about "Use of GNU statement expression extension" which is raised by our use of +// the XCTAssert___ macros. +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wgnu" /*! @brief Test value for the @c authorizationCode property. */ @@ -113,33 +123,34 @@ + (OIDAuthorizationResponse *)testInstanceCodeFlowClientAuth { */ - (void)testCopying { OIDAuthorizationResponse *response = [[self class] testInstance]; - XCTAssertEqualObjects(response.authorizationCode, kTestAuthorizationCode); - XCTAssertEqualObjects(response.state, kTestState); - XCTAssertEqualObjects(response.accessToken, kTestAccessToken); - XCTAssertEqualObjects(response.idToken, kTestIDToken); - XCTAssertEqualObjects(response.tokenType, kTestTokenType); - XCTAssertEqualObjects(response.scope, kTestScope); + XCTAssertEqualObjects(response.authorizationCode, kTestAuthorizationCode, @""); + XCTAssertEqualObjects(response.state, kTestState, @""); + XCTAssertEqualObjects(response.accessToken, kTestAccessToken, @""); + XCTAssertEqualObjects(response.idToken, kTestIDToken, @""); + XCTAssertEqualObjects(response.tokenType, kTestTokenType, @""); + XCTAssertEqualObjects(response.scope, kTestScope, @""); XCTAssertEqualObjects(response.additionalParameters[kTestAdditionalParameterKey], - kTestAdditionalParameterValue); + kTestAdditionalParameterValue, @""); // Should be ~ kTestExpirationSeconds seconds. Avoiding swizzling NSDate here for certainty // to keep dependencies down, and simply making an assumption that this check will be executed // relatively quickly after the initialization above (less than 5 seconds.) NSTimeInterval expiration = [response.accessTokenExpirationDate timeIntervalSinceNow]; - XCTAssert(expiration > kTestExpirationSeconds - 5 && expiration <= kTestExpirationSeconds); + XCTAssert(expiration > kTestExpirationSeconds - 5 && expiration <= kTestExpirationSeconds, @""); OIDAuthorizationResponse *responseCopy = [response copy]; - XCTAssertEqualObjects(responseCopy.request, response.request); - XCTAssertEqualObjects(responseCopy.authorizationCode, response.authorizationCode); - XCTAssertEqualObjects(responseCopy.state, response.state); - XCTAssertEqualObjects(responseCopy.accessToken, response.accessToken); - XCTAssertEqualObjects(responseCopy.accessTokenExpirationDate, response.accessTokenExpirationDate); - XCTAssertEqualObjects(responseCopy.idToken, response.idToken); - XCTAssertEqualObjects(responseCopy.tokenType, response.tokenType); - XCTAssertEqualObjects(responseCopy.scope, response.scope); + XCTAssertEqualObjects(responseCopy.request, response.request, @""); + XCTAssertEqualObjects(responseCopy.authorizationCode, response.authorizationCode, @""); + XCTAssertEqualObjects(responseCopy.state, response.state, @""); + XCTAssertEqualObjects(responseCopy.accessToken, response.accessToken, @""); + XCTAssertEqualObjects(responseCopy.accessTokenExpirationDate, + response.accessTokenExpirationDate, @""); + XCTAssertEqualObjects(responseCopy.idToken, response.idToken, @""); + XCTAssertEqualObjects(responseCopy.tokenType, response.tokenType, @""); + XCTAssertEqualObjects(responseCopy.scope, response.scope, @""); XCTAssertEqualObjects(responseCopy.additionalParameters, - response.additionalParameters); + response.additionalParameters, @""); } /*! @brief Tests the @c NSSecureCoding by round-tripping an instance through the coding process and @@ -147,25 +158,42 @@ - (void)testCopying { */ - (void)testSecureCoding { OIDAuthorizationResponse *response = [[self class] testInstance]; - NSData *data = [NSKeyedArchiver archivedDataWithRootObject:response]; - OIDAuthorizationResponse *responseCopy = [NSKeyedUnarchiver unarchiveObjectWithData:data]; + OIDAuthorizationResponse *responseCopy; + NSError *error; + NSData *data; + if (@available(iOS 12.0, macOS 10.13, tvOS 11.0, watchOS 4.0, *)) { + data = [NSKeyedArchiver archivedDataWithRootObject:response + requiringSecureCoding:YES + error:&error]; + responseCopy = [NSKeyedUnarchiver unarchivedObjectOfClass:[OIDAuthorizationResponse class] + fromData:data + error:&error]; + } else { +#if !TARGET_OS_IOS + data = [NSKeyedArchiver archivedDataWithRootObject:response]; + responseCopy = [NSKeyedUnarchiver unarchiveObjectWithData:data]; +#endif + } // Not a full test of the request deserialization, but should be sufficient as a smoke test // to make sure the request IS actually getting serialized and deserialized in the // NSSecureCoding implementation. We'll leave it up to the OIDAuthorizationRequest tests to make // sure the NSSecureCoding implementation of that class is correct. - XCTAssertNotNil(responseCopy.request); - XCTAssertEqualObjects(responseCopy.request.clientID, response.request.clientID); - - XCTAssertEqualObjects(responseCopy.authorizationCode, kTestAuthorizationCode); - XCTAssertEqualObjects(responseCopy.state, kTestState); - XCTAssertEqualObjects(responseCopy.accessToken, kTestAccessToken); - XCTAssertEqualObjects(responseCopy.idToken, kTestIDToken); - XCTAssertEqualObjects(responseCopy.tokenType, kTestTokenType); - XCTAssertEqualObjects(responseCopy.scope, kTestScope); - XCTAssertEqualObjects(responseCopy.accessTokenExpirationDate, response.accessTokenExpirationDate); + XCTAssertNotNil(responseCopy.request, @""); + XCTAssertEqualObjects(responseCopy.request.clientID, response.request.clientID, @""); + + XCTAssertEqualObjects(responseCopy.authorizationCode, kTestAuthorizationCode, @""); + XCTAssertEqualObjects(responseCopy.state, kTestState, @""); + XCTAssertEqualObjects(responseCopy.accessToken, kTestAccessToken, @""); + XCTAssertEqualObjects(responseCopy.idToken, kTestIDToken, @""); + XCTAssertEqualObjects(responseCopy.tokenType, kTestTokenType, @""); + XCTAssertEqualObjects(responseCopy.scope, kTestScope, @""); + XCTAssertEqualObjects(responseCopy.accessTokenExpirationDate, response.accessTokenExpirationDate, + @""); XCTAssertEqualObjects(responseCopy.additionalParameters[kTestAdditionalParameterKey], - kTestAdditionalParameterValue); + kTestAdditionalParameterValue, @""); } @end + +#pragma GCC diagnostic pop diff --git a/Source/macOS/OIDAuthorizationUICoordinatorMac.h b/UnitTests/OIDEndSessionRequestTests.h similarity index 59% rename from Source/macOS/OIDAuthorizationUICoordinatorMac.h rename to UnitTests/OIDEndSessionRequestTests.h index 06f0a3422..48f902757 100644 --- a/Source/macOS/OIDAuthorizationUICoordinatorMac.h +++ b/UnitTests/OIDEndSessionRequestTests.h @@ -1,14 +1,14 @@ -/*! @file OIDAuthorizationUICoordinatorMac.h +/*! @file OIDServiceDiscoveryTests.m @brief AppAuth iOS SDK @copyright - Copyright 2016 Google Inc. All Rights Reserved. + Copyright 2017 The AppAuth Authors. All Rights Reserved. @copydetails Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at - + http://www.apache.org/licenses/LICENSE-2.0 - + Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -16,18 +16,15 @@ limitations under the License. */ -#import "OIDAuthorizationUICoordinator.h" +#import + +@class OIDEndSessionRequest; NS_ASSUME_NONNULL_BEGIN -/*! @brief An Mac specific authorization UI Coordinator that uses the default browser to - present an authorization request. - */ -@interface OIDAuthorizationUICoordinatorMac : NSObject { - // private variables - BOOL _authorizationFlowInProgress; - __weak id _session; -} +@interface OIDEndSessionRequestTests : XCTestCase + ++ (OIDEndSessionRequest *)testInstance; @end diff --git a/UnitTests/OIDEndSessionRequestTests.m b/UnitTests/OIDEndSessionRequestTests.m new file mode 100644 index 000000000..d9ded81be --- /dev/null +++ b/UnitTests/OIDEndSessionRequestTests.m @@ -0,0 +1,143 @@ +/*! @file OIDServiceDiscoveryTests.m + @brief AppAuth iOS SDK + @copyright + Copyright 2017 The AppAuth Authors. All Rights Reserved. + @copydetails + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#import "OIDEndSessionRequestTests.h" + +#import "OIDServiceDiscoveryTests.h" + +#if SWIFT_PACKAGE +@import AppAuthCore; +#else +#import "Sources/AppAuthCore/OIDEndSessionRequest.h" +#import "Sources/AppAuthCore/OIDServiceConfiguration.h" +#import "Sources/AppAuthCore/OIDServiceDiscovery.h" +#endif + +/*! @brief Test value for the @c redirectURL property. + */ +static NSString *const kTestRedirectURL = @"http://www.google.com/"; + +/*! @brief Test key for the @c additionalParameters property. + */ +static NSString *const kTestAdditionalParameterKey = @"A"; + +/*! @brief Test value for the @c additionalParameters property. + */ +static NSString *const kTestAdditionalParameterValue = @"1"; + +/*! @brief Test value for the @c state property. + */ +static NSString *const kTestState = @"State"; + +/*! @brief Test value for the @c idTokenHint property. + */ +static NSString *const kTestIdTokenHint = @"id-token-hint"; + +@implementation OIDEndSessionRequestTests + ++ (OIDEndSessionRequest *)testInstance { + NSDictionary *additionalParameters = + @{ kTestAdditionalParameterKey : kTestAdditionalParameterValue }; + + OIDServiceDiscovery *discoveryDocument = [[OIDServiceDiscovery alloc] initWithDictionary:[OIDServiceDiscoveryTests completeServiceDiscoveryDictionary] error:nil]; + OIDServiceConfiguration *configuration = [[OIDServiceConfiguration alloc] initWithDiscoveryDocument:discoveryDocument]; + + return [[OIDEndSessionRequest alloc] initWithConfiguration:configuration + idTokenHint:kTestIdTokenHint + postLogoutRedirectURL:[NSURL URLWithString:kTestRedirectURL] + state:kTestState + additionalParameters:additionalParameters]; +} + +/*! @brief Tests the @c NSCopying implementation by round-tripping an instance through the copying + process and checking to make sure the source and destination instances are equivalent. + */ +- (void)testCopying { + OIDEndSessionRequest *request = [[self class] testInstance]; + + XCTAssertEqualObjects(request.idTokenHint, kTestIdTokenHint); + XCTAssertEqualObjects(request.postLogoutRedirectURL, [NSURL URLWithString:kTestRedirectURL]); + XCTAssertEqualObjects(request.state, kTestState); + XCTAssertEqualObjects(request.additionalParameters[kTestAdditionalParameterKey], + kTestAdditionalParameterValue); + + OIDEndSessionRequest *requestCopy = [request copy]; + + XCTAssertNotNil(requestCopy.configuration); + XCTAssertEqualObjects(requestCopy.configuration, request.configuration); + XCTAssertEqualObjects(requestCopy.postLogoutRedirectURL, request.postLogoutRedirectURL); + XCTAssertEqualObjects(requestCopy.state, request.state); + XCTAssertEqualObjects(requestCopy.idTokenHint, request.idTokenHint); +} + +/*! @brief Tests the @c NSSecureCoding by round-tripping an instance through the coding process and + checking to make sure the source and destination instances are equivalent. + */ +- (void)testSecureCoding { + OIDEndSessionRequest *request = [[self class] testInstance]; + + XCTAssertEqualObjects(request.idTokenHint, kTestIdTokenHint); + XCTAssertEqualObjects(request.postLogoutRedirectURL, [NSURL URLWithString:kTestRedirectURL]); + XCTAssertEqualObjects(request.state, kTestState); + XCTAssertEqualObjects(request.additionalParameters[kTestAdditionalParameterKey], + kTestAdditionalParameterValue); + + OIDEndSessionRequest *requestCopy; + NSError *error; + NSData *data; + if (@available(iOS 12.0, macOS 10.13, tvOS 11.0, watchOS 4.0, *)) { + data = [NSKeyedArchiver archivedDataWithRootObject:request + requiringSecureCoding:YES + error:&error]; + requestCopy = [NSKeyedUnarchiver unarchivedObjectOfClass:[OIDEndSessionRequest class] + fromData:data + error:&error]; + } else { +#if !TARGET_OS_IOS + data = [NSKeyedArchiver archivedDataWithRootObject:request]; + requestCopy = [NSKeyedUnarchiver unarchiveObjectWithData:data]; +#endif + } + + XCTAssertNotNil(requestCopy.configuration); + XCTAssertEqualObjects(requestCopy.configuration.authorizationEndpoint, + request.configuration.authorizationEndpoint); + XCTAssertEqualObjects(requestCopy.postLogoutRedirectURL, request.postLogoutRedirectURL); + XCTAssertEqualObjects(requestCopy.state, request.state); + XCTAssertEqualObjects(requestCopy.idTokenHint, request.idTokenHint); +} + +- (void)testLogoutRequestURL { + OIDEndSessionRequest *request = [[self class] testInstance]; + NSURL *endSessionRequestURL = request.endSessionRequestURL; + + NSURLComponents *components = [NSURLComponents componentsWithString:endSessionRequestURL.absoluteString]; + + XCTAssertTrue([endSessionRequestURL.absoluteString hasPrefix:@"https://www.example.com/logout"]); + + NSMutableDictionary *query = [[NSMutableDictionary alloc] init]; + for (NSURLQueryItem *queryItem in components.queryItems) { + query[queryItem.name] = queryItem.value; + } + + XCTAssertEqualObjects(query[@"state"], kTestState); + XCTAssertEqualObjects(query[@"id_token_hint"], kTestIdTokenHint); + XCTAssertEqualObjects(query[@"post_logout_redirect_uri"], kTestRedirectURL); +} + +@end diff --git a/UnitTests/OIDGrantTypesTests.m b/UnitTests/OIDGrantTypesTests.m index a9e12e413..b6efc8ea1 100644 --- a/UnitTests/OIDGrantTypesTests.m +++ b/UnitTests/OIDGrantTypesTests.m @@ -18,7 +18,16 @@ #import -#import "Source/OIDGrantTypes.h" +#if SWIFT_PACKAGE +@import AppAuthCore; +#else +#import "Sources/AppAuthCore/OIDGrantTypes.h" +#endif + +// Ignore warnings about "Use of GNU statement expression extension" which is raised by our use of +// the XCTAssert___ macros. +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wgnu" /*! @brief Unit tests for constants in @c OIDGrantTypes.m. @remarks Arguably not worth tests for this file, but adding them for consistency, and so that @@ -45,3 +54,5 @@ - (void)testClientCredentials { } @end + +#pragma GCC diagnostic pop diff --git a/Source/macOS/OIDAuthorizationService+Mac.m b/UnitTests/OIDRPProfileCode.h similarity index 52% rename from Source/macOS/OIDAuthorizationService+Mac.m rename to UnitTests/OIDRPProfileCode.h index d1b138a12..c613b39df 100644 --- a/Source/macOS/OIDAuthorizationService+Mac.m +++ b/UnitTests/OIDRPProfileCode.h @@ -1,7 +1,7 @@ -/*! @file OIDAuthorizationService+Mac.m +/*! @file OIDRPProfileCode.h @brief AppAuth iOS SDK @copyright - Copyright 2016 Google Inc. All Rights Reserved. + Copyright 2017 Google Inc. All Rights Reserved. @copydetails Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -16,20 +16,13 @@ limitations under the License. */ -#import "OIDAuthorizationService+Mac.h" +#import -#import "OIDAuthorizationUICoordinatorMac.h" +#import "OIDExternalUserAgent.h" NS_ASSUME_NONNULL_BEGIN -@implementation OIDAuthorizationService (Mac) -+ (id)presentAuthorizationRequest:(OIDAuthorizationRequest *)request - callback:(OIDAuthorizationCallback)callback { - OIDAuthorizationUICoordinatorMac *coordinator = [[OIDAuthorizationUICoordinatorMac alloc] init]; - return [self presentAuthorizationRequest:request UICoordinator:coordinator callback:callback]; -} -@end NS_ASSUME_NONNULL_END diff --git a/UnitTests/OIDRPProfileCode.m b/UnitTests/OIDRPProfileCode.m new file mode 100644 index 000000000..1c37b1f30 --- /dev/null +++ b/UnitTests/OIDRPProfileCode.m @@ -0,0 +1,571 @@ +/*! @file OIDRPProfileCode.m + @brief AppAuth iOS SDK + @copyright + Copyright 2017 Google Inc. All Rights Reserved. + @copydetails + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#import "OIDRPProfileCode.h" + +#import "OIDAuthorizationRequest.h" +#import "OIDAuthorizationResponse.h" +#import "OIDAuthorizationService.h" +#import "OIDAuthState.h" +#import "OIDExternalUserAgentSession.h" +#import "OIDIDToken.h" +#import "OIDRegistrationRequest.h" +#import "OIDRegistrationResponse.h" +#import "OIDScopes.h" +#import "OIDServiceConfiguration.h" +#import "OIDServiceDiscovery.h" +#import "OIDTokenRequest.h" +#import "OIDTokenResponse.h" + +static NSString *const kRedirectURI = @"com.example.app:/oauth2redirect/example-provider"; + +// Open ID RP Certification test server http://openid.net/certification/rp_testing/ +static NSString *const kTestURIBase = + @"https://rp.certification.openid.net:8080/appauth-ios-macos/"; + +/*! @brief A UI Coordinator for testing, has no user agent and doesn't support user interaction. + Simply performs the authorization request as a GET request, and looks for a redirect in + the response. + */ +@interface OIDAuthorizationUICoordinatorNonInteractive : NSObject { + NSURLSession *_urlSession; + __weak id _session; +} +@end + +@implementation OIDAuthorizationUICoordinatorNonInteractive + +- (BOOL)presentExternalUserAgentRequest:(id )request + session:(id)session { + _session = session; + NSURL *requestURL = [request externalUserAgentRequestURL]; + NSMutableURLRequest *URLRequest = [[NSURLRequest requestWithURL:requestURL] mutableCopy]; + NSURLSessionConfiguration* config = [NSURLSessionConfiguration defaultSessionConfiguration]; + _urlSession = [NSURLSession sessionWithConfiguration:config delegate:self delegateQueue:nil]; + [[_urlSession dataTaskWithRequest:URLRequest + completionHandler:^(NSData *_Nullable data, + NSURLResponse *_Nullable response, + NSError *_Nullable error) { + NSDictionary* headers = [(NSHTTPURLResponse *)response allHeaderFields]; + NSString *location = [headers objectForKey:@"Location"]; + NSURL *url = [NSURL URLWithString:location]; + [session resumeExternalUserAgentFlowWithURL:url]; + }] resume]; + + return YES; +} + +- (void)dismissExternalUserAgentAnimated:(BOOL)animated completion:(void (^)(void))completion { + if (completion) completion(); +} + +- (void)URLSession:(NSURLSession *)session + task:(NSURLSessionTask *)task + willPerformHTTPRedirection:(NSHTTPURLResponse *)response + newRequest:(NSURLRequest *)request + completionHandler:(void (^)(NSURLRequest *))completionHandler { + // Disables HTTP redirection in the NSURLSession + completionHandler(NULL); +} +@end + +@interface OIDAuthorizationSession : NSObject + +- (instancetype)init NS_UNAVAILABLE; + +- (instancetype)initWithRequest:(OIDAuthorizationRequest *)request + NS_DESIGNATED_INITIALIZER; + +@end + +@interface OIDRPProfileCode : XCTestCase { + // private variables + OIDAuthorizationUICoordinatorNonInteractive *_coordinator; + FILE * _logFile; +} +typedef void (^PostRegistrationCallback)(OIDServiceConfiguration *configuration, + OIDRegistrationResponse *registrationResponse, + NSError *error + ); + +typedef void (^CodeExchangeCompletion)(OIDAuthorizationResponse *_Nullable authorizationResponse, + OIDTokenResponse *_Nullable tokenResponse, + NSError *tokenError + ); + +typedef void (^UserInfoCompletion)(OIDAuthState *_Nullable authState, + NSDictionary *_Nullable userInfoDictionary, + NSError *userInfo + ); + +@end + +@implementation OIDRPProfileCode + +- (void)setUp { + XCTSkip("Need to migrate to the new OpenID conformance testing system."); + [super setUp]; +} + +- (void)tearDown { + XCTSkip("Need to migrate to the new OpenID conformance testing system."); + [super tearDown]; + + [self endCertificationTest]; +} + +/*! @brief Performs client registration. + @param issuer The issuer to register the client with. + @param callback Completion block. + */ +- (void)doRegistrationWithIssuer:(NSURL *)issuer callback:(PostRegistrationCallback)callback { + NSURL *redirectURI = [NSURL URLWithString:kRedirectURI]; + + // discovers endpoints + [OIDAuthorizationService discoverServiceConfigurationForIssuer:issuer + completion:^(OIDServiceConfiguration *_Nullable configuration, NSError *_Nullable error) { + + if (!configuration) { + callback(nil, nil, error); + return; + } + + OIDRegistrationRequest *request = + [[OIDRegistrationRequest alloc] initWithConfiguration:configuration + redirectURIs:@[ redirectURI ] + responseTypes:nil + grantTypes:nil + subjectType:nil + tokenEndpointAuthMethod:@"client_secret_basic" + additionalParameters:@{@"id_token_signed_response_alg": + @"none", + @"contacts": + @"appauth@wdenniss.com"}]; + + [self certificationLog:@"Registration request: %@", request]; + + // performs registration request + [OIDAuthorizationService performRegistrationRequest:request + completion:^(OIDRegistrationResponse *_Nullable regResp, NSError *_Nullable error) { + if (regResp) { + callback(configuration, regResp, nil); + } else { + callback(nil, nil, error); + } + }]; + }]; +} + +/*! @brief Performs the code flow on the test server. + @param test The test ID used to configure the test server. + @param completion Completion block. + */ +- (void)codeFlowWithExchangeForTest:(NSString *)test completion:(CodeExchangeCompletion)completion { + [self codeFlowWithExchangeForTest:test scope:@[ OIDScopeOpenID ] completion:completion]; +} + +/*! @brief Performs the code flow on the test server. + @param test The test ID used to configure the test server. + @param scope Scope to use in the authorization request. + @param completion Completion block. + */ +- (void)codeFlowWithExchangeForTest:(NSString *)test + scope:(NSArray *)scope + completion:(CodeExchangeCompletion)completion { + + NSString *issuerString = [kTestURIBase stringByAppendingString:test]; + + XCTestExpectation *expectation = + [self expectationWithDescription:@"Discovery and registration should complete."]; + XCTestExpectation *auth_complete = + [self expectationWithDescription:@"Authorization should complete."]; + XCTestExpectation *token_exchange = + [self expectationWithDescription:@"Token Exchange should complete."]; + + NSURL *issuer = [NSURL URLWithString:issuerString]; + + [self doRegistrationWithIssuer:issuer callback:^(OIDServiceConfiguration *configuration, + OIDRegistrationResponse *registrationResponse, + NSError *error) { + [expectation fulfill]; + XCTAssertNotNil(configuration); + XCTAssertNotNil(registrationResponse); + XCTAssertNil(error); + + if (error) { + return; + } + + NSURL *redirectURI = [NSURL URLWithString:kRedirectURI]; + // builds authentication request + OIDAuthorizationRequest *request = + [[OIDAuthorizationRequest alloc] initWithConfiguration:configuration + clientId:registrationResponse.clientID + clientSecret:registrationResponse.clientSecret + scopes:scope + redirectURL:redirectURI + responseType:OIDResponseTypeCode + additionalParameters:nil]; + + self->_coordinator = [[OIDAuthorizationUICoordinatorNonInteractive alloc] init]; + + [self certificationLog:@"Initiating authorization request: %@", + [request authorizationRequestURL]]; + + [OIDAuthorizationService presentAuthorizationRequest:request + externalUserAgent:self->_coordinator + callback:^(OIDAuthorizationResponse *_Nullable authorizationResponse, + NSError *error) { + [auth_complete fulfill]; + XCTAssertNotNil(authorizationResponse); + XCTAssertNil(error); + + OIDTokenRequest *tokenExchangeRequest = [authorizationResponse tokenExchangeRequest]; + [OIDAuthorizationService performTokenRequest:tokenExchangeRequest + originalAuthorizationResponse:authorizationResponse + callback:^(OIDTokenResponse *_Nullable tokenResponse, + NSError *_Nullable tokenError) { + [token_exchange fulfill]; + completion(authorizationResponse, tokenResponse, tokenError); + }]; + }]; + }]; + [self waitForExpectationsWithTimeout:30 handler:nil]; +} + +/*! @brief Performs the code flow on the test server and expects a successful result. + @param test The test ID. + */ +- (void)codeFlowWithExchangeExpectSuccessForTest:(NSString *)test { + [self codeFlowWithExchangeForTest:test + completion:^(OIDAuthorizationResponse * _Nullable authorizationResponse, + OIDTokenResponse * _Nullable tokenResponse, + NSError *tokenError) { + XCTAssertNotNil(tokenResponse); + XCTAssertNil(tokenError); + // testRP_id_token_sig_none + XCTAssertNotNil(tokenResponse.idToken); + + [self certificationLog:@"PASS: Got token response: %@", tokenResponse]; + }]; +} + +- (void)testRP_response_type_code { + XCTSkip("Not working at the moment."); + NSString *testName = @"rp-response_type-code"; + [self startCertificationTest:testName]; + [self codeFlowWithExchangeExpectSuccessForTest:testName]; +} + +- (void)testRP_id_token_sig_none { + NSString *testName = @"rp-id_token-sig-none"; + [self startCertificationTest:testName]; + [self codeFlowWithExchangeExpectSuccessForTest:testName]; +} + +- (void)testRP_token_endpoint_client_secret_basic { + NSString *testName = @"rp-token_endpoint-client_secret_basic"; + [self startCertificationTest:testName]; + + [self codeFlowWithExchangeExpectSuccessForTest:testName]; +} + +/*! @brief Performs the code flow on the test server and expects a failure result. + @param test The test ID. + */ +- (void)codeFlowWithExchangeExpectFailForTest:(NSString *)test { + [self codeFlowWithExchangeForTest:test + completion:^(OIDAuthorizationResponse * _Nullable authorizationResponse, + OIDTokenResponse * _Nullable tokenResponse, + NSError *tokenError) { + XCTAssertNil(tokenResponse); + XCTAssertNotNil(tokenError); + + if (tokenError) { + [self certificationLog:@"PASS: Token exchange failed with %@", tokenError]; + } + }]; +} + +- (void)testRP_id_token_aud { + NSString *testName = @"rp-id_token-aud"; + [self startCertificationTest:testName]; + [self codeFlowWithExchangeExpectFailForTest:testName]; +} + +- (void)testRP_id_token_iat { + NSString *testName = @"rp-id_token-iat"; + [self startCertificationTest:testName]; + [self codeFlowWithExchangeExpectFailForTest:testName]; +} + +- (void)testRP_id_token_sub { + NSString *testName = @"rp-id_token-sub"; + [self startCertificationTest:testName]; + [self codeFlowWithExchangeExpectFailForTest:testName]; +} + +- (void)testRP_id_token_issuer_mismatch { + NSString *testName = @"rp-id_token-issuer-mismatch"; + [self startCertificationTest:testName]; + [self codeFlowWithExchangeExpectFailForTest:testName]; +} + +- (void)testRP_nonce_invalid { + NSString *testName = @"rp-nonce-invalid"; + [self startCertificationTest:testName]; + [self codeFlowWithExchangeExpectFailForTest:testName]; +} + +/*! @brief Makes a UserInfo request then calls completion block. + @param test The test ID used to configure the test server. + @param completion Completion block. + */ +- (void)codeFlowThenUserInfoForTest:(NSString *)test completion:(UserInfoCompletion)completion { + + // Adds another expectation that codeFlowWithExchangeForTest will wait for. + XCTestExpectation *userinfoExpectation = + [self expectationWithDescription:@"Userinfo response."]; + + NSArray *scope = + @[ OIDScopeOpenID, OIDScopeProfile, OIDScopeEmail, OIDScopeAddress, OIDScopePhone ]; + [self codeFlowWithExchangeForTest:test + scope:scope + completion:^(OIDAuthorizationResponse * _Nullable authorizationResponse, + OIDTokenResponse * _Nullable tokenResponse, + NSError *tokenError) { + XCTAssertNotNil(tokenResponse); + XCTAssertNil(tokenError); + + [self certificationLog:@"Got access token: %@", tokenResponse.accessToken]; + + OIDAuthState *authState = + [[OIDAuthState alloc] initWithAuthorizationResponse:authorizationResponse + tokenResponse:tokenResponse]; + + NSURL *userinfoEndpoint = + authState.lastAuthorizationResponse.request.configuration.discoveryDocument.userinfoEndpoint; + XCTAssertNotNil(userinfoEndpoint); + + [authState performActionWithFreshTokens:^(NSString *_Nonnull accessToken, + NSString *_Nonnull idToken, + NSError *_Nullable error) { + XCTAssertNil(error); + + // creates request to the userinfo endpoint, with access token in the Authorization header + NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:userinfoEndpoint]; + NSString *authorizationHeaderValue = [NSString stringWithFormat:@"Bearer %@", accessToken]; + [request addValue:authorizationHeaderValue forHTTPHeaderField:@"Authorization"]; + + NSURLSessionConfiguration *configuration = + [NSURLSessionConfiguration defaultSessionConfiguration]; + NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration + delegate:nil + delegateQueue:nil]; + + [self certificationLog:@"Performing UserInfo request to: %@", userinfoEndpoint]; + [self certificationLog:@"- Headers: %@", request.allHTTPHeaderFields]; + + // performs HTTP request + NSURLSessionDataTask *postDataTask = + [session dataTaskWithRequest:request + completionHandler:^(NSData *_Nullable data, + NSURLResponse *_Nullable response, + NSError *_Nullable error) { + dispatch_async(dispatch_get_main_queue(), ^() { + [userinfoExpectation fulfill]; + XCTAssertNil(error); + XCTAssert([response isKindOfClass:[NSHTTPURLResponse class]]); + NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response; + XCTAssert( (int)httpResponse.statusCode == 200); + id jsonDictionaryOrArray = + [NSJSONSerialization JSONObjectWithData:data options:0 error:NULL]; + completion(authState, jsonDictionaryOrArray, error); + }); + }]; + + [postDataTask resume]; + }]; + }]; +} + +- (void)testRP_userinfo_bearer_header { + NSString *testName = @"rp-userinfo-bearer-header"; + [self startCertificationTest:testName]; + [self codeFlowThenUserInfoForTest:testName + completion:^(OIDAuthState * _Nullable authState, + NSDictionary * _Nullable userInfoDictionary, + NSError *userInfoError) { + XCTAssertNotNil(userInfoDictionary); + [self certificationLog:@"PASS: User info dictionary: %@", userInfoDictionary]; + }]; +} + +- (void)testRP_userinfo_bad_sub_claim { + NSString *testName = @"rp-userinfo-bad-sub-claim"; + [self startCertificationTest:testName]; + + [self codeFlowThenUserInfoForTest:testName + completion:^(OIDAuthState * _Nullable authState, + NSDictionary * _Nullable userInfoDictionary, + NSError *userInfo) { + + NSString *sub = userInfoDictionary[@"sub"]; + XCTAssertNotNil(sub); + OIDIDToken *idToken = + [[OIDIDToken alloc] initWithIDTokenString:authState.lastTokenResponse.idToken]; + XCTAssertNotNil(idToken); + XCTAssertNotEqual(sub, idToken.subject); + + if (![sub isEqualToString:idToken.subject]) { + [self certificationLog:@"PASS: UserInfo subject '%@' does not match id token subject '%@'", + sub, + idToken.subject]; + } + }]; +} + +- (void)testRP_scope_userinfo_claims { + NSString *testName = @"rp-scope-userinfo-claims"; + [self startCertificationTest:testName]; + [self codeFlowThenUserInfoForTest:testName + completion:^(OIDAuthState * _Nullable authState, + NSDictionary * _Nullable userInfoDictionary, + NSError *userInfo) { + + [self certificationLog:@"User info dictionary: %@", userInfoDictionary]; + + XCTAssertNotNil(userInfoDictionary[@"name"]); + XCTAssertNotNil(userInfoDictionary[@"email"]); + XCTAssertNotNil(userInfoDictionary[@"email_verified"]); + XCTAssertNotNil(userInfoDictionary[@"address"]); + XCTAssertNotNil(userInfoDictionary[@"phone_number"]); + if (userInfoDictionary[@"name"] + && userInfoDictionary[@"email"] + && userInfoDictionary[@"email_verified"] + && userInfoDictionary[@"address"] + && userInfoDictionary[@"phone_number"]) { + [self certificationLog:@"PASS: name, email, email_verified, address, phone_number " + "claims present"]; + } + }]; +} + +- (void)testRP_id_token_kid_absent_single_jwks { + NSString *testName = @"rp-id_token-kid-absent-single-jwks"; + [self skippedTest:testName]; +} +- (void)testRP_id_token_kid_absent_multiple_jwks { + NSString *testName = @"rp-id_token-kid-absent-multiple-jwks"; + [self skippedTest:testName]; +} +- (void)testRP_rp_id_token_bad_sig_rs256 { + NSString *testName = @"rp-id_token-bad-sig-rs256"; + [self skippedTest:testName]; +} + +- (void)testRP_id_token_sig_rs256 { + NSString *testName = @"rp-id_token-sig-rs256"; + [self skippedTest:testName]; +} + +- (void)skippedTest:(NSString *)testName { + [self startCertificationTest:testName]; + + NSString *issuerString = [kTestURIBase stringByAppendingString:testName]; + + XCTestExpectation *expectation = + [self expectationWithDescription:@"Discovery and registration should complete."]; + + NSURL *issuer = [NSURL URLWithString:issuerString]; + + [self doRegistrationWithIssuer:issuer callback:^(OIDServiceConfiguration *configuration, + OIDRegistrationResponse *registrationResponse, + NSError *error) { + [expectation fulfill]; + + XCTAssertNil(registrationResponse); + XCTAssertNotNil(error); + + if (error) { + [self certificationLog:@"Registration error: %@", error]; + [self certificationLog:@"SKIP. With id_token_signed_response_alg set to `none` in registration, error recieved and test skipped."]; + } + + }]; + [self waitForExpectationsWithTimeout:30 handler:nil]; +} + + +/*! @brief Creates a log file to record the certification logs. + @param testName The test ID used to configure the test server. + */ +- (void)startCertificationTest:(NSString *)testName { + if (_logFile) { + [self endCertificationTest]; + } + + NSString* filename = [NSString stringWithFormat:@"%@.txt", testName]; + + NSString *documentsDirectory = + NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES)[0]; + NSString *codeDir = [documentsDirectory stringByAppendingPathComponent:@"code"]; + [[NSFileManager defaultManager] createDirectoryAtPath:codeDir + withIntermediateDirectories:NO + attributes:nil + error:nil]; + NSString *pathForLog = [codeDir stringByAppendingPathComponent:filename]; + + NSLog(@"Writing logs for test %@ to %@", testName, pathForLog); + _logFile = fopen([pathForLog cStringUsingEncoding:NSASCIIStringEncoding], "w"); + NSAssert(_logFile, @"Unable to create log file"); + + NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init]; + dateFormatter.dateFormat = @"yyyy-MM-dd HH:mm:ss"; + NSString *dateString = [dateFormatter stringFromDate:[NSDate date]]; + [self certificationLog:@"# Starting test `%@` at %@ for AppAuth for iOS and macOS", + testName, + dateString]; +} + +/*! @brief Logs string to the certification log. + */ +- (void)certificationLog:(NSString *)format, ... NS_FORMAT_FUNCTION(1,2) { + NSAssert(_logFile, @"No active log"); + + // Gets log message as a string. + va_list argp; + va_start(argp, format); + NSString *log = [[NSString alloc] initWithFormat:format arguments:argp]; + va_end(argp); + + // Logs to file. + fprintf(_logFile, "%s\n", [log UTF8String]); +} + +/*! @brief Closes the certification log file. + */ +- (void)endCertificationTest { + // Adds a newline. + [self certificationLog:@""]; + fclose(_logFile); + _logFile = NULL; +} + +@end + diff --git a/UnitTests/OIDRegistrationRequestTests.m b/UnitTests/OIDRegistrationRequestTests.m index 3248dbabd..f2a7ba3e1 100644 --- a/UnitTests/OIDRegistrationRequestTests.m +++ b/UnitTests/OIDRegistrationRequestTests.m @@ -19,9 +19,14 @@ #import "OIDRegistrationRequestTests.h" #import "OIDServiceConfigurationTests.h" -#import "Source/OIDClientMetadataParameters.h" -#import "Source/OIDRegistrationRequest.h" -#import "Source/OIDServiceConfiguration.h" + +#if SWIFT_PACKAGE +@import AppAuthCore; +#else +#import "Sources/AppAuthCore/OIDClientMetadataParameters.h" +#import "Sources/AppAuthCore/OIDRegistrationRequest.h" +#import "Sources/AppAuthCore/OIDServiceConfiguration.h" +#endif /*! @brief Test key for the @c additionalParameters property. */ @@ -31,6 +36,10 @@ */ static NSString *const kTestAdditionalParameterValue = @"1"; +/*! @brief Test value for the @c initialAccessToken property. + */ +static NSString *const kInitialAccessTokenTestValue = @"test"; + /*! @brief Test value for the @c redirectURL property. */ static NSString *kRedirectURLTestValue = @"https://client.example.com/redirect"; @@ -66,6 +75,7 @@ + (OIDRegistrationRequest *)testInstance { grantTypes:@[ kGrantTypeTestValue ] subjectType:kSubjectTypeTestValue tokenEndpointAuthMethod:kTokenEndpointAuthMethodTestValue + initialAccessToken:kInitialAccessTokenTestValue additionalParameters:additionalParameters]; return request; @@ -84,6 +94,7 @@ - (void)testCopying { XCTAssertNotNil(request.configuration); XCTAssertEqualObjects(request.applicationType, OIDApplicationTypeNative); + XCTAssertEqualObjects(request.initialAccessToken, kInitialAccessTokenTestValue); XCTAssertEqualObjects(request.redirectURIs, @[ [NSURL URLWithString:kRedirectURLTestValue] ]); XCTAssertEqualObjects(request.responseTypes, @[ kResponseTypeTestValue ]); XCTAssertEqualObjects(request.grantTypes, @[ kGrantTypeTestValue ]); @@ -101,6 +112,7 @@ - (void)testCopying { XCTAssertEqualObjects(requestCopy.configuration, request.configuration); XCTAssertEqualObjects(requestCopy.applicationType, request.applicationType); + XCTAssertEqualObjects(requestCopy.initialAccessToken, kInitialAccessTokenTestValue); XCTAssertEqualObjects(requestCopy.redirectURIs, request.redirectURIs); XCTAssertEqualObjects(requestCopy.responseTypes, request.responseTypes); XCTAssertEqualObjects(requestCopy.grantTypes, request.grantTypes); @@ -120,6 +132,7 @@ - (void)testSecureCoding { XCTAssertNotNil(request.configuration); XCTAssertEqualObjects(request.applicationType, OIDApplicationTypeNative); + XCTAssertEqualObjects(request.initialAccessToken, kInitialAccessTokenTestValue); XCTAssertEqualObjects(request.redirectURIs, @[ [NSURL URLWithString:kRedirectURLTestValue] ]); XCTAssertEqualObjects(request.responseTypes, @[ kResponseTypeTestValue ]); XCTAssertEqualObjects(request.grantTypes, @[ kGrantTypeTestValue ]); @@ -129,8 +142,22 @@ - (void)testSecureCoding { XCTAssertEqualObjects(request.additionalParameters[kTestAdditionalParameterKey], kTestAdditionalParameterValue); - NSData *data = [NSKeyedArchiver archivedDataWithRootObject:request]; - OIDRegistrationRequest *requestCopy = [NSKeyedUnarchiver unarchiveObjectWithData:data]; + OIDRegistrationRequest *requestCopy; + NSError *error; + NSData *data; + if (@available(iOS 12.0, macOS 10.13, tvOS 11.0, watchOS 4.0, *)) { + data = [NSKeyedArchiver archivedDataWithRootObject:request + requiringSecureCoding:YES + error:&error]; + requestCopy = [NSKeyedUnarchiver unarchivedObjectOfClass:[OIDRegistrationRequest class] + fromData:data + error:&error]; + } else { +#if !TARGET_OS_IOS + data = [NSKeyedArchiver archivedDataWithRootObject:request]; + requestCopy = [NSKeyedUnarchiver unarchiveObjectWithData:data]; +#endif + } // Not a full test of the configuration deserialization, but should be sufficient as a smoke test // to make sure the configuration IS actually getting serialized and deserialized in the @@ -139,6 +166,7 @@ - (void)testSecureCoding { XCTAssertNotNil(requestCopy.configuration); XCTAssertEqualObjects(requestCopy.applicationType, request.applicationType); + XCTAssertEqualObjects(requestCopy.initialAccessToken, kInitialAccessTokenTestValue); XCTAssertEqualObjects(requestCopy.redirectURIs, request.redirectURIs); XCTAssertEqualObjects(requestCopy.responseTypes, request.responseTypes); XCTAssertEqualObjects(requestCopy.grantTypes, request.grantTypes); @@ -162,6 +190,8 @@ - (void)testURLRequest { XCTAssertEqualObjects(httpRequest.HTTPMethod, @"POST"); XCTAssertEqualObjects([httpRequest valueForHTTPHeaderField:@"Content-Type"], @"application/json"); + XCTAssertEqualObjects([httpRequest valueForHTTPHeaderField:@"Authorization"], + @"Bearer test"); XCTAssertEqualObjects(httpRequest.URL, request.configuration.registrationEndpoint); XCTAssertEqualObjects(parsedJSON[OIDApplicationTypeParam], request.applicationType); XCTAssertEqualObjects(parsedJSON[OIDRedirectURIsParam][0], diff --git a/UnitTests/OIDRegistrationResponseTests.m b/UnitTests/OIDRegistrationResponseTests.m index 1b7befd16..f98787e55 100644 --- a/UnitTests/OIDRegistrationResponseTests.m +++ b/UnitTests/OIDRegistrationResponseTests.m @@ -20,8 +20,13 @@ #import "OIDClientMetadataParameters.h" #import "OIDRegistrationRequestTests.h" -#import "Source/OIDRegistrationRequest.h" -#import "Source/OIDRegistrationResponse.h" + +#if SWIFT_PACKAGE +@import AppAuthCore; +#else +#import "Sources/AppAuthCore/OIDRegistrationRequest.h" +#import "Sources/AppAuthCore/OIDRegistrationResponse.h" +#endif /*! @brief The test value for the @c clientID property. */ @@ -81,32 +86,32 @@ + (OIDRegistrationResponse *)testInstance { */ - (void)testCopying { OIDRegistrationResponse *response = [[self class] testInstance]; - XCTAssertNotNil(response.request); - XCTAssertEqualObjects(response.clientID, kClientIDTestValue); + XCTAssertNotNil(response.request, @""); + XCTAssertEqualObjects(response.clientID, kClientIDTestValue, @""); XCTAssertEqualObjects(response.clientIDIssuedAt, - [NSDate dateWithTimeIntervalSince1970:kClientIDIssuedAtTestValue]); - XCTAssertEqualObjects(response.clientSecret, kClientSecretTestValue); + [NSDate dateWithTimeIntervalSince1970:kClientIDIssuedAtTestValue], @""); + XCTAssertEqualObjects(response.clientSecret, kClientSecretTestValue, @""); XCTAssertEqualObjects(response.clientSecretExpiresAt, - [NSDate dateWithTimeIntervalSince1970:kClientSecretExpiresAtTestValue]); - XCTAssertEqualObjects(response.registrationAccessToken, kClientRegistrationAccessTokenTestValue); + [NSDate dateWithTimeIntervalSince1970:kClientSecretExpiresAtTestValue], @""); + XCTAssertEqualObjects(response.registrationAccessToken, kClientRegistrationAccessTokenTestValue, @""); XCTAssertEqualObjects(response.registrationClientURI, - [NSURL URLWithString:kRegistrationClientURITestValue]); + [NSURL URLWithString:kRegistrationClientURITestValue], @""); XCTAssertEqualObjects(response.tokenEndpointAuthenticationMethod, - kTokenEndpointAuthMethodTestValue); + kTokenEndpointAuthMethodTestValue, @""); XCTAssertEqualObjects(response.additionalParameters[kTestAdditionalParameterKey], - kTestAdditionalParameterValue); + kTestAdditionalParameterValue, @""); OIDRegistrationResponse *responseCopy = [response copy]; - XCTAssertNotNil(responseCopy.request); - XCTAssertEqualObjects(responseCopy.clientID, response.clientID); - XCTAssertEqualObjects(responseCopy.clientIDIssuedAt, response.clientIDIssuedAt); - XCTAssertEqualObjects(responseCopy.clientSecret, response.clientSecret); - XCTAssertEqualObjects(responseCopy.clientSecretExpiresAt, response.clientSecretExpiresAt); - XCTAssertEqualObjects(responseCopy.registrationAccessToken, response.registrationAccessToken); - XCTAssertEqualObjects(responseCopy.registrationClientURI, response.registrationClientURI); + XCTAssertNotNil(responseCopy.request, @""); + XCTAssertEqualObjects(responseCopy.clientID, response.clientID, @""); + XCTAssertEqualObjects(responseCopy.clientIDIssuedAt, response.clientIDIssuedAt, @""); + XCTAssertEqualObjects(responseCopy.clientSecret, response.clientSecret, @""); + XCTAssertEqualObjects(responseCopy.clientSecretExpiresAt, response.clientSecretExpiresAt, @""); + XCTAssertEqualObjects(responseCopy.registrationAccessToken, response.registrationAccessToken, @""); + XCTAssertEqualObjects(responseCopy.registrationClientURI, response.registrationClientURI, @""); XCTAssertEqualObjects(responseCopy.additionalParameters[kTestAdditionalParameterKey], - kTestAdditionalParameterValue); + kTestAdditionalParameterValue, @""); } /*! @brief Tests the @c NSSecureCoding by round-tripping an instance through the coding process and @@ -114,26 +119,40 @@ - (void)testCopying { */ - (void)testSecureCoding { OIDRegistrationResponse *response = [[self class] testInstance]; - NSData *data = [NSKeyedArchiver archivedDataWithRootObject:response]; - OIDRegistrationResponse *responseCopy = [NSKeyedUnarchiver unarchiveObjectWithData:data]; + OIDRegistrationResponse *responseCopy; + NSError *error; + NSData *data; + if (@available(iOS 12.0, macOS 10.13, tvOS 11.0, watchOS 4.0, *)) { + data = [NSKeyedArchiver archivedDataWithRootObject:response + requiringSecureCoding:YES + error:&error]; + responseCopy = [NSKeyedUnarchiver unarchivedObjectOfClass:[OIDRegistrationResponse class] + fromData:data + error:&error]; + } else { +#if !TARGET_OS_IOS + data = [NSKeyedArchiver archivedDataWithRootObject:response]; + responseCopy = [NSKeyedUnarchiver unarchiveObjectWithData:data]; +#endif + } // Not a full test of the request deserialization, but should be sufficient as a smoke test // to make sure the request IS actually getting serialized and deserialized in the // NSSecureCoding implementation. We'll leave it up to the OIDAuthorizationRequest tests to make // sure the NSSecureCoding implementation of that class is correct. - XCTAssertNotNil(responseCopy.request); - XCTAssertEqualObjects(responseCopy.request.applicationType, response.request.applicationType); - - XCTAssertEqualObjects(responseCopy.clientID, response.clientID); - XCTAssertEqualObjects(responseCopy.clientIDIssuedAt, response.clientIDIssuedAt); - XCTAssertEqualObjects(responseCopy.clientSecret, response.clientSecret); - XCTAssertEqualObjects(responseCopy.clientSecretExpiresAt, response.clientSecretExpiresAt); - XCTAssertEqualObjects(responseCopy.registrationAccessToken, response.registrationAccessToken); - XCTAssertEqualObjects(responseCopy.registrationClientURI, response.registrationClientURI); + XCTAssertNotNil(responseCopy.request, @""); + XCTAssertEqualObjects(responseCopy.request.applicationType, response.request.applicationType, @""); + + XCTAssertEqualObjects(responseCopy.clientID, response.clientID, @""); + XCTAssertEqualObjects(responseCopy.clientIDIssuedAt, response.clientIDIssuedAt, @""); + XCTAssertEqualObjects(responseCopy.clientSecret, response.clientSecret, @""); + XCTAssertEqualObjects(responseCopy.clientSecretExpiresAt, response.clientSecretExpiresAt, @""); + XCTAssertEqualObjects(responseCopy.registrationAccessToken, response.registrationAccessToken, @""); + XCTAssertEqualObjects(responseCopy.registrationClientURI, response.registrationClientURI, @""); XCTAssertEqualObjects(responseCopy.tokenEndpointAuthenticationMethod, - response.tokenEndpointAuthenticationMethod); + response.tokenEndpointAuthenticationMethod, @""); XCTAssertEqualObjects(responseCopy.additionalParameters[kTestAdditionalParameterKey], - kTestAdditionalParameterValue); + kTestAdditionalParameterValue, @""); } /*! @brief Make sure the registration response is verified to ensure the 'client_secret_expires_at' @@ -147,7 +166,7 @@ - (void)testMissingClientSecretExpiresAtWithClientSecret { OIDClientIDParam : kClientIDTestValue, OIDClientSecretParam : kClientSecretTestValue, }]; - XCTAssertNil(response); + XCTAssertNil(response, @""); } /*! @brief Make sure the registration response missing 'registration_access_token' is detected when @@ -161,7 +180,7 @@ - (void)testMissingRegistrationAccessTokenWithRegistrationClientURI { OIDClientIDParam : kClientIDTestValue, OIDRegistrationClientURIParam : [NSURL URLWithString:kRegistrationClientURITestValue] }]; - XCTAssertNil(response); + XCTAssertNil(response, @""); } /*! @brief Make sure the registration response missing 'client_registration_uri' is detected when @@ -175,7 +194,7 @@ - (void)testMissingRegistrationClientURIWithRegistrationAccessToken { OIDClientIDParam : kClientIDTestValue, OIDRegistrationAccessTokenParam : kClientRegistrationAccessTokenTestValue }]; - XCTAssertNil(response); + XCTAssertNil(response, @""); } @end diff --git a/UnitTests/OIDResponseTypesTests.m b/UnitTests/OIDResponseTypesTests.m index 9d2b72221..659597735 100644 --- a/UnitTests/OIDResponseTypesTests.m +++ b/UnitTests/OIDResponseTypesTests.m @@ -18,7 +18,16 @@ #import -#import "Source/OIDResponseTypes.h" +#if SWIFT_PACKAGE +@import AppAuthCore; +#else +#import "Sources/AppAuthCore/OIDResponseTypes.h" +#endif + +// Ignore warnings about "Use of GNU statement expression extension" which is raised by our use of +// the XCTAssert___ macros. +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wgnu" /*! @brief Unit tests for constants in @c OIDResponseTypes.m. @remarks Arguably not worth tests for this file, but adding them for consistency, and so that @@ -29,15 +38,17 @@ @interface OIDResponseTypesTests : XCTestCase @implementation OIDResponseTypesTests - (void)testCode { - XCTAssertEqualObjects(OIDResponseTypeCode, @"code"); + XCTAssertEqualObjects(OIDResponseTypeCode, @"code", @""); } - (void)testToken { - XCTAssertEqualObjects(OIDResponseTypeToken, @"token"); + XCTAssertEqualObjects(OIDResponseTypeToken, @"token", @""); } - (void)testIDToken { - XCTAssertEqualObjects(OIDResponseTypeIDToken, @"id_token"); + XCTAssertEqualObjects(OIDResponseTypeIDToken, @"id_token", @""); } @end + +#pragma GCC diagnostic pop diff --git a/UnitTests/OIDScopesTests.m b/UnitTests/OIDScopesTests.m index f28a9731a..b5276ebd8 100644 --- a/UnitTests/OIDScopesTests.m +++ b/UnitTests/OIDScopesTests.m @@ -18,7 +18,16 @@ #import -#import "Source/OIDScopes.h" +#if SWIFT_PACKAGE +@import AppAuthCore; +#else +#import "Sources/AppAuthCore/OIDScopes.h" +#endif + +// Ignore warnings about "Use of GNU statement expression extension" which is raised by our use of +// the XCTAssert___ macros. +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wgnu" /*! @brief Unit tests for constants in @c OIDScopes.m. @remarks Arguably not worth tests for this file, but adding them for consistency, and so that @@ -45,3 +54,5 @@ - (void)testProfile { } @end + +#pragma GCC diagnostic pop diff --git a/UnitTests/OIDServiceConfigurationTests.h b/UnitTests/OIDServiceConfigurationTests.h index e45184db7..982db2b5b 100644 --- a/UnitTests/OIDServiceConfigurationTests.h +++ b/UnitTests/OIDServiceConfigurationTests.h @@ -24,12 +24,7 @@ NS_ASSUME_NONNULL_BEGIN /*! @brief Unit tests for @c OIDServiceConfiguration. */ -@interface OIDServiceConfigurationTests : XCTestCase { - // private variables - /*! @brief A list of tasks to perform during tearDown. - */ - NSMutableArray *_teardownTasks; -} +@interface OIDServiceConfigurationTests : XCTestCase /*! @brief Creates a new @c OIDServiceConfiguration for testing. */ diff --git a/UnitTests/OIDServiceConfigurationTests.m b/UnitTests/OIDServiceConfigurationTests.m index 6605fe5bf..bd4c5babb 100644 --- a/UnitTests/OIDServiceConfigurationTests.m +++ b/UnitTests/OIDServiceConfigurationTests.m @@ -21,10 +21,20 @@ #import #import "OIDServiceDiscoveryTests.h" -#import "Source/OIDAuthorizationService.h" -#import "Source/OIDError.h" -#import "Source/OIDServiceConfiguration.h" -#import "Source/OIDServiceDiscovery.h" + +#if SWIFT_PACKAGE +@import AppAuthCore; +#else +#import "Sources/AppAuthCore/OIDAuthorizationService.h" +#import "Sources/AppAuthCore/OIDError.h" +#import "Sources/AppAuthCore/OIDServiceConfiguration.h" +#import "Sources/AppAuthCore/OIDServiceDiscovery.h" +#endif + +// Ignore warnings about "Use of GNU statement expression extension" which is raised by our use of +// the XCTAssert___ macros. +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wgnu" /*! @brief The callback signature for @c NSURLSession 's @c dataTaskWithURL:completionHandler: method, which we swizzle in @c testFetcher to fake the network response with an OpenID @@ -76,7 +86,11 @@ typedef void(^DataTaskWithURLCompletionHandler)(NSData *_Nullable data, @"https://accounts.google.com/.well-known/openid-configuration"; -@implementation OIDServiceConfigurationTests +@implementation OIDServiceConfigurationTests { + /*! @brief A list of tasks to perform during tearDown. + */ + NSMutableArray *_teardownTasks; +} + (OIDServiceConfiguration *)testInstance { NSURL *authEndpoint = [NSURL URLWithString:kInitializerTestAuthEndpoint]; @@ -143,11 +157,11 @@ - (void)replaceClassMethodForClass:(Class)class selector:(SEL)selector withBlock - (void)testInitializer { OIDServiceConfiguration *configuration = [[self class] testInstance]; XCTAssertEqualObjects(configuration.authorizationEndpoint.absoluteString, - kInitializerTestAuthEndpoint); + kInitializerTestAuthEndpoint, @""); XCTAssertEqualObjects(configuration.tokenEndpoint.absoluteString, kInitializerTestTokenEndpoint); XCTAssertEqualObjects(configuration.registrationEndpoint.absoluteString, - kInitializerTestRegistrationEndpoint); + kInitializerTestRegistrationEndpoint, @""); } - (void)testIssuer { @@ -224,12 +238,12 @@ - (void)testFetcher { ^(OIDServiceConfiguration *_Nullable serviceConfiguration, NSError *_Nullable error) { [expectation fulfill]; - XCTAssertNil(error); - XCTAssertNotNil(serviceConfiguration); + XCTAssertNil(error, @""); + XCTAssertNotNil(serviceConfiguration, @""); XCTAssertEqualObjects(serviceConfiguration.tokenEndpoint, - expectedValues.tokenEndpoint); + expectedValues.tokenEndpoint, @""); XCTAssertEqualObjects(serviceConfiguration.authorizationEndpoint, - expectedValues.authorizationEndpoint); + expectedValues.authorizationEndpoint, @""); }; [OIDAuthorizationService discoverServiceConfigurationForDiscoveryURL:url completion:callback]; @@ -259,8 +273,8 @@ - (void)testFetcherWithNetworkError { ^(OIDServiceConfiguration *_Nullable serviceConfiguration, NSError *_Nullable error) { [expectation fulfill]; - XCTAssertNotNil(error); - XCTAssertNil(serviceConfiguration); + XCTAssertNotNil(error, @""); + XCTAssertNil(serviceConfiguration, @""); }; [OIDAuthorizationService discoverServiceConfigurationForDiscoveryURL:url completion:callback]; @@ -300,8 +314,8 @@ - (void)testFetcherWithErrorCode { ^(OIDServiceConfiguration *_Nullable serviceConfiguration, NSError *_Nullable error) { [expectation fulfill]; - XCTAssertNotNil(error); - XCTAssertNil(serviceConfiguration); + XCTAssertNotNil(error, @""); + XCTAssertNil(serviceConfiguration, @""); }; [OIDAuthorizationService discoverServiceConfigurationForDiscoveryURL:url completion:callback]; @@ -336,8 +350,8 @@ - (void)testFetcherWithBadJSON { ^(OIDServiceConfiguration *_Nullable serviceConfiguration, NSError *_Nullable error) { [expectation fulfill]; - XCTAssertNotNil(error); - XCTAssertNil(serviceConfiguration); + XCTAssertNotNil(error, @""); + XCTAssertNil(serviceConfiguration, @""); }; [OIDAuthorizationService discoverServiceConfigurationForDiscoveryURL:url completion:callback]; @@ -349,12 +363,26 @@ - (void)testFetcherWithBadJSON { */ - (void)testSecureCoding { OIDServiceConfiguration *configuration = [[self class] testInstance]; - NSData *data = [NSKeyedArchiver archivedDataWithRootObject:configuration]; - OIDServiceConfiguration *unarchived = [NSKeyedUnarchiver unarchiveObjectWithData:data]; + OIDServiceConfiguration *unarchived; + NSError *error; + NSData *data; + if (@available(iOS 12.0, macOS 10.13, tvOS 11.0, watchOS 4.0, *)) { + data = [NSKeyedArchiver archivedDataWithRootObject:configuration + requiringSecureCoding:YES + error:&error]; + unarchived = [NSKeyedUnarchiver unarchivedObjectOfClass:[OIDServiceConfiguration class] + fromData:data + error:&error]; + } else { +#if !TARGET_OS_IOS + data = [NSKeyedArchiver archivedDataWithRootObject:configuration]; + unarchived = [NSKeyedUnarchiver unarchiveObjectWithData:data]; +#endif + } - XCTAssertEqualObjects(configuration.authorizationEndpoint, unarchived.authorizationEndpoint); - XCTAssertEqualObjects(configuration.tokenEndpoint, unarchived.tokenEndpoint); - XCTAssertEqualObjects(configuration.registrationEndpoint, unarchived.registrationEndpoint); + XCTAssertEqualObjects(configuration.authorizationEndpoint, unarchived.authorizationEndpoint, @""); + XCTAssertEqualObjects(configuration.tokenEndpoint, unarchived.tokenEndpoint, @""); + XCTAssertEqualObjects(configuration.registrationEndpoint, unarchived.registrationEndpoint, @""); } /*! @brief Tests the @c NSCopying implementation by round-tripping an instance through the copying @@ -365,9 +393,11 @@ - (void)testCopying { OIDServiceConfiguration *configuration = [[self class] testInstance]; OIDServiceConfiguration *unarchived = [configuration copy]; - XCTAssertEqualObjects(configuration.authorizationEndpoint, unarchived.authorizationEndpoint); - XCTAssertEqualObjects(configuration.tokenEndpoint, unarchived.tokenEndpoint); - XCTAssertEqualObjects(configuration.registrationEndpoint, unarchived.registrationEndpoint); + XCTAssertEqualObjects(configuration.authorizationEndpoint, unarchived.authorizationEndpoint, @""); + XCTAssertEqualObjects(configuration.tokenEndpoint, unarchived.tokenEndpoint, @""); + XCTAssertEqualObjects(configuration.registrationEndpoint, unarchived.registrationEndpoint, @""); } @end + +#pragma GCC diagnostic pop diff --git a/UnitTests/OIDServiceDiscoveryTests.m b/UnitTests/OIDServiceDiscoveryTests.m index 4352c0b9c..f07d5e2a0 100644 --- a/UnitTests/OIDServiceDiscoveryTests.m +++ b/UnitTests/OIDServiceDiscoveryTests.m @@ -18,8 +18,53 @@ #import "OIDServiceDiscoveryTests.h" -#import "Source/OIDError.h" -#import "Source/OIDServiceDiscovery.h" +#if SWIFT_PACKAGE +@import AppAuthCore; +#else +#import "Sources/AppAuthCore/OIDError.h" +#import "Sources/AppAuthCore/OIDServiceDiscovery.h" +#endif + +// Ignore warnings about "Use of GNU statement expression extension" which is raised by our use of +// the XCTAssert___ macros. +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wgnu" + +// Define a subclass of @c OIDServiceDiscovery that provides the old NSCoding decoding +// implementation. +@interface OIDServiceDiscoveryOldDecoding : OIDServiceDiscovery +@end + +@implementation OIDServiceDiscoveryOldDecoding + ++ (BOOL)supportsSecureCoding { + return YES; +} + +- (nullable instancetype)initWithCoder:(NSCoder *)aDecoder { + NSError *error; + NSDictionary *dictionary = [[NSDictionary alloc] initWithCoder:aDecoder]; + self = [self initWithDictionary:dictionary error:&error]; + if (error) { + return nil; + } + return self; +} + +@end + +// Define a subclass of @c OIDServiceDiscovery that provides the old NSCoding encoding +// implementation. +@interface OIDServiceDiscoveryOldEncoding : OIDServiceDiscovery +@end + +@implementation OIDServiceDiscoveryOldEncoding + +- (void)encodeWithCoder:(NSCoder *)coder { + [self.discoveryDictionary encodeWithCoder:coder]; +} + +@end /*! Testing URL used when testing URL conversions. */ static NSString *const kTestURL = @"http://www.google.com/"; @@ -30,10 +75,12 @@ /*! Field keys associated with an OpenID Connect Discovery Document. */ static NSString *const kIssuerKey = @"issuer"; static NSString *const kAuthorizationEndpointKey = @"authorization_endpoint"; +static NSString *const kDeviceAuthorizationEndpointKey = @"device_authorization_endpoint"; static NSString *const kTokenEndpointKey = @"token_endpoint"; static NSString *const kUserinfoEndpointKey = @"userinfo_endpoint"; static NSString *const kJWKSURLKey = @"jwks_uri"; static NSString *const kRegistrationEndpointKey = @"registration_endpoint"; +static NSString *const kEndSessionEndpointKey = @"end_session_endpoint"; static NSString *const kScopesSupportedKey = @"scopes_supported"; static NSString *const kResponseTypesSupportedKey = @"response_types_supported"; static NSString *const kResponseModesSupportedKey = @"response_modes_supported"; @@ -93,11 +140,13 @@ + (NSDictionary *)completeServiceDiscoveryDictionary { return @{ kIssuerKey : @"http://www.example.com/issuer", kAuthorizationEndpointKey : @"http://www.example.com/authorization", + kDeviceAuthorizationEndpointKey : @"http://www.example.com/device", kTokenEndpointKey : @"http://www.example.com/token", kUserinfoEndpointKey : @"User Info Endpoint", kJWKSURLKey : @"http://www.example.com/jwks", kRegistrationEndpointKey : @"Registration Endpoint", - kScopesSupportedKey : @"Scopes Supported", + kEndSessionEndpointKey : @"https://www.example.com/logout", + kScopesSupportedKey : @[ @"scope1", @"scope2" ], kResponseTypesSupportedKey : @"Response Types Supported", kResponseModesSupportedKey : @"Response Modes Supported", kGrantTypesSupportedKey : @"Grant Types Supported", @@ -141,47 +190,62 @@ + (NSURL *)googleDiscoveryAuthorizationEndpoint { // from https://accounts.google.com/.well-known/openid-configuration static NSString *const kDiscoveryDocument = - @"{\"issuer\":\"https://accounts.google.com\",\"authorization_endpoint\":\"https://account" - "s.google.com/o/oauth2/v2/auth\",\"token_endpoint\":\"https://www.googleapis.com/oauth2/v4/to" - "ken\",\"userinfo_endpoint\":\"https://www.googleapis.com/oauth2/v3/userinfo\",\"revocation_e" - "ndpoint\":\"https://accounts.google.com/o/oauth2/revoke\",\"jwks_uri\":\"https://www.googlea" - "pis.com/oauth2/v3/certs\",\"response_types_supported\":[\"code\",\"token\",\"id_token\",\"co" - "de token\",\"code id_token\",\"token id_token\",\"code token id_token\",\"none\"],\"subject_" - "types_supported\":[\"public\"],\"id_token_signing_alg_values_supported\":[\"RS256\"],\"scope" - "s_supported\":[\"openid\",\"email\",\"profile\"],\"token_endpoint_auth_methods_supported\":[" - "\"client_secret_post\",\"client_secret_basic\"],\"claims_supported\":[\"aud\",\"email\",\"em" - "ail_verified\",\"exp\",\"family_name\",\"given_name\",\"iat\",\"iss\",\"locale\",\"name\",\"" - "picture\",\"sub\"]}"; + @"{\"issuer\":\"https://accounts.google.com\",\"authorization_endpoint\":\"https://" + @"accounts.google.com/o/oauth2/v2/auth\",\"device_authorization_endpoint\":\"https://" + @"oauth2.googleapis.com/device/code\",\"token_endpoint\":\"https://oauth2.googleapis.com/" + @"token\",\"userinfo_endpoint\":\"https://openidconnect.googleapis.com/v1/" + @"userinfo\",\"revocation_endpoint\":\"https://oauth2.googleapis.com/" + @"revoke\",\"jwks_uri\":\"https://www.googleapis.com/oauth2/v3/" + @"certs\",\"response_types_supported\":[\"code\",\"token\",\"id_token\",\"codetoken\",\"codeid_" + @"token\",\"tokenid_token\",\"codetokenid_token\",\"none\"],\"subject_types_supported\":[" + @"\"public\"],\"id_token_signing_alg_values_supported\":[\"RS256\"],\"scopes_supported\":[" + @"\"openid\",\"email\",\"profile\"],\"token_endpoint_auth_methods_supported\":[\"client_secret_" + @"post\",\"client_secret_basic\"],\"claims_supported\":[\"aud\",\"email\",\"email_verified\"," + @"\"exp\",\"family_name\",\"given_name\",\"iat\",\"iss\",\"locale\",\"name\",\"picture\"," + @"\"sub\"],\"code_challenge_methods_supported\":[\"plain\",\"S256\"],\"grant_types_supported\":" + @"[\"authorization_code\",\"refresh_token\",\"urn:ietf:params:oauth:grant-type:device_code\"," + @"\"urn:ietf:params:oauth:grant-type:jwt-bearer\"]}"; // from https://accounts.google.com/.well-known/openid-configuration with authorization_endpoint // removed static NSString *const kDiscoveryDocumentMissingField = - @"{\"issuer\":\"https://accounts.google.com\",\"token_endpoint\":\"https://www.googleapis." - "com/oauth2/v4/to" - "ken\",\"userinfo_endpoint\":\"https://www.googleapis.com/oauth2/v3/userinfo\",\"revocation_e" - "ndpoint\":\"https://accounts.google.com/o/oauth2/revoke\",\"jwks_uri\":\"https://www.googlea" - "pis.com/oauth2/v3/certs\",\"response_types_supported\":[\"code\",\"token\",\"id_token\",\"co" - "de token\",\"code id_token\",\"token id_token\",\"code token id_token\",\"none\"],\"subject_" - "types_supported\":[\"public\"],\"id_token_signing_alg_values_supported\":[\"RS256\"],\"scope" - "s_supported\":[\"openid\",\"email\",\"profile\"],\"token_endpoint_auth_methods_supported\":[" - "\"client_secret_post\",\"client_secret_basic\"],\"claims_supported\":[\"aud\",\"email\",\"em" - "ail_verified\",\"exp\",\"family_name\",\"given_name\",\"iat\",\"iss\",\"locale\",\"name\",\"" - "picture\",\"sub\"]}"; - - // from https://accounts.google.com/.well-known/openid-configuration with authorization_endpoint - // and token_endpoint set to JSON 'null' + @"{\"issuer\":\"https://accounts.google.com\",\"device_authorization_endpoint\":\"https://" + @"oauth2.googleapis.com/device/code\",\"token_endpoint\":\"https://oauth2.googleapis.com/" + @"token\",\"userinfo_endpoint\":\"https://openidconnect.googleapis.com/v1/" + @"userinfo\",\"revocation_endpoint\":\"https://oauth2.googleapis.com/" + @"revoke\",\"jwks_uri\":\"https://www.googleapis.com/oauth2/v3/" + @"certs\",\"response_types_supported\":[\"code\",\"token\",\"id_token\",\"codetoken\",\"codeid_" + @"token\",\"tokenid_token\",\"codetokenid_token\",\"none\"],\"subject_types_supported\":[" + @"\"public\"],\"id_token_signing_alg_values_supported\":[\"RS256\"],\"scopes_supported\":[" + @"\"openid\",\"email\",\"profile\"],\"token_endpoint_auth_methods_supported\":[\"client_secret_" + @"post\",\"client_secret_basic\"],\"claims_supported\":[\"aud\",\"email\",\"email_verified\"," + @"\"exp\",\"family_name\",\"given_name\",\"iat\",\"iss\",\"locale\",\"name\",\"picture\"," + @"\"sub\"],\"code_challenge_methods_supported\":[\"plain\",\"S256\"],\"grant_types_supported\":" + @"[\"authorization_code\",\"refresh_token\",\"urn:ietf:params:oauth:grant-type:device_code\"," + @"\"urn:ietf:params:oauth:grant-type:jwt-bearer\"]}"; + +// from https://accounts.google.com/.well-known/openid-configuration with authorization_endpoint +// and token_endpoint set to JSON 'null' static NSString *const kDiscoveryDocumentNullField = - @"{\"issuer\":\"https://accounts.google.com\",\"authorization_endpoint\":null," - "\"token_endpoint\":null" - ",\"userinfo_endpoint\":\"https://www.googleapis.com/oauth2/v3/userinfo\",\"revocation_e" - "ndpoint\":\"https://accounts.google.com/o/oauth2/revoke\",\"jwks_uri\":\"https://www.googlea" - "pis.com/oauth2/v3/certs\",\"response_types_supported\":[\"code\",\"token\",\"id_token\",\"co" - "de token\",\"code id_token\",\"token id_token\",\"code token id_token\",\"none\"],\"subject_" - "types_supported\":[\"public\"],\"id_token_signing_alg_values_supported\":[\"RS256\"],\"scope" - "s_supported\":[\"openid\",\"email\",\"profile\"],\"token_endpoint_auth_methods_supported\":[" - "\"client_secret_post\",\"client_secret_basic\"],\"claims_supported\":[\"aud\",\"email\",\"em" - "ail_verified\",\"exp\",\"family_name\",\"given_name\",\"iat\",\"iss\",\"locale\",\"name\",\"" - "picture\",\"sub\"]}"; + @"{\"issuer\":\"https://" + @"accounts.google.com\",\"authorization_endpoint\":null,\"device_authorization_endpoint\":" + @"\"https://oauth2.googleapis.com/device/" + @"code\",\"token_endpoint\":null,\"userinfo_endpoint\":\"https://openidconnect.googleapis.com/" + @"v1/userinfo\",\"revocation_endpoint\":\"https://oauth2.googleapis.com/" + @"revoke\",\"jwks_uri\":\"https://www.googleapis.com/oauth2/v3/" + @"certs\",\"response_types_supported\":[\"code\",\"token\",\"id_token\",\"codetoken\",\"codeid_" + @"token\",\"tokenid_token\",\"codetokenid_token\",\"none\"],\"subject_types_supported\":[" + @"\"public\"],\"id_token_signing_alg_values_supported\":[\"RS256\"],\"scopes_supported\":[" + @"\"openid\",\"email\",\"profile\"],\"token_endpoint_auth_methods_supported\":[\"client_secret_" + @"post\",\"client_secret_basic\"],\"claims_supported\":[\"aud\",\"email\",\"email_verified\"," + @"\"exp\",\"family_name\",\"given_name\",\"iat\",\"iss\",\"locale\",\"name\",\"picture\"," + @"\"sub\"],\"code_challenge_methods_supported\":[\"plain\",\"S256\"],\"grant_types_supported\":" + @"[\"authorization_code\",\"refresh_token\",\"urn:ietf:params:oauth:grant-type:device_code\"," + @"\"urn:ietf:params:oauth:grant-type:jwt-bearer\"]}"; + +static NSString *const kDiscoveryDocumentNotDictionary = + @"[\"code\",\"token\",\"id_token\",\"code token\",\"code id_token\",\"token id_token\",\"code to" + "ken id_token\",\"none\"]"; /*! @brief Tests that URLs are handled properly when converted from the dictionary's string representation. @@ -202,7 +266,7 @@ - (void)testURLs { NSURL *testPolicyURL = [NSURL URLWithString:kTestURL]; - XCTAssertEqualObjects(discovery.OPPolicyURI, testPolicyURL); + XCTAssertEqualObjects(discovery.OPPolicyURI, testPolicyURL, @""); } /*! @brief Tests that we get an error when the document is not valid JSON. @@ -213,8 +277,20 @@ - (void)testErrorWhenBadFormat { XCTAssertNil(discovery, @"When initializing a discovery document, it should not return an " "instance if it is not valid JSON."); XCTAssertNotNil(error, @"There should be an error indicating we received bad JSON."); - XCTAssertEqualObjects(error.domain, OIDGeneralErrorDomain); - XCTAssertEqual(error.code, OIDErrorCodeJSONDeserializationError); + XCTAssertEqualObjects(error.domain, OIDGeneralErrorDomain, @""); + XCTAssertEqual(error.code, OIDErrorCodeJSONDeserializationError, @""); +} + +/*! @brief Tests that we get an error when the root JSON object isn't a dictionary. + */ +- (void)testErrorWhenRootObjectNotNSDictionary { + NSError *error; + OIDServiceDiscovery *discovery = [[OIDServiceDiscovery alloc] initWithJSON:kDiscoveryDocumentNotDictionary error:&error]; + XCTAssertNil(discovery, @"When initializing a discovery document, it should not return an " + "instance if the root JSON object is not a NSDictionary."); + XCTAssertNotNil(error, @"There should be an error indicating we received bad JSON."); + XCTAssertEqualObjects(error.domain, OIDGeneralErrorDomain, @""); + XCTAssertEqual(error.code, OIDErrorCodeInvalidDiscoveryDocument, @""); } /*! @brief Tests that we get an error when the required fields aren't in the source dictionary. @@ -226,8 +302,8 @@ - (void)testErrorWhenMissingFields { XCTAssertNil(discovery, @"When initializing a discovery document, it should not return an " "instance if there are missing required fields."); XCTAssertNotNil(error, @"There should be an error indicating we are missing required fields."); - XCTAssertEqualObjects(error.domain, OIDGeneralErrorDomain); - XCTAssertEqual(error.code, OIDErrorCodeInvalidDiscoveryDocument); + XCTAssertEqualObjects(error.domain, OIDGeneralErrorDomain, @""); + XCTAssertEqual(error.code, OIDErrorCodeInvalidDiscoveryDocument, @""); } /*! @brief Tests that we get an error when the required fields aren't in the source dictionary. @@ -239,8 +315,8 @@ - (void)testErrorWhenMissingFieldsJSON { XCTAssertNil(discovery, @"When initializing a discovery document with JSON, it should not return" " an instance if there are missing required fields."); XCTAssertNotNil(error, @"There should be an error indicating we are missing required fields."); - XCTAssertEqualObjects(error.domain, OIDGeneralErrorDomain); - XCTAssertEqual(error.code, OIDErrorCodeInvalidDiscoveryDocument); + XCTAssertEqualObjects(error.domain, OIDGeneralErrorDomain, @""); + XCTAssertEqual(error.code, OIDErrorCodeInvalidDiscoveryDocument, @""); } /*! @brief Tests that we do not get an error, and we do get an instance of @@ -276,8 +352,8 @@ - (void)pendingTestErrorWhenMalformedFields { @"When initializing a discovery document, it should not return an instance if there" " are missing required fields."); XCTAssertNotNil(error, @"There should be an error indicating we are missing required fields."); - XCTAssertEqualObjects(error.domain, OIDGeneralErrorDomain); - XCTAssertEqual(error.code, OIDErrorCodeInvalidDiscoveryDocument); + XCTAssertEqualObjects(error.domain, OIDGeneralErrorDomain, @""); + XCTAssertEqual(error.code, OIDErrorCodeInvalidDiscoveryDocument, @""); } /*! @brief Tests that we get an error when null is passed in through JSON. @@ -290,8 +366,8 @@ - (void)pendingTestErrorWhenJSONNullField { XCTAssertNil(discovery, @"When initializing a discovery document, it should not return an " "instance if there are missing required fields."); XCTAssertNotNil(error, @"There should be an error indicating we are missing required fields."); - XCTAssertEqualObjects(error.domain, OIDGeneralErrorDomain); - XCTAssertEqual(error.code, OIDErrorCodeInvalidDiscoveryDocument); + XCTAssertEqualObjects(error.domain, OIDGeneralErrorDomain, @""); + XCTAssertEqual(error.code, OIDErrorCodeInvalidDiscoveryDocument, @""); } /*! @brief Tests that the JSON String version results in a valid object. @@ -304,7 +380,7 @@ - (void)testJSONStringDecoding { "valid object"); XCTAssertNil(error, @"There should not be any errors."); XCTAssertEqualObjects(discovery.authorizationEndpoint, - [[self class] googleDiscoveryAuthorizationEndpoint]); + [[self class] googleDiscoveryAuthorizationEndpoint], @""); } /*! @brief Tests that the JSON NSData version results in a valid object. @@ -318,7 +394,7 @@ - (void)testJSONDataDecoding { "valid object"); XCTAssertNil(error, @"There should not be any errors."); XCTAssertEqualObjects(discovery.authorizationEndpoint, - [[self class] googleDiscoveryAuthorizationEndpoint]); + [[self class] googleDiscoveryAuthorizationEndpoint], @""); } /*! @brief Tests that initialising with the dictionary initialiser and the JSON initialiser result @@ -344,8 +420,8 @@ - (void)pendingTestJSONEqualsDictionary { XCTAssertNotNil(discovery2, @"Discovery document should initialize."); XCTAssertNil(error, @"There should not be any errors."); - XCTAssertEqualObjects(discovery.discoveryDictionary, discovery2.discoveryDictionary); - XCTAssertEqualObjects(discovery, discovery2); + XCTAssertEqualObjects(discovery.discoveryDictionary, discovery2.discoveryDictionary, @""); + XCTAssertEqualObjects(discovery, discovery2, @""); } @@ -371,12 +447,111 @@ - (void)testSecureCoding { OIDServiceDiscovery *discovery = [[OIDServiceDiscovery alloc] initWithDictionary:serviceDiscoveryDictionary error:&error]; - NSData *data = [NSKeyedArchiver archivedDataWithRootObject:discovery]; - OIDServiceDiscovery *unarchived = [NSKeyedUnarchiver unarchiveObjectWithData:data]; + NSData *data; + if (@available(iOS 12.0, macOS 10.13, tvOS 11.0, watchOS 4.0, *)) { + data = [NSKeyedArchiver archivedDataWithRootObject:discovery + requiringSecureCoding:YES + error:&error]; + } else { +#if !TARGET_OS_IOS + data = [NSKeyedArchiver archivedDataWithRootObject:discovery]; +#endif + } + + OIDServiceDiscovery *unarchived; + if (@available(iOS 12.0, macOS 10.13, tvOS 11.0, watchOS 4.0, *)) { + unarchived = [NSKeyedUnarchiver unarchivedObjectOfClass:[OIDServiceDiscovery class] + fromData:data + error:&error]; + } else { +#if !TARGET_OS_IOS + unarchived = [NSKeyedUnarchiver unarchiveObjectWithData:data]; +#endif + } XCTAssertEqualObjects(discovery.discoveryDictionary, unarchived.discoveryDictionary); } +/*! @brief To ensure backward compatibility, test our ability to decode the old encoding of + @c OIDServiceDiscovery. + */ +- (void)testSecureCodingDecodeOld { + NSError *error; + NSDictionary *serviceDiscoveryDictionary = [[self class] completeServiceDiscoveryDictionary]; + OIDServiceDiscoveryOldEncoding *discovery = + [[OIDServiceDiscoveryOldEncoding alloc] initWithDictionary:serviceDiscoveryDictionary + error:&error]; + NSData *data; + if (@available(iOS 12.0, macOS 10.13, tvOS 11.0, watchOS 4.0, *)) { + data = [NSKeyedArchiver archivedDataWithRootObject:discovery + requiringSecureCoding:YES + error:&error]; + } else { +#if !TARGET_OS_IOS + data = [NSKeyedArchiver archivedDataWithRootObject:discovery]; +#endif + } + + OIDServiceDiscovery *unarchived; + if (@available(iOS 12.0, macOS 10.13, tvOS 11.0, watchOS 4.0, *)) { + NSSet *allowedClasses = [NSSet setWithArray:@[[OIDServiceDiscovery class], + [NSDictionary class], + [NSArray class], + [NSString class], + [NSNumber class], + [NSNull class]]]; + unarchived = [NSKeyedUnarchiver unarchivedObjectOfClasses:allowedClasses + fromData:data + error:&error]; + } else { +#if !TARGET_OS_IOS + unarchived = [NSKeyedUnarchiver unarchiveObjectWithData:data]; +#endif + } + + XCTAssertEqualObjects(discovery.discoveryDictionary, unarchived.discoveryDictionary); +} + +/*! @brief To ensure forward compatibility, test the ability of the old implementation to decode + the new encoding of @c OIDServiceDiscovery. + */ +- (void)testSecureCodingOldDecodeNew { + NSError *error; + NSDictionary *serviceDiscoveryDictionary = [[self class] completeServiceDiscoveryDictionary]; + OIDServiceDiscoveryOldDecoding *discovery = + [[OIDServiceDiscoveryOldDecoding alloc] initWithDictionary:serviceDiscoveryDictionary + error:&error]; + NSData *data; + if (@available(iOS 12.0, macOS 10.13, tvOS 11.0, watchOS 4.0, *)) { + data = [NSKeyedArchiver archivedDataWithRootObject:discovery + requiringSecureCoding:YES + error:&error]; + } else { +#if !TARGET_OS_IOS + data = [NSKeyedArchiver archivedDataWithRootObject:discovery]; +#endif + } + + OIDServiceDiscovery *unarchived; + if (@available(iOS 12.0, macOS 10.13, tvOS 11.0, watchOS 4.0, *)) { + NSSet *allowedClasses = [NSSet setWithArray:@[[OIDServiceDiscoveryOldDecoding class], + [NSDictionary class], + [NSArray class], + [NSString class], + [NSNumber class], + [NSNull class]]]; + unarchived = [NSKeyedUnarchiver unarchivedObjectOfClasses:allowedClasses + fromData:data + error:&error]; + } else { +#if !TARGET_OS_IOS + unarchived = [NSKeyedUnarchiver unarchiveObjectWithData:data]; +#endif + } + XCTAssertNil(error); + XCTAssertEqualObjects(discovery.discoveryDictionary, unarchived.discoveryDictionary); +} + /*! @brief Tests the NSCopying implementation by round-tripping an instance through the copying process and checking to make sure the source and destination instances have equivalent dictionaries. @@ -390,7 +565,7 @@ - (void)testCopying { OIDServiceDiscovery *unarchived = [discovery copy]; - XCTAssertEqualObjects(discovery.discoveryDictionary, unarchived.discoveryDictionary); + XCTAssertEqualObjects(discovery.discoveryDictionary, unarchived.discoveryDictionary, @""); } #pragma mark - Field Mappings @@ -460,62 +635,66 @@ - (void)testField_##_field_ { XCTAssertEqualObjects(discovery._field_, [NSURL URLWithString:_test_value_]); \ } -TestURLFieldBackedBy(issuer, kIssuerKey, kTestURL); -TestURLFieldBackedBy(authorizationEndpoint, kAuthorizationEndpointKey, kTestURL); -TestURLFieldBackedBy(tokenEndpoint, kTokenEndpointKey, kTestURL); -TestURLFieldBackedBy(userinfoEndpoint, kUserinfoEndpointKey, kTestURL); -TestURLFieldBackedBy(jwksURL, kJWKSURLKey, kTestURL); -TestURLFieldBackedBy(registrationEndpoint, kRegistrationEndpointKey, kTestURL); -TestFieldBackedBy(scopesSupported, kScopesSupportedKey, @"Scopes Supported"); -TestFieldBackedBy(responseTypesSupported, kResponseTypesSupportedKey, @"Response Types Supported"); -TestFieldBackedBy(responseModesSupported, kResponseModesSupportedKey, @"Response Modes Supported"); -TestFieldBackedBy(grantTypesSupported, kGrantTypesSupportedKey, @"Grant Types Supported"); -TestFieldBackedBy(acrValuesSupported, kACRValuesSupportedKey, @"ACR Values Supported"); -TestFieldBackedBy(subjectTypesSupported, kSubjectTypesSupportedKey, @"Subject Types Supported"); +TestURLFieldBackedBy(issuer, kIssuerKey, kTestURL) +TestURLFieldBackedBy(authorizationEndpoint, kAuthorizationEndpointKey, kTestURL) +TestURLFieldBackedBy(deviceAuthorizationEndpoint, kDeviceAuthorizationEndpointKey, kTestURL) +TestURLFieldBackedBy(tokenEndpoint, kTokenEndpointKey, kTestURL) +TestURLFieldBackedBy(userinfoEndpoint, kUserinfoEndpointKey, kTestURL) +TestURLFieldBackedBy(jwksURL, kJWKSURLKey, kTestURL) +TestURLFieldBackedBy(registrationEndpoint, kRegistrationEndpointKey, kTestURL) +TestURLFieldBackedBy(endSessionEndpoint, kEndSessionEndpointKey, kTestURL) +TestFieldBackedBy(scopesSupported, kScopesSupportedKey, @"Scopes Supported") +TestFieldBackedBy(responseTypesSupported, kResponseTypesSupportedKey, @"Response Types Supported") +TestFieldBackedBy(responseModesSupported, kResponseModesSupportedKey, @"Response Modes Supported") +TestFieldBackedBy(grantTypesSupported, kGrantTypesSupportedKey, @"Grant Types Supported") +TestFieldBackedBy(acrValuesSupported, kACRValuesSupportedKey, @"ACR Values Supported") +TestFieldBackedBy(subjectTypesSupported, kSubjectTypesSupportedKey, @"Subject Types Supported") TestFieldBackedBy(IDTokenSigningAlgorithmValuesSupported, kIDTokenSigningAlgorithmValuesSupportedKey, - @"Token Signing Algorithm Values Supported"); + @"Token Signing Algorithm Values Supported") TestFieldBackedBy(IDTokenEncryptionAlgorithmValuesSupported, kIDTokenEncryptionAlgorithmValuesSupportedKey, - @"Token Encryption Algorithm Values Supported"); + @"Token Encryption Algorithm Values Supported") TestFieldBackedBy(IDTokenEncryptionEncodingValuesSupported, kIDTokenEncryptionEncodingValuesSupportedKey, - @"token Encryption Encoding Values Supported"); + @"token Encryption Encoding Values Supported") TestFieldBackedBy(userinfoSigningAlgorithmValuesSupported, kUserinfoSigningAlgorithmValuesSupportedKey, - @"User Info Signing Algorithm Values Supported"); + @"User Info Signing Algorithm Values Supported") TestFieldBackedBy(userinfoEncryptionAlgorithmValuesSupported, kUserinfoEncryptionAlgorithmValuesSupportedKey, - @"User Info Encryption Algorithm Values Supported"); + @"User Info Encryption Algorithm Values Supported") TestFieldBackedBy(userinfoEncryptionEncodingValuesSupported, kUserinfoEncryptionEncodingValuesSupportedKey, - @"User Info Encryption Encoding Values Supported"); + @"User Info Encryption Encoding Values Supported") TestFieldBackedBy(requestObjectSigningAlgorithmValuesSupported, kRequestObjectSigningAlgorithmValuesSupportedKey, - @"Request Object Signing Algorithm Values Supported"); + @"Request Object Signing Algorithm Values Supported") TestFieldBackedBy(requestObjectEncryptionAlgorithmValuesSupported, kRequestObjectEncryptionAlgorithmValuesSupportedKey, - @"Reqest Object Encryption Algorithm Values Supported"); + @"Reqest Object Encryption Algorithm Values Supported") TestFieldBackedBy(requestObjectEncryptionEncodingValuesSupported, kRequestObjectEncryptionEncodingValuesSupported, - @"Request Object Encryption Encoding Values Supported"); + @"Request Object Encryption Encoding Values Supported") TestFieldBackedBy(tokenEndpointAuthMethodsSupported, kTokenEndpointAuthMethodsSupportedKey, - @"Token Endpoint Auth Methods Supported"); + @"Token Endpoint Auth Methods Supported") TestFieldBackedBy(tokenEndpointAuthSigningAlgorithmValuesSupported, kTokenEndpointAuthSigningAlgorithmValuesSupportedKey, - @"Token Endpoint Auth Signing Algorithm Values Supported"); -TestFieldBackedBy(displayValuesSupported, kDisplayValuesSupportedKey, @"Display Values Supported"); -TestFieldBackedBy(claimTypesSupported, kClaimTypesSupportedKey, @"Claim Types Supported"); -TestFieldBackedBy(claimsSupported, kClaimsSupportedKey, @"Claims Supported"); -TestURLFieldBackedBy(serviceDocumentation, kServiceDocumentationKey, kTestURL); -TestFieldBackedBy(claimsLocalesSupported, kClaimsLocalesSupportedKey, @"Claims Locales Supported"); -TestFieldBackedBy(UILocalesSupported, kUILocalesSupportedKey, @"UI Locales Supported"); -TestBooleanFieldBackedBy(claimsParameterSupported, kClaimsParameterSupportedKey, YES); -TestBooleanFieldBackedBy(requestParameterSupported, kRequestParameterSupportedKey, YES); -TestBooleanFieldBackedBy(requestURIParameterSupported, kRequestURIParameterSupportedKey, NO); -TestBooleanFieldBackedBy(requireRequestURIRegistration, kRequireRequestURIRegistrationKey, YES); -TestURLFieldBackedBy(OPPolicyURI, kOPPolicyURIKey, kTestURL); -TestURLFieldBackedBy(OPTosURI, kOPTosURIKey, kTestURL); + @"Token Endpoint Auth Signing Algorithm Values Supported") +TestFieldBackedBy(displayValuesSupported, kDisplayValuesSupportedKey, @"Display Values Supported") +TestFieldBackedBy(claimTypesSupported, kClaimTypesSupportedKey, @"Claim Types Supported") +TestFieldBackedBy(claimsSupported, kClaimsSupportedKey, @"Claims Supported") +TestURLFieldBackedBy(serviceDocumentation, kServiceDocumentationKey, kTestURL) +TestFieldBackedBy(claimsLocalesSupported, kClaimsLocalesSupportedKey, @"Claims Locales Supported") +TestFieldBackedBy(UILocalesSupported, kUILocalesSupportedKey, @"UI Locales Supported") +TestBooleanFieldBackedBy(claimsParameterSupported, kClaimsParameterSupportedKey, YES) +TestBooleanFieldBackedBy(requestParameterSupported, kRequestParameterSupportedKey, YES) +TestBooleanFieldBackedBy(requestURIParameterSupported, kRequestURIParameterSupportedKey, NO) +TestBooleanFieldBackedBy(requireRequestURIRegistration, kRequireRequestURIRegistrationKey, YES) +TestURLFieldBackedBy(OPPolicyURI, kOPPolicyURIKey, kTestURL) +TestURLFieldBackedBy(OPTosURI, kOPTosURIKey, kTestURL) @end + +#pragma GCC diagnostic pop diff --git a/UnitTests/OIDSwiftTests.swift b/UnitTests/OIDSwiftTests.swift index bd3536922..59ee443cc 100644 --- a/UnitTests/OIDSwiftTests.swift +++ b/UnitTests/OIDSwiftTests.swift @@ -19,6 +19,10 @@ import Foundation import XCTest +#if SWIFT_PACKAGE +import AppAuthCore +#endif + /*! @brief Unit tests to verify Swift compatability. */ class OIDSwiftTests: XCTestCase { diff --git a/UnitTests/OIDTokenRequestTests.m b/UnitTests/OIDTokenRequestTests.m index 3897de81c..59a2f8ae6 100644 --- a/UnitTests/OIDTokenRequestTests.m +++ b/UnitTests/OIDTokenRequestTests.m @@ -20,11 +20,21 @@ #import "OIDAuthorizationResponseTests.h" #import "OIDServiceConfigurationTests.h" -#import "Source/OIDAuthorizationRequest.h" -#import "Source/OIDAuthorizationResponse.h" -#import "Source/OIDScopeUtilities.h" -#import "Source/OIDServiceConfiguration.h" -#import "Source/OIDTokenRequest.h" + +#if SWIFT_PACKAGE +@import AppAuthCore; +#else +#import "Sources/AppAuthCore/OIDAuthorizationRequest.h" +#import "Sources/AppAuthCore/OIDAuthorizationResponse.h" +#import "Sources/AppAuthCore/OIDScopeUtilities.h" +#import "Sources/AppAuthCore/OIDServiceConfiguration.h" +#import "Sources/AppAuthCore/OIDTokenRequest.h" +#endif + +// Ignore warnings about "Use of GNU statement expression extension" which is raised by our use of +// the XCTAssert___ macros. +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wgnu" /*! @brief Test value for the @c refreshToken property. */ @@ -38,6 +48,22 @@ */ static NSString *const kTestAdditionalParameterValue = @"1"; +/*! @brief Test key for the @c additionalHeaders property. + */ +static NSString *const kTestAdditionalHeaderKey = @"B"; + +/*! @brief Test value for the @c additionalHeaders property. + */ +static NSString *const kTestAdditionalHeaderValue = @"2"; + +/*! @brief Test key for the @c additionalHeaders property. + */ +static NSString *const kTestAdditionalHeaderKey2 = @"C"; + +/*! @brief Test value for the @c additionalHeaders property. + */ +static NSString *const kTestAdditionalHeaderValue2 = @"3"; + @implementation OIDTokenRequestTests + (OIDTokenRequest *)testInstance { @@ -46,6 +72,9 @@ + (OIDTokenRequest *)testInstance { [OIDScopeUtilities scopesArrayWithString:authResponse.request.scope]; NSDictionary *additionalParameters = @{ kTestAdditionalParameterKey : kTestAdditionalParameterValue }; + NSDictionary *additionalHeaders = + @{ kTestAdditionalHeaderKey : kTestAdditionalHeaderValue }; + OIDTokenRequest *request = [[OIDTokenRequest alloc] initWithConfiguration:authResponse.request.configuration grantType:OIDGrantTypeAuthorizationCode @@ -56,7 +85,8 @@ + (OIDTokenRequest *)testInstance { scopes:scopesArray refreshToken:kRefreshTokenTestValue codeVerifier:authResponse.request.codeVerifier - additionalParameters:additionalParameters]; + additionalParameters:additionalParameters + additionalHeaders:additionalHeaders]; return request; } @@ -66,6 +96,9 @@ + (OIDTokenRequest *)testInstanceCodeExchange { [OIDScopeUtilities scopesArrayWithString:authResponse.request.scope]; NSDictionary *additionalParameters = @{ kTestAdditionalParameterKey : kTestAdditionalParameterValue }; + NSDictionary *additionalHeaders = + @{ kTestAdditionalHeaderKey : kTestAdditionalHeaderValue }; + OIDTokenRequest *request = [[OIDTokenRequest alloc] initWithConfiguration:authResponse.request.configuration grantType:OIDGrantTypeAuthorizationCode @@ -76,7 +109,8 @@ + (OIDTokenRequest *)testInstanceCodeExchange { scopes:scopesArray refreshToken:kRefreshTokenTestValue codeVerifier:authResponse.request.codeVerifier - additionalParameters:additionalParameters]; + additionalParameters:additionalParameters + additionalHeaders:additionalHeaders]; return request; } @@ -86,6 +120,9 @@ + (OIDTokenRequest *)testInstanceCodeExchangeClientAuth { [OIDScopeUtilities scopesArrayWithString:authResponse.request.scope]; NSDictionary *additionalParameters = @{ kTestAdditionalParameterKey : kTestAdditionalParameterValue }; + NSDictionary *additionalHeaders = + @{ kTestAdditionalHeaderKey : kTestAdditionalHeaderValue }; + OIDTokenRequest *request = [[OIDTokenRequest alloc] initWithConfiguration:authResponse.request.configuration grantType:OIDGrantTypeAuthorizationCode @@ -96,7 +133,8 @@ + (OIDTokenRequest *)testInstanceCodeExchangeClientAuth { scopes:scopesArray refreshToken:kRefreshTokenTestValue codeVerifier:authResponse.request.codeVerifier - additionalParameters:additionalParameters]; + additionalParameters:additionalParameters + additionalHeaders:additionalHeaders]; return request; } @@ -106,6 +144,35 @@ + (OIDTokenRequest *)testInstanceRefresh { [OIDScopeUtilities scopesArrayWithString:authResponse.request.scope]; NSDictionary *additionalParameters = @{ kTestAdditionalParameterKey : kTestAdditionalParameterValue }; + NSDictionary *additionalHeaders = + @{ kTestAdditionalHeaderKey : kTestAdditionalHeaderValue }; + + OIDTokenRequest *request = + [[OIDTokenRequest alloc] initWithConfiguration:authResponse.request.configuration + grantType:OIDGrantTypeAuthorizationCode + authorizationCode:authResponse.authorizationCode + redirectURL:nil + clientID:authResponse.request.clientID + clientSecret:authResponse.request.clientSecret + scopes:scopesArray + refreshToken:kRefreshTokenTestValue + codeVerifier:authResponse.request.codeVerifier + additionalParameters:additionalParameters + additionalHeaders:additionalHeaders]; + return request; +} + ++ (OIDTokenRequest *)testInstanceAdditionalHeaders { + OIDAuthorizationResponse *authResponse = [OIDAuthorizationResponseTests testInstance]; + NSArray *scopesArray = + [OIDScopeUtilities scopesArrayWithString:authResponse.request.scope]; + NSDictionary *additionalParameters = + @{ kTestAdditionalParameterKey : kTestAdditionalParameterValue }; + NSDictionary *additionalHeaders = @{ + kTestAdditionalHeaderKey : kTestAdditionalHeaderValue, + kTestAdditionalHeaderKey2 : kTestAdditionalHeaderValue2 + }; + OIDTokenRequest *request = [[OIDTokenRequest alloc] initWithConfiguration:authResponse.request.configuration grantType:OIDGrantTypeAuthorizationCode @@ -116,7 +183,8 @@ + (OIDTokenRequest *)testInstanceRefresh { scopes:scopesArray refreshToken:kRefreshTokenTestValue codeVerifier:authResponse.request.codeVerifier - additionalParameters:additionalParameters]; + additionalParameters:additionalParameters + additionalHeaders:additionalHeaders]; return request; } @@ -128,37 +196,58 @@ - (void)testCopying { OIDTokenRequest *request = [[self class] testInstance]; XCTAssertEqualObjects(request.configuration.authorizationEndpoint, - authResponse.request.configuration.authorizationEndpoint); - XCTAssertEqualObjects(request.grantType, OIDGrantTypeAuthorizationCode); - XCTAssertEqualObjects(request.authorizationCode, authResponse.authorizationCode); - XCTAssertEqualObjects(request.redirectURL, authResponse.request.redirectURL); - XCTAssertEqualObjects(request.clientID, authResponse.request.clientID); - XCTAssertEqualObjects(request.clientSecret, authResponse.request.clientSecret); - XCTAssertEqualObjects(request.scope, authResponse.request.scope); - XCTAssertEqualObjects(request.refreshToken, kRefreshTokenTestValue); - XCTAssertEqualObjects(request.codeVerifier, authResponse.request.codeVerifier); - XCTAssertNotNil(request.additionalParameters); + authResponse.request.configuration.authorizationEndpoint, + @"Request and response authorization endpoints should be equal."); + XCTAssertEqualObjects(request.grantType, OIDGrantTypeAuthorizationCode, + @"Request grant type should be OIDGrantTypeAuthorizationCode."); + XCTAssertEqualObjects(request.authorizationCode, authResponse.authorizationCode, + @"Request and response authorization codes should be equal."); + XCTAssertEqualObjects(request.redirectURL, authResponse.request.redirectURL, + @"Request and response redirectURLs should be equal."); + XCTAssertEqualObjects(request.clientID, authResponse.request.clientID, + @"Request and response clientIDs should be equal."); + XCTAssertEqualObjects(request.clientSecret, authResponse.request.clientSecret, + @"Request and response clientSecrets should be equal."); + XCTAssertEqualObjects(request.scope, authResponse.request.scope, + @"Request and response scope values should be equal."); + XCTAssertEqualObjects(request.refreshToken, kRefreshTokenTestValue, + @"Request refreshToken should be equal to kRefreshTokenTestValue."); + XCTAssertEqualObjects(request.codeVerifier, authResponse.request.codeVerifier, + @"Request and response codeVerifiers should be equal."); + XCTAssertNotNil(request.additionalParameters, + @"Request's additionalParameters field should not be nil."); XCTAssertEqualObjects(request.additionalParameters[kTestAdditionalParameterKey], - kTestAdditionalParameterValue); + kTestAdditionalParameterValue, + @"The request's kTestAdditionalParameterKey additional parameter should " + "be equal to kTestAdditionalParameterValue."); + XCTAssertNotNil(request.additionalHeaders, + @"Request's additionalHeaders field should not be nil."); + XCTAssertEqualObjects(request.additionalHeaders[kTestAdditionalHeaderKey], + kTestAdditionalHeaderValue, + @"The request's kTestAdditionalHeaderKey additional parameter should " + "be equal to kTestAdditionalHeaderValue."); OIDTokenRequest *requestCopy = [request copy]; // Not a full test of the configuration deserialization, but should be sufficient as a smoke test // to make sure the configuration IS actually getting carried along in the copy implementation. XCTAssertEqualObjects(requestCopy.configuration.authorizationEndpoint, - request.configuration.authorizationEndpoint); - - XCTAssertEqualObjects(requestCopy.grantType, request.grantType); - XCTAssertEqualObjects(requestCopy.authorizationCode, request.authorizationCode); - XCTAssertEqualObjects(requestCopy.redirectURL, request.redirectURL); - XCTAssertEqualObjects(requestCopy.clientID, request.clientID); - XCTAssertEqualObjects(requestCopy.clientSecret, request.clientSecret); - XCTAssertEqualObjects(requestCopy.scope, authResponse.request.scope); - XCTAssertEqualObjects(requestCopy.refreshToken, kRefreshTokenTestValue); - XCTAssertEqualObjects(requestCopy.codeVerifier, authResponse.request.codeVerifier); - XCTAssertNotNil(requestCopy.additionalParameters); + request.configuration.authorizationEndpoint, @""); + + XCTAssertEqualObjects(requestCopy.grantType, request.grantType, @""); + XCTAssertEqualObjects(requestCopy.authorizationCode, request.authorizationCode, @""); + XCTAssertEqualObjects(requestCopy.redirectURL, request.redirectURL, @""); + XCTAssertEqualObjects(requestCopy.clientID, request.clientID, @""); + XCTAssertEqualObjects(requestCopy.clientSecret, request.clientSecret, @""); + XCTAssertEqualObjects(requestCopy.scope, authResponse.request.scope, @""); + XCTAssertEqualObjects(requestCopy.refreshToken, kRefreshTokenTestValue, @""); + XCTAssertEqualObjects(requestCopy.codeVerifier, authResponse.request.codeVerifier, @""); + XCTAssertNotNil(requestCopy.additionalParameters, @""); XCTAssertEqualObjects(requestCopy.additionalParameters[kTestAdditionalParameterKey], - kTestAdditionalParameterValue); + kTestAdditionalParameterValue, @""); + XCTAssertNotNil(requestCopy.additionalHeaders, @""); + XCTAssertEqualObjects(requestCopy.additionalHeaders[kTestAdditionalHeaderKey], + kTestAdditionalHeaderValue, @""); } /*! @brief Tests the @c NSSecureCoding by round-tripping an instance through the coding process and @@ -169,40 +258,67 @@ - (void)testSecureCoding { OIDTokenRequest *request = [[self class] testInstance]; XCTAssertEqualObjects(request.configuration.authorizationEndpoint, - authResponse.request.configuration.authorizationEndpoint); - XCTAssertEqualObjects(request.grantType, OIDGrantTypeAuthorizationCode); - XCTAssertEqualObjects(request.authorizationCode, authResponse.authorizationCode); - XCTAssertEqualObjects(request.redirectURL, authResponse.request.redirectURL); - XCTAssertEqualObjects(request.clientID, authResponse.request.clientID); - XCTAssertEqualObjects(request.clientSecret, authResponse.request.clientSecret); - XCTAssertEqualObjects(request.scope, authResponse.request.scope); - XCTAssertEqualObjects(request.refreshToken, kRefreshTokenTestValue); - XCTAssertEqualObjects(request.codeVerifier, authResponse.request.codeVerifier); - XCTAssertNotNil(request.additionalParameters); + authResponse.request.configuration.authorizationEndpoint, @""); + XCTAssertEqualObjects(request.grantType, OIDGrantTypeAuthorizationCode, @""); + XCTAssertEqualObjects(request.authorizationCode, authResponse.authorizationCode, @""); + XCTAssertEqualObjects(request.redirectURL, authResponse.request.redirectURL, @""); + XCTAssertEqualObjects(request.clientID, authResponse.request.clientID, @""); + XCTAssertEqualObjects(request.clientSecret, authResponse.request.clientSecret, @""); + XCTAssertEqualObjects(request.scope, authResponse.request.scope, @""); + XCTAssertEqualObjects(request.refreshToken, kRefreshTokenTestValue, @""); + XCTAssertEqualObjects(request.codeVerifier, authResponse.request.codeVerifier, @""); + XCTAssertNotNil(request.additionalParameters, @""); XCTAssertEqualObjects(request.additionalParameters[kTestAdditionalParameterKey], - kTestAdditionalParameterValue); + kTestAdditionalParameterValue, @""); + XCTAssertNotNil(request.additionalHeaders, @""); + XCTAssertEqualObjects(request.additionalHeaders[kTestAdditionalHeaderKey], + kTestAdditionalHeaderValue, @""); + + NSURLRequest *urlRequest = [request URLRequest]; + XCTAssertEqualObjects([urlRequest.allHTTPHeaderFields objectForKey:kTestAdditionalHeaderKey], + kTestAdditionalHeaderValue); - NSData *data = [NSKeyedArchiver archivedDataWithRootObject:request]; - OIDTokenRequest *requestCopy = [NSKeyedUnarchiver unarchiveObjectWithData:data]; + OIDTokenRequest *requestCopy; + NSError *error; + NSData *data; + if (@available(iOS 12.0, macOS 10.13, tvOS 11.0, watchOS 4.0, *)) { + data = [NSKeyedArchiver archivedDataWithRootObject:request + requiringSecureCoding:YES + error:&error]; + requestCopy = [NSKeyedUnarchiver unarchivedObjectOfClass:[OIDTokenRequest class] + fromData:data + error:&error]; + } else { +#if !TARGET_OS_IOS + data = [NSKeyedArchiver archivedDataWithRootObject:request]; + requestCopy = [NSKeyedUnarchiver unarchiveObjectWithData:data]; +#endif + } // Not a full test of the configuration deserialization, but should be sufficient as a smoke test // to make sure the configuration IS actually getting serialized and deserialized in the // NSSecureCoding implementation. We'll leave it up to the OIDServiceConfiguration tests to make // sure the NSSecureCoding implementation of that class is correct. XCTAssertEqualObjects(requestCopy.configuration.authorizationEndpoint, - request.configuration.authorizationEndpoint); - - XCTAssertEqualObjects(requestCopy.grantType, request.grantType); - XCTAssertEqualObjects(requestCopy.authorizationCode, request.authorizationCode); - XCTAssertEqualObjects(requestCopy.redirectURL, request.redirectURL); - XCTAssertEqualObjects(requestCopy.clientID, request.clientID); - XCTAssertEqualObjects(requestCopy.clientSecret, request.clientSecret); - XCTAssertEqualObjects(requestCopy.scope, authResponse.request.scope); - XCTAssertEqualObjects(requestCopy.refreshToken, kRefreshTokenTestValue); - XCTAssertEqualObjects(requestCopy.codeVerifier, authResponse.request.codeVerifier); - XCTAssertNotNil(requestCopy.additionalParameters); + request.configuration.authorizationEndpoint, @""); + XCTAssertEqualObjects(requestCopy.grantType, request.grantType, @""); + XCTAssertEqualObjects(requestCopy.authorizationCode, request.authorizationCode, @""); + XCTAssertEqualObjects(requestCopy.redirectURL, request.redirectURL, @""); + XCTAssertEqualObjects(requestCopy.clientID, request.clientID, @""); + XCTAssertEqualObjects(requestCopy.clientSecret, request.clientSecret, @""); + XCTAssertEqualObjects(requestCopy.scope, authResponse.request.scope, @""); + XCTAssertEqualObjects(requestCopy.refreshToken, kRefreshTokenTestValue, @""); + XCTAssertEqualObjects(requestCopy.codeVerifier, authResponse.request.codeVerifier, @""); + XCTAssertNotNil(requestCopy.additionalParameters, @""); XCTAssertEqualObjects(requestCopy.additionalParameters[kTestAdditionalParameterKey], - kTestAdditionalParameterValue); + kTestAdditionalParameterValue, @""); + XCTAssertNotNil(requestCopy.additionalHeaders, @""); + XCTAssertEqualObjects(requestCopy.additionalHeaders[kTestAdditionalHeaderKey], + kTestAdditionalHeaderValue, @""); + + NSURLRequest *urlrequestCopy = [requestCopy URLRequest]; + XCTAssertEqualObjects([urlrequestCopy.allHTTPHeaderFields objectForKey:kTestAdditionalHeaderKey], + kTestAdditionalHeaderValue); } - (void)testURLRequestNoClientAuth { @@ -221,4 +337,38 @@ - (void)testURLRequestBasicClientAuth { XCTAssertNotNil(authorization); } +- (void)testAuthorizationCodeNullRedirectURL { + OIDAuthorizationResponse *authResponse = [OIDAuthorizationResponseTests testInstance]; + NSArray *scopesArray = + [OIDScopeUtilities scopesArrayWithString:authResponse.request.scope]; + NSDictionary *additionalParameters = + @{ kTestAdditionalParameterKey : kTestAdditionalParameterValue }; + NSDictionary *additionalHeaders = + @{ kTestAdditionalHeaderKey : kTestAdditionalHeaderValue }; + XCTAssertThrows([[OIDTokenRequest alloc] initWithConfiguration:authResponse.request.configuration + grantType:OIDGrantTypeAuthorizationCode + authorizationCode:authResponse.authorizationCode + redirectURL:nil + clientID:authResponse.request.clientID + clientSecret:authResponse.request.clientSecret + scopes:scopesArray + refreshToken:kRefreshTokenTestValue + codeVerifier:authResponse.request.codeVerifier + additionalParameters:additionalParameters + additionalHeaders:additionalHeaders], @""); +} + +- (void)testThatAdditionalHeadersAreInTokenRequest { + OIDTokenRequest *request = [[self class] testInstanceAdditionalHeaders]; + NSURLRequest* urlRequest = [request URLRequest]; + + XCTAssertEqualObjects([urlRequest.allHTTPHeaderFields objectForKey:kTestAdditionalHeaderKey], + kTestAdditionalHeaderValue); + + XCTAssertEqualObjects([urlRequest.allHTTPHeaderFields objectForKey:kTestAdditionalHeaderKey2], + kTestAdditionalHeaderValue2); +} + @end + +#pragma GCC diagnostic pop diff --git a/UnitTests/OIDTokenResponseTests.m b/UnitTests/OIDTokenResponseTests.m index 57a96de85..cce020b73 100644 --- a/UnitTests/OIDTokenResponseTests.m +++ b/UnitTests/OIDTokenResponseTests.m @@ -19,8 +19,18 @@ #import "OIDAuthorizationResponseTests.h" #import "OIDTokenRequestTests.h" -#import "Source/OIDTokenRequest.h" -#import "Source/OIDTokenResponse.h" + +#if SWIFT_PACKAGE +@import AppAuthCore; +#else +#import "Sources/AppAuthCore/OIDTokenRequest.h" +#import "Sources/AppAuthCore/OIDTokenResponse.h" +#endif + +// Ignore warnings about "Use of GNU statement expression extension" which is raised by our use of +// the XCTAssert___ macros. +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wgnu" /*! @brief The key for the @c accessToken property in the incoming parameters and for @c NSSecureCoding. @@ -133,31 +143,31 @@ + (OIDTokenResponse *)testInstanceRefresh { */ - (void)testCopying { OIDTokenResponse *response = [[self class] testInstance]; - XCTAssertNotNil(response.request); - XCTAssertEqualObjects(response.accessToken, kAccessTokenTestValue); - XCTAssertEqualObjects(response.tokenType, kTokenTypeTestValue); - XCTAssertEqualObjects(response.idToken, kIDTokenTestValue); - XCTAssertEqualObjects(response.refreshToken, kRefreshTokenTestValue); - XCTAssertEqualObjects(response.scope, kScopesTestValue); + XCTAssertNotNil(response.request, @""); + XCTAssertEqualObjects(response.accessToken, kAccessTokenTestValue, @""); + XCTAssertEqualObjects(response.tokenType, kTokenTypeTestValue, @""); + XCTAssertEqualObjects(response.idToken, kIDTokenTestValue, @""); + XCTAssertEqualObjects(response.refreshToken, kRefreshTokenTestValue, @""); + XCTAssertEqualObjects(response.scope, kScopesTestValue, @""); XCTAssertEqualObjects(response.additionalParameters[kTestAdditionalParameterKey], - kTestAdditionalParameterValue); + kTestAdditionalParameterValue, @""); // Should be ~ kTestExpirationSeconds seconds. Avoiding swizzling NSDate here for certainty // to keep dependencies down, and simply making an assumption that this check will be executed // relatively quickly after the initialization above (less than 5 seconds.) NSTimeInterval expiration = [response.accessTokenExpirationDate timeIntervalSinceNow]; - XCTAssert(expiration > kExpiresInTestValue - 5 && expiration <= kExpiresInTestValue); + XCTAssert(expiration > kExpiresInTestValue - 5 && expiration <= kExpiresInTestValue, @""); OIDTokenResponse *responseCopy = [response copy]; - XCTAssertNotNil(responseCopy.request); - XCTAssertEqualObjects(responseCopy.accessToken, kAccessTokenTestValue); - XCTAssertEqualObjects(responseCopy.tokenType, kTokenTypeTestValue); - XCTAssertEqualObjects(responseCopy.idToken, kIDTokenTestValue); - XCTAssertEqualObjects(responseCopy.refreshToken, kRefreshTokenTestValue); - XCTAssertEqualObjects(responseCopy.scope, kScopesTestValue); + XCTAssertNotNil(responseCopy.request, @""); + XCTAssertEqualObjects(responseCopy.accessToken, kAccessTokenTestValue, @""); + XCTAssertEqualObjects(responseCopy.tokenType, kTokenTypeTestValue, @""); + XCTAssertEqualObjects(responseCopy.idToken, kIDTokenTestValue, @""); + XCTAssertEqualObjects(responseCopy.refreshToken, kRefreshTokenTestValue, @""); + XCTAssertEqualObjects(responseCopy.scope, kScopesTestValue, @""); XCTAssertEqualObjects(responseCopy.additionalParameters[kTestAdditionalParameterKey], - kTestAdditionalParameterValue); + kTestAdditionalParameterValue, @""); } /*! @brief Tests the @c NSSecureCoding by round-tripping an instance through the coding process and @@ -165,23 +175,39 @@ - (void)testCopying { */ - (void)testSecureCoding { OIDTokenResponse *response = [[self class] testInstance]; - NSData *data = [NSKeyedArchiver archivedDataWithRootObject:response]; - OIDTokenResponse *responseCopy = [NSKeyedUnarchiver unarchiveObjectWithData:data]; + OIDTokenResponse *responseCopy; + NSError *error; + NSData *data; + if (@available(iOS 12.0, macOS 10.13, tvOS 11.0, watchOS 4.0, *)) { + data = [NSKeyedArchiver archivedDataWithRootObject:response + requiringSecureCoding:YES + error:&error]; + responseCopy = [NSKeyedUnarchiver unarchivedObjectOfClass:[OIDTokenResponse class] + fromData:data + error:&error]; + } else { +#if !TARGET_OS_IOS + data = [NSKeyedArchiver archivedDataWithRootObject:response]; + responseCopy = [NSKeyedUnarchiver unarchiveObjectWithData:data]; +#endif + } // Not a full test of the request deserialization, but should be sufficient as a smoke test // to make sure the request IS actually getting serialized and deserialized in the // NSSecureCoding implementation. We'll leave it up to the OIDAuthorizationRequest tests to make // sure the NSSecureCoding implementation of that class is correct. - XCTAssertNotNil(responseCopy.request); - XCTAssertEqualObjects(responseCopy.request.clientID, response.request.clientID); - - XCTAssertEqualObjects(responseCopy.accessToken, kAccessTokenTestValue); - XCTAssertEqualObjects(responseCopy.tokenType, kTokenTypeTestValue); - XCTAssertEqualObjects(responseCopy.idToken, kIDTokenTestValue); - XCTAssertEqualObjects(responseCopy.refreshToken, kRefreshTokenTestValue); - XCTAssertEqualObjects(responseCopy.scope, kScopesTestValue); + XCTAssertNotNil(responseCopy.request, @""); + XCTAssertEqualObjects(responseCopy.request.clientID, response.request.clientID, @""); + + XCTAssertEqualObjects(responseCopy.accessToken, kAccessTokenTestValue, @""); + XCTAssertEqualObjects(responseCopy.tokenType, kTokenTypeTestValue, @""); + XCTAssertEqualObjects(responseCopy.idToken, kIDTokenTestValue, @""); + XCTAssertEqualObjects(responseCopy.refreshToken, kRefreshTokenTestValue, @""); + XCTAssertEqualObjects(responseCopy.scope, kScopesTestValue, @""); XCTAssertEqualObjects(responseCopy.additionalParameters[kTestAdditionalParameterKey], - kTestAdditionalParameterValue); + kTestAdditionalParameterValue, @""); } @end + +#pragma GCC diagnostic pop diff --git a/UnitTests/OIDTokenUtilitiesTests.m b/UnitTests/OIDTokenUtilitiesTests.m new file mode 100644 index 000000000..783e81273 --- /dev/null +++ b/UnitTests/OIDTokenUtilitiesTests.m @@ -0,0 +1,56 @@ +/*! @file OIDTokenUtilities.m + @brief AppAuth iOS SDK + @copyright + Copyright 2018 The AppAuth for iOS Authors. All Rights Reserved. + @copydetails + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#import + +#if SWIFT_PACKAGE +@import AppAuthCore; +#else +#import "Sources/AppAuthCore/OIDTokenUtilities.h" +#endif + +@interface OIDTokenUtilitiesTests : XCTestCase +@end +@implementation OIDTokenUtilitiesTests + +- (void)testRedact { + XCTAssertEqualObjects([OIDTokenUtilities redact:@"0123456789"], @"012345...[redacted]", @""); +} + +- (void)testRedactWithNilParamater { + XCTAssertEqualObjects([OIDTokenUtilities redact:nil], nil, @""); +} + +- (void)testRedactWithEmptyString { + XCTAssertEqualObjects([OIDTokenUtilities redact:@""], @"", @""); +} + +- (void)testRedactWithShortInput { + XCTAssertEqualObjects([OIDTokenUtilities redact:@"01234"], @"[redacted]", @""); +} + +- (void)testFormUrlEncode { + XCTAssertEqualObjects([OIDTokenUtilities formUrlEncode:@"t _9V-F*I+Z1Lk.u7:2/8L+w="], + @"t+_9V-F*I%2BZ1Lk.u7%3A2%2F8L%2Bw%3D", @""); +} + +- (void)testFormUrlEncodeEmptyString { + XCTAssertEqualObjects([OIDTokenUtilities formUrlEncode:@""], @"", @""); +} + +@end diff --git a/UnitTests/OIDURLQueryComponentTests.m b/UnitTests/OIDURLQueryComponentTests.m index d854226d6..eac017c43 100644 --- a/UnitTests/OIDURLQueryComponentTests.m +++ b/UnitTests/OIDURLQueryComponentTests.m @@ -18,7 +18,16 @@ #import "OIDURLQueryComponentTests.h" -#import "Source/OIDURLQueryComponent.h" +#if SWIFT_PACKAGE +@import AppAuthCore; +#else +#import "Sources/AppAuthCore/OIDURLQueryComponent.h" +#endif + +// Ignore warnings about "Use of GNU statement expression extension" which is raised by our use of +// the XCTAssert___ macros. +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wgnu" /*! @brief A testing parameter name. */ @@ -72,21 +81,103 @@ - (void)testAddingParameter { OIDURLQueryComponent *query = [[OIDURLQueryComponent alloc] init]; [query addParameter:kTestParameterName value:kTestParameterValue]; XCTAssertEqualObjects([query valuesForParameter:kTestParameterName].firstObject, - kTestParameterValue); + kTestParameterValue, @""); +} + +/*! @brief Test that URI query items are decoded correctly, using application/x-www-form-urlencoded + encoding. + @see https://tools.ietf.org/html/rfc6749#section-4.1.2 + @see https://tools.ietf.org/html/rfc6749#appendix-B + */ +- (void)test_formurlencoded_decoding { + // Authorization response URL template + NSString *responseURLtemplate = @"com.example.apps.1234-tepulg5joaks7:/?state=z634l182&code=4/WQA" + "stm4iiN_0Qi-n4mEo-jL-85CvQ&scope=%@&authuser=0&session_state=ab78c20&prompt=consent#"; + + NSString *expectedDecodedScope = + @"https://www.example.com/auth/userinfo.email https://www.example.com/auth/userinfo.profile"; + + // Tests an encoded scope with a '+'-encoded space + { + NSString* encodedScope = + @"https://www.example.com/auth/userinfo.email+https://www.example.com/auth/userinfo.profile"; + NSString *authorizationResponse = [NSString stringWithFormat:responseURLtemplate,encodedScope]; + OIDURLQueryComponent *query = + [[OIDURLQueryComponent alloc] initWithURL:[NSURL URLWithString:authorizationResponse]]; + NSString* value = [query valuesForParameter:@"scope"][0]; + XCTAssertEqualObjects(value, + expectedDecodedScope, + @"Failed to decode scope with '+' delimiter"); + } + // Tests an encoded scope with a '%20'-encoded space + { + NSString* encodedScope = + @"https://www.example.com/auth/userinfo.email%20https://www.example.com/auth/userinfo.profile"; + NSString *authorizationResponse = [NSString stringWithFormat:responseURLtemplate,encodedScope]; + OIDURLQueryComponent *query = + [[OIDURLQueryComponent alloc] initWithURL:[NSURL URLWithString:authorizationResponse]]; + NSString* value = [query valuesForParameter:@"scope"][0]; + XCTAssertEqualObjects(value, + expectedDecodedScope, + @"Failed to decode scope with '%%20' delimiter"); + } + // Tests that the example string from RFC6749 Appendix B is decoded correctly + { + NSString* encodedScope = @"+%25%26%2B%C2%A3%E2%82%AC"; + NSString *authorizationResponse = [NSString stringWithFormat:responseURLtemplate,encodedScope]; + OIDURLQueryComponent *query = + [[OIDURLQueryComponent alloc] initWithURL:[NSURL URLWithString:authorizationResponse]]; + NSString* value = [query valuesForParameter:@"scope"][0]; + XCTAssertEqualObjects(value, + @" %&+£€", + @"Failed to decode RFC6749 Appendix B sample string correctly."); + } +} + +/*! @brief Test that URI query items are encoded correctly, using application/x-www-form-urlencoded + encoding. Note that AppAuth always encodes "+" as "%20" (as permitted) to reduce + ambiguity. + @see https://tools.ietf.org/html/rfc6749#section-4.1.3 + @see https://tools.ietf.org/html/rfc6749#appendix-B + */ +- (void)test_formurlencoded_encoding { + NSURL *baseURL = [NSURL URLWithString:kTestURLRoot]; + // Tests that space is encoded as %20 + { + OIDURLQueryComponent *query = [[OIDURLQueryComponent alloc] initWithURL:baseURL]; + [query addParameter:@"scope" value:@"openid profile"]; + NSString *encodedParams = [query URLEncodedParameters]; + NSString *expected = @"scope=openid%20profile"; + XCTAssertEqualObjects(encodedParams, + expected, + @"Failed to encode space as %%20."); + } + // Tests that the example string from RFC6749 Appendix B is encoded correctly (but with space + // encoded as %20, not +, as allowed by application/x-www-form-urlencoded. + { + OIDURLQueryComponent *query = [[OIDURLQueryComponent alloc] initWithURL:baseURL]; + [query addParameter:@"scope" value:@" %&+£€"]; + // Tests the URLEncodedParameters method + NSString *encodedParams = [query URLEncodedParameters]; + NSString *expected = @"scope=%20%25%26%2B%C2%A3%E2%82%AC"; + XCTAssertEqualObjects(encodedParams, + expected, + @"Failed to encode RFC6749 Appendix B sample string correctly."); + } } - (void)testAddingTwoParameters { OIDURLQueryComponent *query = [[OIDURLQueryComponent alloc] init]; [query addParameter:kTestParameterName value:kTestParameterValue]; XCTAssertEqualObjects([query valuesForParameter:kTestParameterName].firstObject, - kTestParameterValue); + kTestParameterValue, @""); [query addParameter:kTestParameterName value:kTestParameterValue2]; NSArray *values = [query valuesForParameter:kTestParameterName]; - XCTAssertNotNil(values); - XCTAssertEqual(values.count, 2); - XCTAssertEqualObjects(values.firstObject, kTestParameterValue); - XCTAssertEqualObjects(values[1], kTestParameterValue2); + XCTAssertNotNil(values, @""); + XCTAssertEqual(values.count, 2, @""); + XCTAssertEqualObjects(values.firstObject, kTestParameterValue, @""); + XCTAssertEqualObjects(values[1], kTestParameterValue2, @""); } /* @brief Tests the application/x-www-form-urlencoded encoding. @@ -100,34 +191,34 @@ - (void)testURLEncodedParameters { // Tests the URLEncodedParameters method NSString *encodedParams = [query URLEncodedParameters]; NSString *expected = [NSString stringWithFormat:@"%@=%@", kTestParameterName, kEncodingTestEncoded]; - XCTAssertEqualObjects(encodedParams, expected); + XCTAssertEqualObjects(encodedParams, expected, @""); // Tests that params are correctly encoded when using URLByReplacingQueryInURL NSURL *url = [query URLByReplacingQueryInURL:baseURL]; NSString* expectedURL = [NSString stringWithFormat:@"%@?%@", kTestURLRoot, expected]; - XCTAssertEqualObjects([url absoluteString], expectedURL); + XCTAssertEqualObjects([url absoluteString], expectedURL, @""); } - (void)testAddingThreeParameters { OIDURLQueryComponent *query = [[OIDURLQueryComponent alloc] init]; [query addParameter:kTestParameterName value:kTestParameterValue]; XCTAssertEqualObjects([query valuesForParameter:kTestParameterName].firstObject, - kTestParameterValue); + kTestParameterValue, @""); [query addParameter:kTestParameterName value:kTestParameterValue2]; [query addParameter:kTestParameterName value:kTestParameterValue]; NSArray *values = [query valuesForParameter:kTestParameterName]; - XCTAssertNotNil(values); - XCTAssertEqual(values.count, 3); - XCTAssertEqualObjects(values.firstObject, kTestParameterValue); - XCTAssertEqualObjects(values[1], kTestParameterValue2); - XCTAssertEqualObjects(values[2], kTestParameterValue); + XCTAssertNotNil(values, @""); + XCTAssertEqual(values.count, 3, @""); + XCTAssertEqualObjects(values.firstObject, kTestParameterValue, @""); + XCTAssertEqualObjects(values[1], kTestParameterValue2, @""); + XCTAssertEqualObjects(values[2], kTestParameterValue, @""); NSDictionary *> *parametersAsDictionary = @{ kTestParameterName : @[ kTestParameterValue, kTestParameterValue2, kTestParameterValue ] }; - XCTAssertEqualObjects(query.dictionaryValue, parametersAsDictionary); + XCTAssertEqualObjects(query.dictionaryValue, parametersAsDictionary, @""); } - (void)testBuildingParameterStringWithSimpleParameters { @@ -143,12 +234,13 @@ - (void)testBuildingParameterStringWithSimpleParameters { NSURL *rootURLWithParameters = [query URLByReplacingQueryInURL:rootURL]; XCTAssert([rootURLWithParameters.query isEqualToString:kTestSimpleParameterStringEncoded] - || [rootURLWithParameters.query isEqualToString:kTestSimpleParameterStringEncodedRev]); + || [rootURLWithParameters.query isEqualToString:kTestSimpleParameterStringEncodedRev], + @""); OIDURLQueryComponent *parsedParameters = [[OIDURLQueryComponent alloc] initWithURL:rootURLWithParameters]; - XCTAssertEqualObjects(parsedParameters.dictionaryValue, parameters); + XCTAssertEqualObjects(parsedParameters.dictionaryValue, parameters, @""); } - (void)testParsingQueryString { @@ -163,7 +255,9 @@ - (void)testParsingQueryString { kTestParameterName2 : kTestParameterValue2 }; - XCTAssertEqualObjects(query.dictionaryValue, parameters); + XCTAssertEqualObjects(query.dictionaryValue, parameters, @""); } @end + +#pragma GCC diagnostic pop diff --git a/UnitTests/OIDURLQueryComponentTestsIOS7.m b/UnitTests/OIDURLQueryComponentTestsIOS7.m index 5f4d8ae4e..571f61bba 100644 --- a/UnitTests/OIDURLQueryComponentTestsIOS7.m +++ b/UnitTests/OIDURLQueryComponentTestsIOS7.m @@ -18,7 +18,16 @@ #import "OIDURLQueryComponentTests.h" -#import "Source/OIDURLQueryComponent.h" +#if SWIFT_PACKAGE +@import AppAuthCore; +#else +#import "Sources/AppAuthCore/OIDURLQueryComponent.h" +#endif + +// Ignore warnings about "Use of GNU statement expression extension" which is raised by our use of +// the XCTAssert___ macros. +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wgnu" @interface OIDURLQueryComponentTestsIOS7 : OIDURLQueryComponentTests @end @@ -36,3 +45,5 @@ - (void)tearDown { } @end + +#pragma GCC diagnostic pop diff --git a/UnitTests/OIDURLSessionProviderTests.m b/UnitTests/OIDURLSessionProviderTests.m index 193d7cddf..4bd02f361 100644 --- a/UnitTests/OIDURLSessionProviderTests.m +++ b/UnitTests/OIDURLSessionProviderTests.m @@ -23,7 +23,7 @@ @interface OIDURLSessionProviderTests : XCTestCase @end -/*! @brief Unit tests for @c OIDURLSessionProvider +/*! @brief Unit tests for @c OIDURLSessionProvider. */ @implementation OIDURLSessionProviderTests