├── .github
├── ISSUE_TEMPLATE
│ ├── bug_report.md
│ ├── config.yml
│ └── feature_request.md
└── workflows
│ ├── builds.yml
│ ├── publish-docs.yml
│ ├── scorecards.yml
│ └── tests.yml
├── .gitignore
├── CHANGELOG.md
├── CONTRIBUTING.md
├── Examples
├── Example-iOS
│ ├── .gitignore
│ ├── Example-iOS.xcodeproj
│ │ └── project.pbxproj
│ ├── Example-iOSForPod.xcodeproj
│ │ └── project.pbxproj
│ ├── Podfile
│ ├── README.md
│ └── Source
│ │ ├── AppDelegate.h
│ │ ├── AppDelegate.m
│ │ ├── Assets.xcassets
│ │ └── AppIcon.appiconset
│ │ │ └── Contents.json
│ │ ├── Base.lproj
│ │ └── LaunchScreen.storyboard
│ │ ├── GTMAppAuthExampleViewController.h
│ │ ├── GTMAppAuthExampleViewController.m
│ │ ├── GTMAppAuthExampleViewController.xib
│ │ ├── Info.plist
│ │ └── main.m
└── Example-macOS
│ ├── .gitignore
│ ├── Example-macOS.entitlements
│ ├── Example-macOS.xcodeproj
│ └── project.pbxproj
│ ├── Example-macOSForPod.xcodeproj
│ └── project.pbxproj
│ ├── Podfile
│ ├── README.md
│ └── Source
│ ├── AppDelegate.h
│ ├── AppDelegate.m
│ ├── Assets.xcassets
│ └── AppIcon.appiconset
│ │ └── Contents.json
│ ├── Base.lproj
│ ├── GTMAppAuthExampleViewController.xib
│ └── MainMenu.xib
│ ├── GTMAppAuthExampleViewController.h
│ ├── GTMAppAuthExampleViewController.m
│ ├── Info.plist
│ └── main.m
├── GTMAppAuth.podspec
├── GTMAppAuth
├── Sources
│ ├── AuthSession.swift
│ ├── AuthSessionDelegate.swift
│ ├── AuthSessionStore.swift
│ ├── KeychainStore
│ │ ├── GTMOAuth2Compatibility.swift
│ │ ├── KeychainAttribute.swift
│ │ ├── KeychainHelper.swift
│ │ └── KeychainStore.swift
│ └── Resources
│ │ └── PrivacyInfo.xcprivacy
└── Tests
│ ├── Helpers
│ ├── AuthorizationTestingHelp.swift
│ ├── GTMAppAuthTestingHelpers.swift
│ ├── KeychainHelperFake.swift
│ ├── OIDAuthStateTesting.swift
│ ├── OIDAuthorizationRequestTesting.swift
│ ├── OIDAuthorizationResponseTesting.swift
│ ├── OIDRegistrationRequestTesting.swift
│ ├── OIDRegistrationResponseTesting.swift
│ ├── OIDServiceConfigurationTesting.swift
│ ├── OIDTokenRequestTesting.swift
│ └── OIDTokenResponseTesting.swift
│ ├── ObjCIntegration
│ ├── GTMAuthSessionTests.m
│ └── KeychainStore
│ │ ├── GTMKeychainStoreTests.m
│ │ └── GTMOAuth2CompatibilityTests.m
│ └── Unit
│ ├── AuthSessionTests.swift
│ └── KeychainStore
│ ├── GTMOAuth2CompatibilityTests.swift
│ └── KeychainStoreTests.swift
├── Gemfile
├── Gemfile.lock
├── LICENSE
├── Package.swift
└── README.md
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug Report
3 | about: Submit a bug report if something isn't working as expected.
4 | title: ""
5 | labels: bug, triage
6 | assignees: ""
7 | ---
8 |
9 | **Describe the bug**
10 | A clear and concise description of what the bug is.
11 |
12 | **To Reproduce**
13 | Steps to reproduce the behavior:
14 | 1. Go to '...'
15 | 2. Tap on '....'
16 | 3. Scroll down to '....'
17 | 4. See error
18 |
19 | **Expected behavior**
20 | A clear and concise description of what you expected to happen.
21 |
22 | **Screenshots**
23 | If applicable, add screenshots to help explain your problem.
24 |
25 | **Environment**
26 | - Device: [ e.g. iPhone 13, MacBook Pro, etc ]
27 | - OS: [ e.g. iOS 15, macOS 11, etc ]
28 | - Browser: [ e.g. Safari, Chrome, etc ]
29 |
30 | **Additional context**
31 | Add any other context about the problem here.
32 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/config.yml:
--------------------------------------------------------------------------------
1 | blank_issues_enabled: false
2 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature Request
3 | about: Make a feature request if you have a suggestion for something new.
4 | title: ""
5 | labels: enhancement, triage
6 | assignees: ""
7 | ---
8 |
9 | **Is your feature request related to a problem you're having? Please describe.**
10 | A clear and concise description of what the problem is.
11 |
12 | **Describe the solution you'd like**
13 | A clear and concise description of what you want to happen.
14 |
15 | **Describe alternatives you've considered**
16 | A clear and concise description of any alternative solutions or features you've considered.
17 |
18 | **Additional context**
19 | Add any other context or screenshots about the feature request here.
20 |
--------------------------------------------------------------------------------
/.github/workflows/builds.yml:
--------------------------------------------------------------------------------
1 | name: Build GTMAppAuth for Valid Architectures
2 |
3 | on:
4 | schedule:
5 | - cron: '0 8 * * *' # Cron uses UTC; run at nightly at midnight PST
6 | workflow_dispatch:
7 |
8 | jobs:
9 | cron:
10 | runs-on: ${{ matrix.os }}
11 | strategy:
12 | fail-fast: false
13 | matrix:
14 | os: [macos-12]
15 |
16 | steps:
17 | - uses: actions/checkout@v3
18 | - name: Archive for iOS
19 | run: |
20 | xcodebuild \
21 | archive \
22 | -scheme GTMAppAuth \
23 | -destination "generic/platform=iOS"
24 | - name: Archive for macOS
25 | run: |
26 | xcodebuild \
27 | archive \
28 | -scheme GTMAppAuth \
29 | -destination "platform=OS X"
30 | - name: Archive for watchOS
31 | run: |
32 | xcodebuild \
33 | archive \
34 | -scheme GTMAppAuth \
35 | -destination "generic/platform=watchOS"
36 | - name: Archive for tvOS
37 | run: |
38 | xcodebuild \
39 | archive \
40 | -scheme GTMAppAuth \
41 | -destination "generic/platform=tvOS"
42 |
--------------------------------------------------------------------------------
/.github/workflows/publish-docs.yml:
--------------------------------------------------------------------------------
1 | name: Publish API Documentation
2 |
3 | on:
4 | push:
5 | branches:
6 | - petea-docs
7 | workflow_dispatch:
8 |
9 | concurrency:
10 | group: pages
11 | cancel-in-progress: true
12 |
13 | jobs:
14 |
15 | generate:
16 | runs-on: macos-12
17 | steps:
18 | - name: Checkout
19 | uses: actions/checkout@v3
20 | - name: Build DocC Archive
21 | run: |
22 | xcodebuild docbuild \
23 | -scheme GTMAppAuth \
24 | -derivedDataPath /tmp/xcodebuild_output \
25 | -destination 'generic/platform=iOS' \
26 | DOCC_EXTRACT_OBJC_INFO_FOR_SWIFT_SYMBOLS=YES \
27 | SWIFT_INSTALL_OBJC_HEADER=YES
28 | - name: Generate Static Content
29 | run: |
30 | $(xcrun --find docc) process-archive transform-for-static-hosting \
31 | /tmp/xcodebuild_output/Build/Products/Debug-iphoneos/GTMAppAuth.doccarchive \
32 | --output-path /tmp/docs \
33 | --hosting-base-path /GTMAppAuth/
34 | - name: Insert Redirect
35 | run: |
36 | printf "%s\n" \
37 | "" \
38 | "
" \
39 | "" \
40 | "" \
41 | "" \
42 | "" > /tmp/docs/index.html
43 | - name: Upload GitHub Pages Artifact
44 | uses: actions/upload-pages-artifact@v1
45 | with:
46 | path: /tmp/docs
47 |
48 | deploy:
49 | needs: generate
50 | permissions:
51 | pages: write
52 | id-token: write
53 | environment:
54 | name: github-pages
55 | url: ${{ steps.deployment.outputs.page_url }}
56 | runs-on: ubuntu-latest
57 | steps:
58 | - name: Deploy to GitHub Pages
59 | id: deployment
60 | uses: actions/deploy-pages@v1
61 |
--------------------------------------------------------------------------------
/.github/workflows/scorecards.yml:
--------------------------------------------------------------------------------
1 | name: Scorecards supply-chain security
2 | on:
3 | # Only the default branch is supported.
4 | branch_protection_rule:
5 | schedule:
6 | - cron: '20 23 * * 4'
7 | push:
8 | branches: [ "master" ]
9 |
10 | # Declare default permissions as read only.
11 | permissions: read-all
12 |
13 | jobs:
14 | analysis:
15 | name: Scorecards analysis
16 | runs-on: ubuntu-latest
17 | permissions:
18 | # Needed to upload the results to code-scanning dashboard.
19 | security-events: write
20 | # Used to receive a badge.
21 | id-token: write
22 | # Needs for private repositories.
23 | contents: read
24 | actions: read
25 |
26 | steps:
27 | - name: "Checkout code"
28 | uses: actions/checkout@a12a3943b4bdde767164f792f33f40b04645d846 # tag=v3.0.0
29 | with:
30 | persist-credentials: false
31 |
32 | - name: "Run analysis"
33 | uses: ossf/scorecard-action@3e15ea8318eee9b333819ec77a36aca8d39df13e # tag=v1.1.1
34 | with:
35 | results_file: results.sarif
36 | results_format: sarif
37 | # (Optional) Read-only PAT token. Uncomment the `repo_token` line below if:
38 | # - you want to enable the Branch-Protection check on a *public* repository, or
39 | # - you are installing Scorecards on a *private* repository
40 | # To create the PAT, follow the steps in https://github.com/ossf/scorecard-action#authentication-with-pat.
41 | # repo_token: ${{ secrets.SCORECARD_READ_TOKEN }}
42 |
43 | # Publish the results for public repositories to enable scorecard badges. For more details, see
44 | # https://github.com/ossf/scorecard-action#publishing-results.
45 | # For private repositories, `publish_results` will automatically be set to `false`, regardless
46 | # of the value entered here.
47 | publish_results: true
48 |
49 | # Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF
50 | # format to the repository Actions tab.
51 | - name: "Upload artifact"
52 | uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # tag=v4.6.2
53 | with:
54 | name: SARIF file
55 | path: results.sarif
56 | retention-days: 5
57 |
58 | # Upload the results to GitHub's code scanning dashboard.
59 | - name: "Upload to code-scanning"
60 | uses: github/codeql-action/upload-sarif@5f532563584d71fdef14ee64d17bafb34f751ce5 # tag=v1.0.26
61 | with:
62 | sarif_file: results.sarif
63 |
--------------------------------------------------------------------------------
/.github/workflows/tests.yml:
--------------------------------------------------------------------------------
1 | name: tests
2 |
3 | on:
4 | push:
5 | branches:
6 | - master
7 | pull_request:
8 | branches:
9 | - master
10 | workflow_dispatch:
11 |
12 | jobs:
13 |
14 | pod-lib-lint:
15 | runs-on: macos-latest
16 | steps:
17 | - uses: actions/checkout@v3
18 | - name: Update Bundler
19 | run: bundle update --bundler
20 | - name: Install Ruby gems with Bundler
21 | run: bundle install
22 | - name: Lint GTMAppAuth.podspec using local source
23 | run: pod lib lint GTMAppAuth.podspec --verbose
24 |
25 | spm-build-test:
26 | runs-on: macos-latest
27 | steps:
28 | - uses: actions/checkout@v3
29 | - name: Build package
30 | run: swift build
31 | - name: Run Swift tests
32 | run: swift test
33 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # MacOS
2 | .DS_Store
3 |
4 | # CocoaPods
5 | Pods/
6 | gen/
7 |
8 | # Swift Package Manager
9 | Package.pins
10 | Package.resolved
11 | .swiftpm
12 | .build
13 |
14 | # Xcode
15 | *.xcuserstate
16 | *.xcworkspace/
17 | project.xcworkspace/
18 | xcuserdata/
19 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # 5.0.0
2 | - Update keychain default on Mac to be data protected ([#259](https://github.com/google/GTMAppAuth/pull/259))
3 | - Update AppAuth dependency to 2.0.0 ([#262](https://github.com/google/GTMAppAuth/pull/262))
4 | - Fix Swift 6 mode `@Sendable` closure warning ([#263](https://github.com/google/GTMAppAuth/pull/263))
5 |
6 | # 4.1.1
7 | - Fix PrivacyInfo bundling issues ([#241](https://github.com/google/GTMAppAuth/pull/241))
8 |
9 | # 4.1.0
10 | - Fix keychain key bug ([#237](https://github.com/google/GTMAppAuth/pull/237))
11 | - Add privacy manifest ([#239](https://github.com/google/GTMAppAuth/pull/239))
12 |
13 | # 4.0.0
14 |
15 | - Updated `AuthSessionDelegate` method for updating error to take a completion to support async updates [#229](https://github.com/google/GTMAppAuth/pull/229)
16 |
17 | # 3.0.0
18 |
19 | - GTMAppAuth has been translated from Objective-C to Swift. ([#190](https://github.com/google/GTMAppAuth/pull/190))
20 | - Improved API surface. ([#203](https://github.com/google/GTMAppAuth/pull/203))
21 | - Renamed `GTMAppAuthFetcherAuthorization` to `GTMAuthSession`.
22 | - Added `GTMAuthSessionStore` protocol.
23 | - Added `GTMKeychainStore` class conforming to `GTMAuthSessionStore` providing keychain storage of `GTMAuthSession` objects as well as [GTMOAuth2](https://github.com/google/gtm-oauth2) compatibility.
24 | - Added the ability to specify a keychain access group rather than use the default group.
25 | - Stored representation of `GTMAuthSession` objects remains unchanged from that of `GTMAppAuthFetcherAuthorization`
26 | in previous versions of GTMAppAuth, allowing for backward and forward compatibilty.
27 | - Significantly improved error handling.
28 | - Added [`AuthSessionDelegate`](https://github.com/google/GTMAppAuth/pull/224/files#diff-ad81e5244511faaa4b3d98553128438e30a94914c279fb7d42c0acfbbdc24500) protocol allowing clients to 1) provide additional token refresh parameters and 2) inspect and modify errors before they are returned during the authorize request flow.
29 | - Unit tests added. ([#190](https://github.com/google/GTMAppAuth/pull/190), [#202](https://github.com/google/GTMAppAuth/pull/202))
30 |
31 | # 2.0.0
32 |
33 | * Updated the GTMSessionFetcher dependency to allow 3.x versions. ([#192](https://github.com/google/GTMAppAuth/pull/192))
34 | * Minimum deployment versions for iOS and tvOS increased to 10. ([#188](https://github.com/google/GTMAppAuth/pull/188), [#191](https://github.com/google/GTMAppAuth/pull/191))
35 |
36 | # 1.3.1
37 |
38 | * Updated the GTMSessionFetcher dependency to allow 2.x versions. ([#155](https://github.com/google/GTMAppAuth/pull/155), [#175](https://github.com/google/GTMAppAuth/pull/175))
39 | * Use secure coding with `NSKeyedArchiver` when available. ([#145](https://github.com/google/GTMAppAuth/pull/145))
40 |
41 | # 1.3.0
42 |
43 | * Added the option to use the data protection keychain on macOS. ([#151](https://github.com/google/GTMAppAuth/pull/151))
44 | * Unified the keychain access layer, moving macOS to the modern SecItem API. ([#150](https://github.com/google/GTMAppAuth/pull/150))
45 | * Added Swift Package Manager projects for the example apps. ([#153](https://github.com/google/GTMAppAuth/pull/153))
46 |
47 | # 1.2.3
48 |
49 | * Fixed Keychain duplicate entry error on macOS. ([#138](https://github.com/google/GTMAppAuth/pull/138))
50 | * Match GTMSessionFetcher's min macOS version to avoid warnings. ([#142](https://github.com/google/GTMAppAuth/pull/142))
51 |
52 | # 1.2.2
53 |
54 | * Fixed Swift Package Manager issue with Xcode 12.5.
55 |
56 | # 1.2.1
57 |
58 | * Address CocoaPod packaging issue in the 1.2.0 release.
59 |
60 | # 1.2.0
61 |
62 | * Addressed several Swift Package Manager issues.
63 | * Restructured the project for cleaner Swift Package Manager support.
64 |
65 | # 1.1.0
66 |
67 | * Added Swift Package Manager support.
68 | * Added watchOS support.
69 |
70 | # 1.0.0
71 |
72 | * Moved tvOS authorization support out to a branch.
73 |
74 | # 0.8.0
75 |
76 | * Added `tokenRefreshDelegate` to `GTMAppAuthFetcherAuthorization`.
77 | * Updated to depend on AppAuth/Core 1.0.
78 | * Added CHANGELOG.md.
79 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | Want to contribute? Great! First, read this page (including the small print at the end).
2 |
3 | ### Before you contribute
4 | Before we can use your code, you must sign the
5 | [Google Individual Contributor License Agreement]
6 | (https://cla.developers.google.com/about/google-individual)
7 | (CLA), which you can do online. The CLA is necessary mainly because you own the
8 | copyright to your changes, even after your contribution becomes part of our
9 | codebase, so we need your permission to use and distribute your code. We also
10 | need to be sure of various other things—for instance that you'll tell us if you
11 | know that your code infringes on other people's patents. You don't have to sign
12 | the CLA until after you've submitted your code for review and a member has
13 | approved it, but you must do it before we can put your code into our codebase.
14 | Before you start working on a larger contribution, you should get in touch with
15 | us first through the issue tracker with your idea so that we can help out and
16 | possibly guide you. Coordinating up front makes it much easier to avoid
17 | frustration later on.
18 |
19 | ### Code reviews
20 | All submissions, including submissions by project members, require review. We
21 | use Github pull requests for this purpose.
22 |
23 | ### The small print
24 | Contributions made by corporations are covered by a different agreement than
25 | the one above, the
26 | [Software Grant and Corporate Contributor License Agreement]
27 | (https://cla.developers.google.com/about/google-corporate).
--------------------------------------------------------------------------------
/Examples/Example-iOS/.gitignore:
--------------------------------------------------------------------------------
1 | ## Build generated
2 | build/
3 | DerivedData/
4 |
5 | ## Various settings
6 | *.pbxuser
7 | !default.pbxuser
8 | *.mode1v3
9 | !default.mode1v3
10 | *.mode2v3
11 | !default.mode2v3
12 | *.perspectivev3
13 | !default.perspectivev3
14 | xcuserdata/
15 |
16 | ## Other
17 | *.moved-aside
18 | *.xcuserstate
19 |
20 | # Pods are ignored in the samples as all Pods & their dependencies are either
21 | # development Pods (this repo) or sourced from repos in the same organization.
22 | # Generally we recommend versioning Pods, see the pros & cons here:
23 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
24 | Pods
25 |
--------------------------------------------------------------------------------
/Examples/Example-iOS/Podfile:
--------------------------------------------------------------------------------
1 | platform :ios, '12.0'
2 |
3 | target 'Example-iOSForPod' do
4 | use_frameworks!
5 | project 'Example-iOSForPod'
6 |
7 | # Pods for GTMAppAuth development
8 | pod 'GTMAppAuth', :path => '../../', :testspecs => ['unit', 'objc-api-integration']
9 |
10 | # In production, you would use:
11 | # pod 'GTMAppAuth'
12 |
13 | pod 'AppAuth', '~> 2.0'
14 | pod 'GTMSessionFetcher/Core', '>= 3.3', '< 4.0'
15 | end
16 |
--------------------------------------------------------------------------------
/Examples/Example-iOS/README.md:
--------------------------------------------------------------------------------
1 | # iOS Example
2 |
3 | ## Getting Started
4 |
5 | Choose which package manager you'd like to use.
6 |
7 | ### Swift Package Manager
8 |
9 | In the Example-iOS folder, run the following command to open the Swift Pacakage Manager
10 | project:
11 |
12 | ```
13 | open Example-iOS.xcodeproj
14 | ```
15 |
16 | ### CocoaPods
17 |
18 | 1. In the Example-iOS folder, run the following command to install the required
19 | library pods:
20 |
21 | ```
22 | $ pod install
23 | ```
24 |
25 | 2. Open the generated workspace for the CocoaPods project:
26 |
27 | ```
28 | $ open Example-iOSForPod.xcworkspace
29 | ```
30 |
31 | ## Configuration
32 |
33 | The example doesn't work out of the box, you need to configure it your own
34 | client ID.
35 |
36 | ### Creating a Google OAuth Client
37 |
38 | To configure the sample with a Google OAuth client, visit
39 | https://console.developers.google.com/apis/credentials?project=_ and create a
40 | new project. Then tap "Create credentials" and select "OAuth client ID".
41 | Follow the instructions to configure the consent screen (just the Product Name
42 | is needed).
43 |
44 | Then, complete the OAuth client creation by selecting "iOS" as the Application
45 | type. Enter the Bundle ID of the project (`net.openid.appauth.Example` by
46 | default, but you may want to change this in the project and use your own
47 | Bundle ID).
48 |
49 | Copy the client ID to the clipboard.
50 |
51 | ### Configure the Example
52 |
53 | In `GTMAppAuthExampleViewController.m` update `kClientID` with your new client
54 | id.
55 |
56 | In the same file, update `kRedirectURI` with the *reverse DNS notation* form
57 | of the client ID. For example, if the client ID is
58 | `YOUR_CLIENT.apps.googleusercontent.com`, the reverse DNS notation would be
59 | `com.googleusercontent.apps.YOUR_CLIENT`. A path component is added resulting in
60 | `com.googleusercontent.apps.YOUR_CLIENT:/oauthredirect`.
61 |
62 | Finally, open `Info.plist` and fully expand "URL types" (a.k.a.
63 | "CFBundleURLTypes") and replace `com.googleusercontent.apps.YOUR_CLIENT` with
64 | the reverse DNS notation form of your client id (not including the
65 | `:/oauthredirect` path component).
66 |
67 | Once you have made those three changes, the sample should be ready to try with
68 | your new OAuth client.
69 |
--------------------------------------------------------------------------------
/Examples/Example-iOS/Source/AppDelegate.h:
--------------------------------------------------------------------------------
1 | /*! @file AppDelegate.h
2 | @brief GTMAppAuth SDK iOS Example
3 | @copyright
4 | Copyright 2016 Google Inc.
5 | @copydetails
6 | Licensed under the Apache License, Version 2.0 (the "License");
7 | you may not use this file except in compliance with the License.
8 | You may obtain a copy of the License at
9 |
10 | http://www.apache.org/licenses/LICENSE-2.0
11 |
12 | Unless required by applicable law or agreed to in writing, software
13 | distributed under the License is distributed on an "AS IS" BASIS,
14 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | See the License for the specific language governing permissions and
16 | limitations under the License.
17 | */
18 | #import
19 |
20 | @protocol OIDExternalUserAgentSession;
21 |
22 | /*! @brief The example application's delegate.
23 | */
24 | @interface AppDelegate : UIResponder
25 |
26 | /*! @brief The authorization flow session which receives the return URL from
27 | \SFSafariViewController.
28 | @discussion We need to store this in the app delegate as it's that delegate which receives the
29 | incoming URL on UIApplicationDelegate.application:openURL:options:. This property will be
30 | nil, except when an authorization flow is in progress.
31 | */
32 | @property(nonatomic, strong, nullable) id currentAuthorizationFlow;
33 |
34 | @end
35 |
36 |
--------------------------------------------------------------------------------
/Examples/Example-iOS/Source/AppDelegate.m:
--------------------------------------------------------------------------------
1 | /*! @file AppDelegate.m
2 | @brief GTMAppAuth SDK iOS Example
3 | @copyright
4 | Copyright 2016 Google Inc.
5 | @copydetails
6 | Licensed under the Apache License, Version 2.0 (the "License");
7 | you may not use this file except in compliance with the License.
8 | You may obtain a copy of the License at
9 |
10 | http://www.apache.org/licenses/LICENSE-2.0
11 |
12 | Unless required by applicable law or agreed to in writing, software
13 | distributed under the License is distributed on an "AS IS" BASIS,
14 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | See the License for the specific language governing permissions and
16 | limitations under the License.
17 | */
18 | #import "AppDelegate.h"
19 |
20 | @import AppAuth;
21 |
22 | #import "GTMAppAuthExampleViewController.h"
23 |
24 | @implementation AppDelegate
25 |
26 | @synthesize window = _window;
27 |
28 | - (BOOL)application:(UIApplication *)application
29 | didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
30 | UIWindow *window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
31 | UIViewController *mainViewController =
32 | [[GTMAppAuthExampleViewController alloc] init];
33 | window.rootViewController = mainViewController;
34 |
35 | _window = window;
36 | [_window makeKeyAndVisible];
37 |
38 | return YES;
39 | }
40 |
41 | /*! @brief Handles inbound URLs. Checks if the URL matches the redirect URI for a pending
42 | AppAuth authorization request.
43 | */
44 | - (BOOL)application:(UIApplication *)app
45 | openURL:(NSURL *)url
46 | options:(NSDictionary *)options {
47 | // Sends the URL to the current authorization flow (if any) which will process it if it relates to
48 | // an authorization response.
49 | if ([_currentAuthorizationFlow resumeExternalUserAgentFlowWithURL:url]) {
50 | _currentAuthorizationFlow = nil;
51 | return YES;
52 | }
53 |
54 | // Your additional URL handling (if any) goes here.
55 |
56 | return NO;
57 | }
58 |
59 | /*! @brief Forwards inbound URLs for iOS 8.x and below to @c application:openURL:options:.
60 | @discussion When you drop support for versions of iOS earlier than 9.0, you can delete this
61 | method. NB. this implementation doesn't forward the sourceApplication or annotations. If you
62 | need these, then you may want @c application:openURL:options to call this method instead.
63 | */
64 | - (BOOL)application:(UIApplication *)application
65 | openURL:(NSURL *)url
66 | sourceApplication:(NSString *)sourceApplication
67 | annotation:(id)annotation {
68 | return [self application:application
69 | openURL:url
70 | options:@{}];
71 | }
72 |
73 | @end
74 |
--------------------------------------------------------------------------------
/Examples/Example-iOS/Source/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "iphone",
5 | "scale" : "2x",
6 | "size" : "20x20"
7 | },
8 | {
9 | "idiom" : "iphone",
10 | "scale" : "3x",
11 | "size" : "20x20"
12 | },
13 | {
14 | "idiom" : "iphone",
15 | "scale" : "2x",
16 | "size" : "29x29"
17 | },
18 | {
19 | "idiom" : "iphone",
20 | "scale" : "3x",
21 | "size" : "29x29"
22 | },
23 | {
24 | "idiom" : "iphone",
25 | "scale" : "2x",
26 | "size" : "40x40"
27 | },
28 | {
29 | "idiom" : "iphone",
30 | "scale" : "3x",
31 | "size" : "40x40"
32 | },
33 | {
34 | "idiom" : "iphone",
35 | "scale" : "2x",
36 | "size" : "60x60"
37 | },
38 | {
39 | "idiom" : "iphone",
40 | "scale" : "3x",
41 | "size" : "60x60"
42 | },
43 | {
44 | "idiom" : "ipad",
45 | "scale" : "1x",
46 | "size" : "20x20"
47 | },
48 | {
49 | "idiom" : "ipad",
50 | "scale" : "2x",
51 | "size" : "20x20"
52 | },
53 | {
54 | "idiom" : "ipad",
55 | "scale" : "1x",
56 | "size" : "29x29"
57 | },
58 | {
59 | "idiom" : "ipad",
60 | "scale" : "2x",
61 | "size" : "29x29"
62 | },
63 | {
64 | "idiom" : "ipad",
65 | "scale" : "1x",
66 | "size" : "40x40"
67 | },
68 | {
69 | "idiom" : "ipad",
70 | "scale" : "2x",
71 | "size" : "40x40"
72 | },
73 | {
74 | "idiom" : "ipad",
75 | "scale" : "1x",
76 | "size" : "76x76"
77 | },
78 | {
79 | "idiom" : "ipad",
80 | "scale" : "2x",
81 | "size" : "76x76"
82 | },
83 | {
84 | "idiom" : "ipad",
85 | "scale" : "2x",
86 | "size" : "83.5x83.5"
87 | },
88 | {
89 | "idiom" : "ios-marketing",
90 | "scale" : "1x",
91 | "size" : "1024x1024"
92 | }
93 | ],
94 | "info" : {
95 | "author" : "xcode",
96 | "version" : 1
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/Examples/Example-iOS/Source/Base.lproj/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/Examples/Example-iOS/Source/GTMAppAuthExampleViewController.h:
--------------------------------------------------------------------------------
1 | /*! @file AppAuthExampleViewController.h
2 | @brief GTMAppAuth SDK iOS Example
3 | @copyright
4 | Copyright 2016 Google Inc.
5 | @copydetails
6 | Licensed under the Apache License, Version 2.0 (the "License");
7 | you may not use this file except in compliance with the License.
8 | You may obtain a copy of the License at
9 |
10 | http://www.apache.org/licenses/LICENSE-2.0
11 |
12 | Unless required by applicable law or agreed to in writing, software
13 | distributed under the License is distributed on an "AS IS" BASIS,
14 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | See the License for the specific language governing permissions and
16 | limitations under the License.
17 | */
18 | #import
19 |
20 | @class OIDAuthState;
21 | @class GTMAuthSession;
22 | @class OIDServiceConfiguration;
23 |
24 | NS_ASSUME_NONNULL_BEGIN
25 |
26 | /*! @brief The example application's view controller.
27 | */
28 | @interface GTMAppAuthExampleViewController : UIViewController
29 |
30 | @property(nullable) IBOutlet UIButton *authAutoButton;
31 | @property(nullable) IBOutlet UIButton *userinfoButton;
32 | @property(nullable) IBOutlet UIButton *clearAuthStateButton;
33 | @property(nullable) IBOutlet UITextView *logTextView;
34 |
35 | /*! @brief The auth session.
36 | */
37 | @property(nonatomic, nullable) GTMAuthSession *authSession;
38 |
39 | /*! @brief Authorization code flow using @c OIDAuthState automatic code exchanges.
40 | @param sender IBAction sender.
41 | */
42 | - (IBAction)authWithAutoCodeExchange:(nullable id)sender;
43 |
44 | /*! @brief Performs a Userinfo API call using @c GTMAuthSession.
45 | @param sender IBAction sender.
46 | */
47 | - (IBAction)userinfo:(nullable id)sender;
48 |
49 | /*! @brief Nils the @c authSession.
50 | @param sender IBAction sender.
51 | */
52 | - (IBAction)clearAuthState:(nullable id)sender;
53 |
54 | /*! @brief Clears the UI log.
55 | @param sender IBAction sender.
56 | */
57 | - (IBAction)clearLog:(nullable id)sender;
58 |
59 | @end
60 |
61 | NS_ASSUME_NONNULL_END
62 |
--------------------------------------------------------------------------------
/Examples/Example-iOS/Source/GTMAppAuthExampleViewController.m:
--------------------------------------------------------------------------------
1 | /*! @file AppAuthExampleViewController.m
2 | @brief GTMAppAuth SDK iOS Example
3 | @copyright
4 | Copyright 2016 Google Inc.
5 | @copydetails
6 | Licensed under the Apache License, Version 2.0 (the "License");
7 | you may not use this file except in compliance with the License.
8 | You may obtain a copy of the License at
9 |
10 | http://www.apache.org/licenses/LICENSE-2.0
11 |
12 | Unless required by applicable law or agreed to in writing, software
13 | distributed under the License is distributed on an "AS IS" BASIS,
14 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | See the License for the specific language governing permissions and
16 | limitations under the License.
17 | */
18 |
19 | #import "GTMAppAuthExampleViewController.h"
20 |
21 | #import
22 |
23 | @import AppAuth;
24 | @import GTMAppAuth;
25 | #ifdef COCOAPODS
26 | @import GTMSessionFetcher;
27 | #else // SPM
28 | @import GTMSessionFetcherCore;
29 | #endif
30 |
31 | #import "AppDelegate.h"
32 |
33 | /*! @brief The OIDC issuer from which the configuration will be discovered.
34 | */
35 | static NSString *const kIssuer = @"https://accounts.google.com";
36 |
37 | /*! @brief The OAuth client ID.
38 | @discussion For Google, register your client at
39 | https://console.developers.google.com/apis/credentials?project=_
40 | The client should be registered with the "iOS" type.
41 | */
42 | static NSString *const kClientID = @"YOUR_CLIENT.apps.googleusercontent.com";
43 |
44 | /*! @brief The OAuth redirect URI for the client @c kClientID.
45 | @discussion With Google, the scheme of the redirect URI is the reverse DNS notation of the
46 | client ID. This scheme must be registered as a scheme in the project's Info
47 | property list ("CFBundleURLTypes" plist key). Any path component will work, we use
48 | 'oauthredirect' here to help disambiguate from any other use of this scheme.
49 | */
50 | static NSString *const kRedirectURI =
51 | @"com.googleusercontent.apps.YOUR_CLIENT:/oauthredirect";
52 |
53 | /*! @brief The key used to store our `GTMAuthSession` in the keychain.
54 | */
55 | static NSString *const kKeychainStoreItemName = @"authorization";
56 |
57 | @interface GTMAppAuthExampleViewController ()
59 |
60 | @property (nonatomic, strong) GTMKeychainStore *keychainStore;
61 |
62 | @end
63 |
64 | @implementation GTMAppAuthExampleViewController
65 |
66 | - (void)viewDidLoad {
67 | [super viewDidLoad];
68 |
69 | self.keychainStore = [[GTMKeychainStore alloc] initWithItemName:kKeychainStoreItemName];
70 | #if !defined(NS_BLOCK_ASSERTIONS)
71 | // NOTE:
72 | //
73 | // To run this sample, you need to register your own iOS client at
74 | // https://console.developers.google.com/apis/credentials?project=_ and update three configuration
75 | // points in the sample: kClientID and kRedirectURI constants in AppAuthExampleViewController.m
76 | // and the URI scheme in Info.plist (URL Types -> Item 0 -> URL Schemes -> Item 0).
77 | // Full instructions: https://github.com/openid/AppAuth-iOS/blob/master/Example/README.md
78 |
79 | NSAssert(![kClientID isEqualToString:@"YOUR_CLIENT.apps.googleusercontent.com"],
80 | @"Update kClientID with your own client ID. "
81 | "Instructions: https://github.com/openid/AppAuth-iOS/blob/master/Example/README.md");
82 |
83 | NSAssert(![kRedirectURI isEqualToString:@"com.googleusercontent.apps.YOUR_CLIENT:/oauthredirect"],
84 | @"Update kRedirectURI with your own redirect URI. "
85 | "Instructions: https://github.com/openid/AppAuth-iOS/blob/master/Example/README.md");
86 |
87 | // verifies that the custom URI scheme has been updated in the Info.plist
88 | NSArray *urlTypes = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleURLTypes"];
89 | NSAssert(urlTypes.count > 0, @"No custom URI scheme has been configured for the project.");
90 | NSArray *urlSchemes = ((NSDictionary *)urlTypes.firstObject)[@"CFBundleURLSchemes"];
91 | NSAssert(urlSchemes.count > 0, @"No custom URI scheme has been configured for the project.");
92 | NSString *urlScheme = urlSchemes.firstObject;
93 |
94 | NSAssert(![urlScheme isEqualToString:@"com.googleusercontent.apps.YOUR_CLIENT"],
95 | @"Configure the URI scheme in Info.plist (URL Types -> Item 0 -> URL Schemes -> Item 0) "
96 | "with the scheme of your redirect URI. Full instructions: "
97 | "https://github.com/openid/AppAuth-iOS/blob/master/Example/README.md");
98 |
99 | #endif // !defined(NS_BLOCK_ASSERTIONS)
100 |
101 | _logTextView.layer.borderColor = [UIColor colorWithWhite:0.8 alpha:1.0].CGColor;
102 | _logTextView.layer.borderWidth = 1.0f;
103 | _logTextView.alwaysBounceVertical = YES;
104 | _logTextView.textContainer.lineBreakMode = NSLineBreakByCharWrapping;
105 | _logTextView.text = @"";
106 |
107 | [self loadState];
108 | [self updateUI];
109 | }
110 |
111 | /*! @brief Saves the @c GTMAuthSession to the keychain.
112 | */
113 | - (void)saveState {
114 | NSError *error;
115 | if (_authSession.canAuthorize) {
116 | [self.keychainStore saveAuthSession:_authSession error:&error];
117 | } else {
118 | [self.keychainStore removeAuthSessionWithError:&error];
119 | }
120 | if (error) {
121 | NSLog(@"Error saving state: %@ error", error);
122 | }
123 | }
124 |
125 | /*! @brief Loads the @c GTMAuthSession from the keychain.
126 | */
127 | - (void)loadState {
128 | NSError *error;
129 | GTMAuthSession *authSession =
130 | [self.keychainStore retrieveAuthSessionWithError:&error];
131 | if (error) {
132 | NSLog(@"Error loading state: %@", error);
133 | }
134 | [self setAuthSession:authSession];
135 | }
136 |
137 | - (void)setAuthSession:(GTMAuthSession *)authSession {
138 | if ([_authSession isEqual:authSession]) {
139 | return;
140 | }
141 | _authSession = authSession;
142 | [self stateChanged];
143 | }
144 |
145 | /*! @brief Refreshes UI, typically called after the auth state changed.
146 | */
147 | - (void)updateUI {
148 | _userinfoButton.enabled = _authSession.canAuthorize;
149 | _clearAuthStateButton.enabled = _authSession.canAuthorize;
150 | // dynamically changes authorize button text depending on authorized state
151 | if (!_authSession.canAuthorize) {
152 | [_authAutoButton setTitle:@"Authorize" forState:UIControlStateNormal];
153 | [_authAutoButton setTitle:@"Authorize" forState:UIControlStateHighlighted];
154 | } else {
155 | [_authAutoButton setTitle:@"Re-authorize" forState:UIControlStateNormal];
156 | [_authAutoButton setTitle:@"Re-authorize" forState:UIControlStateHighlighted];
157 | }
158 | }
159 |
160 | - (void)stateChanged {
161 | [self saveState];
162 | [self updateUI];
163 | }
164 |
165 | - (void)didChangeState:(OIDAuthState *)state {
166 | [self stateChanged];
167 | }
168 |
169 | - (void)authState:(OIDAuthState *)state didEncounterAuthorizationError:(NSError *)error {
170 | [self logMessage:@"Received authorization error: %@", error];
171 | }
172 |
173 | - (IBAction)authWithAutoCodeExchange:(nullable id)sender {
174 | NSURL *issuer = [NSURL URLWithString:kIssuer];
175 | NSURL *redirectURI = [NSURL URLWithString:kRedirectURI];
176 |
177 | [self logMessage:@"Fetching configuration for issuer: %@", issuer];
178 |
179 | // discovers endpoints
180 | [OIDAuthorizationService discoverServiceConfigurationForIssuer:issuer
181 | completion:^(OIDServiceConfiguration *_Nullable configuration, NSError *_Nullable error) {
182 |
183 | if (!configuration) {
184 | [self logMessage:@"Error retrieving discovery document: %@", [error localizedDescription]];
185 | [self setAuthSession:nil];
186 | return;
187 | }
188 |
189 | [self logMessage:@"Got configuration: %@", configuration];
190 |
191 | // builds authentication request
192 | OIDAuthorizationRequest *request =
193 | [[OIDAuthorizationRequest alloc] initWithConfiguration:configuration
194 | clientId:kClientID
195 | scopes:@[OIDScopeOpenID, OIDScopeProfile]
196 | redirectURL:redirectURI
197 | responseType:OIDResponseTypeCode
198 | additionalParameters:nil];
199 | // performs authentication request
200 | AppDelegate *appDelegate = (AppDelegate *)[UIApplication sharedApplication].delegate;
201 | [self logMessage:@"Initiating authorization request with scope: %@", request.scope];
202 |
203 | appDelegate.currentAuthorizationFlow =
204 | [OIDAuthState authStateByPresentingAuthorizationRequest:request
205 | presentingViewController:self
206 | callback:^(OIDAuthState *_Nullable authState,
207 | NSError *_Nullable error) {
208 | if (authState) {
209 | GTMAuthSession *authSession = [[GTMAuthSession alloc] initWithAuthState:authState];
210 |
211 | [self setAuthSession:authSession];
212 | [self logMessage:@"Got authorization tokens. Access token: %@",
213 | authState.lastTokenResponse.accessToken];
214 | } else {
215 | [self setAuthSession:nil];
216 | [self logMessage:@"Authorization error: %@", [error localizedDescription]];
217 | }
218 | }];
219 | }];
220 | }
221 |
222 | - (IBAction)clearAuthState:(nullable id)sender {
223 | [self setAuthSession:nil];
224 | }
225 |
226 | - (IBAction)clearLog:(nullable id)sender {
227 | _logTextView.text = @"";
228 | }
229 |
230 | - (IBAction)userinfo:(nullable id)sender {
231 | [self logMessage:@"Performing userinfo request"];
232 |
233 | // Creates a GTMSessionFetcherService with the authorization.
234 | // Normally you would save this service object and re-use it for all REST API calls.
235 | GTMSessionFetcherService *fetcherService = [[GTMSessionFetcherService alloc] init];
236 | fetcherService.authorizer = self.authSession;
237 |
238 | // Creates a fetcher for the API call.
239 | NSURL *userinfoEndpoint = [NSURL URLWithString:@"https://www.googleapis.com/oauth2/v3/userinfo"];
240 | GTMSessionFetcher *fetcher = [fetcherService fetcherWithURL:userinfoEndpoint];
241 | [fetcher beginFetchWithCompletionHandler:^(NSData *data, NSError *error) {
242 | // Checks for an error.
243 | if (error) {
244 | // OIDOAuthTokenErrorDomain indicates an issue with the authorization.
245 | if ([error.domain isEqual:OIDOAuthTokenErrorDomain]) {
246 | [self setAuthSession:nil];
247 | [self logMessage:@"Authorization error during token refresh, clearing state. %@", error];
248 | // Other errors are assumed transient.
249 | } else {
250 | [self logMessage:@"Transient error during token refresh. %@", error];
251 | }
252 | return;
253 | }
254 |
255 | // Parses the JSON response.
256 | NSError *jsonError = nil;
257 | id jsonDictionaryOrArray =
258 | [NSJSONSerialization JSONObjectWithData:data options:0 error:&jsonError];
259 |
260 | // JSON error.
261 | if (jsonError) {
262 | [self logMessage:@"JSON decoding error %@", jsonError];
263 | return;
264 | }
265 |
266 | // Success response!
267 | [self logMessage:@"Success: %@", jsonDictionaryOrArray];
268 | }];
269 | }
270 |
271 | /*! @brief Logs a message to stdout and the textfield.
272 | @param format The format string and arguments.
273 | */
274 | - (void)logMessage:(NSString *)format, ... NS_FORMAT_FUNCTION(1,2) {
275 | // gets message as string
276 | va_list argp;
277 | va_start(argp, format);
278 | NSString *log = [[NSString alloc] initWithFormat:format arguments:argp];
279 | va_end(argp);
280 |
281 | // outputs to stdout
282 | NSLog(@"%@", log);
283 |
284 | // appends to output log
285 | NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
286 | dateFormatter.dateFormat = @"hh:mm:ss";
287 | NSString *dateString = [dateFormatter stringFromDate:[NSDate date]];
288 | _logTextView.text = [NSString stringWithFormat:@"%@%@%@: %@",
289 | _logTextView.text,
290 | ([_logTextView.text length] > 0) ? @"\n" : @"",
291 | dateString,
292 | log];
293 | }
294 |
295 | @end
296 |
--------------------------------------------------------------------------------
/Examples/Example-iOS/Source/GTMAppAuthExampleViewController.xib:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
31 |
38 |
39 |
40 |
41 |
42 |
43 |
44 | 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.
45 |
46 |
47 |
48 |
58 |
65 |
71 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
--------------------------------------------------------------------------------
/Examples/Example-iOS/Source/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | APPL
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleSignature
20 | ????
21 | CFBundleURLTypes
22 |
23 |
24 | CFBundleTypeRole
25 | Editor
26 | CFBundleURLSchemes
27 |
28 | com.googleusercontent.apps.YOUR_CLIENT
29 |
30 |
31 |
32 | CFBundleVersion
33 | 1
34 | LSRequiresIPhoneOS
35 |
36 | UILaunchStoryboardName
37 | LaunchScreen
38 | UIRequiredDeviceCapabilities
39 |
40 | armv7
41 |
42 | UISupportedInterfaceOrientations
43 |
44 | UIInterfaceOrientationPortrait
45 | UIInterfaceOrientationLandscapeLeft
46 | UIInterfaceOrientationLandscapeRight
47 |
48 | UISupportedInterfaceOrientations~ipad
49 |
50 | UIInterfaceOrientationPortrait
51 | UIInterfaceOrientationPortraitUpsideDown
52 | UIInterfaceOrientationLandscapeLeft
53 | UIInterfaceOrientationLandscapeRight
54 |
55 |
56 |
57 |
--------------------------------------------------------------------------------
/Examples/Example-iOS/Source/main.m:
--------------------------------------------------------------------------------
1 | /*! @file main.m
2 | @brief GTMAppAuth SDK iOS Example
3 | @copyright
4 | Copyright 2016 Google Inc.
5 | @copydetails
6 | Licensed under the Apache License, Version 2.0 (the "License");
7 | you may not use this file except in compliance with the License.
8 | You may obtain a copy of the License at
9 |
10 | http://www.apache.org/licenses/LICENSE-2.0
11 |
12 | Unless required by applicable law or agreed to in writing, software
13 | distributed under the License is distributed on an "AS IS" BASIS,
14 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | See the License for the specific language governing permissions and
16 | limitations under the License.
17 | */
18 |
19 | #import
20 |
21 | #import "AppDelegate.h"
22 |
23 | /*! @brief Application main entry point.
24 | @return Exit code.
25 | */
26 | int main(int argc, char *argv[]) {
27 | @autoreleasepool {
28 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/Examples/Example-macOS/.gitignore:
--------------------------------------------------------------------------------
1 | ## Build generated
2 | build/
3 | DerivedData/
4 |
5 | ## Various settings
6 | *.pbxuser
7 | !default.pbxuser
8 | *.mode1v3
9 | !default.mode1v3
10 | *.mode2v3
11 | !default.mode2v3
12 | *.perspectivev3
13 | !default.perspectivev3
14 | xcuserdata/
15 |
16 | ## Other
17 | *.moved-aside
18 | *.xcuserstate
19 |
20 | # Pods are ignored in the samples as all Pods & their dependencies are either
21 | # development Pods (this repo) or sourced from repos in the same organization.
22 | # Generally we recommend versioning Pods, see the pros & cons here:
23 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
24 | Pods
25 |
--------------------------------------------------------------------------------
/Examples/Example-macOS/Example-macOS.entitlements:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | keychain-access-groups
6 |
7 | $(AppIdentifierPrefix)com.example.GTMAppAuth.Example-macOS.test-group
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/Examples/Example-macOS/Podfile:
--------------------------------------------------------------------------------
1 | platform :osx, '10.12'
2 |
3 | target 'Example-macOSForPod' do
4 | use_frameworks!
5 | project 'Example-macOSForPod'
6 |
7 | # Pods for GTMAppAuth development
8 | pod 'GTMAppAuth', :path => '../../', :testspecs => ['unit', 'objc-api-integration']
9 |
10 | # In production, you would use:
11 | # pod 'GTMAppAuth'
12 |
13 | pod 'AppAuth', '~> 2.0'
14 | pod 'GTMSessionFetcher/Core', '>= 3.3', '< 4.0'
15 | end
16 |
--------------------------------------------------------------------------------
/Examples/Example-macOS/README.md:
--------------------------------------------------------------------------------
1 | # macOS Example
2 |
3 | ## Getting Started
4 |
5 | Choose which package manager you'd like to use.
6 |
7 | ### Swift Package Manager
8 |
9 | In the Example-macOS folder, run the following command to open the Swift Pacakage Manager
10 | project:
11 |
12 | ```
13 | open Example-macOS.xcodeproj
14 | ```
15 |
16 | ### CocoaPods
17 |
18 | 1. In the Example-macOS folder, run the following command to install the required
19 | library pods:
20 |
21 | ```
22 | $ pod install
23 | ```
24 |
25 | 2. Open the generated workspace for the CocoaPods project:
26 |
27 | ```
28 | $ open Example-macOSForPod.xcworkspace
29 | ```
30 |
31 | ## Configuration
32 |
33 | The example doesn't work out of the box, you need to configure your own client
34 | ID.
35 |
36 | ### Creating a Google OAuth Client
37 |
38 | To configure the sample with a Google OAuth client, visit
39 | https://console.developers.google.com/apis/credentials?project=_ and create a
40 | new project. Then tap "Create credentials" and select "OAuth client ID".
41 | Follow the instructions to configure the consent screen (just the Product Name
42 | is needed).
43 |
44 | Then, complete the OAuth client creation by selecting "iOS" as the Application
45 | type.
46 |
47 | Copy the client ID to the clipboard.
48 |
49 | ### Configure the Example
50 |
51 | In `GTMAppAuthExampleViewController.m` update `kClientID` with your new client
52 | ID.
53 |
54 | In the same file, update `kRedirectURI` with the *reverse DNS notation* form
55 | of the client ID. For example, if the client ID is
56 | `YOUR_CLIENT.apps.googleusercontent.com`, the reverse DNS notation would be
57 | `com.googleusercontent.apps.YOUR_CLIENT`. A path component is added resulting in
58 | `com.googleusercontent.apps.YOUR_CLIENT:/oauthredirect`.
59 |
60 | Finally, open `Info.plist` and fully expand "URL types" (a.k.a.
61 | "CFBundleURLTypes") and replace `com.googleusercontent.apps.YOUR_CLIENT` with
62 | the reverse DNS notation form of your client ID (not including the
63 | `:/oauthredirect` path component).
64 |
65 | Once you have made those three changes, the sample should be ready to try with
66 | your new OAuth client.
67 |
--------------------------------------------------------------------------------
/Examples/Example-macOS/Source/AppDelegate.h:
--------------------------------------------------------------------------------
1 | /*! @file AppDelegate.h
2 | @brief GTMAppAuth macOS SDK Example
3 | @copyright
4 | Copyright 2016 Google Inc.
5 | @copydetails
6 | Licensed under the Apache License, Version 2.0 (the "License");
7 | you may not use this file except in compliance with the License.
8 | You may obtain a copy of the License at
9 |
10 | http://www.apache.org/licenses/LICENSE-2.0
11 |
12 | Unless required by applicable law or agreed to in writing, software
13 | distributed under the License is distributed on an "AS IS" BASIS,
14 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | See the License for the specific language governing permissions and
16 | limitations under the License.
17 | */
18 |
19 | #import
20 |
21 | NS_ASSUME_NONNULL_BEGIN
22 |
23 | @protocol OIDExternalUserAgentSession;
24 |
25 | /*! @class AppDelegate
26 | @brief The example application's delegate.
27 | */
28 | @interface AppDelegate : NSObject
29 |
30 | /*! @property currentAuthorizationFlow
31 | @brief The authorization flow session which receives the return URL from the browser.
32 | @discussion We need to store this in the app delegate as it's that delegate which receives the
33 | incoming URL. This property will be nil, except when an authorization flow is in progress.
34 | */
35 | @property(nonatomic, strong, nullable) id currentAuthorizationFlow;
36 |
37 | @end
38 |
39 | NS_ASSUME_NONNULL_END
40 |
--------------------------------------------------------------------------------
/Examples/Example-macOS/Source/AppDelegate.m:
--------------------------------------------------------------------------------
1 | /*! @file AppDelegate.m
2 | @brief GTMAppAuth macOS SDK Example
3 | @copyright
4 | Copyright 2016 Google Inc.
5 | @copydetails
6 | Licensed under the Apache License, Version 2.0 (the "License");
7 | you may not use this file except in compliance with the License.
8 | You may obtain a copy of the License at
9 |
10 | http://www.apache.org/licenses/LICENSE-2.0
11 |
12 | Unless required by applicable law or agreed to in writing, software
13 | distributed under the License is distributed on an "AS IS" BASIS,
14 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | See the License for the specific language governing permissions and
16 | limitations under the License.
17 | */
18 | #import "AppDelegate.h"
19 |
20 | @import AppAuth;
21 |
22 | #import "GTMAppAuthExampleViewController.h"
23 |
24 | NS_ASSUME_NONNULL_BEGIN
25 |
26 | @interface AppDelegate ()
27 | @property(nullable) IBOutlet NSWindow *window;
28 | @end
29 |
30 | @implementation AppDelegate
31 |
32 | - (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
33 | _window.title = @"AppAuth Example for macOS";
34 | GTMAppAuthExampleViewController *contentViewController =
35 | [[GTMAppAuthExampleViewController alloc] init];
36 | contentViewController.appDelegate = self;
37 | _window.contentViewController = contentViewController;
38 |
39 | // Register for GetURL events.
40 | NSAppleEventManager *appleEventManager = [NSAppleEventManager sharedAppleEventManager];
41 | [appleEventManager setEventHandler:self
42 | andSelector:@selector(handleGetURLEvent:withReplyEvent:)
43 | forEventClass:kInternetEventClass
44 | andEventID:kAEGetURL];
45 | }
46 |
47 | - (void)handleGetURLEvent:(NSAppleEventDescriptor *)event
48 | withReplyEvent:(NSAppleEventDescriptor *)replyEvent {
49 | NSString *URLString = [[event paramDescriptorForKeyword:keyDirectObject] stringValue];
50 | NSURL *URL = [NSURL URLWithString:URLString];
51 | [_currentAuthorizationFlow resumeExternalUserAgentFlowWithURL:URL];
52 | }
53 |
54 | @end
55 |
56 | NS_ASSUME_NONNULL_END
57 |
--------------------------------------------------------------------------------
/Examples/Example-macOS/Source/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "mac",
5 | "size" : "16x16",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "mac",
10 | "size" : "16x16",
11 | "scale" : "2x"
12 | },
13 | {
14 | "idiom" : "mac",
15 | "size" : "32x32",
16 | "scale" : "1x"
17 | },
18 | {
19 | "idiom" : "mac",
20 | "size" : "32x32",
21 | "scale" : "2x"
22 | },
23 | {
24 | "idiom" : "mac",
25 | "size" : "128x128",
26 | "scale" : "1x"
27 | },
28 | {
29 | "idiom" : "mac",
30 | "size" : "128x128",
31 | "scale" : "2x"
32 | },
33 | {
34 | "idiom" : "mac",
35 | "size" : "256x256",
36 | "scale" : "1x"
37 | },
38 | {
39 | "idiom" : "mac",
40 | "size" : "256x256",
41 | "scale" : "2x"
42 | },
43 | {
44 | "idiom" : "mac",
45 | "size" : "512x512",
46 | "scale" : "1x"
47 | },
48 | {
49 | "idiom" : "mac",
50 | "size" : "512x512",
51 | "scale" : "2x"
52 | }
53 | ],
54 | "info" : {
55 | "version" : 1,
56 | "author" : "xcode"
57 | }
58 | }
--------------------------------------------------------------------------------
/Examples/Example-macOS/Source/Base.lproj/GTMAppAuthExampleViewController.xib:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
35 |
45 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
99 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
139 |
140 |
141 |
--------------------------------------------------------------------------------
/Examples/Example-macOS/Source/Base.lproj/MainMenu.xib:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
--------------------------------------------------------------------------------
/Examples/Example-macOS/Source/GTMAppAuthExampleViewController.h:
--------------------------------------------------------------------------------
1 | /*! @file AppAuthExampleViewController.h
2 | @brief GTMAppAuth macOS SDK Example
3 | @copyright
4 | Copyright 2016 Google Inc.
5 | @copydetails
6 | Licensed under the Apache License, Version 2.0 (the "License");
7 | you may not use this file except in compliance with the License.
8 | You may obtain a copy of the License at
9 |
10 | http://www.apache.org/licenses/LICENSE-2.0
11 |
12 | Unless required by applicable law or agreed to in writing, software
13 | distributed under the License is distributed on an "AS IS" BASIS,
14 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | See the License for the specific language governing permissions and
16 | limitations under the License.
17 | */
18 | #import
19 |
20 | @class AppDelegate;
21 | @class GTMAuthSession;
22 | @class OIDServiceConfiguration;
23 |
24 | NS_ASSUME_NONNULL_BEGIN
25 |
26 | /*! @brief The example application's view controller.
27 | */
28 | @interface GTMAppAuthExampleViewController : NSViewController
29 |
30 | @property(nullable) IBOutlet NSButton *authAutoButton;
31 | @property(nullable) IBOutlet NSButton *userinfoButton;
32 | @property(nullable) IBOutlet NSButton *forceRefreshButton;
33 | @property(nullable) IBOutlet NSButton *clearAuthStateButton;
34 | @property(nullable) IBOutlet NSTextView *logTextView;
35 |
36 | /*! @brief The application delegate. This is used to store the current authorization flow.
37 | */
38 | @property (nonatomic, weak, nullable) AppDelegate *appDelegate;
39 |
40 | /*! @brief The auth session.
41 | */
42 | @property(nonatomic, nullable) GTMAuthSession *authSession;
43 |
44 | /*! @brief Authorization code flow using @c OIDAuthState automatic code exchanges.
45 | @param sender IBAction sender.
46 | */
47 | - (IBAction)authWithAutoCodeExchange:(nullable id)sender;
48 |
49 | /*! @brief Forces a token refresh.
50 | @param sender IBAction sender.
51 | */
52 | - (IBAction)forceRefresh:(nullable id)sender;
53 |
54 | /*! @brief Performs a Userinfo API call using @c GTMAuthSession.
55 | @param sender IBAction sender.
56 | */
57 | - (IBAction)userinfo:(nullable id)sender;
58 |
59 | /*! @brief Nils the @c authSession.
60 | @param sender IBAction sender.
61 | */
62 | - (IBAction)clearAuthState:(nullable id)sender;
63 |
64 | /*! @brief Clears the UI log.
65 | @param sender IBAction sender.
66 | */
67 | - (IBAction)clearLog:(nullable id)sender;
68 |
69 | @end
70 |
71 | NS_ASSUME_NONNULL_END
72 |
--------------------------------------------------------------------------------
/Examples/Example-macOS/Source/GTMAppAuthExampleViewController.m:
--------------------------------------------------------------------------------
1 | /*! @file AppAuthExampleViewController.m
2 | @brief GTMAppAuth macOS SDK Example
3 | @copyright
4 | Copyright 2016 Google Inc.
5 | @copydetails
6 | Licensed under the Apache License, Version 2.0 (the "License");
7 | you may not use this file except in compliance with the License.
8 | You may obtain a copy of the License at
9 |
10 | http://www.apache.org/licenses/LICENSE-2.0
11 |
12 | Unless required by applicable law or agreed to in writing, software
13 | distributed under the License is distributed on an "AS IS" BASIS,
14 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | See the License for the specific language governing permissions and
16 | limitations under the License.
17 | */
18 |
19 | #import "GTMAppAuthExampleViewController.h"
20 |
21 | #import
22 |
23 | @import AppAuth;
24 | @import GTMAppAuth;
25 | #ifdef COCOAPODS
26 | @import GTMSessionFetcher;
27 | #else // SPM
28 | @import GTMSessionFetcherCore;
29 | #endif
30 |
31 | #import "AppDelegate.h"
32 |
33 | /*! @brief The bundle ID will use in constructing the app group string for keychain queries.
34 | @discussion The string here is a combination of this example app's bundle ID and the keychain
35 | access group name added in the app's entitlements file.
36 | */
37 | static NSString *kBundleIDAccessGroup = @"com.example.GTMAppAuth.Example-macOS.test-group";
38 |
39 | /*! @brief The team ID you will use in constructing the app group string for keychain queries.
40 | @discussion The team ID you will use can be found in your developer team profile page on
41 | developer.apple.com.
42 | */
43 | static NSString *const kTeamIDPrefix = @"YOUR_TEAM_ID";
44 |
45 | /*! @brief The OIDC issuer from which the configuration will be discovered.
46 | */
47 | static NSString *const kIssuer = @"https://accounts.google.com";
48 |
49 | /*! @brief The OAuth client ID.
50 | @discussion For Google, register your client at
51 | https://console.developers.google.com/apis/credentials?project=_
52 | */
53 | static NSString *const kClientID = @"YOUR_CLIENT.apps.googleusercontent.com";
54 |
55 | /*! @brief The OAuth redirect URI for the client @c kClientID.
56 | @discussion With Google, the scheme of the redirect URI is the reverse DNS notation of the
57 | client ID. This scheme must be registered as a scheme in the project's Info
58 | property list ("CFBundleURLTypes" plist key). Any path component will work, we use
59 | 'oauthredirect' here to help disambiguate from any other use of this scheme.
60 | */
61 | static NSString *const kRedirectURI =
62 | @"com.googleusercontent.apps.YOUR_CLIENT:/oauthredirect";
63 |
64 | /*! @brief NSCoding key for the authorization property.
65 | */
66 | static NSString *const kExampleAuthorizerKey = @"authorization";
67 |
68 | @interface GTMAppAuthExampleViewController ()
70 |
71 | @property (nonatomic, strong) GTMKeychainStore *keychainStore;
72 |
73 | @end
74 |
75 | @implementation GTMAppAuthExampleViewController
76 |
77 | - (void)viewDidLoad {
78 | [super viewDidLoad];
79 |
80 | NSString *testGroup = [NSString stringWithFormat:@"%@.%@", kTeamIDPrefix, kBundleIDAccessGroup];
81 | GTMKeychainAttribute *accessGroup = [GTMKeychainAttribute keychainAccessGroupWithName:testGroup];
82 | NSSet *attributes = [NSSet setWithArray:@[accessGroup]];
83 | self.keychainStore = [[GTMKeychainStore alloc] initWithItemName:kExampleAuthorizerKey
84 | keychainAttributes:attributes];
85 | #if !defined(NS_BLOCK_ASSERTIONS)
86 |
87 | NSAssert(![kTeamIDPrefix isEqualToString:@"YOUR_TEAM_ID"],
88 | @"Update kTeamIDPrefix with your own team ID.");
89 |
90 | // NOTE:
91 | //
92 | // To run this sample, you need to register your own Google API client at
93 | // https://console.developers.google.com/apis/credentials?project=_ and update three configuration
94 | // points in the sample: kClientID and kRedirectURI constants in AppAuthExampleViewController.m
95 | // and the URI scheme in Info.plist (URL Types -> Item 0 -> URL Schemes -> Item 0).
96 | // Full instructions: https://github.com/openid/AppAuth-iOS/blob/master/Example-Mac/README.md
97 |
98 | NSAssert(![kClientID isEqualToString:@"YOUR_CLIENT.apps.googleusercontent.com"],
99 | @"Update kClientID with your own client ID. "
100 | "Instructions: https://github.com/openid/AppAuth-iOS/blob/master/Example-Mac/README.md");
101 |
102 | NSAssert(![kRedirectURI isEqualToString:@"com.googleusercontent.apps.YOUR_CLIENT:/oauthredirect"],
103 | @"Update kRedirectURI with your own redirect URI. "
104 | "Instructions: https://github.com/openid/AppAuth-iOS/blob/master/Example-Mac/README.md");
105 |
106 | // verifies that the custom URI scheme has been updated in the Info.plist
107 | NSArray *urlTypes = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleURLTypes"];
108 | NSAssert(urlTypes.count > 0, @"No custom URI scheme has been configured for the project.");
109 | NSArray *urlSchemes = ((NSDictionary *)urlTypes.firstObject)[@"CFBundleURLSchemes"];
110 | NSAssert(urlSchemes.count > 0, @"No custom URI scheme has been configured for the project.");
111 | NSString *urlScheme = urlSchemes.firstObject;
112 |
113 | NSAssert(![urlScheme isEqualToString:@"com.googleusercontent.apps.YOUR_CLIENT"],
114 | @"Configure the URI scheme in Info.plist (URL Types -> Item 0 -> URL Schemes -> Item 0) "
115 | "with the scheme of your redirect URI. Full instructions: "
116 | "https://github.com/openid/AppAuth-iOS/blob/master/Example-Mac/README.md");
117 |
118 | #endif // !defined(NS_BLOCK_ASSERTIONS)
119 |
120 | _logTextView.layer.borderColor = [NSColor colorWithWhite:0.8 alpha:1.0].CGColor;
121 | _logTextView.layer.borderWidth = 1.0f;
122 | _logTextView.textContainer.lineBreakMode = NSLineBreakByCharWrapping;
123 |
124 | [self loadState];
125 | [self updateUI];
126 | }
127 |
128 | /*! @brief Saves the @c GTMAuthSession to the keychain.
129 | */
130 | - (void)saveState {
131 | NSError *error;
132 | if (_authSession.canAuthorize) {
133 | [self.keychainStore saveAuthSession:_authSession error:&error];
134 | } else {
135 | [self.keychainStore removeAuthSessionWithError:&error];
136 | }
137 | if (error) {
138 | NSLog(@"Error saving state: %@", error);
139 | }
140 | }
141 |
142 | /*! @brief Loads the @c GTMAuthSession from the keychain.
143 | */
144 | - (void)loadState {
145 | NSError *error;
146 | GTMAuthSession *authSession = [self.keychainStore retrieveAuthSessionWithError:&error];
147 | [self setAuthSession:authSession];
148 | if (error) {
149 | NSLog(@"Error loading state: %@", error);
150 | }
151 | }
152 |
153 | /*! @brief Refreshes UI, typically called after the auth state changed.
154 | */
155 | - (void)updateUI {
156 | _userinfoButton.enabled = [_authSession canAuthorize];
157 | _forceRefreshButton.enabled = [_authSession canAuthorize];
158 | _clearAuthStateButton.enabled = _authSession != nil;
159 | // dynamically changes authorize button text depending on authorized state
160 | if (!_authSession) {
161 | _authAutoButton.title = @"Authorize";
162 | } else {
163 | _authAutoButton.title = @"Re-authorize";
164 | }
165 | }
166 |
167 | /*! @brief Forces a token refresh.
168 | @param sender IBAction sender.
169 | */
170 | - (IBAction)forceRefresh:(nullable id)sender {
171 | [_authSession.authState setNeedsTokenRefresh];
172 | }
173 |
174 | - (void)stateChanged {
175 | [self saveState];
176 | [self updateUI];
177 | }
178 |
179 | - (void)didChangeState:(OIDAuthState *)state {
180 | // TODO(wdenniss): update for GTMAppAuth
181 | [self stateChanged];
182 | }
183 |
184 | - (void)setAuthSession:(GTMAuthSession *)authSession {
185 | _authSession = authSession;
186 | [self saveState];
187 | [self updateUI];
188 | }
189 |
190 |
191 | - (void)authState:(OIDAuthState *)state didEncounterAuthorizationError:(nonnull NSError *)error {
192 | [self logMessage:@"Received authorization error: %@", error];
193 | }
194 |
195 | - (IBAction)authWithAutoCodeExchange:(nullable id)sender {
196 | NSURL *issuer = [NSURL URLWithString:kIssuer];
197 | NSURL *redirectURI = [NSURL URLWithString:kRedirectURI];
198 |
199 | [self logMessage:@"Fetching configuration for issuer: %@", issuer];
200 |
201 | // discovers endpoints
202 | [OIDAuthorizationService discoverServiceConfigurationForIssuer:issuer
203 | completion:^(OIDServiceConfiguration *_Nullable configuration, NSError *_Nullable error) {
204 |
205 | if (!configuration) {
206 | [self logMessage:@"Error retrieving discovery document: %@", [error localizedDescription]];
207 | [self setAuthSession:nil];
208 | return;
209 | }
210 |
211 | [self logMessage:@"Got configuration: %@", configuration];
212 |
213 | // builds authentication request
214 | OIDAuthorizationRequest *request =
215 | [[OIDAuthorizationRequest alloc] initWithConfiguration:configuration
216 | clientId:kClientID
217 | scopes:@[OIDScopeOpenID, OIDScopeProfile]
218 | redirectURL:redirectURI
219 | responseType:OIDResponseTypeCode
220 | additionalParameters:nil];
221 | // performs authentication request
222 | self.appDelegate.currentAuthorizationFlow =
223 | [OIDAuthState authStateByPresentingAuthorizationRequest:request
224 | presentingWindow:self.view.window
225 | callback:^(OIDAuthState *_Nullable authState,
226 | NSError *_Nullable error) {
227 | if (authState) {
228 | GTMAuthSession *authSession = [[GTMAuthSession alloc] initWithAuthState:authState];
229 | [self setAuthSession:authSession];
230 | [self logMessage:@"Got authorization tokens. Access token: %@",
231 | authState.lastTokenResponse.accessToken];
232 | } else {
233 | [self setAuthSession:nil];
234 | [self logMessage:@"Authorization error: %@", [error localizedDescription]];
235 | }
236 | }];
237 | }];
238 | }
239 |
240 | - (IBAction)clearAuthState:(nullable id)sender {
241 | [self setAuthSession:nil];
242 | }
243 |
244 | - (IBAction)clearLog:(nullable id)sender {
245 | [_logTextView.textStorage setAttributedString:[[NSAttributedString alloc] initWithString:@""]];
246 | }
247 |
248 | - (IBAction)userinfo:(nullable id)sender {
249 | [self logMessage:@"Performing userinfo request"];
250 |
251 | // Creates a GTMSessionFetcherService with the authorization.
252 | // Normally you would save this service object and re-use it for all REST API calls.
253 | GTMSessionFetcherService *fetcherService = [[GTMSessionFetcherService alloc] init];
254 | fetcherService.authorizer = self.authSession;
255 |
256 | // Creates a fetcher for the API call.
257 | NSURL *userinfoEndpoint = [NSURL URLWithString:@"https://www.googleapis.com/oauth2/v3/userinfo"];
258 | GTMSessionFetcher *fetcher = [fetcherService fetcherWithURL:userinfoEndpoint];
259 | [fetcher beginFetchWithCompletionHandler:^(NSData *data, NSError *error) {
260 |
261 | // Checks for an error.
262 | if (error) {
263 | // OIDOAuthTokenErrorDomain indicates an issue with the authorization.
264 | if ([error.domain isEqual:OIDOAuthTokenErrorDomain]) {
265 | [self setAuthSession:nil];
266 | [self logMessage:@"Authorization error during token refresh, clearing state. %@", error];
267 | // Other errors are assumed transient.
268 | } else {
269 | [self logMessage:@"Transient error during token refresh. %@", error];
270 | }
271 | return;
272 | }
273 |
274 | // Parses the JSON response.
275 | NSError *jsonError = nil;
276 | id jsonDictionaryOrArray =
277 | [NSJSONSerialization JSONObjectWithData:data options:0 error:&jsonError];
278 |
279 | // JSON error.
280 | if (jsonError) {
281 | [self logMessage:@"JSON decoding error %@", jsonError];
282 | return;
283 | }
284 |
285 | // Success response!
286 | [self logMessage:@"Success: %@", jsonDictionaryOrArray];
287 | }];
288 | }
289 |
290 | /*! @brief Logs a message to stdout and the textfield.
291 | @param format The format string and arguments.
292 | */
293 | - (void)logMessage:(NSString *)format, ... NS_FORMAT_FUNCTION(1,2) {
294 | // gets message as string
295 | va_list argp;
296 | va_start(argp, format);
297 | NSString *log = [[NSString alloc] initWithFormat:format arguments:argp];
298 | va_end(argp);
299 |
300 | // outputs to stdout
301 | NSLog(@"%@", log);
302 |
303 | // appends to output log
304 | NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
305 | dateFormatter.dateFormat = @"hh:mm:ss";
306 | NSString *dateString = [dateFormatter stringFromDate:[NSDate date]];
307 | NSString *logLine = [NSString stringWithFormat:@"\n%@: %@", dateString, log];
308 | NSAttributedString* logLineAttr = [[NSAttributedString alloc] initWithString:logLine];
309 | [[_logTextView textStorage] appendAttributedString:logLineAttr];
310 | }
311 |
312 | @end
313 |
--------------------------------------------------------------------------------
/Examples/Example-macOS/Source/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIconFile
10 |
11 | CFBundleIdentifier
12 | $(PRODUCT_BUNDLE_IDENTIFIER)
13 | CFBundleInfoDictionaryVersion
14 | 6.0
15 | CFBundleName
16 | $(PRODUCT_NAME)
17 | CFBundlePackageType
18 | APPL
19 | CFBundleShortVersionString
20 | 1.0
21 | CFBundleSignature
22 | ????
23 | CFBundleVersion
24 | 1
25 | LSMinimumSystemVersion
26 | $(MACOSX_DEPLOYMENT_TARGET)
27 | NSHumanReadableCopyright
28 | Copyright © 2016 Google. All rights reserved.
29 | NSMainNibFile
30 | MainMenu
31 | NSPrincipalClass
32 | NSApplication
33 | CFBundleURLTypes
34 |
35 |
36 | CFBundleTypeRole
37 | Editor
38 | CFBundleURLSchemes
39 |
40 | com.googleusercontent.apps.YOUR_CLIENT
41 |
42 |
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/Examples/Example-macOS/Source/main.m:
--------------------------------------------------------------------------------
1 | /*! @file main.m
2 | @brief Application main entry point.
3 | @copyright
4 | Copyright 2016 Google Inc.
5 | @copydetails
6 | Licensed under the Apache License, Version 2.0 (the "License");
7 | you may not use this file except in compliance with the License.
8 | You may obtain a copy of the License at
9 |
10 | http://www.apache.org/licenses/LICENSE-2.0
11 |
12 | Unless required by applicable law or agreed to in writing, software
13 | distributed under the License is distributed on an "AS IS" BASIS,
14 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | See the License for the specific language governing permissions and
16 | limitations under the License.
17 | */
18 |
19 | #import
20 |
21 | int main(int argc, const char * argv[]) {
22 | return NSApplicationMain(argc, argv);
23 | }
24 |
--------------------------------------------------------------------------------
/GTMAppAuth.podspec:
--------------------------------------------------------------------------------
1 | Pod::Spec.new do |s|
2 |
3 | s.name = 'GTMAppAuth'
4 | s.version = '5.0.0'
5 | s.swift_version = '4.0'
6 | s.summary = 'Authorize GTM Session Fetcher requests with AppAuth via GTMAppAuth'
7 |
8 | s.description = <<-DESC
9 |
10 | GTMAppAuth enables you to use AppAuth with the Google Toolbox for Mac - Session
11 | Fetcher and Google APIs Client Library for Objective-C For REST libraries by
12 | providing an implementation of GTMFetcherAuthorizationProtocol for authorizing
13 | requests with AppAuth.
14 |
15 | DESC
16 |
17 | s.homepage = 'https://github.com/google/GTMAppAuth'
18 | s.license = { :type => 'Apache', :file => 'LICENSE' }
19 | s.author = 'Google LLC'
20 |
21 | s.source = { :git => 'https://github.com/google/GTMAppAuth.git', :tag => s.version }
22 | s.prefix_header_file = false
23 | s.source_files = 'GTMAppAuth/Sources/**/*.swift'
24 | s.resource_bundles = {
25 | "GTMAppAuth_Privacy" => "GTMAppAuth/Sources/Resources/PrivacyInfo.xcprivacy"
26 | }
27 |
28 | ios_deployment_target = '12.0'
29 | osx_deployment_target = '10.12'
30 | tvos_deployment_target = '10.0'
31 | watchos_deployment_target = '6.0'
32 | s.ios.deployment_target = ios_deployment_target
33 | s.osx.deployment_target = osx_deployment_target
34 | s.tvos.deployment_target = tvos_deployment_target
35 | s.watchos.deployment_target = watchos_deployment_target
36 |
37 | s.framework = 'Security'
38 | s.dependency 'GTMSessionFetcher/Core', '>= 3.3', '< 4.0'
39 | s.dependency 'AppAuth/Core', '~> 2.0'
40 |
41 | s.test_spec 'unit' do |unit_tests|
42 | unit_tests.platforms = {
43 | :ios => ios_deployment_target,
44 | :osx => osx_deployment_target,
45 | :tvos => tvos_deployment_target,
46 | }
47 | unit_tests.source_files = [
48 | 'GTMAppAuth/Tests/Unit/**/*.swift',
49 | 'GTMAppAuth/Tests/Helpers/**/*.swift',
50 | ]
51 | unit_tests.dependency 'AppAuth/Core'
52 | unit_tests.requires_app_host = true
53 | end
54 |
55 | s.test_spec 'objc-api-integration' do |api_tests|
56 | api_tests.platforms = {
57 | :ios => ios_deployment_target,
58 | :osx => osx_deployment_target,
59 | :tvos => tvos_deployment_target,
60 | }
61 | api_tests.source_files = [
62 | 'GTMAppAuth/Tests/ObjCIntegration/**/*.m',
63 | 'GTMAppAuth/Tests/Helpers/**/*.swift',
64 | ]
65 | api_tests.dependency 'AppAuth/Core'
66 | api_tests.requires_app_host = true
67 | end
68 |
69 | end
70 |
--------------------------------------------------------------------------------
/GTMAppAuth/Sources/AuthSessionDelegate.swift:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2023 Google LLC
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | import Foundation
18 |
19 | /// Methods defining the `AuthSession`'s delegate.
20 | @objc(GTMAuthSessionDelegate)
21 | public protocol AuthSessionDelegate {
22 | /// Used to supply additional parameters on token refresh.
23 | ///
24 | /// - Parameters:
25 | /// - authSession: The `AuthSession` needing additional token refresh parameters.
26 | /// - Returns: An optional `[String: String]` supplying the additional token refresh parameters.
27 | @objc optional func additionalTokenRefreshParameters(
28 | forAuthSession authSession: AuthSession
29 | ) -> [String: String]?
30 |
31 | /// A method notifying the delegate that the authorization request failed.
32 | ///
33 | /// Use this method to examine the error behind the failed authorization request and supply a
34 | /// customized error created asynchronously that specifies whatever context is needed.
35 | ///
36 | /// - Parameters:
37 | /// - authSession: The `AuthSession` whose authorization request failed.
38 | /// - originalError: The original `Error` associated with the failure.
39 | /// - completion: An escaping closure to pass back the updated error.
40 | @objc optional func updateError(
41 | forAuthSession authSession: AuthSession,
42 | originalError: Error,
43 | completion: @escaping (Error?) -> Void
44 | )
45 | }
46 |
--------------------------------------------------------------------------------
/GTMAppAuth/Sources/AuthSessionStore.swift:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2022 Google LLC
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | import Foundation
18 |
19 | /// Represents the operations needed to provide `AuthSession` storage.
20 | @objc(GTMAuthSessionStore)
21 | public protocol AuthSessionStore {
22 | /// Saves the provided `AuthSession`.
23 | ///
24 | /// - Parameter authSession: An instance of `AuthSession` to save.
25 | /// - Throws: Any error that may arise during the save.
26 | @objc(saveAuthSession:error:)
27 | func save(authSession: AuthSession) throws
28 |
29 | /// Removes the stored `AuthSession`.
30 | ///
31 | /// - Throws: Any error that may arise during the removal.
32 | @objc(removeAuthSessionWithError:)
33 | func removeAuthSession() throws
34 |
35 | /// Retrieves the stored `AuthSession`.
36 | ///
37 | /// - Throws: Any error that may arise during the retrieval.
38 | @objc(retrieveAuthSessionWithError:)
39 | func retrieveAuthSession() throws -> AuthSession
40 | }
41 |
--------------------------------------------------------------------------------
/GTMAppAuth/Sources/KeychainStore/GTMOAuth2Compatibility.swift:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2022 Google LLC
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | import Foundation
18 | // Ensure that we import the correct dependency for both SPM and CocoaPods since
19 | // the latter doesn't define separate Clang modules for subspecs
20 | #if SWIFT_PACKAGE
21 | import AppAuthCore
22 | import GTMSessionFetcherCore
23 | #else
24 | import AppAuth
25 | import GTMSessionFetcher
26 | #endif
27 |
28 | // Standard OAuth keys
29 | let oauth2AccessTokenKey = "access_token"
30 | let oauth2RefreshTokenKey = "refresh_token"
31 | let oauth2ScopeKey = "scope"
32 | let oauth2ErrorKey = "error"
33 | let oauth2TokenTypeKey = "token_type"
34 | let oauth2ExpiresInKey = "expires_in"
35 | let oauth2CodeKey = "code"
36 | let oauth2AssertionKey = "assertion"
37 | let oauth2RefreshScopeKey = "refreshScope"
38 |
39 | // URI indicating an installed app is signing in. This is described at
40 | //
41 | // https://developers.google.com/identity/protocols/OAuth2InstalledApp#formingtheurl
42 | //
43 | let oobString = "urn:ietf:wg:oauth:2.0:oob"
44 |
45 | /// Class to support serialization and deserialization of `AuthSession` in the format used by
46 | /// GTMOAuth2.
47 | ///
48 | /// The methods of this class are capable of serializing and deserializing auth objects in a way
49 | /// compatible with the serialization in `GTMOAuth2ViewControllerTouch` and
50 | /// `GTMOAuth2WindowController` in GTMOAuth2.
51 | @objc(GTMOAuth2Compatibility)
52 | public final class GTMOAuth2Compatibility: NSObject {
53 |
54 | @available(*, unavailable)
55 | override init() {
56 | super.init()
57 | }
58 |
59 | // MARK: - GTMOAuth2 Utilities
60 |
61 | /// Encodes the given `AuthSession` in a GTMOAuth2 compatible persistence string using URL param
62 | /// key/value encoding.
63 | ///
64 | /// - Parameters:
65 | /// - authSession: The `AuthSession` to serialize in GTMOAuth2 format.
66 | /// - Returns: A `String?` representing the GTMOAuth2 persistence representation of the
67 | /// authorization object.
68 | @objc(persistenceResponseStringForAuthSession:)
69 | public static func persistenceResponseString(forAuthSession authSession: AuthSession) -> String? {
70 | // TODO: (mdmathias) Write a test for this method that ensures nil is returned.
71 | let refreshToken = authSession.authState.refreshToken
72 | let accessToken = authSession.authState.lastTokenResponse?.accessToken
73 |
74 | let dict = [
75 | oauth2RefreshTokenKey: refreshToken,
76 | oauth2AccessTokenKey: accessToken,
77 | AuthSession.serviceProviderKey: authSession.serviceProvider,
78 | AuthSession.userIDKey: authSession.userID,
79 | AuthSession.userEmailKey: authSession.userEmail,
80 | AuthSession.userEmailIsVerifiedKey: authSession._userEmailIsVerified,
81 | oauth2ScopeKey: authSession.authState.scope
82 | ]
83 |
84 | let responseString = dict
85 | .sorted { $0.key < $1.key }
86 | .compactMap { (key, _) -> String? in
87 | guard let val = dict[key] as? String,
88 | let encodedKey = encodedOAuthValue(forOriginalString: key),
89 | let encodedValue = encodedOAuthValue(forOriginalString: val) else {
90 | return nil
91 | }
92 | return String(format: "%@=%@", arguments: [encodedKey, encodedValue])
93 | }
94 | .joined(separator: "&")
95 |
96 | return responseString.isEmpty ? nil : responseString
97 | }
98 |
99 | // MARK: - Encoded OAuth Value
100 |
101 | private static func encodedOAuthValue(
102 | forOriginalString originalString: String
103 | ) -> String? {
104 | // For parameters, we'll explicitly leave spaces unescaped now, and replace them with +'s
105 | let forceEscape = "!*'();:@&=+$,/?%#[]"
106 | let escapeCharacters = CharacterSet(charactersIn: forceEscape)
107 | let urlQueryCharacters = CharacterSet.urlQueryAllowed.symmetricDifference(escapeCharacters)
108 | return originalString.addingPercentEncoding(withAllowedCharacters: urlQueryCharacters)
109 | }
110 |
111 | private static var googleAuthorizationURL = URL(string: "https://accounts.google.com/o/oauth2/v2/auth")!
112 | static let googleTokenURL = URL(string: "https://www.googleapis.com/oauth2/v4/token")!
113 | static var googleRevocationURL = URL(string: "https://accounts.google.com/o/oauth2/revoke")!
114 | static var googleUserInfoURL = URL(string: "https://www.googleapis.com/oauth2/v3/userinfo")!
115 | static var nativeClientRedirectURI: String {
116 | return oobString
117 | }
118 |
119 | static func dictionary(
120 | fromKeychainPassword keychainPassword: String
121 | ) -> [String: String] {
122 | let keyValueTuples: [(String, String)] = keychainPassword
123 | .components(separatedBy: "&")
124 | .compactMap {
125 | let equalComps = $0.components(separatedBy: "=")
126 | guard let key = equalComps.first,
127 | let percentsRemovedKey = key.removingPercentEncoding,
128 | let value = equalComps.last,
129 | let percentsRemovedValue = value.removingPercentEncoding else {
130 | return nil
131 | }
132 | return (percentsRemovedKey, percentsRemovedValue)
133 | }
134 | let passwordDictionary = Dictionary(uniqueKeysWithValues: keyValueTuples)
135 | return passwordDictionary
136 | }
137 |
138 | /// Creates an `AuthSession` from the provided persistence string.
139 | /// - Parameters:
140 | /// - persistenceString: The `String` representing the `AuthSession` to create.
141 | /// - tokenURL: The `URL` to use when creating the `AuthSession`.
142 | /// - redirectURI: The `String` URI to use for the `AuthSession`.
143 | /// - clientID: The `String` client ID for the `AuthSession`.
144 | /// - clientSecret: The optional `String` for the `AuthSession`.
145 | /// - Throws: `KeychainStore.Error.failedToConvertRedirectURItoURL` if `redirectURI` cannot be
146 | /// converted to a `URL`.
147 | /// - Returns: An instance of `AuthSession` if successful.
148 | @objc static public func authSession(
149 | forPersistenceString persistenceString: String,
150 | tokenURL: URL,
151 | redirectURI: String,
152 | clientID: String,
153 | clientSecret: String?
154 | ) throws -> AuthSession {
155 | let persistenceDictionary = GTMOAuth2Compatibility.dictionary(
156 | fromKeychainPassword: persistenceString
157 | )
158 | guard let redirectURL = URL(string: redirectURI) else {
159 | throw KeychainStore.Error.failedToConvertRedirectURItoURL(redirectURI)
160 | }
161 |
162 | let authConfig = OIDServiceConfiguration(
163 | authorizationEndpoint: tokenURL,
164 | tokenEndpoint: tokenURL
165 | )
166 |
167 | let authRequest = OIDAuthorizationRequest(
168 | configuration: authConfig,
169 | clientId: clientID,
170 | clientSecret: clientSecret,
171 | scope: persistenceDictionary[oauth2ScopeKey],
172 | redirectURL: redirectURL,
173 | responseType: OIDResponseTypeCode,
174 | state: nil,
175 | nonce: nil,
176 | codeVerifier: nil,
177 | codeChallenge: nil,
178 | codeChallengeMethod: nil,
179 | additionalParameters: nil
180 | )
181 |
182 | let authResponse = OIDAuthorizationResponse(
183 | request: authRequest,
184 | parameters: persistenceDictionary as [String: NSString]
185 | )
186 | var additionalParameters = persistenceDictionary
187 | additionalParameters.removeValue(forKey: oauth2ScopeKey)
188 | additionalParameters.removeValue(forKey: oauth2RefreshTokenKey)
189 |
190 | let tokenRequest = OIDTokenRequest(
191 | configuration: authConfig,
192 | grantType: "token",
193 | authorizationCode: nil,
194 | redirectURL: redirectURL,
195 | clientID: clientID,
196 | clientSecret: clientSecret,
197 | scope: persistenceDictionary[oauth2ScopeKey],
198 | refreshToken: persistenceDictionary[oauth2RefreshTokenKey],
199 | codeVerifier: nil,
200 | additionalParameters: additionalParameters
201 | )
202 | let tokenResponse = OIDTokenResponse(
203 | request: tokenRequest,
204 | parameters: persistenceDictionary as [String: NSString]
205 | )
206 |
207 | let authState = OIDAuthState(authorizationResponse: authResponse, tokenResponse: tokenResponse)
208 | // We're not serializing the token expiry date, so the first refresh needs to be forced.
209 | authState.setNeedsTokenRefresh()
210 |
211 | let authSession = AuthSession(
212 | authState: authState,
213 | serviceProvider: persistenceDictionary[AuthSession.serviceProviderKey],
214 | userID: persistenceDictionary[AuthSession.userIDKey],
215 | userEmail: persistenceDictionary[AuthSession.userEmailKey],
216 | userEmailIsVerified: persistenceDictionary[AuthSession.userEmailIsVerifiedKey]
217 | )
218 | return authSession
219 | }
220 | }
221 |
--------------------------------------------------------------------------------
/GTMAppAuth/Sources/KeychainStore/KeychainAttribute.swift:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2022 Google LLC
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | import Foundation
18 |
19 | /// The Keychain attribute used to configure the way the keychain stores your items.
20 | @objc(GTMKeychainAttribute)
21 | public final class KeychainAttribute: NSObject {
22 | /// An enumeratiion listing the various attributes used to configure the Keychain.
23 | public enum Attribute {
24 | /// Indicates whether to use the legacy file-based keychain on macOS.
25 | ///
26 | /// This attribute will set `kSecUseDataProtectionKeychain` as `false` in the Keychain query.
27 | case useFileBasedKeychain
28 | /// The `String` name for the access group to use in the Keychain query.
29 | case accessGroup(String)
30 |
31 | /// A `String` representation of the attribute.
32 | public var keyName: String {
33 | switch self {
34 | case .useFileBasedKeychain:
35 | if #available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) {
36 | return kSecUseDataProtectionKeychain as String
37 | } else {
38 | fatalError("`kSecUseDataProtectionKeychain is only available on macOS 10.15 and greater")
39 | }
40 | case .accessGroup:
41 | return kSecAttrAccessGroup as String
42 | }
43 | }
44 | }
45 |
46 | /// The set `Attribute` given upon initialization.
47 | public let attribute: Attribute
48 |
49 | /// Creates an instance of `KeychainAttribute`.
50 | /// - Parameters:
51 | /// - attribute: An instance of `KeychainAttribute.Attribute` used to configure Keychain
52 | /// queries.
53 | public init(attribute: Attribute) {
54 | self.attribute = attribute
55 | }
56 |
57 | /// Creates an instance of `KeychainAttribute` whose attribute is set to
58 | /// `.useFileBasedKeychain`.
59 | /// - Returns: An instance of `KeychainAttribute`.
60 | @objc public static let useFileBasedKeychain = KeychainAttribute(
61 | attribute: .useFileBasedKeychain
62 | )
63 |
64 | /// Creates an instance of `KeychainAttribute` whose attribute is set to `.accessGroup`.
65 | /// - Parameters:
66 | /// - name: The `String` name for the access group.
67 | /// - Returns: An instance of `KeychainAttribute`.
68 | @objc public static func keychainAccessGroup(name: String) -> KeychainAttribute {
69 | return KeychainAttribute(attribute: .accessGroup(name))
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/GTMAppAuth/Sources/KeychainStore/KeychainHelper.swift:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2022 Google LLC
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | import Foundation
18 |
19 | /// A protocol defining the helper API for interacting with the Keychain.
20 | @objc(GTMKeychainHelper)
21 | public protocol KeychainHelper {
22 | var accountName: String { get }
23 | var keychainAttributes: Set { get }
24 | init(keychainAttributes: Set)
25 | func keychainQuery(forService service: String) -> [String: Any]
26 | func password(forService service: String) throws -> String
27 | func passwordData(forService service: String) throws -> Data
28 | func removePassword(forService service: String) throws
29 | func setPassword(_ password: String, forService service: String) throws
30 | func setPassword(_ password: String, forService service: String, accessibility: CFTypeRef) throws
31 | func setPassword(data: Data, forService service: String, accessibility: CFTypeRef?) throws
32 | }
33 |
34 | /// An internally scoped keychain helper.
35 | final class KeychainWrapper: KeychainHelper {
36 | let accountName = "OAuth"
37 | let keychainAttributes: Set
38 |
39 | init(keychainAttributes: Set = []) {
40 | self.keychainAttributes = keychainAttributes
41 | }
42 |
43 | func keychainQuery(forService service: String) -> [String: Any] {
44 | var query: [String: Any] = [
45 | kSecClass as String: kSecClassGenericPassword,
46 | kSecAttrAccount as String : accountName,
47 | kSecAttrService as String: service,
48 | ]
49 |
50 | if #available(macOS 10.15, macCatalyst 13.1, iOS 13.0, tvOS 13.0, watchOS 6.0, *) {
51 | query[kSecUseDataProtectionKeychain as String] = kCFBooleanTrue
52 | }
53 |
54 | keychainAttributes.forEach { configuration in
55 | switch configuration.attribute {
56 | case .useFileBasedKeychain:
57 | if #available(macOS 10.15, macCatalyst 13.1, iOS 13.0, tvOS 13.0, watchOS 6.0, *) {
58 | query[configuration.attribute.keyName] = kCFBooleanFalse
59 | }
60 | case .accessGroup(let name):
61 | query[configuration.attribute.keyName] = name
62 | }
63 | }
64 |
65 | return query
66 | }
67 |
68 | func password(forService service: String) throws -> String {
69 | let passwordData = try passwordData(forService: service)
70 | guard let result = String(data: passwordData, encoding: .utf8) else {
71 | throw KeychainStore.Error.unexpectedPasswordData(forItemName: service)
72 | }
73 | return result
74 | }
75 |
76 | func passwordData(forService service: String) throws -> Data {
77 | guard !service.isEmpty else { throw KeychainStore.Error.noService }
78 |
79 | var passwordItem: AnyObject?
80 | var keychainQuery = keychainQuery(forService: service)
81 | keychainQuery[kSecReturnData as String] = true
82 | keychainQuery[kSecMatchLimit as String] = kSecMatchLimitOne
83 | let status = SecItemCopyMatching(keychainQuery as CFDictionary, &passwordItem)
84 |
85 | guard status != errSecItemNotFound else {
86 | throw KeychainStore.Error.passwordNotFound(forItemName: service)
87 | }
88 |
89 | guard status == errSecSuccess else { throw KeychainStore.Error.unhandled(status: status) }
90 |
91 | guard let result = passwordItem as? Data else {
92 | throw KeychainStore.Error.unexpectedPasswordData(forItemName: service)
93 | }
94 |
95 | return result
96 | }
97 |
98 | func removePassword(forService service: String) throws {
99 | guard !service.isEmpty else { throw KeychainStore.Error.noService }
100 | let keychainQuery = keychainQuery(forService: service)
101 | let status = SecItemDelete(keychainQuery as CFDictionary)
102 |
103 | guard status != errSecItemNotFound else {
104 | throw KeychainStore.Error.failedToDeletePasswordBecauseItemNotFound(itemName: service)
105 | }
106 | guard status == noErr else {
107 | throw KeychainStore.Error.failedToDeletePassword(forItemName: service)
108 | }
109 | }
110 |
111 | func setPassword(_ password: String, forService service: String) throws {
112 | let passwordData = Data(password.utf8)
113 | try setPassword(data: passwordData, forService: service, accessibility: nil)
114 | }
115 |
116 | func setPassword(
117 | _ password: String,
118 | forService service: String,
119 | accessibility: CFTypeRef
120 | ) throws {
121 | let passwordData = Data(password.utf8)
122 | try setPassword(data: passwordData, forService: service, accessibility: accessibility)
123 | }
124 |
125 | func setPassword(data: Data, forService service: String, accessibility: CFTypeRef?) throws {
126 | guard !service.isEmpty else { throw KeychainStore.Error.noService }
127 | do {
128 | try removePassword(forService: service)
129 | } catch KeychainStore.Error.failedToDeletePasswordBecauseItemNotFound {
130 | // Don't throw; password doesn't exist since the password is being saved for the first time
131 | } catch {
132 | // throw here since this is some other error
133 | throw error
134 | }
135 | guard !data.isEmpty else { return }
136 | var keychainQuery = keychainQuery(forService: service)
137 | keychainQuery[kSecValueData as String] = data
138 |
139 | if let accessibility = accessibility {
140 | keychainQuery[kSecAttrAccessible as String] = accessibility
141 | }
142 |
143 | let status = SecItemAdd(keychainQuery as CFDictionary, nil)
144 | guard status == noErr else {
145 | throw KeychainStore.Error.failedToSetPassword(forItemName: service)
146 | }
147 | }
148 | }
149 |
--------------------------------------------------------------------------------
/GTMAppAuth/Sources/Resources/PrivacyInfo.xcprivacy:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | NSPrivacyCollectedDataTypes
6 |
7 | NSPrivacyAccessedAPITypes
8 |
9 | NSPrivacyTrackingDomains
10 |
11 | NSPrivacyTracking
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/GTMAppAuth/Tests/Helpers/AuthorizationTestingHelp.swift:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2022 Google LLC
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | import Foundation
18 | // Ensure that we import the correct dependency for both SPM and CocoaPods since
19 | // the latter doesn't define separate Clang modules for subspecs
20 | #if SWIFT_PACKAGE
21 | import AppAuthCore
22 | #else
23 | import AppAuth
24 | #endif
25 | import XCTest
26 | import GTMAppAuth
27 |
28 | /// The delegate object passed to selector-based callback to authorize requests.
29 | @objc(GTMAuthorizationTestDelegate)
30 | public class AuthorizationTestDelegate: NSObject {
31 | /// The authorization passed back to this delegate.
32 | @objc public var passedAuthorization: AuthSession?
33 | /// The request passed back to this delegate.
34 | @objc public var passedRequest: NSMutableURLRequest?
35 | /// The error passed back to this delegate.
36 | @objc public var passedError: NSError?
37 | /// The expectation needing fulfillment when this delegate receives its callback.
38 | @objc public let expectation: XCTestExpectation
39 |
40 | /// An initializer creating the delegate.
41 | ///
42 | /// - Parameter expectation: The `XCTestExpectation` to be fulfilled.
43 | @objc public init(expectation: XCTestExpectation) {
44 | self.expectation = expectation
45 | }
46 |
47 | /// The function serving as the callback for this delegate.
48 | ///
49 | /// - Parameters:
50 | /// - authSession: The `AuthSession` authorizing the request.
51 | /// - request: The request to be authorized.
52 | /// - error: The `NSError?` should one arise during authorization.
53 | @objc public func authentication(
54 | _ authSession: AuthSession,
55 | request: NSMutableURLRequest,
56 | finishedWithError error: NSError?
57 | ) {
58 | passedAuthorization = authSession
59 | passedRequest = request
60 | passedError = error
61 |
62 | expectation.fulfill()
63 | }
64 | }
65 |
66 | /// A testing helper that does not implement the update error method in `AuthSessionDelegate`.
67 | @objc(GTMAuthSessionDelegateProvideMissingEMMErrorHandling)
68 | public class AuthSessionDelegateProvideMissingEMMErrorHandling: NSObject, AuthSessionDelegate {
69 | /// The `AuthSession` to which this delegate was given.
70 | @objc public var originalAuthSession: AuthSession?
71 |
72 | /// Whether or not the delegate callback for additional refresh parameters was called.
73 | ///
74 | /// - Note: Defaults to `false`.
75 | @objc public var additionalRefreshParametersCalled = false
76 |
77 | /// Whether or not the delegate callback for authorization request failure was called.
78 | ///
79 | /// - Note: Defaults to `false`.
80 | @objc public var updatedErrorCalled = false
81 |
82 | /// The expected error from the delegate callback.
83 | @objc public let expectedError: NSError?
84 |
85 | @objc public init(originalAuthSession: AuthSession, expectedError: NSError? = nil) {
86 | self.originalAuthSession = originalAuthSession
87 | self.expectedError = expectedError
88 | }
89 |
90 | public func additionalTokenRefreshParameters(
91 | forAuthSession authSession: AuthSession
92 | ) -> [String : String]? {
93 | XCTAssertEqual(authSession, originalAuthSession)
94 | additionalRefreshParametersCalled = true
95 | return [:]
96 | }
97 | }
98 |
99 | /// A testing helper given to `AuthSession`'s `delegate` to verify delegate callbacks.
100 | @objc(GTMAuthSessionDelegateProvider)
101 | public class AuthSessionDelegateProvider: NSObject, AuthSessionDelegate {
102 | /// The `AuthSession` to which this delegate was given.
103 | @objc public var originalAuthSession: AuthSession?
104 |
105 | /// Whether or not the delegate callback for additional refresh parameters was called.
106 | ///
107 | /// - Note: Defaults to `false`.
108 | @objc public var additionalRefreshParametersCalled = false
109 |
110 | /// Whether or not the delegate callback for authorization request failure was called.
111 | ///
112 | /// - Note: Defaults to `false`.
113 | @objc public var updatedErrorCalled = false
114 |
115 | /// The expected error from the delegate callback.
116 | @objc public let expectedError: NSError?
117 |
118 | @objc public init(originalAuthSession: AuthSession, expectedError: NSError? = nil) {
119 | self.originalAuthSession = originalAuthSession
120 | self.expectedError = expectedError
121 | }
122 |
123 | public func additionalTokenRefreshParameters(
124 | forAuthSession authSession: AuthSession
125 | ) -> [String : String]? {
126 | XCTAssertEqual(authSession, originalAuthSession)
127 | additionalRefreshParametersCalled = true
128 | return [:]
129 | }
130 |
131 | public func updateError(
132 | forAuthSession authSession: AuthSession,
133 | originalError: Error,
134 | completion: @escaping (Error?) -> Void
135 | ) {
136 | XCTAssertEqual(authSession, originalAuthSession)
137 | updatedErrorCalled = true
138 | completion(expectedError)
139 | }
140 | }
141 |
--------------------------------------------------------------------------------
/GTMAppAuth/Tests/Helpers/GTMAppAuthTestingHelpers.swift:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2022 Google LLC
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | import Foundation
18 |
19 | /// A protocol for creating a test instance of `Self`.
20 | @objc(GTMTesting)
21 | public protocol Testing {
22 | static func testInstance() -> Self
23 | }
24 |
25 | // MARK: - Constants
26 |
27 | @objc(GTMTestingConstants)
28 | public class TestingConstants: NSObject {
29 | @objc public static let testAccessGroup = "testAccessGroup"
30 | @objc public static let testAccessToken = "access_token"
31 | @objc public static let accessTokenExpiresIn = 3600
32 | @objc public static let testRefreshToken = "refresh_token"
33 | @objc public static let serverAuthCode = "server_auth_code"
34 |
35 | @objc public static let alg = "RS256"
36 | @objc public static let kid = "alkjdfas"
37 | @objc public static let typ = "JWT"
38 | @objc public static let userID = "123456789"
39 | @objc public static let hostedDomain = "fakehosteddomain.com"
40 | @objc public static let issuer = "https://test.com"
41 | @objc public static let audience = "audience"
42 | @objc public static let IDTokenExpires = 1000
43 | @objc public static let issuedAt = 0
44 |
45 | @objc public static let fatNameKey = "name";
46 | @objc public static let fatGivenNameKey = "given_name"
47 | @objc public static let fatFamilyNameKey = "family_name"
48 | @objc public static let fatPictureURLKey = "picture"
49 |
50 | @objc public static let fatName = "fake username"
51 | @objc public static let fatGivenName = "fake"
52 | @objc public static let fatFamilyName = "username"
53 | @objc public static let fatPictureURL = "fake_user_picture_url"
54 |
55 | @objc public static let testClientID = "87654321.googleusercontent.com"
56 | @objc public static let testScope1 = "email"
57 | @objc public static let testScope2 = "profile"
58 | @objc public static let testCodeVerifier = "codeVerifier"
59 | @objc public static let testPassword = "foo"
60 | @objc public static let testKeychainItemName = "testName"
61 | @objc public static let testServiceProvider = "fooProvider"
62 | @objc public static let testUserID = "123456789"
63 | @objc public static let testEmail = "foo@foo.com"
64 | @objc public static let testClientSecret = "fooSecret"
65 | @objc public static let testTokenURL: URL = URL(string: "https://testTokenURL.com")!
66 | @objc public static let testRedirectURI = "https://testRedirectURI.com"
67 | }
68 |
--------------------------------------------------------------------------------
/GTMAppAuth/Tests/Helpers/KeychainHelperFake.swift:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2022 Google LLC
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | import Foundation
18 | import GTMAppAuth
19 |
20 | @objc(GTMKeychainHelperFake)
21 | public class KeychainHelperFake: NSObject, KeychainHelper {
22 | @objc public var useFileBasedKeychain = false
23 | @objc public var passwordStore = [String: Data]()
24 | @objc public let accountName = "OauthTest"
25 | @objc public let keychainAttributes: Set
26 | @objc public var generatedKeychainQuery: [String: Any]?
27 | /// Data generated from `NSKeyedArchiver` treated as a property list to test class name mapping.
28 | private(set) var archiveDataPropertyList: Any?
29 | /// Data generated from `NSKeyedUnarchiver` treated as a property list to test class name mapping.
30 | private(set) var unarchiveDataPropertyList: Any?
31 |
32 | @objc public required init(keychainAttributes: Set) {
33 | self.keychainAttributes = keychainAttributes
34 | }
35 |
36 | @objc public func keychainQuery(forService service: String) -> [String : Any] {
37 | var query: [String: Any] = [
38 | kSecClass as String: kSecClassGenericPassword,
39 | kSecAttrAccount as String : accountName,
40 | kSecAttrService as String: service,
41 | ]
42 |
43 | if #available(macOS 10.15, macCatalyst 13.1, iOS 13.0, tvOS 13.0, watchOS 6.0, *) {
44 | query[kSecUseDataProtectionKeychain as String] = kCFBooleanTrue
45 | }
46 |
47 | keychainAttributes.forEach { configuration in
48 | switch configuration.attribute {
49 | case .useFileBasedKeychain:
50 | query[configuration.attribute.keyName] = kCFBooleanFalse
51 | case .accessGroup(let name):
52 | query[configuration.attribute.keyName] = name
53 | }
54 | }
55 |
56 | return query
57 | }
58 |
59 | @objc public func password(forService service: String) throws -> String {
60 | guard !service.isEmpty else { throw KeychainStore.Error.noService }
61 |
62 | let passwordData = try passwordData(forService: service)
63 | guard let password = String(data: passwordData, encoding: .utf8) else {
64 | throw KeychainStore.Error.passwordNotFound(forItemName: service)
65 | }
66 | return password
67 | }
68 |
69 | @objc public func passwordData(forService service: String) throws -> Data {
70 | guard !service.isEmpty else { throw KeychainStore.Error.noService }
71 |
72 | generatedKeychainQuery = keychainQuery(forService: service)
73 | guard let passwordData = passwordStore[service + accountName] else {
74 | throw KeychainStore.Error.passwordNotFound(forItemName: service)
75 | }
76 |
77 | // Use `try?` instead of try to avoid throwing in the ObjC integration tests and test failures
78 | self.unarchiveDataPropertyList = try? PropertyListSerialization.propertyList(
79 | from: passwordData,
80 | format: nil
81 | )
82 |
83 | return passwordData
84 | }
85 |
86 | @objc public func removePassword(forService service: String) throws {
87 | guard !service.isEmpty else { throw KeychainStore.Error.noService }
88 |
89 | generatedKeychainQuery = keychainQuery(forService: service)
90 | guard let _ = passwordStore.removeValue(forKey: service + accountName) else {
91 | throw KeychainStore.Error.failedToDeletePasswordBecauseItemNotFound(itemName: service)
92 | }
93 | }
94 |
95 | @objc public func setPassword(_ password: String, forService service: String) throws {
96 | do {
97 | try removePassword(forService: service)
98 | } catch KeychainStore.Error.failedToDeletePasswordBecauseItemNotFound {
99 | // No need to throw this error since we are setting a new password
100 | } catch {
101 | throw error
102 | }
103 |
104 | guard let passwordData = password.data(using: .utf8) else {
105 | throw KeychainStore.Error.unexpectedPasswordData(forItemName: service)
106 | }
107 | try setPassword(data: passwordData, forService: service, accessibility: nil)
108 | }
109 |
110 | @objc public func setPassword(
111 | _ password: String,
112 | forService service: String,
113 | accessibility: CFTypeRef
114 | ) throws {
115 | do {
116 | try removePassword(forService: service)
117 | } catch KeychainStore.Error.failedToDeletePasswordBecauseItemNotFound {
118 | // No need to throw this error since we are setting a new password
119 | } catch {
120 | throw error
121 | }
122 |
123 | guard let passwordData = password.data(using: .utf8) else {
124 | throw KeychainStore.Error.unexpectedPasswordData(forItemName: service)
125 | }
126 | try setPassword(data: passwordData, forService: service, accessibility: accessibility)
127 | }
128 |
129 | @objc public func setPassword(
130 | data: Data,
131 | forService service: String,
132 | accessibility: CFTypeRef?
133 | ) throws {
134 | guard !service.isEmpty else { throw KeychainStore.Error.noService }
135 |
136 | // Use `try?` instead of try to avoid throwing in the ObjC integration tests and test failures
137 | self.archiveDataPropertyList = try? PropertyListSerialization.propertyList(
138 | from: data,
139 | format: nil
140 | )
141 | generatedKeychainQuery = keychainQuery(forService: service)
142 | passwordStore.updateValue(data, forKey: service + accountName)
143 | }
144 | }
145 |
--------------------------------------------------------------------------------
/GTMAppAuth/Tests/Helpers/OIDAuthStateTesting.swift:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2022 Google LLC
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | // Ensure that we import the correct dependency for both SPM and CocoaPods since
18 | // the latter doesn't define separate Clang modules for subspecs
19 | #if SWIFT_PACKAGE
20 | import AppAuthCore
21 | #else
22 | import AppAuth
23 | #endif
24 |
25 | /// A protocol to help create test instances of `OIDAuthState` with various
26 | /// arguments.
27 | @objc public protocol AuthStateTesting: Testing {
28 | static func testInstance(idToken: String) -> Self
29 | static func testInstance(tokenResponse: OIDTokenResponse) -> Self
30 | static func testInstance(
31 | authorizationResponse: OIDAuthorizationResponse?,
32 | tokenResponse: OIDTokenResponse?,
33 | registrationResponse: OIDRegistrationResponse?
34 | ) -> Self
35 | }
36 |
37 | @objc extension OIDAuthState: AuthStateTesting {
38 | public static func testInstance() -> Self {
39 | return OIDAuthState(
40 | authorizationResponse: OIDAuthorizationResponse.testInstance(),
41 | tokenResponse: OIDTokenResponse.testInstance()
42 | ) as! Self
43 | }
44 |
45 | public static func testInstance(idToken: String) -> Self {
46 | return testInstance(
47 | tokenResponse: OIDTokenResponse.testInstance(idToken: idToken)
48 | )
49 | }
50 |
51 | public static func testInstance(tokenResponse: OIDTokenResponse) -> Self {
52 | return testInstance(
53 | authorizationResponse: OIDAuthorizationResponse.testInstance(),
54 | tokenResponse: tokenResponse,
55 | registrationResponse: nil
56 | )
57 | }
58 |
59 | public static func testInstance(
60 | authorizationResponse: OIDAuthorizationResponse?,
61 | tokenResponse: OIDTokenResponse?,
62 | registrationResponse: OIDRegistrationResponse?
63 | ) -> Self {
64 | return OIDAuthState(
65 | authorizationResponse: authorizationResponse,
66 | tokenResponse: tokenResponse,
67 | registrationResponse: registrationResponse
68 | ) as! Self
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/GTMAppAuth/Tests/Helpers/OIDAuthorizationRequestTesting.swift:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2022 Google LLC
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | // Ensure that we import the correct dependency for both SPM and CocoaPods since
18 | // the latter doesn't define separate Clang modules for subspecs
19 | #if SWIFT_PACKAGE
20 | import AppAuthCore
21 | #else
22 | import AppAuth
23 | #endif
24 |
25 | @objc extension OIDAuthorizationRequest: Testing {
26 | public static func testInstance() -> Self {
27 | return OIDAuthorizationRequest(
28 | configuration: OIDServiceConfiguration.testInstance(),
29 | clientId: TestingConstants.testClientID,
30 | scopes: [TestingConstants.testScope1, TestingConstants.testScope2],
31 | redirectURL: URL(string: "http://test.com")!,
32 | responseType: OIDResponseTypeCode,
33 | additionalParameters: nil
34 | ) as! Self
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/GTMAppAuth/Tests/Helpers/OIDAuthorizationResponseTesting.swift:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2022 Google LLC
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | // Ensure that we import the correct dependency for both SPM and CocoaPods since
18 | // the latter doesn't define separate Clang modules for subspecs
19 | #if SWIFT_PACKAGE
20 | import AppAuthCore
21 | #else
22 | import AppAuth
23 | #endif
24 |
25 | /// Create a testing instance of `OIDAuthorizationResponse` taking arguments for additional
26 | /// parameters and an error description.
27 | @objc public protocol AuthorizationResponseTesting: Testing {
28 | static func testInstance(
29 | additionalParameters: [String: String]?,
30 | errorDescription: String?
31 | ) -> Self
32 | }
33 |
34 | @objc extension OIDAuthorizationResponse: AuthorizationResponseTesting {
35 | @objc public static func testInstance() -> Self {
36 | return testInstance(
37 | additionalParameters: nil,
38 | errorDescription: nil
39 | )
40 | }
41 |
42 | @objc public static func testInstance(
43 | additionalParameters: [String : String]?,
44 | errorDescription: String?
45 | ) -> Self {
46 | var parameters = [String: String]()
47 | if let errorString = errorDescription {
48 | parameters["error"] = errorString
49 | } else {
50 | parameters["code"] = "authorization_code"
51 | parameters["state"] = "state"
52 | parameters["token_type"] = OIDGrantTypeAuthorizationCode
53 | if let additionalParameters = additionalParameters {
54 | parameters.merge(additionalParameters) { _, new in new }
55 | }
56 | }
57 | return OIDAuthorizationResponse(
58 | request: OIDAuthorizationRequest.testInstance(),
59 | parameters: parameters as [String: NSCopying & NSObjectProtocol]
60 | ) as! Self
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/GTMAppAuth/Tests/Helpers/OIDRegistrationRequestTesting.swift:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2022 Google LLC
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | // Ensure that we import the correct dependency for both SPM and CocoaPods since
18 | // the latter doesn't define separate Clang modules for subspecs
19 | #if SWIFT_PACKAGE
20 | import AppAuthCore
21 | #else
22 | import AppAuth
23 | #endif
24 |
25 | /// Protocol for creating a test instance of `OIDRegistrationRequest` with a configuration.
26 | @objc public protocol RegistrationRequestTesting: Testing {
27 | static func testInstance(configuration: OIDServiceConfiguration) -> Self
28 | }
29 |
30 | @objc extension OIDRegistrationRequest: RegistrationRequestTesting {
31 | public static func testInstance() -> Self {
32 | testInstance(configuration: OIDServiceConfiguration.testInstance())
33 | }
34 |
35 | public static func testInstance(configuration: OIDServiceConfiguration) -> Self {
36 | return OIDRegistrationRequest(
37 | configuration: configuration,
38 | redirectURIs: [],
39 | responseTypes: nil,
40 | grantTypes: nil,
41 | subjectType: nil,
42 | tokenEndpointAuthMethod: nil,
43 | additionalParameters: nil
44 | ) as! Self
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/GTMAppAuth/Tests/Helpers/OIDRegistrationResponseTesting.swift:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2022 Google LLC
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | // Ensure that we import the correct dependency for both SPM and CocoaPods since
18 | // the latter doesn't define separate Clang modules for subspecs
19 | #if SWIFT_PACKAGE
20 | import AppAuthCore
21 | #else
22 | import AppAuth
23 | #endif
24 |
25 |
26 | /// Protocol for creating a test instance of `OIDRegistrationResponse` with a request and
27 | /// parameters.
28 | @objc public protocol RegistrationResponseTesting: Testing {
29 | static func testInstance(
30 | request: OIDRegistrationRequest,
31 | parameters: [String: String]
32 | ) -> Self
33 | }
34 |
35 | @objc extension OIDRegistrationResponse: RegistrationResponseTesting {
36 | public static func testInstance() -> Self {
37 | return testInstance(
38 | request: OIDRegistrationRequest.testInstance(),
39 | parameters: [:]
40 | )
41 | }
42 |
43 | public static func testInstance(
44 | request: OIDRegistrationRequest,
45 | parameters: [String: String]
46 | ) -> Self {
47 | return OIDRegistrationResponse(
48 | request: request,
49 | parameters: parameters as [String: NSCopying & NSObjectProtocol]
50 | ) as! Self
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/GTMAppAuth/Tests/Helpers/OIDServiceConfigurationTesting.swift:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2022 Google LLC
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | // Ensure that we import the correct dependency for both SPM and CocoaPods since
18 | // the latter doesn't define separate Clang modules for subspecs
19 | #if SWIFT_PACKAGE
20 | import AppAuthCore
21 | #else
22 | import AppAuth
23 | #endif
24 |
25 | /// Extension for creating a test instance of `OIDServiceConfiguration`.
26 | @objc extension OIDServiceConfiguration: Testing {
27 | public static func testInstance() -> Self {
28 | return OIDServiceConfiguration(
29 | authorizationEndpoint: URL(string: "https://test.com/auth")!,
30 | tokenEndpoint: URL(string: "https://test.com/token")!
31 | ) as! Self
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/GTMAppAuth/Tests/Helpers/OIDTokenRequestTesting.swift:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2022 Google LLC
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | // Ensure that we import the correct dependency for both SPM and CocoaPods since
18 | // the latter doesn't define separate Clang modules for subspecs
19 | #if SWIFT_PACKAGE
20 | import AppAuthCore
21 | #else
22 | import AppAuth
23 | #endif
24 |
25 | /// Protocol for creating a test instance of `OIDTokenRequest` with additional parameters.
26 | @objc public protocol TokenRequestTesting: Testing {
27 | static func testInstance(additionalParameters: [String: String]?) -> Self
28 | }
29 |
30 | @objc extension OIDTokenRequest: TokenRequestTesting {
31 | public static func testInstance() -> Self {
32 | testInstance(additionalParameters: nil)
33 | }
34 |
35 | public static func testInstance(additionalParameters: [String : String]?) -> Self {
36 | let authorizationResponse = OIDAuthorizationResponse.testInstance()
37 | let authorizationRequest = authorizationResponse.request
38 | let scopes = OIDScopeUtilities.scopesArray(
39 | with: authorizationRequest.scope ?? ""
40 | )
41 | return OIDTokenRequest(
42 | configuration: authorizationResponse.request.configuration,
43 | grantType: OIDGrantTypeAuthorizationCode,
44 | authorizationCode: authorizationResponse.authorizationCode,
45 | redirectURL: authorizationRequest.redirectURL,
46 | clientID: authorizationRequest.clientID,
47 | clientSecret: authorizationRequest.clientID,
48 | scopes: scopes,
49 | refreshToken: "refreshToken",
50 | codeVerifier: authorizationRequest.codeVerifier,
51 | additionalParameters: additionalParameters
52 | ) as! Self
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/GTMAppAuth/Tests/Helpers/OIDTokenResponseTesting.swift:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2022 Google LLC
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | // Ensure that we import the correct dependency for both SPM and CocoaPods since
18 | // the latter doesn't define separate Clang modules for subspecs
19 | #if SWIFT_PACKAGE
20 | import AppAuthCore
21 | #else
22 | import AppAuth
23 | #endif
24 |
25 | /// Protocol for creating a test instance of `OIDTokenResponse`.
26 | @objc public protocol TokenResponseTesting: Testing {
27 | static func testInstance(idToken: String) -> Self
28 | static func testInstance(
29 | idToken: String,
30 | accessToken: String?,
31 | expires: NSNumber?,
32 | tokenRequest: OIDTokenRequest?
33 | ) -> Self
34 | }
35 |
36 | @objc extension OIDTokenResponse: TokenResponseTesting {
37 | public static func testInstance() -> Self {
38 | return testInstance(idToken: idToken)
39 | }
40 |
41 | public static func testInstance(idToken: String) -> Self {
42 | return OIDTokenResponse.testInstance(
43 | idToken: idToken,
44 | accessToken: nil,
45 | expires: nil,
46 | tokenRequest: nil
47 | ) as! Self
48 | }
49 |
50 | public static func testInstanceWithoutAccessToken(
51 | idToken: String,
52 | expires: NSNumber?,
53 | tokenRequest: OIDTokenRequest?
54 | ) -> Self {
55 | let parameters: [String: NSObject & NSCopying] = [
56 | "expires_in": (expires ?? NSNumber(value: TestingConstants.accessTokenExpiresIn)),
57 | "token_type": "example_token_type" as NSString,
58 | "refresh_token": TestingConstants.testRefreshToken as NSString,
59 | "scope": OIDScopeUtilities.scopes(with: [TestingConstants.testScope2]) as NSString,
60 | "server_code": TestingConstants.serverAuthCode as NSString,
61 | "id_token": idToken as NSString
62 | ]
63 |
64 | return OIDTokenResponse(
65 | request: tokenRequest ?? OIDTokenRequest.testInstance(),
66 | parameters: parameters
67 | ) as! Self
68 | }
69 |
70 | public static func testInstanceWithEmptyAccessToken(
71 | idToken: String,
72 | expires: NSNumber?,
73 | tokenRequest: OIDTokenRequest?
74 | ) -> Self {
75 | let parameters: [String: NSObject & NSCopying] = [
76 | "access_token": "" as NSString,
77 | "expires_in": (expires ?? NSNumber(value: TestingConstants.accessTokenExpiresIn)),
78 | "token_type": "example_token_type" as NSString,
79 | "refresh_token": TestingConstants.testRefreshToken as NSString,
80 | "scope": OIDScopeUtilities.scopes(with: [TestingConstants.testScope2]) as NSString,
81 | "server_code": TestingConstants.serverAuthCode as NSString,
82 | "id_token": idToken as NSString
83 | ]
84 |
85 | return OIDTokenResponse(
86 | request: tokenRequest ?? OIDTokenRequest.testInstance(),
87 | parameters: parameters
88 | ) as! Self
89 | }
90 |
91 | public static func testInstance(
92 | idToken: String,
93 | accessToken: String?,
94 | expires: NSNumber?,
95 | tokenRequest: OIDTokenRequest?
96 | ) -> Self {
97 | let parameters: [String: NSObject & NSCopying] = [
98 | "access_token": (accessToken ?? TestingConstants.testAccessToken) as NSString,
99 | "expires_in": (expires ?? NSNumber(value: TestingConstants.accessTokenExpiresIn)),
100 | "token_type": "example_token_type" as NSString,
101 | "refresh_token": TestingConstants.testRefreshToken as NSString,
102 | "scope": OIDScopeUtilities.scopes(with: [TestingConstants.testScope2]) as NSString,
103 | "server_code": TestingConstants.serverAuthCode as NSString,
104 | "id_token": idToken as NSString
105 | ]
106 |
107 | return OIDTokenResponse(
108 | request: tokenRequest ?? OIDTokenRequest.testInstance(),
109 | parameters: parameters
110 | ) as! Self
111 | }
112 |
113 | static var idToken: String {
114 | return idToken(sub: TestingConstants.userID, exp: TestingConstants.IDTokenExpires, fat: false)
115 | }
116 |
117 | static func idToken(sub: String, exp: Int, fat: Bool) -> String {
118 | let headerContents = [
119 | "alg": TestingConstants.alg,
120 | "kid": TestingConstants.kid,
121 | "typ": TestingConstants.typ,
122 | ]
123 |
124 | // `try!` is fine here since failing is okay in the test
125 | let headerJson = try! JSONSerialization.data(
126 | withJSONObject: headerContents,
127 | options: .prettyPrinted
128 | )
129 |
130 | var payloadContents = [
131 | "sub": sub,
132 | "hd": TestingConstants.hostedDomain,
133 | "iss": TestingConstants.issuer,
134 | "aud": TestingConstants.audience,
135 | "exp": exp,
136 | "iat": TestingConstants.issuedAt
137 | ] as [String : Any]
138 |
139 | if fat {
140 | payloadContents[TestingConstants.fatNameKey] = TestingConstants.fatName
141 | payloadContents[TestingConstants.fatGivenNameKey] = TestingConstants.fatGivenName
142 | payloadContents[TestingConstants.fatFamilyNameKey] = TestingConstants.fatFamilyName
143 | payloadContents[TestingConstants.fatPictureURLKey] = TestingConstants.fatPictureURL
144 | }
145 |
146 | let payloadData = try! JSONSerialization.data(
147 | withJSONObject: payloadContents,
148 | options: .prettyPrinted
149 | )
150 |
151 | return "\(headerJson.base64EncodedString()).\(payloadData.base64EncodedString()).FakeSignature"
152 | }
153 | }
154 |
--------------------------------------------------------------------------------
/GTMAppAuth/Tests/ObjCIntegration/GTMAuthSessionTests.m:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2022 Google LLC
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | #import
18 |
19 | #if SWIFT_PACKAGE
20 | @import AppAuthCore;
21 | @import TestHelpers;
22 | #else
23 | @import AppAuth;
24 | #import "GTMAppAuth_Unit_objc_api_integration-Swift.h"
25 | #endif
26 | @import GTMAppAuth;
27 |
28 | @interface GTMAuthSessionTests : XCTestCase
29 |
30 | @property (nonatomic) NSURL *googleAuthzEndpoint;
31 | @property (nonatomic) NSURL *tokenEndpoint;
32 | @property (nonatomic) NSURL *secureURL;
33 | @property (nonatomic) NSURL *insecureURL;
34 | @property (nonatomic) NSTimeInterval expectationTimeout;
35 |
36 | @end
37 |
38 | @implementation GTMAuthSessionTests
39 |
40 | - (void)setUp {
41 | self.secureURL = [NSURL URLWithString:@"https://fake.com"];
42 | self.insecureURL = [NSURL URLWithString:@"http://fake.com"];
43 | self.googleAuthzEndpoint = [NSURL URLWithString:@"https://accounts.google.com/o/oauth2/v2/auth"];
44 | self.tokenEndpoint = [NSURL URLWithString:@"https://www.googleapis.com/oauth2/v4/token"];
45 | self.expectationTimeout = 5;
46 | [super setUp];
47 | }
48 |
49 | - (void)testInitWithOIDAuthState {
50 | GTMAuthSession *authSession =
51 | [[GTMAuthSession alloc] initWithAuthState:[OIDAuthState testInstance]];
52 | XCTAssertNotNil(authSession);
53 | }
54 |
55 | - (void)testDesignatedInitializer {
56 | GTMAuthSession *authSession =
57 | [[GTMAuthSession alloc] initWithAuthState:OIDAuthState.testInstance
58 | serviceProvider:GTMTestingConstants.testServiceProvider
59 | userID:GTMTestingConstants.testUserID
60 | userEmail:GTMTestingConstants.testEmail
61 | userEmailIsVerified:@"y"];
62 | XCTAssertNotNil(authSession);
63 | XCTAssertTrue(authSession.authState.isAuthorized);
64 | XCTAssertEqualObjects(authSession.serviceProvider, [GTMTestingConstants testServiceProvider]);
65 | XCTAssertEqualObjects(authSession.userID, [GTMTestingConstants testUserID]);
66 | XCTAssertEqualObjects(authSession.userEmail, [GTMTestingConstants testEmail]);
67 | XCTAssertTrue(authSession.userEmailIsVerified);
68 | }
69 |
70 | - (void)testAuthorizeSecureRequestWithCompletion {
71 | XCTestExpectation *authRequestExpectation =
72 | [[XCTestExpectation alloc] initWithDescription:@"Authorize with completion"];
73 |
74 | GTMAuthSession *authSession =
75 | [[GTMAuthSession alloc] initWithAuthState:OIDAuthState.testInstance];
76 | NSMutableURLRequest *secureRequest = [NSMutableURLRequest requestWithURL:self.secureURL];
77 |
78 | [authSession authorizeRequest:secureRequest completionHandler:^(NSError * _Nullable error) {
79 | XCTAssertNil(error);
80 | [authRequestExpectation fulfill];
81 | }];
82 |
83 | XCTAssertTrue([authSession isAuthorizingRequest:secureRequest]);
84 | [self waitForExpectations:@[authRequestExpectation] timeout:self.expectationTimeout];
85 | XCTAssertTrue([authSession isAuthorizedRequest:secureRequest]);
86 | }
87 |
88 | - (void)testAuthorizeSecureRequestWithCompletionEmptyAccessTokenError {
89 | XCTestExpectation *authRequestExpectation =
90 | [[XCTestExpectation alloc] initWithDescription:@"Authorize with completion"];
91 |
92 | OIDTokenResponse *tokenResponse =
93 | [OIDTokenResponse testInstanceWithEmptyAccessTokenWithIdToken:@""
94 | expires:nil
95 | tokenRequest:nil];
96 | GTMAuthSession *authSession =
97 | [[GTMAuthSession alloc] initWithAuthState:[OIDAuthState testInstanceWithTokenResponse:tokenResponse]];
98 | NSMutableURLRequest *secureRequest = [NSMutableURLRequest requestWithURL:self.secureURL];
99 |
100 | [authSession authorizeRequest:secureRequest completionHandler:^(NSError * _Nullable error) {
101 | XCTAssertNotNil(error);
102 | XCTAssertEqual(error.code, GTMAuthSessionErrorCodeAccessTokenEmptyForRequest);
103 | [authRequestExpectation fulfill];
104 | }];
105 |
106 | XCTAssertTrue([authSession isAuthorizingRequest:secureRequest]);
107 | [self waitForExpectations:@[authRequestExpectation] timeout:self.expectationTimeout];
108 | XCTAssertFalse([authSession isAuthorizedRequest:secureRequest]);
109 | }
110 |
111 | - (void)testAuthorizeSecureRequestWithCompletionNilAccessTokenError {
112 | XCTestExpectation *authRequestExpectation =
113 | [[XCTestExpectation alloc] initWithDescription:@"Authorize with completion"];
114 |
115 | OIDTokenResponse *tokenResponse =
116 | [OIDTokenResponse testInstanceWithoutAccessTokenWithIdToken:@""
117 | expires:nil
118 | tokenRequest:nil];
119 |
120 | GTMAuthSession *authSession =
121 | [[GTMAuthSession alloc] initWithAuthState:[OIDAuthState testInstanceWithTokenResponse:tokenResponse]];
122 | NSMutableURLRequest *secureRequest = [NSMutableURLRequest requestWithURL:self.secureURL];
123 |
124 | [authSession authorizeRequest:secureRequest completionHandler:^(NSError * _Nullable error) {
125 | XCTAssertNotNil(error);
126 | XCTAssertEqual(error.code, GTMAuthSessionErrorCodeAccessTokenEmptyForRequest);
127 | [authRequestExpectation fulfill];
128 | }];
129 |
130 | XCTAssertTrue([authSession isAuthorizingRequest:secureRequest]);
131 | [self waitForExpectations:@[authRequestExpectation] timeout:self.expectationTimeout];
132 | XCTAssertFalse([authSession isAuthorizedRequest:secureRequest]);
133 | }
134 |
135 | - (void)testAuthorizeInsecureRequestWithCompletionError {
136 | XCTestExpectation *authRequestExpectation =
137 | [[XCTestExpectation alloc] initWithDescription:@"Authorize with completion"];
138 |
139 | GTMAuthSession *authSession =
140 | [[GTMAuthSession alloc] initWithAuthState:[OIDAuthState testInstance]];
141 | NSMutableURLRequest *secureRequest = [NSMutableURLRequest requestWithURL:self.insecureURL];
142 |
143 | [authSession authorizeRequest:secureRequest completionHandler:^(NSError * _Nullable error) {
144 | XCTAssertNotNil(error);
145 | XCTAssertEqual(error.code, GTMAuthSessionErrorCodeCannotAuthorizeRequest);
146 | [authRequestExpectation fulfill];
147 | }];
148 |
149 | XCTAssertTrue([authSession isAuthorizingRequest:secureRequest]);
150 | [self waitForExpectations:@[authRequestExpectation] timeout:self.expectationTimeout];
151 | XCTAssertFalse([authSession isAuthorizedRequest:secureRequest]);
152 | }
153 |
154 | - (void)testAuthorizeSecureRequestWithDelegate {
155 | XCTestExpectation *delegateExpectation =
156 | [[XCTestExpectation alloc] initWithDescription:@"Authorize with delegate"];
157 |
158 | OIDAuthState *authState = OIDAuthState.testInstance;
159 | GTMAuthSession *originalAuthorization =
160 | [[GTMAuthSession alloc] initWithAuthState:authState];
161 | GTMAuthorizationTestDelegate *testingDelegate =
162 | [[GTMAuthorizationTestDelegate alloc] initWithExpectation:delegateExpectation];
163 |
164 | NSMutableURLRequest *originalRequest = [[NSMutableURLRequest alloc] initWithURL:self.secureURL];
165 | [originalAuthorization authorizeRequest:originalRequest
166 | delegate:testingDelegate
167 | didFinishSelector:@selector(authentication:request:finishedWithError:)];
168 |
169 | [self waitForExpectations:@[delegateExpectation] timeout:self.expectationTimeout];
170 |
171 | XCTAssertNotNil(testingDelegate.passedRequest);
172 | XCTAssertEqualObjects(originalRequest, testingDelegate.passedRequest);
173 | XCTAssertNotNil(testingDelegate.passedAuthorization);
174 | XCTAssertEqual(originalAuthorization, testingDelegate.passedAuthorization);
175 | XCTAssertNil(testingDelegate.passedError);
176 | }
177 |
178 | - (void)testStopAuthorization {
179 | XCTestExpectation *authorizeSecureRequestExpectation =
180 | [self expectationWithDescription:@"Authorize with completion expectation"];
181 |
182 | GTMAuthSession *authSession =
183 | [[GTMAuthSession alloc] initWithAuthState:OIDAuthState.testInstance];
184 | NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:self.secureURL];
185 |
186 | [authSession authorizeRequest:request completionHandler:^(NSError * _Nullable error) {
187 | XCTAssertNil(error);
188 | [authorizeSecureRequestExpectation fulfill];
189 | }];
190 |
191 | XCTAssertTrue([authSession isAuthorizingRequest:request]);
192 | [authSession stopAuthorization];
193 | XCTAssertFalse([authSession isAuthorizingRequest:request]);
194 | [authorizeSecureRequestExpectation fulfill];
195 | [self waitForExpectations:@[authorizeSecureRequestExpectation] timeout:self.expectationTimeout];
196 | }
197 |
198 | - (void)testStopAuthorizationForRequest {
199 | XCTestExpectation *authorizeSecureRequestExpectation =
200 | [self expectationWithDescription:@"Authorize with completion expectation"];
201 |
202 | GTMAuthSession *authSession =
203 | [[GTMAuthSession alloc] initWithAuthState:OIDAuthState.testInstance];
204 | NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:self.secureURL];
205 |
206 | [authSession authorizeRequest:request completionHandler:^(NSError * _Nullable error) {
207 | XCTAssertNil(error);
208 | [authorizeSecureRequestExpectation fulfill];
209 | }];
210 |
211 | XCTAssertTrue([authSession isAuthorizingRequest:request]);
212 | [authSession stopAuthorizationForRequest:request];
213 | XCTAssertFalse([authSession isAuthorizingRequest:request]);
214 | [authorizeSecureRequestExpectation fulfill];
215 | [self waitForExpectations:@[authorizeSecureRequestExpectation] timeout:self.expectationTimeout];
216 | }
217 |
218 | - (void)testIsAuthorizedRequest {
219 | GTMAuthSession *authSession =
220 | [[GTMAuthSession alloc] initWithAuthState:OIDAuthState.testInstance];
221 | NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:self.secureURL];
222 | XCTAssertFalse([authSession isAuthorizedRequest:request]);
223 | }
224 |
225 | - (void)testCanAuthorizeRequest {
226 | GTMAuthSession *authSession =
227 | [[GTMAuthSession alloc] initWithAuthState:OIDAuthState.testInstance];
228 | XCTAssertTrue([authSession canAuthorize]);
229 | }
230 |
231 | - (void)testCannotAuthorizeRequest {
232 | OIDAuthState *testAuthState =
233 | [OIDAuthState testInstanceWithAuthorizationResponse:nil
234 | tokenResponse:nil
235 | registrationResponse:OIDRegistrationResponse.testInstance];
236 | GTMAuthSession *authSession = [[GTMAuthSession alloc] initWithAuthState:testAuthState];
237 | XCTAssertFalse([authSession canAuthorize]);
238 | }
239 |
240 | - (void)testIsNotPrimeForRefresh {
241 | GTMAuthSession *authSession =
242 | [[GTMAuthSession alloc] initWithAuthState:OIDAuthState.testInstance];
243 | XCTAssertFalse([authSession primeForRefresh]);
244 | }
245 |
246 | - (void)testIsPrimeForRefresh {
247 | OIDAuthState *testAuthState =
248 | [OIDAuthState testInstanceWithAuthorizationResponse:nil
249 | tokenResponse:nil
250 | registrationResponse:OIDRegistrationResponse.testInstance];
251 | GTMAuthSession *authSession = [[GTMAuthSession alloc] initWithAuthState:testAuthState];
252 | XCTAssertTrue([authSession primeForRefresh]);
253 | }
254 |
255 | - (void)testConfigurationForGoogle {
256 | OIDServiceConfiguration *configuration = [GTMAuthSession configurationForGoogle];
257 | XCTAssertNotNil(configuration);
258 | XCTAssertEqualObjects(configuration.authorizationEndpoint, self.googleAuthzEndpoint);
259 | XCTAssertEqualObjects(configuration.tokenEndpoint, self.tokenEndpoint);
260 | }
261 |
262 | @end
263 |
264 |
--------------------------------------------------------------------------------
/GTMAppAuth/Tests/ObjCIntegration/KeychainStore/GTMKeychainStoreTests.m:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2022 Google LLC
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | #import
18 |
19 | #if SWIFT_PACKAGE
20 | @import AppAuthCore;
21 | @import TestHelpers;
22 | #else
23 | @import AppAuth;
24 | #import "GTMAppAuth_Unit_objc_api_integration-Swift.h"
25 | #endif
26 | @import GTMAppAuth;
27 |
28 | @interface GTMKeychainStoreTests : XCTestCase
29 |
30 | @property (nonatomic) GTMAuthSession *authSession;
31 | @property (nonatomic) GTMKeychainStore *keychainStore;
32 |
33 | @end
34 |
35 | @implementation GTMKeychainStoreTests
36 |
37 | - (void)setUp {
38 | self.authSession = [[GTMAuthSession alloc] initWithAuthState:[OIDAuthState testInstance]];
39 |
40 | NSSet *emptyKeychainAttributes = [NSSet set];
41 | GTMKeychainHelperFake *fakeKeychain =
42 | [[GTMKeychainHelperFake alloc] initWithKeychainAttributes:emptyKeychainAttributes];
43 | self.keychainStore =
44 | [[GTMKeychainStore alloc] initWithItemName:[GTMTestingConstants testKeychainItemName]
45 | keychainAttributes:emptyKeychainAttributes
46 | keychainHelper:fakeKeychain];
47 | [super setUp];
48 | }
49 |
50 | - (void)testInitWithItemName {
51 | GTMKeychainStore *keychainStore =
52 | [[GTMKeychainStore alloc] initWithItemName:[GTMTestingConstants testKeychainItemName]];
53 | XCTAssertNotNil(keychainStore);
54 | }
55 |
56 | - (void)testInitWithItemNameKeychainAttributes {
57 | GTMKeychainStore *keychainStore =
58 | [[GTMKeychainStore alloc] initWithItemName:[GTMTestingConstants testKeychainItemName]
59 | keychainAttributes:[NSSet set]];
60 | XCTAssertNotNil(keychainStore);
61 | }
62 |
63 | - (void)testInitWithItemNameKeychainHelper {
64 | GTMKeychainHelperFake *fakeKeychain =
65 | [[GTMKeychainHelperFake alloc] initWithKeychainAttributes:[NSSet set]];
66 | GTMKeychainStore *keychainStore =
67 | [[GTMKeychainStore alloc] initWithItemName:[GTMTestingConstants testKeychainItemName]
68 | keychainHelper:fakeKeychain];
69 | XCTAssertNotNil(keychainStore);
70 | }
71 |
72 | - (void)testInitWithItemNameKeychainAttributesKeychainHelper {
73 | GTMKeychainHelperFake *fakeKeychain =
74 | [[GTMKeychainHelperFake alloc] initWithKeychainAttributes:[NSSet set]];
75 | GTMKeychainStore *keychainStore =
76 | [[GTMKeychainStore alloc] initWithItemName:[GTMTestingConstants testKeychainItemName]
77 | keychainAttributes:[NSSet set]
78 | keychainHelper:fakeKeychain];
79 | XCTAssertNotNil(keychainStore);
80 | }
81 |
82 | - (void)testSaveAuthorization {
83 | NSError *error;
84 | [self.keychainStore saveAuthSession:self.authSession error:&error];
85 | XCTAssertNil(error);
86 | }
87 |
88 | - (void)testSavingWithNewItemName {
89 | NSString *newItemName = @"newItemName";
90 | self.keychainStore.itemName = newItemName;
91 |
92 | NSError *error;
93 | [self.keychainStore saveAuthSession:self.authSession
94 | withItemName:newItemName
95 | error:&error];
96 | XCTAssertNil(error);
97 |
98 | XCTAssertEqualObjects(newItemName, self.keychainStore.itemName);
99 | GTMAuthSession *authSession =
100 | [self.keychainStore retrieveAuthSessionWithItemName:self.keychainStore.itemName error:&error];
101 | XCTAssertNotNil(authSession);
102 |
103 | XCTAssertEqual(authSession.authState.isAuthorized, self.authSession.authState.isAuthorized);
104 | XCTAssertEqualObjects(authSession.serviceProvider, self.authSession.serviceProvider);
105 | XCTAssertEqualObjects(authSession.userID, self.authSession.userID);
106 | XCTAssertEqualObjects(authSession.userEmail, self.authSession.userEmail);
107 | XCTAssertEqual(authSession.userEmailIsVerified, self.authSession.userEmailIsVerified);
108 | }
109 |
110 | - (void)testSavingWithCustomItemName {
111 | NSString *customItemName = @"customItemName";
112 | NSError *error;
113 |
114 | [self.keychainStore saveAuthSession:self.authSession
115 | withItemName:customItemName
116 | error:&error];
117 | XCTAssertNil(error);
118 |
119 | GTMAuthSession *retrievedAuth =
120 | [self.keychainStore retrieveAuthSessionWithItemName:customItemName error:&error];
121 | XCTAssertNotNil(retrievedAuth);
122 | XCTAssertNil(error);
123 |
124 | XCTAssertNotNil(retrievedAuth);
125 |
126 | XCTAssertEqual(retrievedAuth.authState.isAuthorized,
127 | self.authSession.authState.isAuthorized);
128 | XCTAssertEqualObjects(retrievedAuth.serviceProvider, self.authSession.serviceProvider);
129 | XCTAssertEqualObjects(retrievedAuth.userID, self.authSession.userID);
130 | XCTAssertEqualObjects(retrievedAuth.userEmail, self.authSession.userEmail);
131 | XCTAssertEqual(retrievedAuth.userEmailIsVerified, self.authSession.userEmailIsVerified);
132 | }
133 |
134 | - (void)testSaveAuthSessionErrorNoServiceName {
135 | NSError *error;
136 | [self.keychainStore saveAuthSession:self.authSession
137 | withItemName:@""
138 | error:&error];
139 | XCTAssertNotNil(error);
140 | XCTAssertEqualObjects(error.domain, @"GTMAppAuthKeychainErrorDomain");
141 | XCTAssertEqual(error.code, GTMKeychainStoreErrorCodeNoService);
142 | }
143 |
144 | - (void)testRetrieveAuthSession {
145 | NSError *error;
146 | [self.keychainStore saveAuthSession:self.authSession error:&error];
147 | XCTAssertNil(error);
148 |
149 | GTMAuthSession *authSession = [self.keychainStore retrieveAuthSessionWithError:&error];
150 | XCTAssertNil(error);
151 |
152 | XCTAssertNotNil(authSession);
153 | XCTAssertEqual(authSession.authState.isAuthorized, self.authSession.authState.isAuthorized);
154 | XCTAssertEqualObjects(authSession.serviceProvider, self.authSession.serviceProvider);
155 | XCTAssertEqualObjects(authSession.userID, self.authSession.userID);
156 | XCTAssertEqualObjects(authSession.userEmail, self.authSession.userEmail);
157 | XCTAssertEqual(authSession.userEmailIsVerified, self.authSession.userEmailIsVerified);
158 | }
159 |
160 | - (void)testRetrieveAuthSessionForMissingItemName {
161 | NSError *error;
162 | NSString *missingItemName = @"missingItemName";
163 | GTMAuthSession *missingAuthSession =
164 | [self.keychainStore retrieveAuthSessionWithItemName:missingItemName error:&error];
165 |
166 | XCTAssertNil(missingAuthSession);
167 | XCTAssertNotNil(error);
168 | XCTAssertEqual(error.code, GTMKeychainStoreErrorCodePasswordNotFound);
169 | }
170 |
171 | - (void)testRetrieveAuthSessionForCustomItemName {
172 | NSError *error;
173 | NSString *customItemName = @"customItemName";
174 | self.keychainStore.itemName = customItemName;
175 | [self.keychainStore saveAuthSession:self.authSession withItemName:customItemName error:&error];
176 | XCTAssertNil(error);
177 |
178 | GTMAuthSession *retrievedAuthSession =
179 | [self.keychainStore retrieveAuthSessionWithError:&error];
180 | XCTAssertNotNil(retrievedAuthSession);
181 | XCTAssertNotNil(retrievedAuthSession);
182 | XCTAssertEqual(retrievedAuthSession.authState.isAuthorized,
183 | self.authSession.authState.isAuthorized);
184 | XCTAssertEqualObjects(retrievedAuthSession.serviceProvider, self.authSession.serviceProvider);
185 | XCTAssertEqualObjects(retrievedAuthSession.userID, self.authSession.userID);
186 | XCTAssertEqualObjects(retrievedAuthSession.userEmail, self.authSession.userEmail);
187 | XCTAssertEqual(retrievedAuthSession.userEmailIsVerified, self.authSession.userEmailIsVerified);
188 | }
189 |
190 | - (void)testRemoveAuthSession {
191 | NSError *error;
192 | [self.keychainStore saveAuthSession:self.authSession error:&error];
193 | XCTAssertNil(error);
194 |
195 | [self.keychainStore removeAuthSessionWithError:&error];
196 | XCTAssertNil(error);
197 | }
198 |
199 | - (void)testRemoveAuthSessionForMissingItemNameThrowsError {
200 | NSError *error;
201 | NSString *missingItemName = @"missingItemName";
202 | [self.keychainStore saveAuthSession:self.authSession error:&error];
203 | XCTAssertNil(error);
204 |
205 | [self.keychainStore removeAuthSessionWithItemName:missingItemName error:&error];
206 | XCTAssertNotNil(error);
207 | XCTAssertEqual(error.code, GTMKeychainStoreErrorCodeFailedToDeletePasswordBecauseItemNotFound);
208 | }
209 |
210 | - (void)testKeychainStoreAttributes {
211 | NSString *testAccessGroupName = @"testKeychainAccessGroup";
212 | GTMKeychainAttribute *keychainAccessGroup =
213 | [GTMKeychainAttribute keychainAccessGroupWithName:testAccessGroupName];
214 | NSSet *attributes = [NSSet setWithArray:@[keychainAccessGroup]];
215 |
216 | GTMKeychainStore *keychainStore =
217 | [[GTMKeychainStore alloc] initWithItemName:[GTMTestingConstants testKeychainItemName]
218 | keychainAttributes:attributes];
219 | XCTAssertTrue(keychainStore.keychainAttributes.count > 0);
220 | XCTAssertFalse([keychainStore.keychainAttributes
221 | containsObject:[GTMKeychainAttribute useFileBasedKeychain]]);
222 | XCTAssertTrue([keychainStore.keychainAttributes containsObject:keychainAccessGroup]);
223 | }
224 |
225 | - (void)testKeychainStoreWithFileBasedAttribute {
226 | GTMKeychainAttribute *useFileBasedKeychain = [GTMKeychainAttribute useFileBasedKeychain];
227 | NSSet *attributes =
228 | [NSSet setWithArray:@[
229 | useFileBasedKeychain
230 | ]];
231 |
232 | GTMKeychainStore *keychainStore =
233 | [[GTMKeychainStore alloc] initWithItemName:[GTMTestingConstants testKeychainItemName]
234 | keychainAttributes:attributes];
235 | XCTAssertTrue(keychainStore.keychainAttributes.count > 0);
236 | XCTAssertTrue([keychainStore.keychainAttributes containsObject:useFileBasedKeychain]);
237 | }
238 |
239 | - (void)testSaveAuthSessionInGTMOAuth2Format {
240 | NSError *error;
241 | [self.keychainStore saveWithGTMOAuth2FormatForAuthSession:self.authSession error:&error];
242 | XCTAssertNil(error);
243 | }
244 |
245 | - (void)testRetrieveAuthSessionInGTMOAuth2Format {
246 | NSError *error;
247 | GTMAuthSession *expectedAuthSession =
248 | [[GTMAuthSession alloc] initWithAuthState:[OIDAuthState testInstance]
249 | serviceProvider:[GTMTestingConstants testServiceProvider]
250 | userID:[GTMTestingConstants testUserID]
251 | userEmail:[GTMTestingConstants testEmail]
252 | userEmailIsVerified:@"y"];
253 | [self.keychainStore saveWithGTMOAuth2FormatForAuthSession:expectedAuthSession error:&error];
254 | XCTAssertNil(error);
255 |
256 | GTMAuthSession *testAuth =
257 | [self.keychainStore
258 | retrieveAuthSessionInGTMOAuth2FormatWithTokenURL:[GTMTestingConstants testTokenURL]
259 | redirectURI:[GTMTestingConstants testRedirectURI]
260 | clientID:[GTMTestingConstants testClientID]
261 | clientSecret:[GTMTestingConstants testClientSecret]
262 | error:&error];
263 |
264 | XCTAssertNil(error);
265 | XCTAssertEqualObjects(testAuth.authState.scope, expectedAuthSession.authState.scope);
266 | XCTAssertEqualObjects(testAuth.authState.lastTokenResponse.accessToken,
267 | expectedAuthSession.authState.lastTokenResponse.accessToken);
268 | XCTAssertEqualObjects(testAuth.authState.refreshToken,
269 | expectedAuthSession.authState.refreshToken);
270 | XCTAssertEqual(testAuth.authState.isAuthorized, expectedAuthSession.authState.isAuthorized);
271 | XCTAssertEqualObjects(testAuth.serviceProvider, expectedAuthSession.serviceProvider);
272 | XCTAssertEqualObjects(testAuth.userID, expectedAuthSession.userID);
273 | XCTAssertEqualObjects(testAuth.userEmail, expectedAuthSession.userEmail);
274 | XCTAssertEqual(testAuth.userEmailIsVerified, expectedAuthSession.userEmailIsVerified);
275 | XCTAssertEqual(testAuth.canAuthorize, expectedAuthSession.canAuthorize);
276 | }
277 |
278 | - (void)testRemoveAuthSessionInGTMOAuth2Format {
279 | NSError *error;
280 | [self.keychainStore saveWithGTMOAuth2FormatForAuthSession:self.authSession error:&error];
281 | XCTAssertNil(error);
282 |
283 | [self.keychainStore removeAuthSessionWithError:&error];
284 | XCTAssertNil(error);
285 | }
286 |
287 | @end
288 |
--------------------------------------------------------------------------------
/GTMAppAuth/Tests/ObjCIntegration/KeychainStore/GTMOAuth2CompatibilityTests.m:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2022 Google LLC
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | #import
18 |
19 | #if SWIFT_PACKAGE
20 | @import AppAuthCore;
21 | @import TestHelpers;
22 | #else
23 | @import AppAuth;
24 | #import "GTMAppAuth_Unit_objc_api_integration-Swift.h"
25 | #endif
26 | @import GTMAppAuth;
27 |
28 | @interface GTMOAuth2CompatibilityTests : XCTestCase
29 |
30 | @property (nonatomic) GTMAuthSession *expectedAuthSession;
31 |
32 | @end
33 |
34 | @implementation GTMOAuth2CompatibilityTests
35 |
36 | - (void)setUp {
37 | self.expectedAuthSession =
38 | [[GTMAuthSession alloc] initWithAuthState:OIDAuthState.testInstance
39 | serviceProvider:GTMTestingConstants.testServiceProvider
40 | userID:GTMTestingConstants.userID
41 | userEmail:GTMTestingConstants.testEmail
42 | userEmailIsVerified:@"y"];
43 | [super setUp];
44 | }
45 |
46 | - (NSString *)expectedPersistenceResponseString {
47 | return [NSString stringWithFormat:@"access_token=%@&refresh_token=%@&scope=%@&serviceProvider=%@&userEmail=foo%%40foo.com&userEmailIsVerified=y&userID=%@", GTMTestingConstants.testAccessToken, GTMTestingConstants.testRefreshToken, GTMTestingConstants.testScope2, GTMTestingConstants.testServiceProvider, GTMTestingConstants.testUserID];
48 | }
49 |
50 | - (void)testPersistenceResponseString {
51 | NSString *testPersistenceResponseString =
52 | [GTMOAuth2Compatibility persistenceResponseStringForAuthSession:self.expectedAuthSession];
53 | XCTAssertNotNil(testPersistenceResponseString);
54 | XCTAssertEqualObjects([self expectedPersistenceResponseString], testPersistenceResponseString);
55 | }
56 |
57 | - (void)testAuthStateForPersistenceString {
58 | NSError *error;
59 | GTMAuthSession *testPersistAuthSession =
60 | [GTMOAuth2Compatibility authSessionForPersistenceString:[self expectedPersistenceResponseString]
61 | tokenURL:GTMTestingConstants.testTokenURL
62 | redirectURI:GTMTestingConstants.testRedirectURI
63 | clientID:GTMTestingConstants.testClientID
64 | clientSecret:GTMTestingConstants.testClientSecret
65 | error:&error];
66 | XCTAssertNil(error);
67 | XCTAssertEqual(testPersistAuthSession.authState.scope, self.expectedAuthSession.authState.scope);
68 | XCTAssertEqualObjects(testPersistAuthSession.authState.lastTokenResponse.accessToken,
69 | self.expectedAuthSession.authState.lastTokenResponse.accessToken);
70 | XCTAssertEqualObjects(testPersistAuthSession.authState.refreshToken,
71 | self.expectedAuthSession.authState.refreshToken);
72 | XCTAssertEqual(testPersistAuthSession.authState.isAuthorized,
73 | self.expectedAuthSession.authState.isAuthorized);
74 | XCTAssertEqualObjects(testPersistAuthSession.serviceProvider,
75 | self.expectedAuthSession.serviceProvider);
76 | XCTAssertEqualObjects(testPersistAuthSession.userID, self.expectedAuthSession.userID);
77 | XCTAssertEqualObjects(testPersistAuthSession.userEmail, self.expectedAuthSession.userEmail);
78 | XCTAssertEqual(testPersistAuthSession.userEmailIsVerified,
79 | self.expectedAuthSession.userEmailIsVerified);
80 | XCTAssertEqual(testPersistAuthSession.canAuthorize, self.expectedAuthSession.canAuthorize);
81 | }
82 |
83 | - (void)testAuthSessionForPersistenceStringThrows {
84 | NSError *error;
85 | GTMAuthSession *testPersistAuthSession __unused =
86 | [GTMOAuth2Compatibility authSessionForPersistenceString:[self expectedPersistenceResponseString]
87 | tokenURL:GTMTestingConstants.testTokenURL
88 | redirectURI:@""
89 | clientID:GTMTestingConstants.testClientID
90 | clientSecret:GTMTestingConstants.testClientSecret
91 | error:&error];
92 | XCTAssertNotNil(error);
93 | XCTAssertEqual(error.code, GTMKeychainStoreErrorCodeFailedToConvertRedirectURItoURL);
94 | }
95 |
96 | @end
97 |
--------------------------------------------------------------------------------
/GTMAppAuth/Tests/Unit/KeychainStore/GTMOAuth2CompatibilityTests.swift:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2022 Google LLC
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | import XCTest
18 | // Ensure that we import the correct dependency for both SPM and CocoaPods since
19 | // the latter doesn't define separate Clang modules for subspecs
20 | #if SWIFT_PACKAGE
21 | import AppAuthCore
22 | import TestHelpers
23 | #else
24 | import AppAuth
25 | #endif
26 | @testable import GTMAppAuth
27 |
28 | class GTMOAuth2CompatibilityTests: XCTestCase {
29 | private lazy var testPersistenceString: String = {
30 | return "access_token=\(TestingConstants.testAccessToken)&refresh_token=\(TestingConstants.testRefreshToken)&scope=\(TestingConstants.testScope2)&serviceProvider=\(TestingConstants.testServiceProvider)&userEmail=foo%40foo.com&userEmailIsVerified=y&userID=\(TestingConstants.testUserID)"
31 | }()
32 | private lazy var keychainStoreWithAttributes: KeychainStore = {
33 | let attributes: Set = [
34 | .keychainAccessGroup(name: TestingConstants.testAccessGroup)
35 | ]
36 | let keychainHelperWithAttributes = KeychainHelperFake(keychainAttributes: attributes)
37 | return KeychainStore(
38 | itemName: TestingConstants.testKeychainItemName,
39 | keychainHelper: keychainHelperWithAttributes
40 | )
41 | }()
42 | private let keychainHelper = KeychainHelperFake(keychainAttributes: [])
43 | private lazy var keychainStore: KeychainStore = {
44 | return KeychainStore(
45 | itemName: TestingConstants.testKeychainItemName,
46 | keychainHelper: keychainHelper
47 | )
48 | }()
49 | private var expectedAuthSession: AuthSession {
50 | AuthSession(
51 | authState: OIDAuthState.testInstance(),
52 | serviceProvider: TestingConstants.testServiceProvider,
53 | userID: TestingConstants.testUserID,
54 | userEmail: TestingConstants.testEmail,
55 | userEmailIsVerified: "y"
56 | )
57 | }
58 |
59 | override func tearDown() {
60 | super.tearDown()
61 | keychainHelper.passwordStore.removeAll()
62 | }
63 |
64 | func testPersistenceResponseString() {
65 | let response = GTMOAuth2Compatibility.persistenceResponseString(
66 | forAuthSession: expectedAuthSession
67 | )
68 | guard let response = response else {
69 | return XCTFail("Response shouldn't be nil")
70 | }
71 | XCTAssertEqual(response, testPersistenceString)
72 | }
73 |
74 | func testSaveOAuth2AuthSession() throws {
75 | try keychainStore.saveWithGTMOAuth2Format(forAuthSession: expectedAuthSession)
76 | // Save with keychain attributes to simulate macOS environment with
77 | // `kSecUseDataProtectionKeychain`
78 | try keychainStoreWithAttributes.saveWithGTMOAuth2Format(forAuthSession: expectedAuthSession)
79 | }
80 |
81 | func testSaveGTMOAuth2AuthSessionThrowsError() {
82 | let emptyItemName = ""
83 | keychainStore.itemName = emptyItemName
84 | XCTAssertThrowsError(
85 | try keychainStore.saveWithGTMOAuth2Format(forAuthSession: expectedAuthSession)
86 | ) { thrownError in
87 | XCTAssertEqual(
88 | thrownError as? KeychainStore.Error,
89 | KeychainStore.Error.noService
90 | )
91 | }
92 | }
93 |
94 | func testRemoveOAuth2AuthSession() throws {
95 | try keychainStore.saveWithGTMOAuth2Format(forAuthSession: expectedAuthSession)
96 | let _ = try keychainStore.retrieveAuthSessionInGTMOAuth2Format(
97 | tokenURL: TestingConstants.testTokenURL,
98 | redirectURI: TestingConstants.testRedirectURI,
99 | clientID: TestingConstants.testClientID,
100 | clientSecret: TestingConstants.testClientSecret
101 | )
102 | try keychainStore.removeAuthSession()
103 | XCTAssertThrowsError(try keychainStore.retrieveAuthSession()) { thrownError in
104 | XCTAssertEqual(
105 | thrownError as? KeychainStore.Error,
106 | KeychainStore.Error.passwordNotFound(forItemName: TestingConstants.testKeychainItemName)
107 | )
108 | }
109 |
110 | try keychainStoreWithAttributes.saveWithGTMOAuth2Format(forAuthSession: expectedAuthSession)
111 | let _ = try keychainStoreWithAttributes.retrieveAuthSessionInGTMOAuth2Format(
112 | tokenURL: TestingConstants.testTokenURL,
113 | redirectURI: TestingConstants.testRedirectURI,
114 | clientID: TestingConstants.testClientID,
115 | clientSecret: TestingConstants.testClientSecret
116 | )
117 | try keychainStoreWithAttributes.removeAuthSession()
118 | XCTAssertThrowsError(try keychainStoreWithAttributes.retrieveAuthSession()) { thrownError in
119 | XCTAssertEqual(
120 | thrownError as? KeychainStore.Error,
121 | KeychainStore.Error.passwordNotFound(forItemName: TestingConstants.testKeychainItemName)
122 | )
123 | }
124 | }
125 |
126 | func testRemoveOAuth2AuthSessionhrowsError() {
127 | XCTAssertThrowsError(
128 | try keychainStore.removeAuthSession()
129 | ) { thrownError in
130 | XCTAssertEqual(
131 | thrownError as? KeychainStore.Error,
132 | .failedToDeletePasswordBecauseItemNotFound(itemName: TestingConstants.testKeychainItemName)
133 | )
134 | }
135 | }
136 |
137 | func testAuthSessionFromKeychainForName() throws {
138 | try keychainStore.saveWithGTMOAuth2Format(forAuthSession: expectedAuthSession)
139 | let authSession = try keychainStore.retrieveAuthSessionInGTMOAuth2Format(
140 | tokenURL: TestingConstants.testTokenURL,
141 | redirectURI: TestingConstants.testRedirectURI,
142 | clientID: TestingConstants.testClientID,
143 | clientSecret: TestingConstants.testClientID
144 | )
145 |
146 | XCTAssertEqual(authSession.authState.scope, expectedAuthSession.authState.scope)
147 | XCTAssertEqual(
148 | authSession.authState.lastTokenResponse?.accessToken,
149 | expectedAuthSession.authState.lastTokenResponse?.accessToken
150 | )
151 | XCTAssertEqual(authSession.authState.refreshToken, expectedAuthSession.authState.refreshToken)
152 | XCTAssertEqual(authSession.authState.isAuthorized, expectedAuthSession.authState.isAuthorized)
153 | XCTAssertEqual(authSession.serviceProvider, expectedAuthSession.serviceProvider)
154 | XCTAssertEqual(authSession.userID, expectedAuthSession.userID)
155 | XCTAssertEqual(authSession.userEmail, expectedAuthSession.userEmail)
156 | XCTAssertEqual(authSession.userEmailIsVerified, expectedAuthSession.userEmailIsVerified)
157 | XCTAssertEqual(authSession.canAuthorize, expectedAuthSession.canAuthorize)
158 | }
159 |
160 | func testAuthSessionFromKeychainWithAttributesForName() throws {
161 | try keychainStoreWithAttributes.saveWithGTMOAuth2Format(forAuthSession: expectedAuthSession)
162 | let authSession = try keychainStoreWithAttributes.retrieveAuthSessionInGTMOAuth2Format(
163 | tokenURL: TestingConstants.testTokenURL,
164 | redirectURI: TestingConstants.testRedirectURI,
165 | clientID: TestingConstants.testClientID,
166 | clientSecret: TestingConstants.testClientID
167 | )
168 |
169 | XCTAssertEqual(authSession.authState.scope, expectedAuthSession.authState.scope)
170 | XCTAssertEqual(
171 | authSession.authState.lastTokenResponse?.accessToken,
172 | expectedAuthSession.authState.lastTokenResponse?.accessToken
173 | )
174 | XCTAssertEqual(authSession.authState.refreshToken, expectedAuthSession.authState.refreshToken)
175 | XCTAssertEqual(authSession.authState.isAuthorized, expectedAuthSession.authState.isAuthorized)
176 | XCTAssertEqual(authSession.serviceProvider, expectedAuthSession.serviceProvider)
177 | XCTAssertEqual(authSession.userID, expectedAuthSession.userID)
178 | XCTAssertEqual(authSession.userEmail, expectedAuthSession.userEmail)
179 | XCTAssertEqual(authSession.userEmailIsVerified, expectedAuthSession.userEmailIsVerified)
180 | XCTAssertEqual(authSession.canAuthorize, expectedAuthSession.canAuthorize)
181 | }
182 |
183 | func testAuthSessionFromKeychainForNameThrowsError() throws {
184 | try keychainStore.saveWithGTMOAuth2Format(forAuthSession: expectedAuthSession)
185 | let badRedirectURI = ""
186 | XCTAssertThrowsError(
187 | _ = try keychainStore.retrieveAuthSessionInGTMOAuth2Format(
188 | tokenURL: TestingConstants.testTokenURL,
189 | redirectURI: badRedirectURI,
190 | clientID: TestingConstants.testClientID,
191 | clientSecret: TestingConstants.testClientSecret
192 | )
193 | ) { thrownError in
194 | XCTAssertEqual(
195 | thrownError as? KeychainStore.Error,
196 | .failedToConvertRedirectURItoURL(badRedirectURI)
197 | )
198 | }
199 | }
200 |
201 | func testAuthSessionFromKeychainForPersistenceStringFailedWithBadURI() {
202 | let badURI = ""
203 | XCTAssertThrowsError(
204 | try GTMOAuth2Compatibility.authSession(
205 | forPersistenceString: testPersistenceString,
206 | tokenURL: TestingConstants.testTokenURL,
207 | redirectURI: badURI,
208 | clientID: TestingConstants.testClientID,
209 | clientSecret: TestingConstants.testClientSecret
210 | )
211 | ) { thrownError in
212 | XCTAssertEqual(
213 | thrownError as? KeychainStore.Error,
214 | .failedToConvertRedirectURItoURL(badURI)
215 | )
216 | }
217 | }
218 |
219 | func testAuthSessionFromKeychainForPersistenceString() throws {
220 | let authSession = try GTMOAuth2Compatibility.authSession(
221 | forPersistenceString: testPersistenceString,
222 | tokenURL: TestingConstants.testTokenURL,
223 | redirectURI: TestingConstants.testRedirectURI,
224 | clientID: TestingConstants.testClientID,
225 | clientSecret: TestingConstants.testClientSecret
226 | )
227 |
228 | XCTAssertEqual(authSession.authState.scope, expectedAuthSession.authState.scope)
229 | XCTAssertEqual(
230 | authSession.authState.lastTokenResponse?.accessToken,
231 | expectedAuthSession.authState.lastTokenResponse?.accessToken
232 | )
233 | XCTAssertEqual(authSession.authState.refreshToken, expectedAuthSession.authState.refreshToken)
234 | XCTAssertEqual(authSession.authState.isAuthorized, expectedAuthSession.authState.isAuthorized)
235 | XCTAssertEqual(authSession.serviceProvider, expectedAuthSession.serviceProvider)
236 | XCTAssertEqual(authSession.userID, expectedAuthSession.userID)
237 | XCTAssertEqual(authSession.userEmail, expectedAuthSession.userEmail)
238 | XCTAssertEqual(authSession.userEmailIsVerified, expectedAuthSession.userEmailIsVerified)
239 | XCTAssertEqual(authSession.canAuthorize, expectedAuthSession.canAuthorize)
240 | }
241 |
242 | func testAuthSessionFromKeychainMatchesForNameAndPersistenceString() throws {
243 | let expectedPersistAuth = try GTMOAuth2Compatibility.authSession(
244 | forPersistenceString: testPersistenceString,
245 | tokenURL: TestingConstants.testTokenURL,
246 | redirectURI: TestingConstants.testRedirectURI,
247 | clientID: TestingConstants.testClientID,
248 | clientSecret: TestingConstants.testClientSecret
249 | )
250 | try keychainStore.saveWithGTMOAuth2Format(forAuthSession: expectedPersistAuth)
251 |
252 | let testPersistAuth = try keychainStore.retrieveAuthSessionInGTMOAuth2Format(
253 | tokenURL: TestingConstants.testTokenURL,
254 | redirectURI: TestingConstants.testRedirectURI,
255 | clientID: TestingConstants.testClientID,
256 | clientSecret: TestingConstants.testClientSecret
257 | )
258 |
259 | XCTAssertEqual(testPersistAuth.authState.scope, expectedPersistAuth.authState.scope)
260 | XCTAssertEqual(
261 | testPersistAuth.authState.lastTokenResponse?.accessToken,
262 | expectedPersistAuth.authState.lastTokenResponse?.accessToken
263 | )
264 | XCTAssertEqual(
265 | testPersistAuth.authState.refreshToken,
266 | expectedPersistAuth.authState.refreshToken
267 | )
268 | XCTAssertEqual(
269 | testPersistAuth.authState.isAuthorized,
270 | expectedPersistAuth.authState.isAuthorized
271 | )
272 | XCTAssertEqual(testPersistAuth.serviceProvider, expectedPersistAuth.serviceProvider)
273 | XCTAssertEqual(testPersistAuth.userID, expectedPersistAuth.userID)
274 | XCTAssertEqual(testPersistAuth.userEmail, expectedPersistAuth.userEmail)
275 | XCTAssertEqual(testPersistAuth.userEmailIsVerified, expectedPersistAuth.userEmailIsVerified)
276 | XCTAssertEqual(testPersistAuth.canAuthorize, expectedPersistAuth.canAuthorize)
277 | }
278 |
279 | func testAuthSessionFromKeychainUsingGoogleOAuthProviderInformation() throws {
280 | let expectedPersistAuth = try GTMOAuth2Compatibility.authSession(
281 | forPersistenceString: testPersistenceString,
282 | tokenURL: TestingConstants.testTokenURL,
283 | redirectURI: TestingConstants.testRedirectURI,
284 | clientID: TestingConstants.testClientID,
285 | clientSecret: TestingConstants.testClientSecret
286 | )
287 | try keychainStore.saveWithGTMOAuth2Format(forAuthSession: expectedAuthSession)
288 |
289 | let testAuthSession = try keychainStore.retrieveAuthSessionForGoogleInGTMOAuth2Format(
290 | clientID: TestingConstants.testClientID,
291 | clientSecret: TestingConstants.testClientSecret
292 | )
293 |
294 | XCTAssertEqual(testAuthSession.authState.scope, expectedPersistAuth.authState.scope)
295 | XCTAssertEqual(
296 | testAuthSession.authState.lastTokenResponse?.accessToken,
297 | expectedPersistAuth.authState.lastTokenResponse?.accessToken
298 | )
299 | XCTAssertEqual(testAuthSession.authState.refreshToken, expectedPersistAuth.authState.refreshToken)
300 | XCTAssertEqual(testAuthSession.authState.isAuthorized, expectedPersistAuth.authState.isAuthorized)
301 | XCTAssertEqual(testAuthSession.serviceProvider, expectedPersistAuth.serviceProvider)
302 | XCTAssertEqual(testAuthSession.userID, expectedPersistAuth.userID)
303 | XCTAssertEqual(testAuthSession.userEmail, expectedPersistAuth.userEmail)
304 | XCTAssertEqual(testAuthSession.userEmailIsVerified, expectedPersistAuth.userEmailIsVerified)
305 | XCTAssertEqual(testAuthSession.canAuthorize, expectedPersistAuth.canAuthorize)
306 | }
307 | }
308 |
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | source 'https://rubygems.org'
2 |
3 | gem 'cocoapods', '1.15.2'
4 |
--------------------------------------------------------------------------------
/Gemfile.lock:
--------------------------------------------------------------------------------
1 | GEM
2 | remote: https://rubygems.org/
3 | specs:
4 | CFPropertyList (3.0.3)
5 | activesupport (7.1.3.4)
6 | base64
7 | bigdecimal
8 | concurrent-ruby (~> 1.0, >= 1.0.2)
9 | connection_pool (>= 2.2.5)
10 | drb
11 | i18n (>= 1.6, < 2)
12 | minitest (>= 5.1)
13 | mutex_m
14 | tzinfo (~> 2.0)
15 | addressable (2.8.7)
16 | public_suffix (>= 2.0.2, < 7.0)
17 | algoliasearch (1.27.5)
18 | httpclient (~> 2.8, >= 2.8.3)
19 | json (>= 1.5.1)
20 | atomos (0.1.3)
21 | base64 (0.2.0)
22 | bigdecimal (3.1.8)
23 | claide (1.0.3)
24 | cocoapods (1.15.2)
25 | addressable (~> 2.6)
26 | claide (>= 1.0.2, < 2.0)
27 | cocoapods-core (= 1.15.2)
28 | cocoapods-deintegrate (>= 1.0.3, < 2.0)
29 | cocoapods-downloader (>= 1.4.0, < 2.0)
30 | cocoapods-plugins (>= 1.0.0, < 2.0)
31 | cocoapods-search (>= 1.0.0, < 2.0)
32 | cocoapods-trunk (>= 1.4.0, < 2.0)
33 | cocoapods-try (>= 1.1.0, < 2.0)
34 | colored2 (~> 3.1)
35 | escape (~> 0.0.4)
36 | fourflusher (>= 2.3.0, < 3.0)
37 | gh_inspector (~> 1.0)
38 | molinillo (~> 0.6.6)
39 | nap (~> 1.0)
40 | ruby-macho (~> 1.4)
41 | xcodeproj (>= 1.19.0, < 2.0)
42 | cocoapods-core (1.15.2)
43 | activesupport (>= 5.0, < 8)
44 | addressable (~> 2.8)
45 | algoliasearch (~> 1.0)
46 | concurrent-ruby (~> 1.1)
47 | fuzzy_match (~> 2.0.4)
48 | nap (~> 1.0)
49 | netrc (~> 0.11)
50 | public_suffix (~> 4.0)
51 | typhoeus (~> 1.0)
52 | cocoapods-deintegrate (1.0.4)
53 | cocoapods-downloader (1.6.3)
54 | cocoapods-plugins (1.0.0)
55 | nap
56 | cocoapods-search (1.0.0)
57 | cocoapods-trunk (1.5.0)
58 | nap (>= 0.8, < 2.0)
59 | netrc (~> 0.11)
60 | cocoapods-try (1.2.0)
61 | colored2 (3.1.2)
62 | concurrent-ruby (1.3.3)
63 | connection_pool (2.4.1)
64 | drb (2.2.1)
65 | escape (0.0.4)
66 | ethon (0.16.0)
67 | ffi (>= 1.15.0)
68 | ffi (1.17.0)
69 | fourflusher (2.3.1)
70 | fuzzy_match (2.0.4)
71 | gh_inspector (1.1.3)
72 | httpclient (2.8.3)
73 | i18n (1.14.5)
74 | concurrent-ruby (~> 1.0)
75 | json (2.7.2)
76 | minitest (5.24.1)
77 | molinillo (0.6.6)
78 | mutex_m (0.2.0)
79 | nanaimo (0.3.0)
80 | nap (1.1.0)
81 | netrc (0.11.0)
82 | public_suffix (4.0.7)
83 | ruby-macho (1.4.0)
84 | typhoeus (1.4.1)
85 | ethon (>= 0.9.0)
86 | tzinfo (2.0.6)
87 | concurrent-ruby (~> 1.0)
88 | xcodeproj (1.19.0)
89 | CFPropertyList (>= 2.3.3, < 4.0)
90 | atomos (~> 0.1.3)
91 | claide (>= 1.0.2, < 2.0)
92 | colored2 (~> 3.1)
93 | nanaimo (~> 0.3.0)
94 |
95 | PLATFORMS
96 | ruby
97 |
98 | DEPENDENCIES
99 | cocoapods (= 1.15.2)
100 |
101 | BUNDLED WITH
102 | 2.5.4
103 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 |
2 | Apache License
3 | Version 2.0, January 2004
4 | http://www.apache.org/licenses/
5 |
6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7 |
8 | 1. Definitions.
9 |
10 | "License" shall mean the terms and conditions for use, reproduction,
11 | and distribution as defined by Sections 1 through 9 of this document.
12 |
13 | "Licensor" shall mean the copyright owner or entity authorized by
14 | the copyright owner that is granting the License.
15 |
16 | "Legal Entity" shall mean the union of the acting entity and all
17 | other entities that control, are controlled by, or are under common
18 | control with that entity. For the purposes of this definition,
19 | "control" means (i) the power, direct or indirect, to cause the
20 | direction or management of such entity, whether by contract or
21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
22 | outstanding shares, or (iii) beneficial ownership of such entity.
23 |
24 | "You" (or "Your") shall mean an individual or Legal Entity
25 | exercising permissions granted by this License.
26 |
27 | "Source" form shall mean the preferred form for making modifications,
28 | including but not limited to software source code, documentation
29 | source, and configuration files.
30 |
31 | "Object" form shall mean any form resulting from mechanical
32 | transformation or translation of a Source form, including but
33 | not limited to compiled object code, generated documentation,
34 | and conversions to other media types.
35 |
36 | "Work" shall mean the work of authorship, whether in Source or
37 | Object form, made available under the License, as indicated by a
38 | copyright notice that is included in or attached to the work
39 | (an example is provided in the Appendix below).
40 |
41 | "Derivative Works" shall mean any work, whether in Source or Object
42 | form, that is based on (or derived from) the Work and for which the
43 | editorial revisions, annotations, elaborations, or other modifications
44 | represent, as a whole, an original work of authorship. For the purposes
45 | of this License, Derivative Works shall not include works that remain
46 | separable from, or merely link (or bind by name) to the interfaces of,
47 | the Work and Derivative Works thereof.
48 |
49 | "Contribution" shall mean any work of authorship, including
50 | the original version of the Work and any modifications or additions
51 | to that Work or Derivative Works thereof, that is intentionally
52 | submitted to Licensor for inclusion in the Work by the copyright owner
53 | or by an individual or Legal Entity authorized to submit on behalf of
54 | the copyright owner. For the purposes of this definition, "submitted"
55 | means any form of electronic, verbal, or written communication sent
56 | to the Licensor or its representatives, including but not limited to
57 | communication on electronic mailing lists, source code control systems,
58 | and issue tracking systems that are managed by, or on behalf of, the
59 | Licensor for the purpose of discussing and improving the Work, but
60 | excluding communication that is conspicuously marked or otherwise
61 | designated in writing by the copyright owner as "Not a Contribution."
62 |
63 | "Contributor" shall mean Licensor and any individual or Legal Entity
64 | on behalf of whom a Contribution has been received by Licensor and
65 | subsequently incorporated within the Work.
66 |
67 | 2. Grant of Copyright License. Subject to the terms and conditions of
68 | this License, each Contributor hereby grants to You a perpetual,
69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70 | copyright license to reproduce, prepare Derivative Works of,
71 | publicly display, publicly perform, sublicense, and distribute the
72 | Work and such Derivative Works in Source or Object form.
73 |
74 | 3. Grant of Patent License. Subject to the terms and conditions of
75 | this License, each Contributor hereby grants to You a perpetual,
76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77 | (except as stated in this section) patent license to make, have made,
78 | use, offer to sell, sell, import, and otherwise transfer the Work,
79 | where such license applies only to those patent claims licensable
80 | by such Contributor that are necessarily infringed by their
81 | Contribution(s) alone or by combination of their Contribution(s)
82 | with the Work to which such Contribution(s) was submitted. If You
83 | institute patent litigation against any entity (including a
84 | cross-claim or counterclaim in a lawsuit) alleging that the Work
85 | or a Contribution incorporated within the Work constitutes direct
86 | or contributory patent infringement, then any patent licenses
87 | granted to You under this License for that Work shall terminate
88 | as of the date such litigation is filed.
89 |
90 | 4. Redistribution. You may reproduce and distribute copies of the
91 | Work or Derivative Works thereof in any medium, with or without
92 | modifications, and in Source or Object form, provided that You
93 | meet the following conditions:
94 |
95 | (a) You must give any other recipients of the Work or
96 | Derivative Works a copy of this License; and
97 |
98 | (b) You must cause any modified files to carry prominent notices
99 | stating that You changed the files; and
100 |
101 | (c) You must retain, in the Source form of any Derivative Works
102 | that You distribute, all copyright, patent, trademark, and
103 | attribution notices from the Source form of the Work,
104 | excluding those notices that do not pertain to any part of
105 | the Derivative Works; and
106 |
107 | (d) If the Work includes a "NOTICE" text file as part of its
108 | distribution, then any Derivative Works that You distribute must
109 | include a readable copy of the attribution notices contained
110 | within such NOTICE file, excluding those notices that do not
111 | pertain to any part of the Derivative Works, in at least one
112 | of the following places: within a NOTICE text file distributed
113 | as part of the Derivative Works; within the Source form or
114 | documentation, if provided along with the Derivative Works; or,
115 | within a display generated by the Derivative Works, if and
116 | wherever such third-party notices normally appear. The contents
117 | of the NOTICE file are for informational purposes only and
118 | do not modify the License. You may add Your own attribution
119 | notices within Derivative Works that You distribute, alongside
120 | or as an addendum to the NOTICE text from the Work, provided
121 | that such additional attribution notices cannot be construed
122 | as modifying the License.
123 |
124 | You may add Your own copyright statement to Your modifications and
125 | may provide additional or different license terms and conditions
126 | for use, reproduction, or distribution of Your modifications, or
127 | for any such Derivative Works as a whole, provided Your use,
128 | reproduction, and distribution of the Work otherwise complies with
129 | the conditions stated in this License.
130 |
131 | 5. Submission of Contributions. Unless You explicitly state otherwise,
132 | any Contribution intentionally submitted for inclusion in the Work
133 | by You to the Licensor shall be under the terms and conditions of
134 | this License, without any additional terms or conditions.
135 | Notwithstanding the above, nothing herein shall supersede or modify
136 | the terms of any separate license agreement you may have executed
137 | with Licensor regarding such Contributions.
138 |
139 | 6. Trademarks. This License does not grant permission to use the trade
140 | names, trademarks, service marks, or product names of the Licensor,
141 | except as required for reasonable and customary use in describing the
142 | origin of the Work and reproducing the content of the NOTICE file.
143 |
144 | 7. Disclaimer of Warranty. Unless required by applicable law or
145 | agreed to in writing, Licensor provides the Work (and each
146 | Contributor provides its Contributions) on an "AS IS" BASIS,
147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148 | implied, including, without limitation, any warranties or conditions
149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150 | PARTICULAR PURPOSE. You are solely responsible for determining the
151 | appropriateness of using or redistributing the Work and assume any
152 | risks associated with Your exercise of permissions under this License.
153 |
154 | 8. Limitation of Liability. In no event and under no legal theory,
155 | whether in tort (including negligence), contract, or otherwise,
156 | unless required by applicable law (such as deliberate and grossly
157 | negligent acts) or agreed to in writing, shall any Contributor be
158 | liable to You for damages, including any direct, indirect, special,
159 | incidental, or consequential damages of any character arising as a
160 | result of this License or out of the use or inability to use the
161 | Work (including but not limited to damages for loss of goodwill,
162 | work stoppage, computer failure or malfunction, or any and all
163 | other commercial damages or losses), even if such Contributor
164 | has been advised of the possibility of such damages.
165 |
166 | 9. Accepting Warranty or Additional Liability. While redistributing
167 | the Work or Derivative Works thereof, You may choose to offer,
168 | and charge a fee for, acceptance of support, warranty, indemnity,
169 | or other liability obligations and/or rights consistent with this
170 | License. However, in accepting such obligations, You may act only
171 | on Your own behalf and on Your sole responsibility, not on behalf
172 | of any other Contributor, and only if You agree to indemnify,
173 | defend, and hold each Contributor harmless for any liability
174 | incurred by, or claims asserted against, such Contributor by reason
175 | of your accepting any such warranty or additional liability.
176 |
177 | END OF TERMS AND CONDITIONS
178 |
179 | APPENDIX: How to apply the Apache License to your work.
180 |
181 | To apply the Apache License to your work, attach the following
182 | boilerplate notice, with the fields enclosed by brackets "[]"
183 | replaced with your own identifying information. (Don't include
184 | the brackets!) The text should be enclosed in the appropriate
185 | comment syntax for the file format. We also recommend that a
186 | file or class name and description of purpose be included on the
187 | same "printed page" as the copyright notice for easier
188 | identification within third-party archives.
189 |
190 | Copyright [yyyy] [name of copyright owner]
191 |
192 | Licensed under the Apache License, Version 2.0 (the "License");
193 | you may not use this file except in compliance with the License.
194 | You may obtain a copy of the License at
195 |
196 | http://www.apache.org/licenses/LICENSE-2.0
197 |
198 | Unless required by applicable law or agreed to in writing, software
199 | distributed under the License is distributed on an "AS IS" BASIS,
200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201 | See the License for the specific language governing permissions and
202 | limitations under the License.
203 |
--------------------------------------------------------------------------------
/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version:5.3
2 | // The swift-tools-version declares the minimum version of Swift required to build this package.
3 |
4 | // Copyright 2021 Google LLC
5 | //
6 | // Licensed under the Apache License, Version 2.0 (the "License");
7 | // you may not use this file except in compliance with the License.
8 | // You may obtain a copy of the License at
9 | //
10 | // http://www.apache.org/licenses/LICENSE-2.0
11 | //
12 | // Unless required by applicable law or agreed to in writing, software
13 | // distributed under the License is distributed on an "AS IS" BASIS,
14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | // See the License for the specific language governing permissions and
16 | // limitations under the License.
17 |
18 | import PackageDescription
19 |
20 | let package = Package(
21 | name: "GTMAppAuth",
22 | platforms: [
23 | .macOS(.v10_12),
24 | .iOS(.v12),
25 | .tvOS(.v10),
26 | .watchOS(.v6)
27 | ],
28 | products: [
29 | .library(
30 | name: "GTMAppAuth",
31 | targets: ["GTMAppAuth"]
32 | )
33 | ],
34 | dependencies: [
35 | .package(url: "https://github.com/google/gtm-session-fetcher.git", "3.3.0" ..< "4.0.0"),
36 | .package(url: "https://github.com/openid/AppAuth-iOS.git", "2.0.0" ..< "3.0.0")
37 | ],
38 | targets: [
39 | .target(
40 | name: "GTMAppAuth",
41 | dependencies: [
42 | .product(name: "GTMSessionFetcherCore", package: "gtm-session-fetcher"),
43 | .product(name: "AppAuthCore", package: "AppAuth-iOS")
44 | ],
45 | path: "GTMAppAuth/Sources",
46 | resources: [.copy("Resources/PrivacyInfo.xcprivacy")],
47 | linkerSettings: [
48 | .linkedFramework("Security"),
49 | ]
50 | ),
51 | .target(
52 | name: "TestHelpers",
53 | dependencies: [
54 | .product(name: "AppAuthCore", package: "AppAuth-iOS"),
55 | "GTMAppAuth"
56 | ],
57 | path: "GTMAppAuth/Tests/Helpers"
58 | ),
59 | .testTarget(
60 | name: "GTMAppAuthTests",
61 | dependencies: [
62 | "GTMAppAuth",
63 | "TestHelpers"
64 | ],
65 | path: "GTMAppAuth/Tests/Unit"
66 | ),
67 | .testTarget(
68 | name: "swift-objc-interop-tests",
69 | dependencies: [
70 | .product(name: "AppAuthCore", package: "AppAuth-iOS"),
71 | "GTMAppAuth",
72 | "TestHelpers"
73 | ],
74 | path: "GTMAppAuth/Tests/ObjCIntegration"
75 | )
76 | ]
77 | )
78 |
--------------------------------------------------------------------------------