├── .gitignore
├── .ruby-version
├── .slather.yml
├── .swift-version
├── .travis.yml
├── IBMCloudAppID.podspec
├── IBMCloudAppID.xcodeproj
├── project.pbxproj
├── project.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ │ └── IDEWorkspaceChecks.plist
└── xcshareddata
│ └── xcschemes
│ ├── IBMCloudAppID.xcscheme
│ └── IBMCloudAppIDTests.xcscheme
├── IBMCloudAppIDTests
├── AppIDAuthorizationManagerTests.swift
├── AppIDTestConstants.swift
├── AppIDTests.swift
├── AuthorizationHeaderHelperTests.swift
├── AuthorizationManagerTests.swift
├── AuthorizationUIManagerTests.swift
├── ConfigTests.swift
├── Info.plist
├── PreferencesTests.swift
├── RegistrationManagerTests.swift
├── SecurityUtilsTests.swift
├── TestHelpers.swift
├── TokenManagerTests.swift
├── TokenTests.swift
├── UserProfileTests.swift
└── UtilsTests.swift
├── LICENSE
├── Podfile
├── README.md
├── Source
├── IBMCloudAppID
│ ├── api
│ │ ├── AppID.swift
│ │ ├── AppIDAuthorizationManager.swift
│ │ ├── AuthorizationDelegate.swift
│ │ ├── AuthorizationError.swift
│ │ ├── IdentityToken.swift
│ │ ├── LoginWidget.swift
│ │ ├── SecAttrAccessible.swift
│ │ ├── TokenResponseDelegate.swift
│ │ ├── Tokens
│ │ │ ├── AccessToken.swift
│ │ │ └── RefreshToken.swift
│ │ ├── UserAttributeError.swift
│ │ ├── UserProfileError.swift
│ │ └── UserProfileManager.swift
│ └── internal
│ │ ├── AppIDConstants.swift
│ │ ├── AppIDError.swift
│ │ ├── AuthorizationHeaderHelper.swift
│ │ ├── AuthorizationManager.swift
│ │ ├── AuthorizationUIManager.swift
│ │ ├── Config.swift
│ │ ├── JSONPreference.swift
│ │ ├── LoginWidgetImpl.swift
│ │ ├── OAuthManager.swift
│ │ ├── PreferenceManager.swift
│ │ ├── RegistrationManager.swift
│ │ ├── SecurityUtils.swift
│ │ ├── StringPreference.swift
│ │ ├── TokenManager.swift
│ │ ├── UserProfileManagerImpl.swift
│ │ ├── Utils.swift
│ │ ├── safariView.swift
│ │ └── tokens
│ │ ├── AbstractToken.swift
│ │ ├── AccessTokenImpl.swift
│ │ ├── IdentityTokenImpl.swift
│ │ └── RefreshTokenImpl.swift
├── Info.plist
└── Resources
│ └── IBMCloudAppID.h
├── dummyAppForKeyChain
├── AppDelegate.swift
├── Assets.xcassets
│ └── AppIcon.appiconset
│ │ └── Contents.json
├── Base.lproj
│ ├── LaunchScreen.storyboard
│ └── Main.storyboard
├── Info.plist
├── ViewController.swift
└── dummyAppForKeyChain.entitlements
└── scripts
└── release.sh
/.gitignore:
--------------------------------------------------------------------------------
1 | IBMCloudAppID.xcodeproj/
2 | IBMCloudAppID.xcworkspace/
3 | Pods/
4 | Podfile.lock
5 | .idea/
6 | *.DS_Store
7 |
--------------------------------------------------------------------------------
/.ruby-version:
--------------------------------------------------------------------------------
1 | 2.4.0
2 |
--------------------------------------------------------------------------------
/.slather.yml:
--------------------------------------------------------------------------------
1 | coverage_service: coveralls
2 | workspace: IBMCloudAppID
3 | xcodeproj: IBMCloudAppID.xcodeproj
4 | scheme: IBMCloudAppIDTests
5 | ignore:
6 | - IBMCloudAppIDTests/*
7 |
--------------------------------------------------------------------------------
/.swift-version:
--------------------------------------------------------------------------------
1 | 4.0
2 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: objective-c
2 | matrix:
3 | include:
4 | - osx_image: xcode12.3
5 | install:
6 | - gem install jazzy
7 | - gem install slather -v 2.4.5
8 | before_script:
9 | - rm -rf ~/Library/Developer/Xcode/DerivedData
10 | script:
11 | # Test that the framework can be installed and built, and passes all unit tests
12 | - travis_wait pod update
13 | - pod lib lint --allow-warnings
14 | - xcodebuild -workspace 'IBMCloudAppID.xcworkspace' -scheme 'IBMCloudAppID' clean build CODE_SIGN_IDENTITY= CODE_SIGNING_REQUIRED=NO
15 | - travis_retry xcodebuild -workspace 'IBMCloudAppID.xcworkspace' test -scheme 'IBMCloudAppIDTests' -destination 'platform=iOS Simulator,name=iPhone 6' -enableCodeCoverage YES
16 | - slather coverage --coveralls --binary-basename IBMCloudAppID.framework -v
17 | # When merging or pushing to the master branch, release a new version and publish the API documentation
18 | #- if [ "${TRAVIS_PULL_REQUEST}" = "false" ] && [ "${TRAVIS_BRANCH}" = "master" ] ; then
19 | # bash scripts/release.sh;
20 | # fi
21 |
--------------------------------------------------------------------------------
/IBMCloudAppID.podspec:
--------------------------------------------------------------------------------
1 | Pod::Spec.new do |s|
2 | s.name = "IBMCloudAppID"
3 | s.version = '6.0.3'
4 | s.summary = "AppID Swift SDK"
5 | s.homepage = "https://github.com/ibm-cloud-security/appid-clientsdk-swift"
6 | s.license = 'Apache License, Version 2.0'
7 | s.author = { "IBM Cloud Services Mobile SDK" => "mobilsdk@us.ibm.com" }
8 | s.swift_version = "4.0"
9 | s.source = { :git => 'https://github.com/ibm-cloud-security/appid-clientsdk-swift.git', :tag => "#{s.version}" }
10 | s.dependency 'BMSCore'
11 | s.dependency 'JOSESwift'
12 | s.requires_arc = true
13 | s.source_files = 'Source/**/*.swift', 'Source/Resources/IBMCloudAppID.h'
14 | s.ios.deployment_target = '10.0'
15 | end
16 |
--------------------------------------------------------------------------------
/IBMCloudAppID.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/IBMCloudAppID.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/IBMCloudAppID.xcodeproj/xcshareddata/xcschemes/IBMCloudAppID.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
31 |
32 |
34 |
40 |
41 |
42 |
43 |
44 |
50 |
51 |
52 |
53 |
54 |
55 |
65 |
66 |
72 |
73 |
74 |
75 |
76 |
77 |
83 |
84 |
90 |
91 |
92 |
93 |
95 |
96 |
99 |
100 |
101 |
--------------------------------------------------------------------------------
/IBMCloudAppID.xcodeproj/xcshareddata/xcschemes/IBMCloudAppIDTests.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
16 |
18 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
40 |
41 |
42 |
43 |
49 |
50 |
52 |
53 |
56 |
57 |
58 |
--------------------------------------------------------------------------------
/IBMCloudAppIDTests/AppIDAuthorizationManagerTests.swift:
--------------------------------------------------------------------------------
1 | /* * Copyright 2016, 2017 IBM Corp.
2 | * Licensed under the Apache License, Version 2.0 (the "License");
3 | * you may not use this file except in compliance with the License.
4 | * You may obtain a copy of the License at
5 | * http://www.apache.org/licenses/LICENSE-2.0
6 | * Unless required by applicable law or agreed to in writing, software
7 | * distributed under the License is distributed on an "AS IS" BASIS,
8 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9 | * See the License for the specific language governing permissions and
10 | * limitations under the License.
11 | */
12 |
13 | import Foundation
14 |
15 | import XCTest
16 | import BMSCore
17 | @testable import IBMCloudAppID
18 |
19 | public class AppIDAuthorizationManagerTests: XCTestCase {
20 |
21 | static var appid:AppID? = nil
22 | static var manager:AppIDAuthorizationManager? = nil
23 |
24 | override public func setUp() {
25 | super.setUp()
26 | AppID.sharedInstance.initialize(tenantId: "123", region: "123")
27 | AppIDAuthorizationManagerTests.appid = AppID.sharedInstance
28 | AppIDAuthorizationManagerTests.manager = AppIDAuthorizationManager(appid: AppIDAuthorizationManagerTests.appid!)
29 | }
30 |
31 | public func testIsAuthorizationRequired () {
32 |
33 | // 401 status, Www-Authenticate header exist, but invalid value
34 | XCTAssertFalse((AppIDAuthorizationManagerTests.manager?.isAuthorizationRequired(for: 401, httpResponseAuthorizationHeader: "Dummy"))!)
35 |
36 | // 401 status, Www-Authenticate header exists, Bearer exists, but not appid scope
37 | XCTAssertFalse((AppIDAuthorizationManagerTests.manager?.isAuthorizationRequired(for: 401, httpResponseAuthorizationHeader: "Bearer Dummy"))!)
38 |
39 | // 401 with bearer and correct scope
40 | XCTAssertTrue((AppIDAuthorizationManagerTests.manager?.isAuthorizationRequired(for: 401, httpResponseAuthorizationHeader: "Bearer scope=\"appid_default\""))!)
41 |
42 | // Check with http response
43 |
44 | let response = HTTPURLResponse(url: URL(string: "ADS")!, statusCode: 401, httpVersion: nil, headerFields: [AppIDConstants.WWW_AUTHENTICATE_HEADER : "Bearer scope=\"appid_default\""])
45 | XCTAssertTrue((AppIDAuthorizationManagerTests.manager?.isAuthorizationRequired(for: Response(responseData: nil, httpResponse: response, isRedirect: false)))!)
46 | }
47 |
48 | static var expectedResponse:Response = Response(responseData: nil, httpResponse: HTTPURLResponse(url: URL(string: "ADS")!, statusCode: 401, httpVersion: nil, headerFields: [AppIDConstants.WWW_AUTHENTICATE_HEADER : "Bearer scope=\"appid_default\""]), isRedirect: false)
49 | class MockAuthorizationManager: IBMCloudAppID.AuthorizationManager {
50 | static var res = "cancel"
51 |
52 | var shouldCallObtainTokensRefreshToken = false
53 | var obtainTokensRefreshTokenCalled = false
54 |
55 | override func launchAuthorizationUI(accessTokenString: String? = nil, authorizationDelegate:AuthorizationDelegate) {
56 | if MockAuthorizationManager.res == "success" {
57 | authorizationDelegate.onAuthorizationSuccess(
58 | accessToken:AccessTokenImpl(with: AppIDTestConstants.ACCESS_TOKEN)!,
59 | identityToken : IdentityTokenImpl(with: AppIDTestConstants.ID_TOKEN)!,
60 | refreshToken: nil,
61 | response: AppIDAuthorizationManagerTests.expectedResponse)
62 | } else if MockAuthorizationManager.res == "failure" {
63 | authorizationDelegate.onAuthorizationFailure(error: AuthorizationError.authorizationFailure("someerr"))
64 | } else {
65 | authorizationDelegate.onAuthorizationCanceled()
66 | }
67 |
68 | }
69 |
70 | override func signinWithRefreshToken(refreshTokenString: String?, tokenResponseDelegate: TokenResponseDelegate) {
71 | obtainTokensRefreshTokenCalled = true
72 | if !shouldCallObtainTokensRefreshToken {
73 | XCTFail("Unexpected call to obtainTokensRefreshToken")
74 | }
75 | }
76 |
77 | func verify() {
78 | if shouldCallObtainTokensRefreshToken && !obtainTokensRefreshTokenCalled {
79 | XCTFail("Should have called obtainTokensRefreshToken, but the function wasn't called")
80 | }
81 | }
82 | }
83 |
84 |
85 | public func testObtainAuthorizationCanceled() {
86 |
87 | MockAuthorizationManager.res = "cancel"
88 | AppIDAuthorizationManagerTests.manager?.oAuthManager.authorizationManager = MockAuthorizationManager(oAuthManager: (AppIDAuthorizationManagerTests.manager?.oAuthManager)!)
89 | let callback:BMSCompletionHandler = {(response:Response?, error:Error?) in
90 | XCTAssertNil(response)
91 | XCTAssertEqual((error as? AuthorizationError)?.description, "Authorization canceled")
92 | }
93 | AppIDAuthorizationManagerTests.manager?.obtainAuthorization(completionHandler: callback)
94 |
95 | }
96 |
97 | public func testObtainAuthorizationSuccess() {
98 | MockAuthorizationManager.res = "success"
99 | AppIDAuthorizationManagerTests.manager?.oAuthManager.authorizationManager = MockAuthorizationManager(oAuthManager: (AppIDAuthorizationManagerTests.manager?.oAuthManager)!)
100 | let callback:BMSCompletionHandler = {(response:Response?, error:Error?) in
101 | XCTAssertNotNil(response)
102 | XCTAssertEqual(AppIDAuthorizationManagerTests.expectedResponse.statusCode, response?.statusCode)
103 | XCTAssertEqual(AppIDAuthorizationManagerTests.expectedResponse.responseText, response?.responseText)
104 | XCTAssertEqual(AppIDAuthorizationManagerTests.expectedResponse.responseData, response?.responseData)
105 | XCTAssertNil(error)
106 | }
107 | AppIDAuthorizationManagerTests.manager?.obtainAuthorization(completionHandler: callback)
108 | }
109 |
110 | public func testObtainAuthorizationWithRefreshTokenSuccess() {
111 | MockAuthorizationManager.res = "failure"
112 |
113 | AppIDAuthorizationManagerTests.manager?.oAuthManager.authorizationManager = MockAuthorizationManager(oAuthManager: (AppIDAuthorizationManagerTests.manager?.oAuthManager)!)
114 |
115 | let tokenManager = TestHelpers.MockTokenManager(
116 | oAuthManager: AppIDAuthorizationManagerTests.manager!.oAuthManager)
117 |
118 | AppIDAuthorizationManagerTests.manager?.oAuthManager.tokenManager = tokenManager
119 | tokenManager.latestRefreshToken = RefreshTokenImpl(with: "ststs")
120 | tokenManager.shouldCallObtainWithRefresh = true
121 | let callback:BMSCompletionHandler = {(response:Response?, error:Error?) in
122 | XCTAssertNotNil(response)
123 | XCTAssertNil(error)
124 | }
125 | AppIDAuthorizationManagerTests.manager?.obtainAuthorization(completionHandler: callback)
126 | tokenManager.verify()
127 | }
128 |
129 | public func testObtainAuthorizationSuccessAfterRefreshFails() {
130 | MockAuthorizationManager.res = "success"
131 | AppIDAuthorizationManagerTests.manager?.oAuthManager.authorizationManager = MockAuthorizationManager(oAuthManager: (AppIDAuthorizationManagerTests.manager?.oAuthManager)!)
132 | let tokenManager = TestHelpers.MockTokenManager(
133 | oAuthManager: AppIDAuthorizationManagerTests.manager!.oAuthManager)
134 | AppIDAuthorizationManagerTests.manager?.oAuthManager.tokenManager = tokenManager
135 | tokenManager.shouldCallObtainWithRefresh = true
136 | tokenManager.obtainWithRefreshShouldFail = true
137 | tokenManager.latestRefreshToken = RefreshTokenImpl(with: "ststs")
138 |
139 | let callback:BMSCompletionHandler = {(response:Response?, error:Error?) in
140 | XCTAssertNotNil(response)
141 | XCTAssertEqual(AppIDAuthorizationManagerTests.expectedResponse.statusCode, response?.statusCode)
142 | XCTAssertEqual(AppIDAuthorizationManagerTests.expectedResponse.responseText, response?.responseText)
143 | XCTAssertEqual(AppIDAuthorizationManagerTests.expectedResponse.responseData, response?.responseData)
144 | XCTAssertNil(error)
145 | }
146 | AppIDAuthorizationManagerTests.manager?.obtainAuthorization(completionHandler: callback)
147 | tokenManager.verify()
148 | }
149 |
150 |
151 | public func testObtainAuthorizationFailure() {
152 |
153 | MockAuthorizationManager.res = "failure"
154 | AppIDAuthorizationManagerTests.manager?.oAuthManager.authorizationManager = MockAuthorizationManager(oAuthManager: (AppIDAuthorizationManagerTests.manager?.oAuthManager)!)
155 | let callback:BMSCompletionHandler = {(response:Response?, error:Error?) in
156 | XCTAssertNil(response)
157 | XCTAssertEqual((error as? AuthorizationError)?.description, "someerr")
158 | }
159 | AppIDAuthorizationManagerTests.manager?.obtainAuthorization(completionHandler: callback)
160 |
161 | }
162 |
163 | public func testObtainAuthorizationFailsAfterRefreshFails() {
164 | MockAuthorizationManager.res = "failure"
165 | AppIDAuthorizationManagerTests.manager?.oAuthManager.authorizationManager = MockAuthorizationManager(oAuthManager: (AppIDAuthorizationManagerTests.manager?.oAuthManager)!)
166 | let tokenManager = TestHelpers.MockTokenManager(
167 | oAuthManager: AppIDAuthorizationManagerTests.manager!.oAuthManager)
168 | AppIDAuthorizationManagerTests.manager?.oAuthManager.tokenManager = tokenManager
169 | tokenManager.shouldCallObtainWithRefresh = true
170 | tokenManager.obtainWithRefreshShouldFail = true
171 | tokenManager.latestRefreshToken = RefreshTokenImpl(with: "ststs")
172 | let callback:BMSCompletionHandler = {(response:Response?, error:Error?) in
173 | XCTAssertNil(response)
174 | XCTAssertEqual((error as? AuthorizationError)?.description, "someerr")
175 | }
176 | AppIDAuthorizationManagerTests.manager?.obtainAuthorization(completionHandler: callback)
177 | tokenManager.verify()
178 | }
179 |
180 |
181 | public func testGetCachedAuthorizationHeader () {
182 | class AppIDAuthorizationManagerMock: AppIDAuthorizationManager {
183 | var aToken:AccessToken?
184 | var iToken:IdentityToken?
185 | init(accessToken:AccessToken?, idToken:IdentityToken?) {
186 | self.aToken = accessToken
187 | self.iToken = idToken
188 | super.init(appid: AppIDAuthorizationManagerTests.appid!)
189 | }
190 |
191 | public override var accessToken: AccessToken? {
192 | get {
193 | return aToken
194 | }
195 | }
196 |
197 | public override var identityToken: IdentityToken? {
198 | get {
199 | return iToken
200 | }
201 | }
202 | }
203 | let accessToken = AccessTokenImpl(with: AppIDTestConstants.ACCESS_TOKEN)
204 | let idToken = IdentityTokenImpl(with: AppIDTestConstants.ID_TOKEN)
205 | XCTAssertNil(AppIDAuthorizationManagerMock(accessToken: nil,idToken: nil).cachedAuthorizationHeader)
206 | XCTAssertNil(AppIDAuthorizationManagerMock(accessToken: accessToken,idToken: nil).cachedAuthorizationHeader)
207 | XCTAssertNil(AppIDAuthorizationManagerMock(accessToken: nil,idToken: idToken).cachedAuthorizationHeader)
208 | XCTAssertEqual((AppIDAuthorizationManagerMock(accessToken: accessToken,idToken: idToken).cachedAuthorizationHeader), "Bearer " + accessToken!.raw + " " + idToken!.raw)
209 |
210 |
211 |
212 | }
213 | }
214 |
--------------------------------------------------------------------------------
/IBMCloudAppIDTests/AppIDTestConstants.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppIDTestConstants.swift
3 | // AppID
4 | //
5 | // Created by Oded Betzalel on 13/02/2017.
6 | // Copyright © 2017 Oded Betzalel. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | public class AppIDTestConstants {
12 |
13 |
14 | public static var publicKeyData:Data = Data(base64Encoded: "MEgCQQDh/pwAlN3AqKQM+v0sybg7VMjeJx4Z5PcnxfQxYhj3LVz28DF6H2b3fVnGEKrcPsN1lf8obovT6zlX1QYZOgpjAgMBAAE=", options: NSData.Base64DecodingOptions(rawValue:0))!
15 |
16 | public static var privateKeyData:Data = Data(base64Encoded: "MIIBOgIBAAJBAOH+nACU3cCopAz6/SzJuDtUyN4nHhnk9yfF9DFiGPctXPbwMXofZvd9WcYQqtw+w3WV/yhui9PrOVfVBhk6CmMCAwEAAQJAJ4H8QbnEnoacz0wdcHP/ShgDWZrbD0nQz1oy22M73BHidwDvy1rIeM6PgkK1tyHNWrqyo1kAnp7DuNVmfGbJ0QIhAc3gVBJCrVbiO23OasUuYTN2y2KrZ2DUcjLp5ZOID1/LAiB9Qo1mx3yz4HT4wJvddb9AqSTlmSrrdXcNGNhWFRT8yQIhAbepkD3lrL2lEy8+q9JRiQOFVKvzP7Aj6yVeE0Sx4virAiAk2ITbrOajyuzdl1rCBDbkAF1YJHwZkw4YDizk9YKc8QIhAV0VZFoZidVBTsoi7xeufS0GSDqPxskq7gJGY70p4dco", options: NSData.Base64DecodingOptions(rawValue:0))!
17 |
18 | public static var ACCESS_TOKEN = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImFwcElkLWRiOGEyN2M0LWI4ODctNGY4ZC1hODlmLWYxMmZiNzc1YjMxMS0yMDE4LTA4LTAyVDEyOjA0OjA5LjcyOCIsInZlciI6NH0.eyJpc3MiOiJodHRwczovL2V1LWdiLmFwcGlkLnRlc3QuY2xvdWQuaWJtLmNvbS9vYXV0aC92NC9kYjhhMjdjNC1iODg3LTRmOGQtYTg5Zi1mMTJmYjc3NWIzMTEiLCJleHAiOjE1NTI1MDI0MjQsImF1ZCI6WyIyMWU4YjUyMy1lYjQyLTRhMzQtYTA1Ny0wNGNhOTQ0NWY2ZmYiXSwic3ViIjoiMGU4NzRhZDEtMzJiZS00NWI5LWExNmEtZmEyOGIyZjMyZmNkIiwiYW1yIjpbImdvb2dsZSJdLCJpYXQiOjE1NTI1MDI0MjIsInRlbmFudCI6ImRiOGEyN2M0LWI4ODctNGY4ZC1hODlmLWYxMmZiNzc1YjMxMSIsInNjb3BlIjoib3BlbmlkIGFwcGlkX2RlZmF1bHQgYXBwaWRfcmVhZHByb2ZpbGUgYXBwaWRfcmVhZHVzZXJhdHRyIGFwcGlkX3dyaXRldXNlcmF0dHIgYXBwaWRfYXV0aGVudGljYXRlZCJ9.QZ1uz8ywb8A_KQrovTosNUqTXWi1-aZnBZ9QKmYY99UM-S8MdzpbTcZBh1gdP1NaxRlB_xNlLOp22tKLA5tdiHGW1y9BDjRqUR04rCUDMVgwRUU2j7X6v1wHpA05op0goWwOPzlX3oEfbTYjBsBpvtqHvXlbTdQg0rToMmbKne_F8bnQjKLHRWv2eJ5UND7UZ0Wcv2jJyNkbiAqziDtcuEYCy955D1pJka9eW9b5yFNvjh31zqL8Cd5gOoIez1V4PFlWL2IDAG27F5-hrAet1meqWwrO-rmm6kUXT6jgtUJpk48zngmwknbr5JUr93aSjlFnkVRzPiI1_KetRbdsxQ"
19 |
20 | public static var ID_TOKEN = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImFwcElkLWRiOGEyN2M0LWI4ODctNGY4ZC1hODlmLWYxMmZiNzc1YjMxMS0yMDE4LTA4LTAyVDEyOjA0OjA5LjcyOCIsInZlciI6NH0.eyJpc3MiOiJodHRwczovL2V1LWdiLmFwcGlkLnRlc3QuY2xvdWQuaWJtLmNvbS9vYXV0aC92NC9kYjhhMjdjNC1iODg3LTRmOGQtYTg5Zi1mMTJmYjc3NWIzMTEiLCJhdWQiOlsiMjFlOGI1MjMtZWI0Mi00YTM0LWEwNTctMDRjYTk0NDVmNmZmIl0sImV4cCI6MTU1MjUwMjQyNCwidGVuYW50IjoiZGI4YTI3YzQtYjg4Ny00ZjhkLWE4OWYtZjEyZmI3NzViMzExIiwiaWF0IjoxNTUyNTAyNDIyLCJlbWFpbCI6ImRvbmxvbnF3ZXJ0eUBnbWFpbC5jb20iLCJuYW1lIjoiTG9uIERvbiIsImxvY2FsZSI6ImVuIiwicGljdHVyZSI6Imh0dHBzOi8vbGg2Lmdvb2dsZXVzZXJjb250ZW50LmNvbS8tTHlLSFo5UFdoaWMvQUFBQUFBQUFBQUkvQUFBQUFBQUFBQ2svQW1TamU0SEVpMUEvcGhvdG8uanBnIiwic3ViIjoiMGU4NzRhZDEtMzJiZS00NWI5LWExNmEtZmEyOGIyZjMyZmNkIiwiaWRlbnRpdGllcyI6W3sicHJvdmlkZXIiOiJnb29nbGUiLCJpZCI6IjEwNTc0NzcyNTA2ODYwNTA4NDY1NyJ9XSwiYW1yIjpbImdvb2dsZSJdfQ.QSD6li3Lo2zFG_Iy-IWdh0wJ4tWauc0Mj5IekP5ai3puLocuk6ucQnwKgqOt5lxALSosmXLb8fQsrZixmDWmthkdmY523t6rRIJvRO9-dJXc8fCkdYJdG6AuOwb_e9eHgg41U-E3AeIoc4n0JKkXkQKDTz8I6gfPQua7UPfzsODMjqCp95JevjLJbxHm2lLq-aT2zR0YDG4P-hJb335fxFGlQNldLvYtN8hQfHo_8xeriIH3zYjTqYiXMgSoM6xsU3WwFOD_IShqR2CEXD9sxEXfpdt4SJeJ79--0kTQ958CCb0nnbOjrjqzSSh-U52DWFLgU2jdkQg0nfB29lcGnQ"
21 |
22 | public static var malformedIdTokenNoSubject = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImFwcElkLWJkOWZiOGM4LWU4ZDctNDY3MS1hN2JiLTQ4ZTJlZDVmY2I3Ny0yMDE5LTAxLTIzVDIyOjQyOjM0LjI4NCJ9.eyJpc3MiOiJodHRwczovL2FwcGlkLW9hdXRoLnN0YWdlMS5ldS1nYi5ibHVlbWl4Lm5ldCIsImF1ZCI6WyJlMjY0NjYwNWY1YjQzZTQ0YzUzYzcwMjhiYWM2NTlmMjNmZmI1ZTM5Il0sImV4cCI6MTU1MDQ1NjU3MCwidGVuYW50IjoiYmQ5ZmI4YzgtZThkNy00NjcxLWE3YmItNDhlMmVkNWZjYjc3IiwiaWF0IjoxNTUwNDU2MjcwLCJ2ZXJzaW9uIjoidjQiLCJhbXIiOlsiZmFjZWJvb2siXSwiYXpwIjoiZTI2NDY2MDVmNWI0M2U0NGM1M2M3MDI4YmFjNjU5ZjIzZmZiNWUzOSJ9.gYwk5ZL4xUjN-aEUDcTytMm5GIac7DQunO4LVJmlFqk_jiRqjZp_oXVJn_1GfVjJ2rmiF65wcZIG2CJ6Xb45uUQHqWiIsOfB82g9HoS25BquVpZT_90-bHAuZDsWz7BTiU5FxQErnv_RuymWGSkEwusagibE3HGwnmOBliX2AbAXUPVjzSesfs4Axiy_bU0O-ALfftJDIyzfh8lymSssTy-fL7rBDmEMyLt95LB0YzYT7I4c6Tu46N59wRtG3HYAPSgGRuy-hoFK-M9TNFtf55_UVhws9tG4AffjRajemVZV8pyqEai2T_E80Zj-OIfBhqkTkc72dno8u9E7-krnPg"
23 |
24 | public static var malformedAccessTokenMissingKid = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpPU0UifQ.eyJpc3MiOiJhcHBpZC1vYXV0aC5uZy5ibHVlbWl4Lm5ldCIsImV4cCI6MTUyOTY5NjQyNSwiYXVkIjpbIjA2YTQyMDQ2OWM0YmVhMmE2OTRhYTdlOTcxZDE1NjgwYmI2OTc5NGEiXSwic3ViIjoiOTY5ZGViZmUtMWE3Mi00OTk0LWJlMTQtY2Y4MzJhNzFiNTllIiwiYW1yIjpbImFwcGlkX2Fub24iXSwiaWF0IjoxNTI3MTA0NDI1LCJ0ZW5hbnQiOiJjMmQzZGE5NC1jOTAxLTQzOTItOGYyNy1kOTBlZmQyOGI1YjciLCJzY29wZSI6Im9wZW5pZCBhcHBpZF9kZWZhdWx0IGFwcGlkX3JlYWRwcm9maWxlIGFwcGlkX3JlYWR1c2VyYXR0ciBhcHBpZF93cml0ZXVzZXJhdHRyIn0.YTVq0j6ApiN-DAQH0wHetk4NWml52alid5OjMjzUUVywl5LYuiNPBEAtbgRUQ9un7M7IyTTkUZhZUpIjm1Hh5rbpxecau-3X84CzzMU98shZYoMtjHdwl-zF_cRvu0jnL4AEuV9oF5pEwFzFmBOboZYxeNRTZwFKCIekkfBhvw4"
25 |
26 | public static let jwks = "{\"keys\":[{\"kty\":\"RSA\",\"use\":\"sig\",\"n\":\"AJvyFiaRrL1IiQyV8Uy-xjmvvjB7Zsaz3VqeUhFMuvRNudKx5F4o8Etd3xYHCd_aGuOR2GbDSGcoVsXrc00rs-vpj1IWhP5QTofParfRScZsi4i0tyihD6uzaHGe9Bc3__iGwzZFSFTVadCxsmEwJ176ExfYHptY1Dv3TCmVZ-6LE0KghhY2PnaR9zua88TToOES7w2UN2EhMm3490eFV3llnKG02dX5x0QSBuP_7PITMHUTxy1MCmqso4KhwwD_qrCUuepcKc1u9S2DWPV6-gqApvKHn8DTqrdNXqbIyfNTGy3SVo1JFeJpWwLH31IKmZHWQ6A4tdyoHK7GrtcokfM\",\"e\":\"AQAB\",\"kid\":\"appId-bd9fb8c8-e8d7-4671-a7bb-48e2ed5fcb77-2019-01-23T22:42:34.284\"}]}"
27 |
28 | public static let jwk = "{\r\n \"keys\": [\r\n {\r\n \"kty\": \"RSA\",\r\n \"use\": \"sig\",\r\n \"n\": \"ALePj2tZTsUDtGlBKMPU1GjbdpVdKPITqDyLM4YhktHzrB2tt690Sdkr5g8wTFflhMEsNARxQnDr7ZywIgsCvpAqv8JSzuoIu-N8hp3FJeGvMJ_4Fh7mlrxh_KVE7Xv1zbqCGSrmsiWsA-Y0Fxt4QEcPlPd_BDh1W7_vm5WuP0sCNsclziq9t7UIrIrvHXFRA9nuxMsM2OfaisU0T9PczfO16EuJW6jflmP6J3ewoJ1AT1SbX7e98ecyD2Ke5I0ta33yk7AVCLtzubJz2NCDGPTWRivqFC0J1OkV90jzme4Eo7zs-CDK-ItVCkV4mgX6Caknd_j2hucGN4fMUDviWwE\",\r\n \"e\": \"AQAB\",\r\n \"kid\": \"appId-1533805626000-71b34890-a94f-4ef2-a4b6-ce094aa68092\"\r\n },\r\n {\r\n \"kty\": \"RSA\",\r\n \"use\": \"sig\",\r\n \"n\": \"AMniJfma7obdg2AMkucEo5QV4ohy6rHPnuYl7gOGTKLdkQ2cpPx4a5viHaKiny3KpqfR2ny7OvsmB3UAYk3_rfCaNrtB5_zz2H-GxxDPYEPniYztU9aRyw5NlWUtpcAAkaXPRkzfKndUFg74W8h_HHm0DL-5KySiAPcfNnyT6fvf0ycNtYbngh0CSNzJQq7vZDZboZMaVkASgR11uOGV-RGnQ4shRc4z3qv7f4_jnDW4WsB0RzrgPGRJ9fSNrQS78LAfIbdzigfgR4_TxifhemwzYwpJ5PYV2pxHs6DuLUODbvIhWahZR_iJWoxpZZdxNDirycJ2CP_On1T3-urz4SM\",\r\n \"e\": \"AQAB\",\r\n \"kid\": \"appId-71b34890-a94f-4ef2-a4b6-ce094aa68092-2018-08-02T11:53:36.497\"\r\n }\r\n ]\r\n}"
29 |
30 | public static let kid = "appId-71b34890-a94f-4ef2-a4b6-ce094aa68092-2018-08-02T11:53:36.497"
31 |
32 | public static let malformedAccessTokenInvalidAlg = "eyJraWQiOiJraWQiLCJ0eXAiOiJKV1QiLCJhbGciOiJub25lIn0.eyJpc3MiOiJtb2JpbGVjbGllbnRhY2Nlc3Muc3RhZ2UxLm5nLmJsdWVtaXgubmV0IiwiZXhwIjoxNDg3MDg0ODc4LCJhdWQiOiIyNmNiMDEyZWIzMjdjNjEyZDkwYTY4MTkxNjNiNmJjYmQ0ODQ5Y2JiIiwiaWF0IjoxNDg3MDgxMjc4LCJhdXRoX2J5IjoiZmFjZWJvb2siLCJ0ZW5hbnQiOiI0ZGJhOTQzMC01NGU2LTRjZjItYTUxNi02ZjczZmViNzAyYmIiLCJzY29wZSI6ImFwcGlkX2RlZmF1bHQgYXBwaWRfcmVhZHByb2ZpbGUgYXBwaWRfcmVhZHVzZXJhdHRyIGFwcGlkX3dyaXRldXNlcmF0dHIifQ.HHterec250JSDY1965cM2DadBznl2wTKmzKNSnfjpdTAqax9VZvV3EwuFbEnGp9-i6AC-OlsVj7xvbALkdjwG2lZvpQx0M_gRc_3E0NiYuOGVolcm0wEXtbtDUFFqZQAf9BYYOPZ8OintdBiwUGETbH1ZRVtUvt3nalIko1OPE1Q12LvuRlhz5MClNHmvxJcXc7kucxCx4s4UFFy_HJA1gow7HWFqc9-PZf4JMWA-siYqPrdw_zYeBTBzE5co92F6JBEtGLLCjhJVz9eYgLLECXbak3z6hOaY9352Weuj7AgMOWxzw56jKKsiixMtvzrCzLVIcRUG96UJszwPHtPlA"
33 |
34 | public static let expAcessToken = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpPU0UifQ.eyJpc3MiOiJpbWYtYXV0aHNlcnZlci5zdGFnZTEubXlibHVlbWl4Lm5ldCIsImV4cCI6MTQ4OTk1NzQ1OSwiYXVkIjoiNDA4ZWIzNmEyYTA2OWFkODljZDE5Yzc4OWE5NmI3Y2YzNmI1NTBlYyIsInN1YiI6IjA5YjdmZWE1LTJlNGUtNDBiOC05ZDgxLWRmNTAwNzFhMzA1MyIsImFtciI6WyJmYWNlYm9vayJdLCJpYXQiOjE0ODczNjU0NTksInRlbmFudCI6IjUwZDBiZWVkLWFkZDctNDhkZC04YjBhLWM4MThjYjQ1NmJiNCIsInNjb3BlIjoiYXBwaWRfZGVmYXVsdCBhcHBpZF9yZWFkcHJvZmlsZSBhcHBpZF9yZWFkdXNlcmF0dHIgYXBwaWRfd3JpdGV1c2VyYXR0ciJ9.gQq4_IxbkPg1FsVZiiTqsejURL4E_Ijr8U1vDob-06GcsorVijS7HHf0kgWD84cDNa6z4Lp7HkmvI8vmiUIfV6ch-xJS_LSJphKy5nZxXqVHchRDJAMUNMiAYqC5ohZ4MXmjuGFIrVl1iZdTyP5Oz-5e6UzDccdAGkPokNs_IyXwiSmGWF5fOKSgfqANYwRBaC-JeXlzEcVZ697q92kiErBNl3ziuSFWxss86ZHHiKdLoHUpkDRKgPHwSQmE_Kwzj8v8Td9WuIVwXCF-D4koTuPJSe2aPqCLuV28PE9wRh5j3sFraKbQIcjuHuiAd5KBhzwaeVT20_0zrgyr3QG0Vg"
35 |
36 | public static var ACCESS_TOKEN_INVALID_AUD = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImFwcElkLTcxYjM0ODkwLWE5NGYtNGVmMi1hNGI2LWNlMDk0YWE2ODA5Mi0yMDE4LTA4LTAyVDExOjUzOjM2LjQ5NyIsInZlcnNpb24iOjR9.eyJpc3MiOiJodHRwczovL2V1LWdiLmFwcGlkLnRlc3QuY2xvdWQuaWJtLmNvbS9vYXV0aC92NC83MWIzNDg5MC1hOTRmLTRlZjItYTRiNi1jZTA5NGFhNjgwOTIiLCJleHAiOjE1NTA4NzMwNzQsImF1ZCI6WyIzYjljNDE0ZTIzYjU3ZWY1Y2I3NDFjMGQ3ZjZkNzM3MmQyMTI2NzYzIl0sImF6cCI6IjNiOWM0MTRlMjNiNTdlZjVjYjc0MWMwZDdmNmQ3MzcyZDIxMjY3NjMiLCJzdWIiOiJmNGJiNzczMy02ZTRlLTRhNTMtOWE0YS04YzVkMmNlZTA2ZWEiLCJlbWFpbF92ZXJpZmllZCI6dHJ1ZSwiYW1yIjpbImNsb3VkX2RpcmVjdG9yeSJdLCJpYXQiOjE1NTA4Njk0NzQsInRlbmFudCI6IjcxYjM0ODkwLWE5NGYtNGVmMi1hNGI2LWNlMDk0YWE2ODA5MiIsInNjb3BlIjoib3BlbmlkIGFwcGlkX2RlZmF1bHQgYXBwaWRfcmVhZHByb2ZpbGUgYXBwaWRfcmVhZHVzZXJhdHRyIGFwcGlkX3dyaXRldXNlcmF0dHIgYXBwaWRfYXV0aGVudGljYXRlZCJ9.Yg_13wauGdw13jtLNyG0KZqQhHJvRvCZB4aRvsCE7vyLmTS1qb4Yz7UasxvMdNOPvtk74KFVtg-gup2ptbCpJB7sH6QgQAWxp4eNVRbjAPgP-q1gZ-_5P-uxU2Sr5YwiMUin_bnIRImqaoRayqbkRV30BbB9enAt-VIONDAO002d8yOLr5ReWPcFCCfPLnVnIne2gv3-S8grbTHV7AwQ7TYrQbmC9VgAy678qttIg7shGxSKWyNAlybzPl7wN6YlXclilog5yhhDL9gGemDlez_SAQyyDi1dFpoNuv_xQRBfdXaLpmB9bFQ-zCx2xlDWHiPv5AON8stDwEXkwsfBaA"
37 |
38 | public static var ID_TOKEN_INVALID_AUD = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImFwcElkLTcxYjM0ODkwLWE5NGYtNGVmMi1hNGI2LWNlMDk0YWE2ODA5Mi0yMDE4LTA4LTAyVDExOjUzOjM2LjQ5NyIsInZlcnNpb24iOjR9.eyJpc3MiOiJodHRwczovL2V1LWdiLmFwcGlkLnRlc3QuY2xvdWQuaWJtLmNvbS9vYXV0aC92NC83MWIzNDg5MC1hOTRmLTRlZjItYTRiNi1jZTA5NGFhNjgwOTIiLCJhdWQiOlsiM2I5YzQxNGUyM2I1N2VmNWNiNzQxYzBkN2Y2ZDczNzJkMjEyNjc2MyJdLCJleHAiOjE1NTA4NzMwNzQsInRlbmFudCI6IjcxYjM0ODkwLWE5NGYtNGVmMi1hNGI2LWNlMDk0YWE2ODA5MiIsImlhdCI6MTU1MDg2OTQ3NCwiZW1haWwiOiJ0ZXN0dXNlckBpYm0uY29tIiwibmFtZSI6InRlc3R1c2VyIiwic3ViIjoiZjRiYjc3MzMtNmU0ZS00YTUzLTlhNGEtOGM1ZDJjZWUwNmVhIiwiZW1haWxfdmVyaWZpZWQiOnRydWUsInByZWZlcnJlZF91c2VybmFtZSI6InRlc3R1c2VyIiwiaWRlbnRpdGllcyI6W3sicHJvdmlkZXIiOiJjbG91ZF9kaXJlY3RvcnkiLCJpZCI6IjAwYWE2OTE2LTlhYmUtNDUyNy04ZmU5LTk3ZTk4ZjQ4ZWRhNyJ9XSwiYW1yIjpbImNsb3VkX2RpcmVjdG9yeSJdLCJhenAiOiIzYjljNDE0ZTIzYjU3ZWY1Y2I3NDFjMGQ3ZjZkNzM3MmQyMTI2NzYzIn0.uRdv7XhU8EwAWJx1FiAIKj9pDEC8dQWSWUArj84exTBGpoUMSiDDbKWR6yBDUeKrtFlWoHoS0PJOXeJZd4bU3a3o-wsIf5pF4aXHkpfCvzmCOFYNgyYQF-VqrCxzbrAc-L1UmmE7b65Xx_h4LCQirkfkXkrfZcQ9scv8Jk-V5GdUlmFC-1cqgEYY4q6KJoqf_8QX5_WR4_wpBRA8Vjwtk6jvQEZe5e2SNrfz17AfKyX9YIaezPzO7ss3JoRQUKrggXotlr7yjTCPAeQ-23cSYofkTXVTctSPhb7QlVP7_811ltNSFGlcH2djQUPZQpph2edEcw5zV6jwx2lVZgNXaQ"
39 |
40 | public static let invalidAudClientId = "3b9c414e23b57ef5cb741c0d7f6d7372d2126763"
41 | public static let invalidAudtenantId = "71b34890-a94f-4ef2-a4b6-ce094aa68092"
42 |
43 | public static let appAnonAccessToken = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImFwcElkLTcxYjM0ODkwLWE5NGYtNGVmMi1hNGI2LWNlMDk0YWE2ODA5Mi0yMDE4LTA4LTAyVDExOjUzOjM2LjQ5NyIsInZlcnNpb24iOjR9.eyJpc3MiOiJodHRwczovL2V1LWdiLmFwcGlkLnRlc3QuY2xvdWQuaWJtLmNvbS9vYXV0aC92NC83MWIzNDg5MC1hOTRmLTRlZjItYTRiNi1jZTA5NGFhNjgwOTIiLCJleHAiOjE1NTM0NjMzMTEsImF1ZCI6WyJiN2FjMWU1NGM3OWIzNzNkYTMzOWY3NTdlMzEwMzFjNTA4ZmNmMTU5Il0sImF6cCI6ImI3YWMxZTU0Yzc5YjM3M2RhMzM5Zjc1N2UzMTAzMWM1MDhmY2YxNTkiLCJzdWIiOiIzZDZiNDU0Zi03MGNmLTRhYTktOTcxYi1hOTQwMjJkNjE2MTMiLCJhbXIiOlsiYXBwaWRfYW5vbiJdLCJpYXQiOjE1NTA4NzEzMTEsInRlbmFudCI6IjcxYjM0ODkwLWE5NGYtNGVmMi1hNGI2LWNlMDk0YWE2ODA5MiIsInNjb3BlIjoib3BlbmlkIGFwcGlkX2RlZmF1bHQgYXBwaWRfcmVhZHByb2ZpbGUgYXBwaWRfcmVhZHVzZXJhdHRyIGFwcGlkX3dyaXRldXNlcmF0dHIifQ.JiMrascZ8kkcCMGmsB1KL4mX2dRq5VrhXeRCWtxzBR8p-SF70xg1mKqRhU1At9YS0ew66zN7r7IxhTOxHEnvsKD_IJdbWQe9PBzAcdXxz_yHyRnbWU1Vd1GI46x3-_CG3kNuTAJ2LXCwZlUcJDLe-v2V2Xz6Sx7Ckj-WTcj1PIt8Tc3lh7KoHnFgjw3hzg07qORnh1HC5QM672nvH-O5hMTMK9cLO0t6PtMWUs1AiH13budDGdi-TlcZ2qrn2c0KJYjpOuYHb7XuEtFJNmumfRSbrqs_XLQ9U-Qm0XdhKk_pKIFx1WtkFOc1_DKrZ1PmFA_uoo2fqpIT4Awg4hnw2g"
44 |
45 | public static let clientId = "21e8b523-eb42-4a34-a057-04ca9445f6ff"
46 | public static let tenantId = "db8a27c4-b887-4f8d-a89f-f12fb775b311"
47 | public static let region = "https://eu-gb.appid.test.cloud.ibm.com"
48 | public static let subject = "0e874ad1-32be-45b9-a16a-fa28b2f32fcd"
49 |
50 | public static var ID_TOKEN_WITH_SUBJECT = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpPU0UifQ.eyJzdWIiOiIxMjMifQ.enbXHtja8BJd9_hlIbCgwyMXl8o9s74yDlqH4_11h7xLVasDO8Yy4jNyhVmIIb8jpl4fQfjWjqaOJoD2TqgfhqwQ-tGRjzYYR-f0qAMb99pNDtLS9IFf1yHYM2y65UerZ8qTD4g2s-ZWPk7yvxPMQx-Nrvu-X2uUwvdBCBr02rXpsHdMbeLYA6iwUs58p5hMxOxf3yKrBcTpTJ4EE164BhruEU5HyHhqSM9DTVLvliuapFFIK4CGV3FjvrKnT38yWdxSWtd9ETC79bfBwWTsE0ykMzb7Nq3vA2O0C_pv5IUixkLtTCiT3s5m55WZaqxdFCvOe4BjAt6AWH7slwgZdg"
51 |
52 | }
53 |
--------------------------------------------------------------------------------
/IBMCloudAppIDTests/AppIDTests.swift:
--------------------------------------------------------------------------------
1 | /// * * Copyright 2016, 2017 IBM Corp.
2 | // * Licensed under the Apache License, Version 2.0 (the "License");
3 | // * you may not use this file except in compliance with the License.
4 | // * You may obtain a copy of the License at
5 | // * http://www.apache.org/licenses/LICENSE-2.0
6 | // * Unless required by applicable law or agreed to in writing, software
7 | // * distributed under the License is distributed on an "AS IS" BASIS,
8 | // * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9 | // * See the License for the specific language governing permissions and
10 | // * limitations under the License.
11 | // */
12 | //
13 | // import Foundation
14 | //
15 | // import XCTest
16 | // import BMSCore
17 | // @testable import AppID
18 | //
19 | // class AppIDTests: XCTestCase {
20 | // static var appId = AppID.sharedInstance
21 | // static let tenantId = "1"
22 | // static let clientId = "2"
23 | // static var callbackErr = false;
24 | // static var callbackResponse = false;
25 | // var pref = AppIDPreferences()
26 | //
27 | // static let defaultCallBack : BMSCompletionHandler = {(response: Response?, error: Error?) in
28 | //
29 | // }
30 | //
31 | //
32 | // class MockRegistrationManager : RegistrationManager {
33 | // var response:Response? = nil
34 | // var err:Error? = nil
35 | // override func ensureRegistered(registrationDelegate: RegistrationDelegate) {
36 | // if (response != nil) {
37 | // AppIDTests.appId.preferences.clientId.set(AppIDTests.clientId)
38 | // }
39 | // callback(response,err)
40 | // }
41 | // }
42 | // var mockRegistrationManager:MockRegistrationManager?
43 | //
44 | // class MockTokenManager : TokenManager {
45 | // var response:Response? = nil
46 | // var err:Error? = nil
47 | // override func invokeTokenRequest(_ grantCode: String, callback: BMSCompletionHandler?) {
48 | // callback?(response,err)
49 | // }
50 | // }
51 | //
52 | // // var mockTokenManager:MockTokenManager?
53 | //
54 | // override func setUp() {
55 | // mockRegistrationManager = MockRegistrationManager(preferences: pref)
56 | // AppIDTests.appId.initialize(tenantId: AppIDTests.tenantId, region: BMSClient.Region.usSouth)
57 | // AppIDTests.appId.registrationManager = mockRegistrationManager!
58 | // AppIDTests.appId.preferences.clientId.set(nil)
59 | // // appId.tokenManager = mockTokenManager!
60 | // super.setUp();
61 | // }
62 | //
63 | // func testLoginRegisterSuccess() {
64 | // let defaultCallBack:BMSCompletionHandler = {(response: Response?, error: Error?) in
65 | // }
66 | // let params = [
67 | // AppIDConstants.JSON_RESPONSE_TYPE_KEY : AppIDConstants.JSON_CODE_KEY,
68 | // AppIDConstants.client_id_String : AppIDTests.clientId,
69 | // AppIDConstants.JSON_REDIRECT_URI_KEY : AppIDConstants.REDIRECT_URI_VALUE,
70 | // AppIDConstants.JSON_SCOPE_KEY : AppIDConstants.OPEN_ID_VALUE,
71 | // AppIDConstants.JSON_USE_LOGIN_WIDGET : AppIDConstants.TRUE_VALUE,
72 | // ]
73 | //
74 | // let url = AppID.sharedInstance.serverUrl + "/" + AppIDConstants.V3_AUTH_PATH + AppIDTests.tenantId + "/" + AppIDConstants.authorizationEndPoint + Utils.getQueryString(params: params)
75 | //
76 | //
77 | // let response = HTTPURLResponse(url: URL(string : "SOMEurl")!, statusCode: 200, httpVersion: nil, headerFields: nil)
78 | //
79 | // mockRegistrationManager?.response = Response(responseData: nil, httpResponse: response, isRedirect: false)
80 | // mockRegistrationManager?.err = nil
81 | //
82 | // AppIDTests.appId.login(onTokenCompletion: defaultCallBack)
83 | //
84 | //
85 | // var viewUrl = AppIDTests.appId.loginView!.url!.absoluteString
86 | // let firstPart = viewUrl.components(separatedBy: "state=")[0]
87 | // var secondPart = ""
88 | // let comp = viewUrl.components(separatedBy: "state=")[1].components(separatedBy: "&")
89 | // for i in 1...comp.count - 1 {
90 | //
91 | // secondPart += comp[i] + (i == comp.count - 1 ? "" : "&")
92 | // }
93 | // viewUrl = firstPart + secondPart
94 | // XCTAssertEqual(viewUrl , url)
95 | // XCTAssertEqual(AppIDTests.appId.preferences.registrationTenantId.get(), AppIDTests.tenantId)
96 | // XCTAssertNotNil(AppIDTests.appId.tokenRequest)
97 | // XCTAssertNotNil(AppIDTests.appId.loginView?.callback)
98 | // }
99 | //
100 | //
101 | //
102 | //
103 | // func testLoginRegisterFailed() {
104 | //
105 | // //no saved client id tests
106 | // var callbackCalled = 0
107 | // mockRegistrationManager?.response = nil
108 | // mockRegistrationManager?.err = nil
109 | // let expectation1 = expectation(description: "Callback1 called")
110 | // let expectation2 = expectation(description: "Callback2 called")
111 | // let expectation3 = expectation(description: "Callback3 called")
112 | // let testCallBack = {(response: Response?, error: Error?) in
113 | // callbackCalled += 1
114 | // XCTAssertNil(response)
115 | // XCTAssertNotNil(error)
116 | // expectation1.fulfill()
117 | // }
118 | // AppIDTests.appId.login(onTokenCompletion: testCallBack)
119 | // mockRegistrationManager?.response = nil
120 | // mockRegistrationManager?.err = AppIDError.registrationError(msg: "REGISTRATION err")
121 | // let testCallBack2 = {(response: Response?, error: Error?) in
122 | // XCTAssertNil(response)
123 | // XCTAssertNotNil(error)
124 | // expectation2.fulfill()
125 | // }
126 | // AppIDTests.appId.login(onTokenCompletion: testCallBack2)
127 | //
128 | // //saved client different tenant
129 | // pref.clientId.set("1")
130 | // pref.registrationTenantId.set("2")
131 | // let testCallBack3 = {(response: Response?, error: Error?) in
132 | // XCTAssertEqual(self.mockRegistrationManager?.err?.localizedDescription, error?.localizedDescription)
133 | // XCTAssertNil(response)
134 | // expectation3.fulfill()
135 | // }
136 | // AppIDTests.appId.login(onTokenCompletion: testCallBack3)
137 | // waitForExpectations(timeout: 1) { error in
138 | // if let error = error {
139 | // XCTFail("err: \(error)")
140 | // }
141 | // }
142 | // }
143 | //
144 | // class mockLoginView : safariView {
145 | // override func dismiss(animated flag: Bool, completion: (() -> Void)? = nil) {
146 | // completion?()
147 | // }
148 | // }
149 | //
150 | // func testTokens() {
151 | // let accessToken = "thisIsAccessToken"
152 | // let idToken = "thisIsAccessToken"
153 | // // XCTAssertEqual(AppIDTests.appId.accessToken, AppIDTests.appId.preferences.accessToken.get())
154 | // // XCTAssertEqual(AppIDTests.appId.idToken, AppIDTests.appId.preferences.idToken.get())
155 | // // AppIDTests.appId.preferences.accessToken.set(accessToken)
156 | // // AppIDTests.appId.preferences.idToken.set(idToken)
157 | // // XCTAssertEqual(AppIDTests.appId.accessToken, AppIDTests.appId.preferences.accessToken.get())
158 | // // XCTAssertEqual(AppIDTests.appId.idToken, AppIDTests.appId.preferences.idToken.get())
159 | // // XCTAssertEqual(AppIDTests.appId.accessToken, accessToken)
160 | // // XCTAssertEqual(AppIDTests.appId.idToken, idToken)
161 | // }
162 | //
163 | // func testApplication() {
164 | // //happy flow
165 | // let testcode = "testcode"
166 | // AppIDTests.appId.loginView = mockLoginView(url: URL(string : "http://www.a.com")!)
167 | // let expectation1 = expectation(description: "Callback1 called")
168 | // AppIDTests.appId.tokenRequest = { (code: String?, errMsg:String?) -> Void in
169 | // XCTAssertEqual(code!, testcode)
170 | // XCTAssertNil(errMsg)
171 | // expectation1.fulfill()
172 | // }
173 | // XCTAssertTrue(AppIDTests.appId.application(UIApplication.shared, open: URL(string: AppIDConstants.REDIRECT_URI_VALUE + "?code=" + testcode)!, options: [:]))
174 | // waitForExpectations(timeout: 1) { error in
175 | // if let error = error {
176 | // XCTFail("err: \(error)")
177 | // }
178 | // }
179 | // //no grant code
180 | // let expectation2 = expectation(description: "Callback2 called")
181 | // AppIDTests.appId.tokenRequest = { (code: String?, errMsg:String?) -> Void in
182 | // XCTAssertNil(code)
183 | // XCTAssertNotNil(errMsg)
184 | // expectation2.fulfill()
185 | // }
186 | // XCTAssertTrue(AppIDTests.appId.application(UIApplication.shared, open: URL(string: AppIDConstants.REDIRECT_URI_VALUE + "?notgrantcode=" + testcode)!, options: [:]))
187 | //
188 | // waitForExpectations(timeout: 1) { error in
189 | // if let error = error {
190 | // XCTFail("err: \(error)")
191 | // }
192 | // }
193 | // //non happy flow
194 | // XCTAssertFalse(AppIDTests.appId.application(UIApplication.shared, open: URL(string: "someurl" + "?notgrantcode=" + testcode)!, options: [:]))
195 | //
196 | // }
197 | //
198 | //
199 | // }
200 | //
201 |
--------------------------------------------------------------------------------
/IBMCloudAppIDTests/AuthorizationHeaderHelperTests.swift:
--------------------------------------------------------------------------------
1 | /* * Copyright 2016, 2017 IBM Corp.
2 | * Licensed under the Apache License, Version 2.0 (the "License");
3 | * you may not use this file except in compliance with the License.
4 | * You may obtain a copy of the License at
5 | * http://www.apache.org/licenses/LICENSE-2.0
6 | * Unless required by applicable law or agreed to in writing, software
7 | * distributed under the License is distributed on an "AS IS" BASIS,
8 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9 | * See the License for the specific language governing permissions and
10 | * limitations under the License.
11 | */
12 | import Foundation
13 | import XCTest
14 | import BMSCore
15 | @testable import IBMCloudAppID
16 | public class AuthorizationHeaderHelperTests: XCTestCase {
17 |
18 |
19 | func testIsAuthorizationRequired() {
20 | var headers:[String: Any] = [:]
21 | headers["Dummy"] = ["Dummy"]
22 |
23 | // Non-401 status
24 | XCTAssertFalse(AuthorizationHeaderHelper.isAuthorizationRequired(statusCode: 200, header: nil))
25 |
26 | // 401 status, but Www-Authenticate header is null
27 | XCTAssertFalse(AuthorizationHeaderHelper.isAuthorizationRequired(statusCode: 401, header: nil))
28 |
29 | // 401 status, Www-Authenticate header exist, but invalid value
30 | XCTAssertFalse(AuthorizationHeaderHelper.isAuthorizationRequired(statusCode: 401, header: "Dummy"))
31 |
32 | // 401 status, Www-Authenticate header exists, Bearer exists, but not appid scope
33 | XCTAssertFalse(AuthorizationHeaderHelper.isAuthorizationRequired(statusCode: 401, header: "Bearer Dummy"))
34 |
35 | // 401 with bearer and correct scope
36 | XCTAssertTrue(AuthorizationHeaderHelper.isAuthorizationRequired(statusCode: 401, header: "Bearer scope=\"appid_default\""))
37 |
38 | // Check with http response
39 |
40 | let response = HTTPURLResponse(url: URL(string: "ADS")!, statusCode: 401, httpVersion: nil, headerFields: [AppIDConstants.WWW_AUTHENTICATE_HEADER : "Bearer scope=\"appid_default\""])
41 | XCTAssertTrue(AuthorizationHeaderHelper.isAuthorizationRequired(for: Response(responseData: nil, httpResponse: response, isRedirect: false)))
42 | }
43 |
44 | }
45 |
--------------------------------------------------------------------------------
/IBMCloudAppIDTests/AuthorizationUIManagerTests.swift:
--------------------------------------------------------------------------------
1 | /* * Copyright 2016, 2017 IBM Corp.
2 | * Licensed under the Apache License, Version 2.0 (the "License");
3 | * you may not use this file except in compliance with the License.
4 | * You may obtain a copy of the License at
5 | * http://www.apache.org/licenses/LICENSE-2.0
6 | * Unless required by applicable law or agreed to in writing, software
7 | * distributed under the License is distributed on an "AS IS" BASIS,
8 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9 | * See the License for the specific language governing permissions and
10 | * limitations under the License.
11 | */
12 | import Foundation
13 |
14 | import XCTest
15 | import BMSCore
16 | @testable import IBMCloudAppID
17 |
18 | public class AuthorizationUIManagerTests: XCTestCase {
19 |
20 |
21 | class MockTokenManager: TokenManager {
22 | var exp:XCTestExpectation
23 | init(oAuthManager: OAuthManager, exp: XCTestExpectation) {
24 | self.exp = exp
25 | super.init(oAuthManager: oAuthManager)
26 | }
27 |
28 | override func obtainTokensAuthCode(code:String, authorizationDelegate:AuthorizationDelegate) {
29 | self.exp.fulfill()
30 | }
31 |
32 | }
33 |
34 | class MockSafariView: safariView {
35 |
36 | override func dismiss(animated flag: Bool, completion: (() -> Swift.Void)? = nil) {
37 | completion!()
38 | }
39 |
40 | }
41 |
42 | class MockAuthorizationUIManager: AuthorizationUIManager {
43 |
44 | override func getStoredState() -> String {
45 | return "validstate"
46 | }
47 |
48 | }
49 |
50 | let oauthManager = OAuthManager(appId: AppID.sharedInstance)
51 |
52 | class delegate: AuthorizationDelegate {
53 | var exp: XCTestExpectation?
54 | var errMsg: String?
55 | public init(exp: XCTestExpectation?, errMsg:String?) {
56 | self.exp = exp
57 | self.errMsg = errMsg
58 | }
59 |
60 | func onAuthorizationFailure(error: AuthorizationError) {
61 | XCTAssertEqual(error.description, errMsg)
62 | self.exp?.fulfill()
63 | }
64 |
65 | func onAuthorizationCanceled() {
66 | XCTFail()
67 | }
68 |
69 | func onAuthorizationSuccess(accessToken: AccessToken?,
70 | identityToken: IdentityToken?,
71 | refreshToken: RefreshToken?,
72 | response:Response?) {
73 | self.exp?.fulfill()
74 | }
75 |
76 | }
77 |
78 | // happy flow
79 | func testApplicationHappyFlow() {
80 |
81 | let expectation1 = expectation(description: "Obtained tokens")
82 | oauthManager.tokenManager = MockTokenManager(oAuthManager: oauthManager, exp: expectation1)
83 | let manager = MockAuthorizationUIManager(oAuthManager: oauthManager, authorizationDelegate: delegate(exp: nil, errMsg: nil), authorizationUrl: "someurl", redirectUri: "someredirect")
84 | manager.loginView = MockSafariView(url:URL(string: "http://www.someurl.com")!)
85 | // happy flow
86 | XCTAssertTrue(manager.application(UIApplication.shared, open: URL(string:AppIDConstants.REDIRECT_URI_VALUE.lowercased() + "?code=somegrantcode&state=validstate")!, options: [:]))
87 |
88 | waitForExpectations(timeout: 1) { error in
89 | if let error = error {
90 | XCTFail("err: \(error)")
91 | }
92 | }
93 | }
94 |
95 | func testApplicationInvalidState() {
96 |
97 | let expectation1 = expectation(description: "Invalid state")
98 | let manager = AuthorizationUIManager(oAuthManager: oauthManager, authorizationDelegate: delegate(exp: expectation1, errMsg: "Mismatched state parameter"), authorizationUrl: "someurl", redirectUri: "someredirect")
99 | manager.loginView = MockSafariView(url:URL(string: "http://www.someurl.com")!)
100 |
101 | XCTAssertFalse(manager.application(UIApplication.shared, open: URL(string:AppIDConstants.REDIRECT_URI_VALUE.lowercased() + "?code=somegrantcode&state=invalidstate")!, options: [:]))
102 |
103 | waitForExpectations(timeout: 1) { error in
104 | if let error = error {
105 | XCTFail("err: \(error)")
106 | }
107 | }
108 | }
109 |
110 | func testApplicationNoState() {
111 |
112 | let expectation1 = expectation(description: "No state")
113 | let manager = AuthorizationUIManager(oAuthManager: oauthManager, authorizationDelegate: delegate(exp: expectation1, errMsg: "Failed to extract state"), authorizationUrl: "someurl", redirectUri: "someredirect")
114 | manager.loginView = MockSafariView(url:URL(string: "http://www.someurl.com")!)
115 |
116 | XCTAssertFalse(manager.application(UIApplication.shared, open: URL(string:AppIDConstants.REDIRECT_URI_VALUE.lowercased() + "?code=somegrantcode")!, options: [:]))
117 |
118 | waitForExpectations(timeout: 1) { error in
119 | if let error = error {
120 | XCTFail("err: \(error)")
121 | }
122 | }
123 | }
124 |
125 | // no code no err
126 | func testApplicationErr() {
127 |
128 | let expectation1 = expectation(description: "Obtained tokens")
129 | let manager = AuthorizationUIManager(oAuthManager: oauthManager, authorizationDelegate: delegate(exp: expectation1, errMsg: "Failed to extract grant code"), authorizationUrl: "someurl", redirectUri: "someredirect")
130 | manager.loginView = MockSafariView(url:URL(string: "http://www.someurl.com")!)
131 | XCTAssertFalse(manager.application(UIApplication.shared, open: URL(string:AppIDConstants.REDIRECT_URI_VALUE.lowercased() + "?nocode=something")!, options: [:]))
132 | waitForExpectations(timeout: 1) { error in
133 | if let error = error {
134 | XCTFail("err: \(error)")
135 | }
136 | }
137 | }
138 |
139 |
140 | // with err msg
141 | func testApplicationErr2() {
142 |
143 | let expectation1 = expectation(description: "Obtained tokens")
144 | let manager = AuthorizationUIManager(oAuthManager: oauthManager, authorizationDelegate: delegate(exp: expectation1, errMsg: "someerr"), authorizationUrl: "someurl", redirectUri: "someredirect")
145 | manager.loginView = MockSafariView(url:URL(string: "http://www.someurl.com")!)
146 | XCTAssertFalse(manager.application(UIApplication.shared, open: URL(string:AppIDConstants.REDIRECT_URI_VALUE.lowercased() + "?code=somecode&error=someerr")!, options: [:]))
147 |
148 | waitForExpectations(timeout: 1) { error in
149 | if let error = error {
150 | XCTFail("err: \(error)")
151 | }
152 | }
153 |
154 | }
155 |
156 | func testApplicationErr3() {
157 | class MockRegistrationManager:RegistrationManager {
158 | static var expectation:XCTestExpectation?
159 |
160 | public override func clearRegistrationData() {
161 | MockRegistrationManager.expectation?.fulfill()
162 | }
163 |
164 | }
165 |
166 | class MockAuthorizationManager:IBMCloudAppID.AuthorizationManager {
167 | static var expectation:XCTestExpectation?
168 |
169 | public override func launchAuthorizationUI(accessTokenString: String?, authorizationDelegate: AuthorizationDelegate) {
170 | XCTAssertNil(accessTokenString)
171 | MockAuthorizationManager.expectation?.fulfill()
172 | }
173 |
174 | }
175 | let expectation1 = expectation(description: "clear data")
176 | let expectation2 = expectation(description: "invoke registration")
177 | oauthManager.registrationManager = MockRegistrationManager(oauthManager:oauthManager)
178 | MockRegistrationManager.expectation = expectation1
179 | oauthManager.authorizationManager = MockAuthorizationManager(oAuthManager: oauthManager)
180 | MockAuthorizationManager.expectation = expectation2
181 |
182 | let manager = AuthorizationUIManager(oAuthManager: oauthManager, authorizationDelegate: delegate(exp: expectation1, errMsg: "Failed to obtain access and identity tokens"), authorizationUrl: "someurl", redirectUri: "someredirect")
183 | manager.loginView = MockSafariView(url:URL(string: "http://www.someurl.com")!)
184 | XCTAssertFalse(manager.application(UIApplication.shared, open: URL(string:AppIDConstants.REDIRECT_URI_VALUE.lowercased() + "?code=somecode&error=invalid_client")!, options: [:]))
185 |
186 | waitForExpectations(timeout: 1) { error in
187 | if let error = error {
188 | XCTFail("err: \(error)")
189 | }
190 | }
191 | }
192 |
193 | // happy flow - sign_up done flow
194 | func testApplicationDoneFlowSignUp() {
195 |
196 | let expectation1 = expectation(description: "Found Flow")
197 | let manager = AuthorizationUIManager(oAuthManager: oauthManager, authorizationDelegate: delegate(exp: expectation1, errMsg: nil), authorizationUrl: "someurl", redirectUri: "someredirect")
198 | manager.loginView = MockSafariView(url:URL(string: "http://www.someurl.com")!)
199 | // happy flow with sign_up done
200 | XCTAssertTrue(manager.application(UIApplication.shared, open: URL(string:AppIDConstants.REDIRECT_URI_VALUE.lowercased() + "?flow=forgot_password")!, options: [:]))
201 | waitForExpectations(timeout: 1) { error in
202 | if let error = error {
203 | XCTFail("err: \(error)")
204 | }
205 | }
206 |
207 | }
208 |
209 | // happy flow - forgot_password done flow
210 | func testApplicationDoneFlowForgotPassword() {
211 |
212 | let expectation1 = expectation(description: "Found Flow")
213 | let manager = AuthorizationUIManager(oAuthManager: oauthManager, authorizationDelegate: delegate(exp: expectation1, errMsg: nil), authorizationUrl: "someurl", redirectUri: "someredirect")
214 | manager.loginView = MockSafariView(url:URL(string: "http://www.someurl.com")!)
215 | // happy flow with forgot_password done
216 | XCTAssertTrue(manager.application(UIApplication.shared, open: URL(string:AppIDConstants.REDIRECT_URI_VALUE.lowercased() + "?flow=forgot_password")!, options: [:]))
217 | waitForExpectations(timeout: 1) { error in
218 | if let error = error {
219 | XCTFail("err: \(error)")
220 | }
221 | }
222 |
223 | }
224 |
225 |
226 |
227 | }
228 |
--------------------------------------------------------------------------------
/IBMCloudAppIDTests/ConfigTests.swift:
--------------------------------------------------------------------------------
1 | /* * Copyright 2016, 2017 IBM Corp.
2 | * Licensed under the Apache License, Version 2.0 (the "License");
3 | * you may not use this file except in compliance with the License.
4 | * You may obtain a copy of the License at
5 | * http://www.apache.org/licenses/LICENSE-2.0
6 | * Unless required by applicable law or agreed to in writing, software
7 | * distributed under the License is distributed on an "AS IS" BASIS,
8 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9 | * See the License for the specific language governing permissions and
10 | * limitations under the License.
11 | */
12 | import Foundation
13 |
14 | import XCTest
15 | import BMSCore
16 | @testable import IBMCloudAppID
17 |
18 | internal var newRegion = "https://us-south.appid.cloud.ibm.com" //full url with https
19 | internal var oldRegion = ".ng.bluemix.net"
20 | internal var customRegion = ".custom"
21 |
22 |
23 |
24 | public class ConfigTests: XCTestCase {
25 |
26 |
27 | func testConfig() {
28 | AppID.sharedInstance = AppID()
29 |
30 | // no region and tenant
31 | let appid = AppID.sharedInstance
32 | XCTAssertEqual("https://appid-oauth", Config.getServerUrl(appId: appid))
33 | XCTAssertEqual("https://appid-oauth", Config.getIssuer(appId: appid))
34 |
35 | // with region and tenant
36 | appid.initialize(tenantId: "sometenant", region: newRegion)
37 | XCTAssertEqual(newRegion + "/oauth/v4/sometenant", Config.getServerUrl(appId: appid))
38 |
39 | XCTAssertEqual(newRegion + "/oauth/v4/sometenant/publickeys", Config.getPublicKeyEndpoint(appId: appid))
40 | XCTAssertEqual(newRegion + "/api/v1/", Config.getAttributesUrl(appId: appid))
41 | XCTAssertEqual(newRegion + "/oauth/v4/sometenant", Config.getIssuer(appId: appid))
42 |
43 | // with OLD .region and tenant
44 | appid.initialize(tenantId: "sometenant", region: oldRegion)
45 | XCTAssertEqual(newRegion + "/oauth/v4/sometenant", Config.getServerUrl(appId: appid))
46 | XCTAssertEqual(newRegion + "/oauth/v4/sometenant/publickeys", Config.getPublicKeyEndpoint(appId: appid))
47 | XCTAssertEqual(newRegion + "/api/v1/", Config.getAttributesUrl(appId: appid))
48 | XCTAssertEqual(newRegion + "/oauth/v4/sometenant", Config.getIssuer(appId: appid))
49 |
50 | //with custom region
51 | appid.initialize(tenantId: "sometenant", region: customRegion)
52 | XCTAssertEqual("https://appid-oauth", Config.getServerUrl(appId: appid))
53 | XCTAssertEqual("https://appid-oauth/publickeys", Config.getPublicKeyEndpoint(appId: appid))
54 | XCTAssertEqual("https://appid-profiles", Config.getAttributesUrl(appId: appid))
55 | XCTAssertEqual("https://appid-oauth", Config.getIssuer(appId: appid))
56 |
57 | // with overrideserverhost
58 | AppID.overrideServerHost = "somehost"
59 | appid.initialize(tenantId: "sometenant", region: newRegion)
60 | XCTAssertEqual("somehost/sometenant", Config.getServerUrl(appId: appid))
61 | XCTAssertEqual("somehost/sometenant", Config.getIssuer(appId: appid))
62 |
63 | }
64 |
65 |
66 |
67 | }
68 |
--------------------------------------------------------------------------------
/IBMCloudAppIDTests/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleDisplayName
8 | IBMCloudAppID
9 | CFBundleExecutable
10 | $(EXECUTABLE_NAME)
11 | CFBundleIdentifier
12 | $(PRODUCT_BUNDLE_IDENTIFIER)
13 | CFBundleInfoDictionaryVersion
14 | 6.0
15 | CFBundleName
16 | $(PRODUCT_NAME)
17 | CFBundlePackageType
18 | BNDL
19 | CFBundleShortVersionString
20 | 1.0
21 | CFBundleVersion
22 | 1
23 |
24 |
25 |
--------------------------------------------------------------------------------
/IBMCloudAppIDTests/PreferencesTests.swift:
--------------------------------------------------------------------------------
1 | /* * Copyright 2016, 2017 IBM Corp.
2 | * Licensed under the Apache License, Version 2.0 (the "License");
3 | * you may not use this file except in compliance with the License.
4 | * You may obtain a copy of the License at
5 | * http://www.apache.org/licenses/LICENSE-2.0
6 | * Unless required by applicable law or agreed to in writing, software
7 | * distributed under the License is distributed on an "AS IS" BASIS,
8 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9 | * See the License for the specific language governing permissions and
10 | * limitations under the License.
11 | */
12 |
13 | import XCTest
14 |
15 | import BMSCore
16 | @testable import IBMCloudAppID
17 | class PreferencesTests: XCTestCase {
18 |
19 | override func setUp() {
20 | }
21 |
22 | func testStringPreference() {
23 | let manager = PreferenceManager()
24 | let s = manager.getStringPreference(name: "testPref")
25 | s.clear()
26 | XCTAssertNil(s.get())
27 | s.set("testValue")
28 | XCTAssertEqual(s.get(), "testValue")
29 | s.clear()
30 | XCTAssertNil(s.get())
31 |
32 | }
33 |
34 | func testJSONPreference() {
35 | let manager = PreferenceManager()
36 | let s = manager.getJSONPreference(name: "testJSONPref")
37 | s.clear()
38 | XCTAssertNil(s.get())
39 | XCTAssertNil(s.getAsJSON())
40 | s.set("testValue")
41 | XCTAssertEqual(s.get(), "testValue")
42 | XCTAssertNil(s.getAsJSON())
43 | s.set("{\"key1\":\"val1\"}")
44 | XCTAssertEqual(s.get(), "{\"key1\":\"val1\"}")
45 | var json:[String:Any]? = s.getAsJSON()
46 | XCTAssertEqual(json?["key1"] as? String, "val1")
47 | XCTAssertEqual(json?.count, 1)
48 | s.set(["key3" : "val3"] as [String:Any])
49 | XCTAssertEqual(s.getAsJSON()?["key3"] as? String, "val3")
50 |
51 | }
52 |
53 | }
54 |
--------------------------------------------------------------------------------
/IBMCloudAppIDTests/SecurityUtilsTests.swift:
--------------------------------------------------------------------------------
1 | /* * Copyright 2016, 2017 IBM Corp.
2 | * Licensed under the Apache License, Version 2.0 (the "License");
3 | * you may not use this file except in compliance with the License.
4 | * You may obtain a copy of the License at
5 | * http://www.apache.org/licenses/LICENSE-2.0
6 | * Unless required by applicable law or agreed to in writing, software
7 | * distributed under the License is distributed on an "AS IS" BASIS,
8 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9 | * See the License for the specific language governing permissions and
10 | * limitations under the License.
11 | */
12 |
13 | import Foundation
14 | import XCTest
15 | import BMSCore
16 | @testable import IBMCloudAppID
17 | class SecurityUtilsTest: XCTestCase {
18 |
19 | var itemLabel = "itemLabel"
20 | var itemData = "testItemString"
21 | var keySize = 512
22 | var publicKeyTag = AppIDConstants.publicKeyIdentifier
23 | var privateKeyTag = AppIDConstants.privateKeyIdentifier
24 |
25 | override func setUp() {
26 | super.setUp()
27 | TestHelpers.clearDictValuesFromKeyChain([publicKeyTag : kSecClassKey, privateKeyTag : kSecClassKey])
28 | TestHelpers.savePublicKeyDataToKeyChain(AppIDTestConstants.publicKeyData, tag: publicKeyTag)
29 | TestHelpers.savePrivateKeyDataToKeyChain(AppIDTestConstants.privateKeyData, tag: privateKeyTag)
30 | }
31 |
32 | func testSecAttrAccessible() {
33 | AppID.secAttrAccess = .accessibleAlways
34 | XCTAssertEqual(AppID.secAttrAccess.rawValue, kSecAttrAccessibleAlways)
35 | }
36 |
37 | func testGenerateKeyPairAttrsPrivate() {
38 | let keyPair = SecurityUtils.generateKeyPairAttrs(keySize, publicTag: publicKeyTag, privateTag: privateKeyTag)
39 | let privateAttrs = keyPair["private"] as! [NSString: AnyObject] // tailor:disable
40 | let accessibility = privateAttrs[kSecAttrAccessible]
41 | XCTAssertEqual(accessibility as! CFString, AppID.secAttrAccess.rawValue) // tailor:disable
42 | }
43 |
44 | func testGenerateKeyPairAttrsPublic() {
45 | let keyPair = SecurityUtils.generateKeyPairAttrs(keySize, publicTag: publicKeyTag, privateTag: privateKeyTag)
46 | let publicAttrs = keyPair["public"] as! [NSString: AnyObject] // tailor:disable
47 | let accessibility = publicAttrs[kSecAttrAccessible]
48 | XCTAssertEqual(accessibility as! CFString, AppID.secAttrAccess.rawValue) // tailor:disable
49 | }
50 |
51 | func testGenerateKeyPairAttrs() {
52 | let keyPair = SecurityUtils.generateKeyPairAttrs(keySize, publicTag: publicKeyTag, privateTag: privateKeyTag)
53 | XCTAssertEqual(keyPair[kSecAttrAccessible] as! CFString, AppID.secAttrAccess.rawValue) // tailor:disable
54 | }
55 |
56 | func testKeyPairGeneration() {
57 | TestHelpers.clearDictValuesFromKeyChain([publicKeyTag : kSecClassKey, privateKeyTag : kSecClassKey])
58 | XCTAssertNotNil(try? SecurityUtils.generateKeyPair(keySize, publicTag: publicKeyTag, privateTag: privateKeyTag))
59 | }
60 |
61 | func testSaveItemToKeyChain() {
62 | _ = SecurityUtils.saveItemToKeyChain(itemData, label: itemLabel)
63 | XCTAssertEqual(SecurityUtils.getItemFromKeyChain(itemLabel), itemData)
64 | _ = SecurityUtils.removeItemFromKeyChain(itemLabel)
65 | XCTAssertNil(SecurityUtils.getItemFromKeyChain(itemLabel))
66 | }
67 |
68 |
69 | func testGetJwksHeader() {
70 | // happy flow
71 | var jwks:[String:Any]? = try? SecurityUtils.getJWKSHeader()
72 | XCTAssertNotNil(jwks)
73 | XCTAssertEqual(jwks?["e"] as? String, "AQAB")
74 | XCTAssertEqual(jwks?["kty"] as? String, "RSA")
75 | XCTAssertEqual(jwks?["n"] as? String, "AOH-nACU3cCopAz6_SzJuDtUyN4nHhnk9yfF9DFiGPctXPbwMXofZvd9WcYQqtw-w3WV_yhui9PrOVfVBhk6CmM=")
76 |
77 | // no public key
78 | TestHelpers.clearDictValuesFromKeyChain([AppIDConstants.publicKeyIdentifier : kSecClassKey])
79 | do {
80 | jwks = try SecurityUtils.getJWKSHeader()
81 | XCTFail()
82 | } catch let e {
83 | XCTAssertEqual((e as? AppIDError)?.description, "General Error")
84 | }
85 | }
86 |
87 | func testSignString() {
88 |
89 | // happy flow
90 | let signature = try? SecurityUtils.signString("somepayload", keyIds: (publicKeyTag, privateKeyTag), keySize: keySize)
91 | XCTAssertEqual(signature, "ODT3jvWINoDIYrdMPMB-n548VKXnVT7wAg378q3vV4b20gkZq66DOPrkM9JmyOsVcrKO7FWCa0VaLu418rkC3w==")
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/IBMCloudAppIDTests/TestHelpers.swift:
--------------------------------------------------------------------------------
1 | /* * Copyright 2016, 2017 IBM Corp.
2 | * Licensed under the Apache License, Version 2.0 (the "License");
3 | * you may not use this file except in compliance with the License.
4 | * You may obtain a copy of the License at
5 | * http://www.apache.org/licenses/LICENSE-2.0
6 | * Unless required by applicable law or agreed to in writing, software
7 | * distributed under the License is distributed on an "AS IS" BASIS,
8 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9 | * See the License for the specific language governing permissions and
10 | * limitations under the License.
11 | */
12 | import Foundation
13 | import BMSCore
14 | import XCTest
15 |
16 | @testable import IBMCloudAppID
17 |
18 | public class TestHelpers {
19 |
20 | public static func savePublicKeyDataToKeyChain(_ key:Data,tag:String) {
21 | let publicKeyAttr : [NSString:AnyObject] = [
22 | kSecValueData: key as AnyObject,
23 | kSecClass : kSecClassKey,
24 | kSecAttrApplicationTag: tag as AnyObject,
25 | kSecAttrKeyType : kSecAttrKeyTypeRSA,
26 | kSecAttrKeyClass : kSecAttrKeyClassPublic
27 |
28 | ]
29 | SecItemAdd(publicKeyAttr as CFDictionary, nil)
30 | }
31 |
32 | public static func savePrivateKeyDataToKeyChain(_ key:Data,tag:String) {
33 | let privateKeyAttr : [NSString:AnyObject] = [
34 | kSecValueData: key as AnyObject,
35 | kSecClass : kSecClassKey,
36 | kSecAttrApplicationTag: tag as AnyObject,
37 | kSecAttrKeyType : kSecAttrKeyTypeRSA,
38 | kSecAttrKeyClass : kSecAttrKeyClassPrivate
39 |
40 | ]
41 | SecItemAdd(privateKeyAttr as CFDictionary, nil)
42 | }
43 |
44 | public static func clearDictValuesFromKeyChain(_ dict : [String : NSString]) {
45 | for (tag, kSecClassName) in dict {
46 | if kSecClassName == kSecClassKey {
47 | _ = SecurityUtils.deleteKeyFromKeyChain(tag)
48 | } else if kSecClassName == kSecClassGenericPassword {
49 | _ = SecurityUtils.removeItemFromKeyChain(tag)
50 | }
51 | }
52 | }
53 |
54 | public static func validateFormData(expected: String, found: String) -> Void {
55 | let expParamsSet = Set(expected.split(separator: "&").map { String($0) })
56 | let fndParamsSet = Set(found.split(separator: "&").map { String($0) })
57 | XCTAssertFalse(expParamsSet.isDisjoint(with: fndParamsSet))
58 | }
59 |
60 | public class MockTokenManager: TokenManager {
61 | var shouldCallObtainWithRefresh = false
62 | var obtainWithRefreshShouldFail = false
63 | var obtainWithRefreshCalled = false
64 |
65 | override public func obtainTokensRefreshToken(refreshTokenString: String, tokenResponseDelegate: TokenResponseDelegate) {
66 | obtainWithRefreshCalled = true
67 | if !shouldCallObtainWithRefresh {
68 | XCTFail("Should not have called obtainTokensRefreshToken")
69 | } else {
70 | if obtainWithRefreshShouldFail {
71 | tokenResponseDelegate.onAuthorizationFailure(error: AuthorizationError.authorizationFailure("Failed to refresh token"))
72 | } else {
73 | tokenResponseDelegate.onAuthorizationSuccess(accessToken: nil, identityToken: nil, refreshToken: nil, response: Response(responseData: nil, httpResponse: nil, isRedirect: false))
74 | }
75 | }
76 | }
77 |
78 | func verify() {
79 | if shouldCallObtainWithRefresh && !obtainWithRefreshCalled {
80 | XCTFail("Should have called obtainTokensRefreshToken but it wasn't called")
81 | }
82 | }
83 |
84 | }
85 |
86 |
87 | }
88 |
--------------------------------------------------------------------------------
/IBMCloudAppIDTests/TokenTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AbstractTokenTests.swift
3 | // AppID
4 | //
5 | // Created by Oded Betzalel on 13/02/2017.
6 | // Copyright © 2017 Oded Betzalel. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | import XCTest
12 | import BMSCore
13 | @testable import IBMCloudAppID
14 | class TokenTests: XCTestCase {
15 |
16 |
17 |
18 | func testValidAccessToken() {
19 | let token = AccessTokenImpl(with: AppIDTestConstants.ACCESS_TOKEN)
20 | XCTAssertNotNil(token)
21 | XCTAssertEqual(token?.scope, "openid appid_default appid_readprofile appid_readuserattr appid_writeuserattr appid_authenticated")
22 | XCTAssertEqual(token?.raw, AppIDTestConstants.ACCESS_TOKEN)
23 | XCTAssertNotNil(token?.header)
24 | XCTAssertNotNil(token?.payload)
25 | XCTAssertNotNil(token?.signature)
26 | XCTAssertEqual(token?.issuer, AppIDTestConstants.region + "/oauth/v4/" + AppIDTestConstants.tenantId)
27 |
28 | XCTAssertEqual(token?.subject, AppIDTestConstants.subject)
29 | XCTAssertEqual(token?.audience, [AppIDTestConstants.clientId])
30 | XCTAssertTrue(token?.issuedAt == Date(timeIntervalSince1970: 1552502422 as Double))
31 | XCTAssertEqual(token?.tenant, AppIDTestConstants.tenantId)
32 | XCTAssertEqual(token?.authenticationMethods?[0], "google")
33 | XCTAssertTrue(token!.isExpired)
34 | XCTAssertFalse(token!.isAnonymous)
35 | XCTAssertTrue(token?.expiration == Date(timeIntervalSince1970: 1552502424 as Double))
36 |
37 | }
38 |
39 |
40 | func testValidIdToken() {
41 | let token = IdentityTokenImpl(with: AppIDTestConstants.ID_TOKEN)
42 |
43 | XCTAssertEqual(token?.email, "donlonqwerty@gmail.com")
44 | XCTAssertNil(token?.gender)
45 | XCTAssertEqual(token?.locale, "en")
46 | XCTAssertEqual(token?.name, "Lon Don")
47 | XCTAssertEqual(token?.raw, AppIDTestConstants.ID_TOKEN)
48 | XCTAssertNotNil(token?.header)
49 | XCTAssertNotNil(token?.payload)
50 | XCTAssertNotNil(token?.signature)
51 | XCTAssertEqual(token?.issuer, AppIDTestConstants.region + "/oauth/v4/" + AppIDTestConstants.tenantId)
52 |
53 | XCTAssertEqual(token?.subject, AppIDTestConstants.subject)
54 | XCTAssertEqual(token?.audience, [AppIDTestConstants.clientId])
55 | XCTAssertTrue(token?.issuedAt == Date(timeIntervalSince1970: 1552502422 as Double))
56 | XCTAssertEqual(token?.tenant, AppIDTestConstants.tenantId)
57 | XCTAssertEqual(token?.authenticationMethods?[0], "google")
58 | XCTAssertTrue(token!.isExpired)
59 | XCTAssertTrue(token?.expiration == Date(timeIntervalSince1970: 1552502424 as Double))
60 |
61 | }
62 |
63 | }
64 |
--------------------------------------------------------------------------------
/IBMCloudAppIDTests/UtilsTests.swift:
--------------------------------------------------------------------------------
1 | /* * Copyright 2016, 2017 IBM Corp.
2 | * Licensed under the Apache License, Version 2.0 (the "License");
3 | * you may not use this file except in compliance with the License.
4 | * You may obtain a copy of the License at
5 | * http://www.apache.org/licenses/LICENSE-2.0
6 | * Unless required by applicable law or agreed to in writing, software
7 | * distributed under the License is distributed on an "AS IS" BASIS,
8 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9 | * See the License for the specific language governing permissions and
10 | * limitations under the License.
11 | */
12 |
13 | import Foundation
14 | import XCTest
15 | import BMSCore
16 | @testable import IBMCloudAppID
17 | class UtilsTest: XCTestCase {
18 |
19 |
20 | func testJSONStringify() {
21 |
22 | let dict:[String:Any] = ["first":true,"second":3, "third" : ["item1","item2",["item3","item4"],"item5"]]
23 | let json = try? Utils.JSONStringify(dict as AnyObject)
24 |
25 | let jsonStringOption1 = "{\"first\":true,\"second\":3,\"third\":[\"item1\",\"item2\",[\"item3\",\"item4\"],\"item5\"]}"
26 | let jsonStringOption2 = "{\"first\":true,\"third\":[\"item1\",\"item2\",[\"item3\",\"item4\"],\"item5\"],\"second\":3}"
27 | let jsonStringOption3 = "{\"third\":[\"item1\",\"item2\",[\"item3\",\"item4\"],\"item5\"],\"first\":true,\"second\":3}"
28 | let jsonStringOption4 = "{\"second\":3,\"third\":[\"item1\",\"item2\",[\"item3\",\"item4\"],\"item5\"],\"first\":true}"
29 | let jsonStringOption5 = "{\"second\":3,\"first\":true,\"third\":[\"item1\",\"item2\",[\"item3\",\"item4\"],\"item5\"]}"
30 | let jsonStringOption6 = "{\"third\":[\"item1\",\"item2\",[\"item3\",\"item4\"],\"item5\"],\"second\":3,\"first\":true}"
31 | let cond = jsonStringOption1 == json || jsonStringOption2 == json || jsonStringOption3 == json || jsonStringOption4 == json || jsonStringOption5 == json || jsonStringOption6 == json
32 | XCTAssertTrue(cond)
33 | }
34 |
35 | func testParseJsonStringtoDictionary() {
36 | let jsonString = "{\"first\":true,\"second\":3,\"third\":[\"item1\",\"item2\",[\"item3\",\"item4\"],\"item5\"]}"
37 |
38 | // var json = try! JSONSerialization.jsonObject(with: jsonString.data(using: String.Encoding.utf8)!, options: JSONSerialization.ReadingOptions()) as! [AnyObject]
39 | let returnedDict:[String:Any]? = try? Utils.parseJsonStringtoDictionary(jsonString)
40 | XCTAssertNotNil(returnedDict)
41 | XCTAssertEqual(returnedDict!["first"] as? Bool, true)
42 | XCTAssertEqual(returnedDict!["second"] as? Int, 3)
43 |
44 | XCTAssertEqual((returnedDict!["third"] as? [AnyObject])?[0] as? String, "item1")
45 | XCTAssertEqual((returnedDict!["third"] as? [AnyObject])?[1] as? String, "item2")
46 | XCTAssertEqual(((returnedDict!["third"] as? [AnyObject])?[2] as? [String])!, ["item3","item4"])
47 | XCTAssertEqual((returnedDict!["third"] as? [AnyObject])?[3] as? String, "item5")
48 |
49 |
50 | }
51 |
52 | private func stringToBase64Data(_ str:String) -> Data {
53 | let utf8str = str.data(using: String.Encoding.utf8)
54 | let base64EncodedStr = utf8str?.base64EncodedString(options: NSData.Base64EncodingOptions(rawValue: 0))
55 | return Data(base64Encoded: base64EncodedStr!, options: NSData.Base64DecodingOptions(rawValue: 0))!
56 | }
57 |
58 | func testGetApplicationDetails() {
59 | let appInfo = Utils.getApplicationDetails()
60 | XCTAssertNotNil(appInfo.name)
61 | XCTAssertNotNil(appInfo.version)
62 | }
63 |
64 | // func testGetDeviceDictionary() {
65 | // let deviceIdentity = AppIDDeviceIdentity()
66 | // let appIdentity = AppIDAppIdentity()
67 | // var dictionary = Utils.getDeviceDictionary()
68 | // XCTAssertEqual(dictionary[AppIDConstants.JSON_DEVICE_ID_KEY] as? String, deviceIdentity.ID)
69 | // XCTAssertEqual(dictionary[AppIDConstants.JSON_MODEL_KEY] as? String, deviceIdentity.model)
70 | // XCTAssertEqual(dictionary[AppIDConstants.JSON_OS_KEY] as? String, deviceIdentity.OS)
71 | // XCTAssertEqual(dictionary[AppIDConstants.JSON_APPLICATION_ID_KEY] as? String, appIdentity.ID)
72 | // XCTAssertEqual(dictionary[AppIDConstants.JSON_APPLICATION_VERSION_KEY] as? String, appIdentity.version)
73 | // XCTAssertEqual(dictionary[AppIDConstants.JSON_ENVIRONMENT_KEY] as? String, AppIDConstants.JSON_IOS_ENVIRONMENT_VALUE)
74 | // }
75 | func testDecodeBase64WithString() {
76 | let str = "VGhpcyBpcyBhIFV0aWxzIHVuaXRUZXN0IHR+c/Q="
77 | let strSafe = "VGhpcyBpcyBhIFV0aWxzIHVuaXRUZXN0IHR-c_Q="
78 | guard let data = Utils.decodeBase64WithString(str, isSafeUrl: false) else {
79 | XCTFail("failed to decode a base64 string")
80 | return
81 | }
82 | XCTAssertEqual(Utils.base64StringFromData(data, isSafeUrl: false),str)
83 | XCTAssertEqual(Utils.base64StringFromData(data, isSafeUrl: true),strSafe)
84 | }
85 |
86 | }
87 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "{}"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright {yyyy} {name of copyright owner}
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
203 |
--------------------------------------------------------------------------------
/Podfile:
--------------------------------------------------------------------------------
1 | use_frameworks!
2 |
3 | def shared_pods
4 | platform :ios, '10.0'
5 | pod 'BMSCore', '~> 2.4.0'
6 | pod 'JOSESwift', '~> 1.8.0'
7 | end
8 |
9 | target 'IBMCloudAppID' do
10 | shared_pods
11 | end
12 |
13 | target 'IBMCloudAppIDTests' do
14 | shared_pods
15 | end
16 |
--------------------------------------------------------------------------------
/Source/IBMCloudAppID/api/AppID.swift:
--------------------------------------------------------------------------------
1 | /* * Copyright 2016, 2017 IBM Corp.
2 | * Licensed under the Apache License, Version 2.0 (the "License");
3 | * you may not use this file except in compliance with the License.
4 | * You may obtain a copy of the License at
5 | * http://www.apache.org/licenses/LICENSE-2.0
6 | * Unless required by applicable law or agreed to in writing, software
7 | * distributed under the License is distributed on an "AS IS" BASIS,
8 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9 | * See the License for the specific language governing permissions and
10 | * limitations under the License.
11 | */
12 |
13 | import Foundation
14 | import SafariServices
15 | import BMSCore
16 |
17 | public class AppID {
18 |
19 | private(set) var tenantId: String?
20 | private(set) var region: String?
21 | private(set) var oauthManager: OAuthManager?
22 | public var loginWidget: LoginWidgetImpl?
23 | public var userProfileManager: UserProfileManagerImpl?
24 |
25 | public static var overrideServerHost: String?
26 | public static var overrideAttributesHost: String?
27 | public static var secAttrAccess: SecAttrAccessible = .accessibleAfterFirstUnlock
28 | public static var sharedInstance = AppID()
29 | internal static let logger = Logger.logger(name: AppIDConstants.AppIDLoggerName)
30 |
31 | static public let REGION_US_SOUTH = "https://us-south.appid.cloud.ibm.com"
32 | static public let REGION_US_SOUTH_STAGE1 = "https://us-south.appid.test.cloud.ibm.com"
33 | static public let REGION_US_EAST = "https://us-east.appid.cloud.ibm.com"
34 | static public let REGION_UK = "https://eu-gb.appid.cloud.ibm.com"
35 | static public let REGION_UK_STAGE1 = "https://eu-gb.appid.test.cloud.ibm.com"
36 | static public let REGION_SYDNEY = "https://au-syd.appid.cloud.ibm.com"
37 | static public let REGION_GERMANY = "https://eu-de.appid.cloud.ibm.com"
38 | static public let REGION_TOKYO = "https://jp-tok.appid.cloud.ibm.com"
39 |
40 | public init() {}
41 |
42 | /**
43 | Intializes the App ID instance
44 | @param tenantId The tenant Id.
45 | @param region The IBM Cloud region.
46 | */
47 | public func initialize(tenantId: String, region: String) {
48 | self.tenantId = tenantId
49 | self.region = region
50 | self.oauthManager = OAuthManager(appId: self)
51 | self.loginWidget = LoginWidgetImpl(oauthManager: self.oauthManager!)
52 | self.userProfileManager = UserProfileManagerImpl(appId: self)
53 | }
54 |
55 | public func setPreferredLocale(_ locale: Locale) {
56 | self.oauthManager?.setPreferredLocale(locale)
57 | }
58 |
59 | public func signinAnonymously(accessTokenString:String? = nil, allowCreateNewAnonymousUsers: Bool = true, authorizationDelegate:AuthorizationDelegate) {
60 | oauthManager?.authorizationManager?.loginAnonymously(accessTokenString: accessTokenString, allowCreateNewAnonymousUsers: allowCreateNewAnonymousUsers, authorizationDelegate: authorizationDelegate)
61 | }
62 |
63 | public func signinWithResourceOwnerPassword(_ accessTokenString:String? = nil, username: String, password: String, tokenResponseDelegate:TokenResponseDelegate) {
64 | oauthManager?.authorizationManager?.signinWithResourceOwnerPassword(accessTokenString: accessTokenString, username: username, password: password, tokenResponseDelegate: tokenResponseDelegate)
65 | }
66 |
67 | /**
68 | Obtain new access and identity tokens using a refresh token.
69 |
70 | Note that the identity itself (user name/details) will not be refreshed by this operation,
71 | it will remain the same identity but in a new token (new expiration time)
72 | */
73 | public func signinWithRefreshToken(refreshTokenString:String? = nil, tokenResponseDelegate:TokenResponseDelegate) {
74 | oauthManager?.authorizationManager?.signinWithRefreshToken(
75 | refreshTokenString: refreshTokenString,
76 | tokenResponseDelegate: tokenResponseDelegate)
77 | }
78 |
79 | @available(swift, deprecated: 3.0, renamed: "signinAnonymously")
80 | public func loginAnonymously(accessTokenString:String? = nil, allowCreateNewAnonymousUsers: Bool = true, authorizationDelegate:AuthorizationDelegate) {
81 | self.signinAnonymously(accessTokenString: accessTokenString, allowCreateNewAnonymousUsers: allowCreateNewAnonymousUsers, authorizationDelegate: authorizationDelegate)
82 | }
83 |
84 | @available(swift, deprecated: 3.0, renamed: "signinWithResourceOwnerPassword")
85 | public func obtainTokensWithROP(_ accessTokenString:String? = nil, username: String, password: String, tokenResponseDelegate:TokenResponseDelegate) {
86 | self.signinWithResourceOwnerPassword(accessTokenString, username: username,
87 | password: password, tokenResponseDelegate: tokenResponseDelegate)
88 | }
89 |
90 | public func application(_ application: UIApplication, open url: URL, options :[UIApplicationOpenURLOptionsKey: Any]) -> Bool {
91 | return (self.oauthManager?.authorizationManager?.application(application, open: url, options: options))!
92 | }
93 |
94 | public func logout() {
95 | let appIDAuthorizationManager = AppIDAuthorizationManager(appid: self)
96 | appIDAuthorizationManager.logout()
97 | }
98 |
99 | }
100 |
--------------------------------------------------------------------------------
/Source/IBMCloudAppID/api/AppIDAuthorizationManager.swift:
--------------------------------------------------------------------------------
1 | /* * Copyright 2016, 2017 IBM Corp.
2 | * Licensed under the Apache License, Version 2.0 (the "License");
3 | * you may not use this file except in compliance with the License.
4 | * You may obtain a copy of the License at
5 | * http://www.apache.org/licenses/LICENSE-2.0
6 | * Unless required by applicable law or agreed to in writing, software
7 | * distributed under the License is distributed on an "AS IS" BASIS,
8 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9 | * See the License for the specific language governing permissions and
10 | * limitations under the License.
11 | */
12 |
13 | import Foundation
14 | import BMSCore
15 |
16 | public class AppIDAuthorizationManager: BMSCore.AuthorizationManager {
17 |
18 |
19 | internal var oAuthManager:OAuthManager
20 | private static let logger = Logger.logger(name: Logger.bmsLoggerPrefix + "AppIDAuthorizationManager")
21 |
22 |
23 | /**
24 | Intializes the App ID Authorization Manager
25 | @param appid An AppID instance
26 | */
27 | public init(appid:AppID) {
28 | self.oAuthManager = appid.oauthManager!
29 | }
30 |
31 | /**
32 | A response is an OAuth error response only if,
33 | 1. it's status is 401 or 403.
34 | 2. The value of the "WWW-Authenticate" header contains 'Bearer'.
35 |
36 | - Parameter httpResponse - Response to check the authorization conditions for.
37 |
38 | - returns: True if the response satisfies both conditions
39 | */
40 |
41 |
42 | public func isAuthorizationRequired(for httpResponse: Response) -> Bool {
43 | AppIDAuthorizationManager.logger.debug(message: "isAuthorizationRequired")
44 | return AuthorizationHeaderHelper.isAuthorizationRequired(for: httpResponse)
45 | }
46 |
47 | /**
48 | Check if the params came from response that requires authorization
49 |
50 | - Parameter statusCode - Status code of the response
51 | - Parameter responseAuthorizationHeader - Response header
52 |
53 | - returns: True if status is 401 or 403 and The value of the header contains 'Bearer'
54 | */
55 |
56 |
57 | public func isAuthorizationRequired(for statusCode: Int, httpResponseAuthorizationHeader
58 | responseAuthorizationHeader: String) -> Bool {
59 | return AuthorizationHeaderHelper.isAuthorizationRequired(statusCode: statusCode, header: responseAuthorizationHeader)
60 | }
61 |
62 | public func obtainAuthorization(completionHandler callback: BMSCompletionHandler?) {
63 | AppIDAuthorizationManager.logger.debug(message: "obtainAuthorization")
64 |
65 | class innerTokenDelegate: TokenResponseDelegate {
66 | var callback:(Response?, AuthorizationError?) -> Void
67 | init(_ callback: @escaping (Response?, AuthorizationError?) -> Void) {
68 | self.callback = callback
69 | }
70 |
71 | func onAuthorizationFailure(error: AuthorizationError) {
72 | self.callback(nil, error)
73 | }
74 |
75 | func onAuthorizationSuccess(accessToken: AccessToken?, identityToken: IdentityToken?, refreshToken: RefreshToken?, response: Response?) {
76 | self.callback(response, nil)
77 | }
78 |
79 | }
80 |
81 | let refreshToken = self.oAuthManager.tokenManager?.latestRefreshToken
82 | if refreshToken != nil {
83 | self.oAuthManager.tokenManager?.obtainTokensRefreshToken(
84 | refreshTokenString: refreshToken!.raw!,
85 | tokenResponseDelegate: innerTokenDelegate { (response, authorizationError) in
86 | if response != nil {
87 | callback?(response, nil)
88 | } else {
89 | self.launchAuthorization(callback)
90 | }
91 | })
92 | } else {
93 | self.launchAuthorization(callback)
94 | }
95 | }
96 |
97 | public func launchAuthorization(_ callback: BMSCompletionHandler?) {
98 | class innerAuthorizationDelegate: AuthorizationDelegate {
99 | var callback:BMSCompletionHandler?
100 | init(callback:BMSCompletionHandler?) {
101 | self.callback = callback
102 | }
103 |
104 | func onAuthorizationFailure(error err:AuthorizationError) {
105 | callback?(nil,err)
106 | }
107 |
108 | func onAuthorizationCanceled () {
109 | callback?(nil, AuthorizationError.authorizationFailure("Authorization canceled"))
110 | }
111 |
112 | func onAuthorizationSuccess (accessToken:AccessToken?,
113 | identityToken:IdentityToken?,
114 | refreshToken: RefreshToken?, response:Response?) {
115 | callback?(response,nil)
116 | }
117 |
118 | }
119 | oAuthManager.authorizationManager?.launchAuthorizationUI(authorizationDelegate: innerAuthorizationDelegate(callback: callback))
120 | }
121 |
122 | public func clearAuthorizationData() {
123 | AppIDAuthorizationManager.logger.debug(message: "clearAuthorizationData")
124 | self.oAuthManager.tokenManager?.notifyLogout();
125 | self.oAuthManager.tokenManager?.clearStoredToken()
126 | }
127 |
128 | /**
129 | - returns: The locally stored authorization header or nil if the value does not exist.
130 | */
131 | public var cachedAuthorizationHeader:String? {
132 | get {
133 | AppIDAuthorizationManager.logger.debug(message: "getCachedAuthorizationHeader")
134 | guard let accessToken = self.accessToken, let identityToken = self.identityToken else {
135 | return nil
136 | }
137 | return "Bearer " + accessToken.raw + " " + identityToken.raw
138 | }
139 | }
140 |
141 |
142 |
143 | /**
144 | Returns the UserIdentity object constructed from the Identity Token if there is one
145 | */
146 | public var userIdentity:UserIdentity? {
147 | let idToken = self.identityToken
148 | let identity:[String:Any] = [
149 | BaseUserIdentity.Key.authorizedBy : idToken?.authenticationMethods ?? "",
150 | BaseUserIdentity.Key.displayName : idToken?.name ?? "",
151 | BaseUserIdentity.Key.ID : idToken?.subject ?? ""
152 | ]
153 | return BaseUserIdentity(map: identity)
154 |
155 | }
156 | /**
157 | Returns the a DeviceIdentity object
158 | */
159 | public var deviceIdentity:DeviceIdentity {
160 | return BaseDeviceIdentity()
161 |
162 | }
163 | /**
164 | Returns the an AppIdentity object
165 | */
166 | public var appIdentity:AppIdentity {
167 | return BaseAppIdentity()
168 | }
169 |
170 | /**
171 | Returns the latest access token
172 | */
173 | public var accessToken:AccessToken? {
174 | return self.oAuthManager.tokenManager?.latestAccessToken
175 | }
176 |
177 | /**
178 | Returns the latest identity token
179 | */
180 | public var identityToken:IdentityToken? {
181 | return self.oAuthManager.tokenManager?.latestIdentityToken
182 | }
183 |
184 | /**
185 | Adds the cached authorization header to the given URL connection object.
186 | If the cached authorization header is equal to nil then this operation has no effect.
187 | - Parameter request - The request to add the header to.
188 | */
189 |
190 | public func addCachedAuthorizationHeader(_ request: NSMutableURLRequest) {
191 | AppIDAuthorizationManager.logger.debug(message: "addCachedAuthorizationHeader")
192 | addAuthorizationHeader(request, header: cachedAuthorizationHeader)
193 | }
194 |
195 | private func addAuthorizationHeader(_ request: NSMutableURLRequest, header:String?) {
196 | AppIDAuthorizationManager.logger.debug(message: "addAuthorizationHeader")
197 | guard let unWrappedHeader = header else {
198 | return
199 | }
200 | request.setValue(unWrappedHeader, forHTTPHeaderField: AppIDConstants.AUTHORIZATION_HEADER)
201 | }
202 |
203 | /**
204 | Removes saved tokens
205 | */
206 | public func logout() {
207 | self.oAuthManager.tokenManager?.notifyLogout();
208 | self.clearAuthorizationData()
209 | }
210 |
211 |
212 |
213 |
214 |
215 | }
216 |
--------------------------------------------------------------------------------
/Source/IBMCloudAppID/api/AuthorizationDelegate.swift:
--------------------------------------------------------------------------------
1 | /* * Copyright 2016, 2017 IBM Corp.
2 | * Licensed under the Apache License, Version 2.0 (the "License");
3 | * you may not use this file except in compliance with the License.
4 | * You may obtain a copy of the License at
5 | * http://www.apache.org/licenses/LICENSE-2.0
6 | * Unless required by applicable law or agreed to in writing, software
7 | * distributed under the License is distributed on an "AS IS" BASIS,
8 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9 | * See the License for the specific language governing permissions and
10 | * limitations under the License.
11 | */
12 |
13 |
14 | import Foundation
15 | import BMSCore
16 |
17 | public protocol AuthorizationDelegate : TokenResponseDelegate {
18 | func onAuthorizationCanceled()
19 | }
20 |
--------------------------------------------------------------------------------
/Source/IBMCloudAppID/api/AuthorizationError.swift:
--------------------------------------------------------------------------------
1 | /* * Copyright 2016, 2017 IBM Corp.
2 | * Licensed under the Apache License, Version 2.0 (the "License");
3 | * you may not use this file except in compliance with the License.
4 | * You may obtain a copy of the License at
5 | * http://www.apache.org/licenses/LICENSE-2.0
6 | * Unless required by applicable law or agreed to in writing, software
7 | * distributed under the License is distributed on an "AS IS" BASIS,
8 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9 | * See the License for the specific language governing permissions and
10 | * limitations under the License.
11 | */
12 |
13 |
14 | import Foundation
15 |
16 | public enum AuthorizationError: Error {
17 | case authorizationFailure(String)
18 |
19 | public var description: String {
20 | switch self {
21 | case .authorizationFailure(let msg) :
22 | return msg
23 | }
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/Source/IBMCloudAppID/api/IdentityToken.swift:
--------------------------------------------------------------------------------
1 | /* * Copyright 2016, 2017 IBM Corp.
2 | * Licensed under the Apache License, Version 2.0 (the "License");
3 | * you may not use this file except in compliance with the License.
4 | * You may obtain a copy of the License at
5 | * http://www.apache.org/licenses/LICENSE-2.0
6 | * Unless required by applicable law or agreed to in writing, software
7 | * distributed under the License is distributed on an "AS IS" BASIS,
8 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9 | * See the License for the specific language governing permissions and
10 | * limitations under the License.
11 | */
12 |
13 |
14 | import Foundation
15 |
16 | public protocol IdentityToken: Token {
17 | var name: String? {get}
18 | var email: String? {get}
19 | var locale: String? {get}
20 | var picture: String? {get}
21 | var identities: Array>? {get}
22 | }
23 |
--------------------------------------------------------------------------------
/Source/IBMCloudAppID/api/LoginWidget.swift:
--------------------------------------------------------------------------------
1 | /* * Copyright 2016, 2017 IBM Corp.
2 | * Licensed under the Apache License, Version 2.0 (the "License");
3 | * you may not use this file except in compliance with the License.
4 | * You may obtain a copy of the License at
5 | * http://www.apache.org/licenses/LICENSE-2.0
6 | * Unless required by applicable law or agreed to in writing, software
7 | * distributed under the License is distributed on an "AS IS" BASIS,
8 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9 | * See the License for the specific language governing permissions and
10 | * limitations under the License.
11 | */
12 |
13 |
14 | import Foundation
15 |
16 | public protocol LoginWidget {
17 | func launch(accessTokenString: String?, delegate: AuthorizationDelegate)
18 | func launchSignUp(_ delegate: AuthorizationDelegate)
19 | func launchChangePassword(_ delegate: AuthorizationDelegate)
20 | func launchChangeDetails(_ delegate: AuthorizationDelegate)
21 | func launchForgotPassword(_ delegate: AuthorizationDelegate)
22 | }
23 |
--------------------------------------------------------------------------------
/Source/IBMCloudAppID/api/SecAttrAccessible.swift:
--------------------------------------------------------------------------------
1 | /* * Copyright 2016, 2017, 2018 IBM Corp.
2 | * Licensed under the Apache License, Version 2.0 (the "License");
3 | * you may not use this file except in compliance with the License.
4 | * You may obtain a copy of the License at
5 | * http://www.apache.org/licenses/LICENSE-2.0
6 | * Unless required by applicable law or agreed to in writing, software
7 | * distributed under the License is distributed on an "AS IS" BASIS,
8 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9 | * See the License for the specific language governing permissions and
10 | * limitations under the License.
11 | */
12 |
13 | import Foundation
14 |
15 | public enum SecAttrAccessible: RawRepresentable {
16 |
17 | case accessibleAlways // kSecAttrAccessibleAlways
18 | case accessibleAlwaysThisDeviceOnly // kSecAttrAccessibleAlwaysThisDeviceOnly
19 | case accessibleAfterFirstUnlock // kSecAttrAccessibleAfterFirstUnlock
20 | case accessibleAfterFirstUnlockThisDeviceOnly // kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly
21 | case accessibleWhenUnlocked // kSecAttrAccessibleWhenUnlocked
22 | case accessibleWhenUnlockedThisDeviceOnly // kSecAttrAccessibleWhenUnlockedThisDeviceOnly
23 | case accessibleWhenPasscodeSetThisDeviceOnly // kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly
24 |
25 | public init?(rawValue: CFString) {
26 | switch rawValue {
27 | case kSecAttrAccessibleAlways: self = .accessibleAlways
28 | case kSecAttrAccessibleAlwaysThisDeviceOnly: self = .accessibleAlwaysThisDeviceOnly
29 | case kSecAttrAccessibleAfterFirstUnlock: self = .accessibleAfterFirstUnlock
30 | case kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly: self = .accessibleAfterFirstUnlockThisDeviceOnly
31 | case kSecAttrAccessibleWhenUnlocked: self = .accessibleWhenUnlocked
32 | case kSecAttrAccessibleWhenUnlockedThisDeviceOnly: self = .accessibleWhenUnlockedThisDeviceOnly
33 | case kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly: self = .accessibleWhenPasscodeSetThisDeviceOnly
34 | default: self = .accessibleAfterFirstUnlock
35 | }
36 | }
37 |
38 | public var rawValue: CFString {
39 | switch self {
40 | case .accessibleAlways: return kSecAttrAccessibleAlways
41 | case .accessibleAlwaysThisDeviceOnly: return kSecAttrAccessibleAlwaysThisDeviceOnly
42 | case .accessibleAfterFirstUnlock: return kSecAttrAccessibleAfterFirstUnlock
43 | case .accessibleAfterFirstUnlockThisDeviceOnly: return kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly
44 | case .accessibleWhenUnlocked: return kSecAttrAccessibleWhenUnlocked
45 | case .accessibleWhenUnlockedThisDeviceOnly: return kSecAttrAccessibleWhenUnlockedThisDeviceOnly
46 | case .accessibleWhenPasscodeSetThisDeviceOnly: return kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly
47 | }
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/Source/IBMCloudAppID/api/TokenResponseDelegate.swift:
--------------------------------------------------------------------------------
1 | /* * Copyright 2016, 2017 IBM Corp.
2 | * Licensed under the Apache License, Version 2.0 (the "License");
3 | * you may not use this file except in compliance with the License.
4 | * You may obtain a copy of the License at
5 | * http://www.apache.org/licenses/LICENSE-2.0
6 | * Unless required by applicable law or agreed to in writing, software
7 | * distributed under the License is distributed on an "AS IS" BASIS,
8 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9 | * See the License for the specific language governing permissions and
10 | * limitations under the License.
11 | */
12 |
13 |
14 | import Foundation
15 | import BMSCore
16 |
17 | public protocol TokenResponseDelegate {
18 | func onAuthorizationFailure(error: AuthorizationError)
19 | func onAuthorizationSuccess(accessToken: AccessToken?, identityToken: IdentityToken?, refreshToken: RefreshToken?, response:Response?)
20 | }
21 |
--------------------------------------------------------------------------------
/Source/IBMCloudAppID/api/Tokens/AccessToken.swift:
--------------------------------------------------------------------------------
1 | /* * Copyright 2016, 2017 IBM Corp.
2 | * Licensed under the Apache License, Version 2.0 (the "License");
3 | * you may not use this file except in compliance with the License.
4 | * You may obtain a copy of the License at
5 | * http://www.apache.org/licenses/LICENSE-2.0
6 | * Unless required by applicable law or agreed to in writing, software
7 | * distributed under the License is distributed on an "AS IS" BASIS,
8 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9 | * See the License for the specific language governing permissions and
10 | * limitations under the License.
11 | */
12 |
13 |
14 | import Foundation
15 |
16 | public protocol AccessToken: Token {
17 | var scope: String? {get}
18 | }
19 |
--------------------------------------------------------------------------------
/Source/IBMCloudAppID/api/Tokens/RefreshToken.swift:
--------------------------------------------------------------------------------
1 | /* *
2 | * Copyright 2018 IBM Corp.
3 | * Licensed under the Apache License, Version 2.0 (the "License");
4 | * you may not use this file except in compliance with the License.
5 | * You may obtain a copy of the License at
6 | * http://www.apache.org/licenses/LICENSE-2.0
7 | * Unless required by applicable law or agreed to in writing, software
8 | * distributed under the License is distributed on an "AS IS" BASIS,
9 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10 | * See the License for the specific language governing permissions and
11 | * limitations under the License.
12 | */
13 |
14 | public protocol RefreshToken {
15 | var raw: String? {get}
16 | }
17 |
--------------------------------------------------------------------------------
/Source/IBMCloudAppID/api/UserAttributeError.swift:
--------------------------------------------------------------------------------
1 | /* * Copyright 2016, 2017 IBM Corp.
2 | * Licensed under the Apache License, Version 2.0 (the "License");
3 | * you may not use this file except in compliance with the License.
4 | * You may obtain a copy of the License at
5 | * http://www.apache.org/licenses/LICENSE-2.0
6 | * Unless required by applicable law or agreed to in writing, software
7 | * distributed under the License is distributed on an "AS IS" BASIS,
8 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9 | * See the License for the specific language governing permissions and
10 | * limitations under the License.
11 | */
12 |
13 | import Foundation
14 |
15 | public enum UserAttributeError: Error {
16 | case userAttributeFailure(String)
17 |
18 | public var description: String {
19 | switch self {
20 | case .userAttributeFailure(let msg) :
21 | return msg
22 | }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/Source/IBMCloudAppID/api/UserProfileError.swift:
--------------------------------------------------------------------------------
1 | /* * Copyright 2016, 2017 IBM Corp.
2 | * Licensed under the Apache License, Version 2.0 (the "License");
3 | * you may not use this file except in compliance with the License.
4 | * You may obtain a copy of the License at
5 | * http://www.apache.org/licenses/LICENSE-2.0
6 | * Unless required by applicable law or agreed to in writing, software
7 | * distributed under the License is distributed on an "AS IS" BASIS,
8 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9 | * See the License for the specific language governing permissions and
10 | * limitations under the License.
11 | */
12 |
13 | import Foundation
14 |
15 | public enum UserProfileError: Error {
16 |
17 | case notFound
18 | case unauthorized
19 | case missingAccessToken
20 | case missingOrMalformedIdToken
21 | case responseValidationError
22 | case invalidUserInfoResponse
23 | case bodyParsingError
24 | case general(String)
25 |
26 | public var description: String {
27 | switch self {
28 | case .general(let msg) : return msg
29 | case .notFound: return "Not Found"
30 | case .unauthorized: return "Unauthorized"
31 | case .missingAccessToken: return "Access token not found. Please login."
32 | case .missingOrMalformedIdToken: return "Identity token not found or is missing subject field. Please login again."
33 | case .responseValidationError: return "Potential token substitution attack. Rejecting: response.sub != identityToken.sub"
34 | case .bodyParsingError: return "Failed to parse server body"
35 | case .invalidUserInfoResponse: return "Invalid user info response"
36 | }
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/Source/IBMCloudAppID/api/UserProfileManager.swift:
--------------------------------------------------------------------------------
1 | /* * Copyright 2016, 2017, 2018 IBM Corp.
2 | * Licensed under the Apache License, Version 2.0 (the "License");
3 | * you may not use this file except in compliance with the License.
4 | * You may obtain a copy of the License at
5 | * http://www.apache.org/licenses/LICENSE-2.0
6 | * Unless required by applicable law or agreed to in writing, software
7 | * distributed under the License is distributed on an "AS IS" BASIS,
8 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9 | * See the License for the specific language governing permissions and
10 | * limitations under the License.
11 | */
12 |
13 | import Foundation
14 |
15 | public protocol UserProfileManager {
16 |
17 | func setAttribute(key: String, value: String, completionHandler: @escaping(Error?, [String:Any]?) -> Void)
18 | func setAttribute(key: String, value: String, accessTokenString: String, completionHandler: @escaping(Error?, [String:Any]?) -> Void)
19 | func getAttribute(key: String, completionHandler: @escaping(Error?, [String:Any]?) -> Void)
20 | func getAttribute(key: String, accessTokenString: String, completionHandler: @escaping(Error?, [String:Any]?) -> Void)
21 | func getAttributes(completionHandler: @escaping(Error?, [String:Any]?) -> Void)
22 | func getAttributes(accessTokenString: String, completionHandler: @escaping(Error?, [String:Any]?) -> Void)
23 | func deleteAttribute(key: String, completionHandler: @escaping(Error?, [String:Any]?) -> Void)
24 | func deleteAttribute(key: String, accessTokenString: String, completionHandler: @escaping(Error?, [String:Any]?) -> Void)
25 | func getUserInfo(completionHandler: @escaping (Error?, [String: Any]?) -> Void)
26 | func getUserInfo(accessTokenString: String, identityTokenString: String?, completionHandler: @escaping (Error?, [String: Any]?) -> Void)
27 |
28 | }
29 |
--------------------------------------------------------------------------------
/Source/IBMCloudAppID/internal/AppIDConstants.swift:
--------------------------------------------------------------------------------
1 | /* * Copyright 2016, 2017 IBM Corp.
2 | * Licensed under the Apache License, Version 2.0 (the "License");
3 | * you may not use this file except in compliance with the License.
4 | * You may obtain a copy of the License at
5 | * http://www.apache.org/licenses/LICENSE-2.0
6 | * Unless required by applicable law or agreed to in writing, software
7 | * distributed under the License is distributed on an "AS IS" BASIS,
8 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9 | * See the License for the specific language governing permissions and
10 | * limitations under the License.
11 | */
12 |
13 | import Foundation
14 | import BMSCore
15 | import BMSAnalyticsAPI
16 |
17 | internal class AppIDConstants {
18 |
19 |
20 | internal static let base64EncodingTable:[Character] = [
21 | "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P",
22 | "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "a", "b", "c", "d", "e", "f",
23 | "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v",
24 | "w", "x", "y", "z", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "+", "/"
25 | ]
26 |
27 | internal static let base64EncodingTableUrlSafe:[Character] = [
28 | "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P",
29 | "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "a", "b", "c", "d", "e", "f",
30 | "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v",
31 | "w", "x", "y", "z", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "-", "_"
32 | ]
33 |
34 |
35 | internal static let base64DecodingTable: [Int8] = [
36 | -2, -2, -2, -2, -2, -2, -2, -2, -2, -1, -1, -2, -1, -1, -2, -2,
37 | -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2,
38 | -1, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, 62, -2, -2, -2, 63,
39 | 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -2, -2, -2, -2, -2, -2,
40 | -2, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
41 | 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -2, -2, -2, -2, -2,
42 | -2, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
43 | 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -2, -2, -2, -2, -2,
44 | -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2,
45 | -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2,
46 | -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2,
47 | -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2,
48 | -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2,
49 | -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2,
50 | -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2,
51 | -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2
52 | ]
53 |
54 | internal static let base64DecodingTableUrlSafe: [Int8] = [
55 | -2, -2, -2, -2, -2, -2, -2, -2, -2, -1, -1, -2, -1, -1, -2, -2,
56 | -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2,
57 | -1, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, 62, -2, -2,
58 | 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -2, -2, -2, -2, -2, -2,
59 | -2, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
60 | 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -2, -2, -2, -2, 63,
61 | -2, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
62 | 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -2, -2, -2, -2, -2,
63 | -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2,
64 | -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2,
65 | -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2,
66 | -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2,
67 | -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2,
68 | -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2,
69 | -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2,
70 | -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2
71 | ]
72 |
73 | internal static let nameAndVer = Utils.getApplicationDetails()
74 |
75 | internal static var AppIDRequestManagerLoggerName = Logger.bmsLoggerPrefix + "AppIDRequestManager"
76 | internal static var RegistrationManagerLoggerName = Logger.bmsLoggerPrefix + "AppIDRegistrationManager"
77 | internal static var UserProfileManagerLoggerName = Logger.bmsLoggerPrefix + "AppIDUserProfileManager"
78 | internal static var TokenManagerLoggerName = Logger.bmsLoggerPrefix + "AppIDTokenManager"
79 | internal static var AuthorizationManagerLoggerName = Logger.bmsLoggerPrefix + "AppIDAuthorizationManager"
80 | internal static var AppIDLoggerName = Logger.bmsLoggerPrefix + "AppID"
81 | internal static var ConfigLoggerName = Logger.bmsLoggerPrefix + "Config"
82 |
83 | internal static var tokenEndPoint = "token"
84 | internal static var clientsEndPoint = "clients"
85 | internal static let userInfoEndPoint = "userinfo"
86 | internal static let attibutesEndpoint = "attributes"
87 |
88 | internal static var REDIRECT_URI_VALUE = Utils.getApplicationDetails().name + "://mobile/callback"
89 | internal static var authorizationEndPoint = "authorization"
90 | internal static var client_id_String = "client_id"
91 |
92 | internal static var authorization_code_String = "authorization_code"
93 | internal static var resource_owner_password_String = "password"
94 | internal static var refresh_token_String = "refresh_token"
95 | internal static var JSON_RSA_VALUE = "RSA"
96 | internal static var JSON_RS256_VALUE = "RS256"
97 | internal static var JSON_ALG_KEY = "alg"
98 | internal static var JSON_MOD_KEY = "mod"
99 | internal static var JSON_EXP_KEY = "exp"
100 | internal static var JSON_JPK_KEY = "jpk"
101 |
102 |
103 | internal static var JSON_RESPONSE_TYPE_KEY = "response_type"
104 | internal static var JSON_IMF_USER_KEY = "imf.user"
105 | internal static var JSON_REDIRECT_URI_KEY = "redirect_uri"
106 | internal static var JSON_CODE_KEY = "code"
107 |
108 | internal static var JSON_ID = "id"
109 | internal static var JSON_PROVIDER = "provider"
110 | internal static var JSON_CLOUD_DIRECTORY = "cloud_directory"
111 | internal static var JSON_USER_ID = "user_id"
112 |
113 | internal static let changePasswordPath = "/cloud_directory/change_password"
114 | internal static let generateCodePath = "/cloud_directory/generate_code"
115 | internal static let changeDetailsPath = "/cloud_directory/change_details"
116 | internal static let FORGOT_PASSWORD_PATH = "/cloud_directory/forgot_password"
117 |
118 | internal static var JSON_SIGN_UP_KEY = "sign_up"
119 | internal static var JSON_FORGOT_PASSWORD_KEY = "forgot_password"
120 | internal static var JSON_GRANT_TYPE_KEY = "grant_type"
121 | internal static var JSON_REFRESH_TOKEN = "refresh_token"
122 | internal static var JSON_FLOW_KEY = "flow"
123 | internal static var JSON_USERNAME = "username"
124 | internal static var JSON_PASSWORD = "password"
125 | internal static var APPID_ACCESS_TOKEN = "appid_access_token"
126 |
127 | internal static let MFP_SECURITY_PACKAGE = Logger.bmsLoggerPrefix + "security"
128 |
129 | internal static let BEARER = "Bearer"
130 | internal static let AUTHORIZATION_HEADER = "Authorization"
131 | internal static let BASIC_AUTHORIZATION_STRING = "Basic"
132 | internal static let WWW_AUTHENTICATE_HEADER = "WWW-Authenticate"
133 | internal static let AUTH_REALM = "\"appid_default\""
134 | /**
135 | * Parts of the path to authorization endpoint.
136 | */
137 | internal static let AUTH_SERVER_NAME = "imf-authserver"
138 | internal static let V4_AUTH_PATH = "oauth/v4/"
139 | internal static let OAUTH_AUTHORIZATION_PATH = "/authorization"
140 |
141 |
142 |
143 | /**
144 | * Name of the standard "www-authenticate" header.
145 | */
146 |
147 | internal static var FACEBOOK_COOKIE_NAME = "c_user"
148 |
149 |
150 |
151 | //JSON keys and values
152 | internal static let JSON_KEYS_KEY = "keys"
153 | internal static let JSON_JWKS_KEY = "jwks"
154 | internal static let JSON_DEVICE_ID_KEY = "device_id"
155 | internal static let JSON_OS_KEY = "device_os"
156 | internal static let jsonOsVersionKey = "device_os_version"
157 | internal static let JSON_ENVIRONMENT_KEY = "environment"
158 | internal static let JSON_MODEL_KEY = "device_model"
159 | internal static let JSON_SOFTWARE_ID_KEY = "software_id"
160 | internal static let JSON_SOFTWARE_VERSION_KEY = "software_version"
161 | internal static let JSON_REDIRECT_URIS_KEY = "redirect_uris"
162 | internal static let JSON_TOKEN_ENDPOINT_AUTH_METHOD_KEY = "token_endpoint_auth_method"
163 | internal static let JSON_RESPONSE_TYPES_KEY = "response_types"
164 | internal static let JSON_GRANT_TYPES_KEY = "grant_types"
165 | internal static let JSON_CLIENT_NAME_KEY = "client_name"
166 | internal static let JSON_CLIENT_TYPE_KEY = "client_type"
167 | internal static let MOBILE_APP_TYPE = "mobileapp"
168 | internal static let CLIENT_SECRET_BASIC = "client_secret_basic"
169 | internal static let PASSWORD_STRING = "password"
170 |
171 | internal static let tenantPrefName = "com.ibm.bluemix.appid.swift.tenantid"
172 | internal static let registrationDataPref = "com.ibm.bluemix.appid.swift.REGISTRATION_DATA"
173 |
174 |
175 | internal static let JSON_IOS_ENVIRONMENT_VALUE = "iOSnative"
176 | internal static let JSON_ACCESS_TOKEN_KEY = "access_token"
177 | internal static let JSON_ID_TOKEN_KEY = "id_token"
178 | internal static var JSON_SCOPE_KEY = "scope"
179 | internal static var JSON_USE_LOGIN_WIDGET = "use_login_widget"
180 | internal static var JSON_STATE_KEY = "state"
181 | internal static var OPEN_ID_VALUE = "openid"
182 | internal static var TRUE_VALUE = "true"
183 |
184 |
185 | // label names
186 | internal static let KEY_CHAIN_PREFIX = "com.ibm.mobilefirstplatform.clientsdk.swift.bmssecurity"
187 | internal static let OAUTH_CERT_LABEL = "\(KEY_CHAIN_PREFIX).certificate"
188 | internal static let _PUBLIC_KEY_LABEL = "\(KEY_CHAIN_PREFIX).publickey"
189 | internal static let CLIENT_ID_KEY_LABEL = "\(KEY_CHAIN_PREFIX).clientid"
190 | internal static let TENANT_ID_KEY_LABEL = "\(KEY_CHAIN_PREFIX).tenantId"
191 | internal static let _PRIVATE_KEY_LABEL = "\(KEY_CHAIN_PREFIX).privatekey"
192 | internal static let OAUTH_ACCESS_TOKEN_LABEL = "\(KEY_CHAIN_PREFIX).accesstoken"
193 | internal static let OAUTH_ID_TOKEN_LABEL = "\(KEY_CHAIN_PREFIX).idtoken"
194 | internal static let PERSISTENCE_POLICY_LABEL = "persistencePolicy"
195 | internal static let APP_IDENTITY_LABEL = "appIdentity"
196 | internal static let DEVICE_IDENTITY_LABEL = "deviceIdentity"
197 | internal static let USER_IDENTITY_LABEL = "userIdentity"
198 | // labels
199 |
200 | internal static let AnonymousIdpName = "appid_anon"
201 | internal static let BMSSecurityErrorDomain = "com.ibm.mobilefirstplatform.clientsdk.swift.bmssecurity"
202 | internal static let privateKeyIdentifier = "\(_PRIVATE_KEY_LABEL):\(nameAndVer.name)"
203 | internal static let publicKeyIdentifier = "\(_PUBLIC_KEY_LABEL):\(nameAndVer.name)"
204 | internal static let localeParamName = "language"
205 |
206 | }
207 |
--------------------------------------------------------------------------------
/Source/IBMCloudAppID/internal/AppIDError.swift:
--------------------------------------------------------------------------------
1 | /* * Copyright 2016, 2017 IBM Corp.
2 | * Licensed under the Apache License, Version 2.0 (the "License");
3 | * you may not use this file except in compliance with the License.
4 | * You may obtain a copy of the License at
5 | * http://www.apache.org/licenses/LICENSE-2.0
6 | * Unless required by applicable law or agreed to in writing, software
7 | * distributed under the License is distributed on an "AS IS" BASIS,
8 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9 | * See the License for the specific language governing permissions and
10 | * limitations under the License.
11 | */
12 |
13 | import Foundation
14 |
15 | internal enum AppIDError: Error {
16 | case authenticationError(msg: String?)
17 | case registrationError(msg: String?)
18 | case tokenRequestError(msg: String?)
19 | case jsonUtilsError(msg: String?)
20 | case generalError
21 |
22 |
23 | var description: String {
24 | switch self {
25 | case .authenticationError(let msg), .jsonUtilsError(let msg), .registrationError(let msg), .tokenRequestError(let msg) :
26 | return msg ?? "error"
27 | case .generalError :
28 | return "General Error"
29 | }
30 | }
31 |
32 |
33 | }
34 |
--------------------------------------------------------------------------------
/Source/IBMCloudAppID/internal/AuthorizationHeaderHelper.swift:
--------------------------------------------------------------------------------
1 | /* * Copyright 2016, 2017 IBM Corp.
2 | * Licensed under the Apache License, Version 2.0 (the "License");
3 | * you may not use this file except in compliance with the License.
4 | * You may obtain a copy of the License at
5 | * http://www.apache.org/licenses/LICENSE-2.0
6 | * Unless required by applicable law or agreed to in writing, software
7 | * distributed under the License is distributed on an "AS IS" BASIS,
8 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9 | * See the License for the specific language governing permissions and
10 | * limitations under the License.
11 | */
12 |
13 | import Foundation
14 | import BMSCore
15 |
16 | public class AuthorizationHeaderHelper {
17 |
18 |
19 | public static func isAuthorizationRequired(for httpResponse: Response) -> Bool {
20 |
21 | let header = httpResponse.headers?.filter({($0.key as? String)?.lowercased() == AppIDConstants.WWW_AUTHENTICATE_HEADER.lowercased() }).first?.1 as? String
22 | return AuthorizationHeaderHelper.isAuthorizationRequired(statusCode: httpResponse.statusCode, header: header)
23 | }
24 |
25 |
26 | public static func isAuthorizationRequired(statusCode: Int?, header: String?) -> Bool {
27 |
28 | guard let code = statusCode, let unwrappedHeader = header else {
29 | return false
30 | }
31 |
32 | if code == 401 || code == 403 {
33 | if unwrappedHeader.lowercased().hasPrefix(AppIDConstants.BEARER.lowercased()) && unwrappedHeader.lowercased().contains(AppIDConstants.AUTH_REALM.lowercased()) {
34 | return true
35 | }
36 | }
37 | return false
38 | }
39 |
40 | }
41 |
--------------------------------------------------------------------------------
/Source/IBMCloudAppID/internal/AuthorizationUIManager.swift:
--------------------------------------------------------------------------------
1 | /* * Copyright 2016, 2017 IBM Corp.
2 | * Licensed under the Apache License, Version 2.0 (the "License");
3 | * you may not use this file except in compliance with the License.
4 | * You may obtain a copy of the License at
5 | * http://www.apache.org/licenses/LICENSE-2.0
6 | * Unless required by applicable law or agreed to in writing, software
7 | * distributed under the License is distributed on an "AS IS" BASIS,
8 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9 | * See the License for the specific language governing permissions and
10 | * limitations under the License.
11 | */
12 |
13 |
14 | import Foundation
15 | import BMSCore
16 |
17 | public class AuthorizationUIManager {
18 | var oAuthManager: OAuthManager
19 | var authorizationDelegate: AuthorizationDelegate
20 | var authorizationUrl: String
21 | var redirectUri: String
22 |
23 | private static let logger = Logger.logger(name: Logger.bmsLoggerPrefix + "AppIDAuthorizationUIManager")
24 | var loginView:safariView?
25 | init(oAuthManager: OAuthManager, authorizationDelegate: AuthorizationDelegate, authorizationUrl: String, redirectUri: String) {
26 | self.oAuthManager = oAuthManager
27 | self.authorizationDelegate = authorizationDelegate
28 | self.authorizationUrl = authorizationUrl
29 | self.redirectUri = redirectUri
30 | }
31 |
32 | public func launch() {
33 | AuthorizationUIManager.logger.debug(message: "Launching safari view")
34 | loginView = safariView(url: URL(string: authorizationUrl)!)
35 | loginView?.authorizationDelegate = authorizationDelegate
36 | DispatchQueue.main.async {
37 | let rootView = UIApplication.shared.keyWindow?.rootViewController
38 | let currentView = rootView?.presentedViewController
39 | let view = currentView != nil ? currentView : rootView
40 | view?.present(self.loginView!, animated: true, completion: nil)
41 | }
42 | }
43 |
44 | public func application(_ application: UIApplication, open url: URL, options :[UIApplicationOpenURLOptionsKey: Any]) -> Bool {
45 |
46 | func tokenRequest(code: String?, errMsg:String?) {
47 | loginView?.dismiss(animated: true, completion: { () -> Void in
48 | guard errMsg == nil else {
49 | self.authorizationDelegate.onAuthorizationFailure(error: AuthorizationError.authorizationFailure(errMsg!))
50 | return
51 | }
52 | guard let unwrappedCode = code else {
53 | self.authorizationDelegate.onAuthorizationFailure(error: AuthorizationError.authorizationFailure("Failed to extract grant code"))
54 | return
55 | }
56 | AuthorizationUIManager.logger.debug(message: "Obtaining tokens")
57 |
58 | self.oAuthManager.tokenManager?.obtainTokensAuthCode(code: unwrappedCode, authorizationDelegate: self.authorizationDelegate)
59 | })
60 | }
61 |
62 | if let err = Utils.getParamFromQuery(url: url, paramName: "error") {
63 | loginView?.dismiss(animated: true, completion: { () -> Void in
64 | if err == "invalid_client" {
65 | self.oAuthManager.registrationManager?.clearRegistrationData()
66 | self.oAuthManager.authorizationManager?.launchAuthorizationUI(authorizationDelegate: self.authorizationDelegate)
67 | } else {
68 | let errorDescription = Utils.getParamFromQuery(url: url, paramName: "error_description")
69 | let errorCode = Utils.getParamFromQuery(url: url, paramName: "error_code")
70 | AuthorizationUIManager.logger.error(message: "Failed to obtain access and identity tokens, error: " + err)
71 | AuthorizationUIManager.logger.error(message: "errorCode: " + (errorCode ?? "not available"))
72 | AuthorizationUIManager.logger.error(message: "errorDescription: " + (errorDescription ?? "not available"))
73 | self.authorizationDelegate.onAuthorizationFailure(error: AuthorizationError.authorizationFailure(err))
74 | }
75 | })
76 | return false
77 | } else if let flow = Utils.getParamFromQuery(url: url, paramName: AppIDConstants.JSON_FLOW_KEY) {
78 | if flow == AppIDConstants.JSON_FORGOT_PASSWORD_KEY || flow == AppIDConstants.JSON_SIGN_UP_KEY {
79 | loginView?.dismiss(animated: true, completion: { () -> Void in
80 | AuthorizationUIManager.logger.debug(message: "Finish " + flow + " flow")
81 | self.authorizationDelegate.onAuthorizationSuccess(accessToken: nil,
82 | identityToken: nil,
83 | refreshToken: nil,
84 | response: nil)
85 | })
86 | return true
87 | }
88 | loginView?.dismiss(animated: true, completion: { () -> Void in
89 | AuthorizationUIManager.logger.error(message: "Bad callback uri:" + url.absoluteString)
90 | self.authorizationDelegate.onAuthorizationFailure(error: AuthorizationError.authorizationFailure("Bad callback uri"))
91 | })
92 | return false
93 | } else {
94 |
95 | let urlString = url.absoluteString
96 | guard urlString.lowercased().hasPrefix(AppIDConstants.REDIRECT_URI_VALUE.lowercased()) else {
97 | return false
98 | }
99 |
100 | // Gets "code" url query parameters
101 | guard let code = Utils.getParamFromQuery(url: url, paramName: AppIDConstants.JSON_CODE_KEY) else {
102 | AuthorizationUIManager.logger.debug(message: "Failed to extract grant code")
103 | tokenRequest(code: nil, errMsg: "Failed to extract grant code")
104 | return false
105 | }
106 |
107 | // Get "state" url query parameters
108 | guard let state = Utils.getParamFromQuery(url: url, paramName: AppIDConstants.JSON_STATE_KEY) else {
109 | AuthorizationUIManager.logger.debug(message: "Failed to extract state")
110 | tokenRequest(code: nil, errMsg: "Failed to extract state")
111 | return false
112 | }
113 |
114 | // Validates state matches the original
115 | guard getStoredState() == state else {
116 | AuthorizationUIManager.logger.debug(message: "Mismatched state parameter")
117 | tokenRequest(code: nil, errMsg: "Mismatched state parameter")
118 | return false
119 | }
120 |
121 | tokenRequest(code: code, errMsg: nil)
122 | return true
123 |
124 | }
125 |
126 | }
127 |
128 | internal func getStoredState() -> String? {
129 | return self.oAuthManager.authorizationManager?.state
130 | }
131 |
132 | }
133 |
--------------------------------------------------------------------------------
/Source/IBMCloudAppID/internal/Config.swift:
--------------------------------------------------------------------------------
1 | /* * Copyright 2016, 2017 IBM Corp.
2 | * Licensed under the Apache License, Version 2.0 (the "License");
3 | * you may not use this file except in compliance with the License.
4 | * You may obtain a copy of the License at
5 | * http://www.apache.org/licenses/LICENSE-2.0
6 | * Unless required by applicable law or agreed to in writing, software
7 | * distributed under the License is distributed on an "AS IS" BASIS,
8 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9 | * See the License for the specific language governing permissions and
10 | * limitations under the License.
11 | */
12 |
13 |
14 | import Foundation
15 | import BMSCore
16 |
17 | internal class Config {
18 |
19 | private static var oauthEndpoint = "/oauth/v4/"
20 | private static var attributesEndpoint = "/api/v1/"
21 | private static var serverUrlPrefix = "https://appid-oauth"
22 | private static var attributesUrlPrefix = "https://appid-profiles"
23 | private static var publicKeysEndpoint = "/publickeys"
24 | private static var urlProtocol = "https"
25 |
26 |
27 | private static var REGION_US_SOUTH_OLD = ".ng.bluemix.net";
28 | private static var REGION_US_EAST_OLD = ".us-east.bluemix.net";
29 | private static var REGION_UK_OLD = ".eu-gb.bluemix.net";
30 | private static var REGION_SYDNEY_OLD = ".au-syd.bluemix.net";
31 | private static var REGION_GERMANY_OLD = ".eu-de.bluemix.net";
32 | private static var REGION_TOKYO_OLD = ".jp-tok.bluemix.net";
33 |
34 | internal static let logger = Logger.logger(name: AppIDConstants.ConfigLoggerName)
35 |
36 | internal static func getServerUrl(appId: AppID) -> String {
37 |
38 | guard var serverUrl = convertOldRegionToNewURL(region: appId.region), let tenant = appId.tenantId else {
39 | logger.error(message: "Could not set server url properly, no tenantId or no region set")
40 | return serverUrlPrefix
41 | }
42 |
43 | serverUrl = serverUrl + oauthEndpoint
44 | if let overrideServerHost = AppID.overrideServerHost {
45 | serverUrl = overrideServerHost + "/"
46 | }
47 |
48 | return serverUrl + tenant
49 | }
50 |
51 | internal static func getAttributesUrl(appId: AppID) -> String {
52 |
53 | guard var attributesUrl = convertOldRegionToNewURL(region: appId.region) else {
54 | logger.error(message: "Could not set server url properly, no region set")
55 | return attributesUrlPrefix
56 | }
57 |
58 | if let overrideHost = AppID.overrideAttributesHost {
59 | attributesUrl = overrideHost
60 | }
61 |
62 | return attributesUrl + attributesEndpoint
63 | }
64 |
65 | internal static func getPublicKeyEndpoint(appId: AppID) -> String {
66 | return getServerUrl(appId: appId) + publicKeysEndpoint
67 | }
68 |
69 | internal static func getIssuer(appId: AppID) -> String? {
70 | return getServerUrl(appId: appId)
71 |
72 | }
73 |
74 | internal static func convertOldRegionToNewURL(region: String?) -> String? {
75 | switch region {
76 | case REGION_US_SOUTH_OLD: return AppID.REGION_US_SOUTH
77 | case REGION_US_EAST_OLD: return AppID.REGION_US_EAST
78 | case REGION_UK_OLD: return AppID.REGION_UK
79 | case REGION_SYDNEY_OLD: return AppID.REGION_SYDNEY
80 | case REGION_GERMANY_OLD: return AppID.REGION_GERMANY
81 | case REGION_TOKYO_OLD: return AppID.REGION_TOKYO
82 | case AppID.REGION_US_SOUTH: return AppID.REGION_US_SOUTH
83 | case AppID.REGION_US_EAST: return AppID.REGION_US_EAST
84 | case AppID.REGION_UK: return AppID.REGION_UK
85 | case AppID.REGION_UK_STAGE1: return AppID.REGION_UK_STAGE1
86 | case AppID.REGION_US_SOUTH_STAGE1: return AppID.REGION_US_SOUTH_STAGE1
87 | case AppID.REGION_SYDNEY: return AppID.REGION_SYDNEY
88 | case AppID.REGION_GERMANY: return AppID.REGION_GERMANY
89 | case AppID.REGION_TOKYO: return AppID.REGION_TOKYO
90 | default: return nil;
91 | }
92 | }
93 |
94 | }
95 |
--------------------------------------------------------------------------------
/Source/IBMCloudAppID/internal/JSONPreference.swift:
--------------------------------------------------------------------------------
1 | /* * Copyright 2016, 2017 IBM Corp.
2 | * Licensed under the Apache License, Version 2.0 (the "License");
3 | * you may not use this file except in compliance with the License.
4 | * You may obtain a copy of the License at
5 | * http://www.apache.org/licenses/LICENSE-2.0
6 | * Unless required by applicable law or agreed to in writing, software
7 | * distributed under the License is distributed on an "AS IS" BASIS,
8 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9 | * See the License for the specific language governing permissions and
10 | * limitations under the License.
11 | */
12 |
13 |
14 | import Foundation
15 |
16 | /**
17 | * Holds single JSON preference value
18 | */
19 | internal class JSONPreference:StringPreference {
20 |
21 | // TODO: should these be syncronized?
22 |
23 | override init(name:String, sharedPreferences: UserDefaults) {
24 | super.init(name: name, sharedPreferences: sharedPreferences)
25 | }
26 |
27 |
28 | public func set(_ value:[String:Any]?) {
29 | try? super.set(Utils.JSONStringify(value as AnyObject))
30 | }
31 |
32 | public func getAsJSON() -> [String:Any]? {
33 | guard let stringValue = super.get() else {
34 | return nil
35 | }
36 | return try? Utils.parseJsonStringtoDictionary(stringValue)
37 |
38 | }
39 |
40 | }
41 |
--------------------------------------------------------------------------------
/Source/IBMCloudAppID/internal/LoginWidgetImpl.swift:
--------------------------------------------------------------------------------
1 | /* * Copyright 2016, 2017 IBM Corp.
2 | * Licensed under the Apache License, Version 2.0 (the "License");
3 | * you may not use this file except in compliance with the License.
4 | * You may obtain a copy of the License at
5 | * http://www.apache.org/licenses/LICENSE-2.0
6 | * Unless required by applicable law or agreed to in writing, software
7 | * distributed under the License is distributed on an "AS IS" BASIS,
8 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9 | * See the License for the specific language governing permissions and
10 | * limitations under the License.
11 | */
12 |
13 |
14 | import Foundation
15 |
16 | public class LoginWidgetImpl: LoginWidget {
17 |
18 | var oauthManager:OAuthManager
19 | init(oauthManager:OAuthManager) {
20 | self.oauthManager = oauthManager
21 | }
22 |
23 | public func launch(accessTokenString: String? = nil, delegate: AuthorizationDelegate) {
24 | self.oauthManager.authorizationManager?.launchAuthorizationUI(accessTokenString: accessTokenString, authorizationDelegate: delegate)
25 | }
26 |
27 | public func launchSignUp(_ delegate: AuthorizationDelegate) {
28 | self.oauthManager.authorizationManager?.launchSignUpAuthorizationUI(authorizationDelegate: delegate)
29 | }
30 |
31 | public func launchChangePassword(_ delegate: AuthorizationDelegate) {
32 | self.oauthManager.authorizationManager?.launchChangePasswordUI(authorizationDelegate: delegate)
33 | }
34 |
35 | public func launchChangeDetails(_ delegate: AuthorizationDelegate) {
36 | self.oauthManager.authorizationManager?.launchChangeDetailsUI(authorizationDelegate: delegate)
37 | }
38 |
39 | public func launchForgotPassword(_ delegate: AuthorizationDelegate) {
40 | self.oauthManager.authorizationManager?.launchForgotPasswordUI(authorizationDelegate: delegate)
41 | }
42 |
43 | }
44 |
--------------------------------------------------------------------------------
/Source/IBMCloudAppID/internal/OAuthManager.swift:
--------------------------------------------------------------------------------
1 | /* * Copyright 2016, 2017 IBM Corp.
2 | * Licensed under the Apache License, Version 2.0 (the "License");
3 | * you may not use this file except in compliance with the License.
4 | * You may obtain a copy of the License at
5 | * http://www.apache.org/licenses/LICENSE-2.0
6 | * Unless required by applicable law or agreed to in writing, software
7 | * distributed under the License is distributed on an "AS IS" BASIS,
8 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9 | * See the License for the specific language governing permissions and
10 | * limitations under the License.
11 | */
12 |
13 |
14 | import Foundation
15 |
16 |
17 | public class OAuthManager {
18 | private(set) var appId:AppID
19 | private(set) var preferenceManager:PreferenceManager
20 | internal var registrationManager:RegistrationManager?
21 | internal var authorizationManager:AuthorizationManager?
22 | internal var tokenManager:TokenManager?
23 |
24 | init(appId:AppID) {
25 | self.appId = appId
26 | self.preferenceManager = PreferenceManager()
27 | self.registrationManager = RegistrationManager(oauthManager: self)
28 | self.authorizationManager = AuthorizationManager(oAuthManager: self)
29 | self.tokenManager = TokenManager(oAuthManager: self)
30 | }
31 |
32 | internal func setPreferredLocale(_ locale: Locale) {
33 | self.authorizationManager?.preferredLocale = locale
34 | }
35 |
36 | }
37 |
--------------------------------------------------------------------------------
/Source/IBMCloudAppID/internal/PreferenceManager.swift:
--------------------------------------------------------------------------------
1 | /* * Copyright 2016, 2017 IBM Corp.
2 | * Licensed under the Apache License, Version 2.0 (the "License");
3 | * you may not use this file except in compliance with the License.
4 | * You may obtain a copy of the License at
5 | * http://www.apache.org/licenses/LICENSE-2.0
6 | * Unless required by applicable law or agreed to in writing, software
7 | * distributed under the License is distributed on an "AS IS" BASIS,
8 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9 | * See the License for the specific language governing permissions and
10 | * limitations under the License.
11 | */
12 |
13 |
14 | import BMSCore
15 |
16 |
17 | internal class PreferenceManager {
18 |
19 | private(set) final var sharedPreferences:UserDefaults = UserDefaults.standard
20 | private static let logger = Logger.logger(name: Logger.bmsLoggerPrefix + "PreferenceManager")
21 |
22 | public func getStringPreference(name:String) -> StringPreference {
23 | return StringPreference(name: name, sharedPreferences: sharedPreferences)
24 | }
25 |
26 | public func getJSONPreference(name:String) -> JSONPreference {
27 | return JSONPreference(name: name, sharedPreferences: sharedPreferences)
28 | }
29 |
30 |
31 |
32 | }
33 |
34 |
35 |
--------------------------------------------------------------------------------
/Source/IBMCloudAppID/internal/RegistrationManager.swift:
--------------------------------------------------------------------------------
1 | /* * Copyright 2016, 2017 IBM Corp.
2 | * Licensed under the Apache License, Version 2.0 (the "License");
3 | * you may not use this file except in compliance with the License.
4 | * You may obtain a copy of the License at
5 | * http://www.apache.org/licenses/LICENSE-2.0
6 | * Unless required by applicable law or agreed to in writing, software
7 | * distributed under the License is distributed on an "AS IS" BASIS,
8 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9 | * See the License for the specific language governing permissions and
10 | * limitations under the License.
11 | */
12 |
13 | import Foundation
14 | import BMSCore
15 | internal class RegistrationManager {
16 | private var appId:AppID
17 | internal var preferenceManager:PreferenceManager
18 |
19 | internal static let logger = Logger.logger(name: AppIDConstants.RegistrationManagerLoggerName)
20 |
21 |
22 | internal init(oauthManager:OAuthManager) {
23 | self.appId = oauthManager.appId
24 | self.preferenceManager = oauthManager.preferenceManager
25 | }
26 |
27 |
28 | public func ensureRegistered(callback : @escaping (AppIDError?) -> Void) {
29 | let storedClientId:String? = self.getRegistrationDataString(name: AppIDConstants.client_id_String)
30 | let storedTenantId:String? = self.preferenceManager.getStringPreference(name: AppIDConstants.tenantPrefName).get()
31 | if storedClientId != nil && self.appId.tenantId == storedTenantId && privateKeyExist() {
32 | RegistrationManager.logger.debug(message: "OAuth client is already registered.")
33 | callback(nil)
34 | } else {
35 | RegistrationManager.logger.info(message: "Registering a new OAuth client")
36 | self.registerOAuthClient(callback: {(error: Error?) in
37 | guard error == nil else {
38 | RegistrationManager.logger.error(message: "Failed to register OAuth client")
39 | callback(AppIDError.registrationError(msg: "Failed to register OAuth client"))
40 | return
41 | }
42 |
43 | RegistrationManager.logger.info(message: "OAuth client successfully registered.")
44 | callback(nil)
45 | })
46 | }
47 | }
48 |
49 | internal func privateKeyExist() -> Bool {
50 | do {
51 | try _ = SecurityUtils.getKeyRefFromKeyChain(AppIDConstants.privateKeyIdentifier)
52 | return true
53 | } catch {
54 | return false
55 | }
56 | }
57 |
58 | internal func registerOAuthClient(callback :@escaping (Error?) -> Void) {
59 | guard let registrationParams = try? createRegistrationParams() else {
60 | callback(AppIDError.registrationError(msg: "Could not create registration params"))
61 | return
62 | }
63 | let internalCallBack:BMSCompletionHandler = {(response: Response?, error: Error?) in
64 | if error == nil {
65 | if let unWrappedResponse = response, unWrappedResponse.isSuccessful, let responseText = unWrappedResponse.responseText {
66 | self.preferenceManager.getJSONPreference(name: AppIDConstants.registrationDataPref).set(try? Utils.parseJsonStringtoDictionary(responseText))
67 | self.preferenceManager.getStringPreference(name: AppIDConstants.tenantPrefName).set(self.appId.tenantId)
68 | callback(nil)
69 | } else {
70 | callback(AppIDError.registrationError(msg: "Could not register client"))
71 | }
72 | } else {
73 | callback(error)
74 | }
75 | }
76 |
77 | let request:Request = Request(url: Config.getServerUrl(appId: self.appId) + "/clients",method: HttpMethod.POST, headers: [Request.contentType : "application/json"], queryParameters: nil, timeout: 0)
78 | request.timeout = BMSClient.sharedInstance.requestTimeout
79 | let registrationParamsAsData = try? Utils.urlEncode(Utils.JSONStringify(registrationParams as AnyObject)).data(using: .utf8) ?? Data()
80 | sendRequest(request: request, registrationParamsAsData: registrationParamsAsData, internalCallBack: internalCallBack)
81 |
82 | }
83 |
84 |
85 | internal func sendRequest(request:Request, registrationParamsAsData:Data?, internalCallBack: @escaping BMSCompletionHandler) {
86 | request.urlSession.isBMSAuthorizationRequest = true
87 | request.send(requestBody: registrationParamsAsData, completionHandler: internalCallBack)
88 | }
89 |
90 | internal func generateKeyPair() throws {
91 | try SecurityUtils.generateKeyPair(512, publicTag: AppIDConstants.publicKeyIdentifier, privateTag: AppIDConstants.privateKeyIdentifier)
92 | }
93 |
94 |
95 | private func createRegistrationParams() throws -> [String:Any] {
96 | do {
97 | try generateKeyPair()
98 | let deviceIdentity = BaseDeviceIdentity()
99 | let appIdentity = BaseAppIdentity()
100 | var params = [String:Any]()
101 | params[AppIDConstants.JSON_REDIRECT_URIS_KEY] = [AppIDConstants.REDIRECT_URI_VALUE]
102 | params[AppIDConstants.JSON_TOKEN_ENDPOINT_AUTH_METHOD_KEY] = AppIDConstants.CLIENT_SECRET_BASIC
103 | params[AppIDConstants.JSON_RESPONSE_TYPES_KEY] = [AppIDConstants.JSON_CODE_KEY]
104 | params[AppIDConstants.JSON_GRANT_TYPES_KEY] = [AppIDConstants.authorization_code_String, AppIDConstants.PASSWORD_STRING]
105 | params[AppIDConstants.JSON_CLIENT_NAME_KEY] = Bundle.main.object(forInfoDictionaryKey: "CFBundleDisplayName") as? String
106 | params[AppIDConstants.JSON_SOFTWARE_ID_KEY] = appIdentity.ID
107 | params[AppIDConstants.JSON_SOFTWARE_VERSION_KEY] = appIdentity.version
108 | params[AppIDConstants.JSON_DEVICE_ID_KEY] = deviceIdentity.ID
109 | params[AppIDConstants.JSON_MODEL_KEY] = deviceIdentity.model
110 | params[AppIDConstants.JSON_OS_KEY] = deviceIdentity.OS
111 | params[AppIDConstants.jsonOsVersionKey] = deviceIdentity.OSVersion
112 | params[AppIDConstants.JSON_CLIENT_TYPE_KEY] = AppIDConstants.MOBILE_APP_TYPE
113 |
114 | let jwks : [[String:Any]] = [try SecurityUtils.getJWKSHeader()]
115 |
116 | let keys = [
117 | AppIDConstants.JSON_KEYS_KEY : jwks
118 | ]
119 |
120 | params[AppIDConstants.JSON_JWKS_KEY] = keys
121 | return params
122 | } catch {
123 | throw AppIDError.registrationError(msg: "Failed to create registration params")
124 | }
125 | }
126 |
127 |
128 | public func getRegistrationData() -> [String:Any]? {
129 | return self.preferenceManager.getJSONPreference(name: AppIDConstants.registrationDataPref).getAsJSON()
130 | }
131 |
132 | public func getRegistrationDataString(name:String) -> String? {
133 | guard let registrationData = self.getRegistrationData() else {
134 | return nil
135 | }
136 | return registrationData[name] as? String
137 | }
138 |
139 | public func getRegistrationDataString(arrayName:String, arrayIndex:Int) -> String? {
140 | guard let registrationData = self.getRegistrationData() else {
141 | return nil
142 | }
143 | return (registrationData[arrayName] as? NSArray)?[arrayIndex] as? String
144 | }
145 |
146 |
147 | public func getRegistrationDataObject(name:String) -> [String:Any]? {
148 | guard let registrationData = self.getRegistrationData() else {
149 | return nil
150 | }
151 | return registrationData[name] as? [String:Any]
152 | }
153 | public func getRegistrationDataArray(name:String) -> NSArray? {
154 | guard let registrationData = self.getRegistrationData() else {
155 | return nil
156 | }
157 | return registrationData[name] as? NSArray
158 | }
159 |
160 |
161 | public func clearRegistrationData() {
162 | self.preferenceManager.getStringPreference(name: AppIDConstants.tenantPrefName).clear()
163 | self.preferenceManager.getJSONPreference(name: AppIDConstants.registrationDataPref).clear()
164 |
165 | }
166 |
167 |
168 | }
169 |
--------------------------------------------------------------------------------
/Source/IBMCloudAppID/internal/SecurityUtils.swift:
--------------------------------------------------------------------------------
1 | /* * Copyright 2016, 2017 IBM Corp.
2 | * Licensed under the Apache License, Version 2.0 (the "License");
3 | * you may not use this file except in compliance with the License.
4 | * You may obtain a copy of the License at
5 | * http://www.apache.org/licenses/LICENSE-2.0
6 | * Unless required by applicable law or agreed to in writing, software
7 | * distributed under the License is distributed on an "AS IS" BASIS,
8 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9 | * See the License for the specific language governing permissions and
10 | * limitations under the License.
11 | */
12 |
13 | import Foundation
14 |
15 |
16 | internal class SecurityUtils {
17 |
18 | private static func getKeyBitsFromKeyChain(_ tag:String) throws -> Data {
19 | let keyAttr : [NSString:AnyObject] = [
20 | kSecClass : kSecClassKey,
21 | kSecAttrApplicationTag: tag as AnyObject,
22 | kSecAttrKeyType : kSecAttrKeyTypeRSA,
23 | kSecReturnData : true as AnyObject
24 | ]
25 | var result: AnyObject?
26 | let status = SecItemCopyMatching(keyAttr as CFDictionary, &result)
27 |
28 | guard status == errSecSuccess else {
29 | throw AppIDError.generalError
30 | }
31 | return result as! Data
32 |
33 | }
34 |
35 | internal static func generateKeyPairAttrs(_ keySize:Int, publicTag:String, privateTag:String) -> [NSString:AnyObject] {
36 | let privateKeyAttr : [NSString:AnyObject] = [
37 | kSecAttrIsPermanent : true as AnyObject,
38 | kSecAttrApplicationTag : privateTag as AnyObject,
39 | kSecAttrKeyClass : kSecAttrKeyClassPrivate,
40 | kSecAttrAccessible: AppID.secAttrAccess.rawValue
41 | ]
42 |
43 | let publicKeyAttr : [NSString:AnyObject] = [
44 | kSecAttrIsPermanent : true as AnyObject,
45 | kSecAttrApplicationTag : publicTag as AnyObject,
46 | kSecAttrKeyClass : kSecAttrKeyClassPublic,
47 | kSecAttrAccessible: AppID.secAttrAccess.rawValue
48 | ]
49 |
50 | let keyPairAttr : [NSString:AnyObject] = [
51 | kSecAttrKeyType : kSecAttrKeyTypeRSA,
52 | kSecAttrAccessible: AppID.secAttrAccess.rawValue,
53 | kSecAttrKeySizeInBits : keySize as AnyObject,
54 | kSecPublicKeyAttrs : publicKeyAttr as AnyObject,
55 | kSecPrivateKeyAttrs : privateKeyAttr as AnyObject
56 | ]
57 | return keyPairAttr
58 | }
59 |
60 | internal static func generateKeyPair(_ keySize:Int, publicTag:String, privateTag:String) throws {
61 | //make sure keys are deleted
62 | _ = SecurityUtils.deleteKeyFromKeyChain(publicTag)
63 | _ = SecurityUtils.deleteKeyFromKeyChain(privateTag)
64 |
65 | var status:OSStatus = noErr
66 | var privateKey:SecKey?
67 | var publicKey:SecKey?
68 | let keyPairAttr = generateKeyPairAttrs(keySize, publicTag: publicTag, privateTag: privateTag)
69 | status = SecKeyGeneratePair(keyPairAttr as CFDictionary, &publicKey, &privateKey)
70 | if (status != errSecSuccess) {
71 | throw AppIDError.generalError
72 | }
73 | }
74 |
75 | static func getKeyRefFromKeyChain(_ tag:String) throws -> SecKey {
76 | let keyAttr : [NSString:AnyObject] = [
77 | kSecClass : kSecClassKey,
78 | kSecAttrApplicationTag: tag as AnyObject,
79 | kSecAttrKeyType : kSecAttrKeyTypeRSA,
80 | kSecReturnRef : kCFBooleanTrue
81 | ]
82 |
83 | var result: AnyObject?
84 |
85 | let status = SecItemCopyMatching(keyAttr as CFDictionary, &result)
86 |
87 | guard status == errSecSuccess else {
88 | throw AppIDError.generalError
89 | }
90 |
91 | return result as! SecKey
92 |
93 | }
94 |
95 |
96 | internal static func getItemFromKeyChain(_ label:String) -> String? {
97 | let query: [NSString: AnyObject] = [
98 | kSecClass: kSecClassGenericPassword,
99 | kSecAttrService: label as AnyObject,
100 | kSecReturnData: kCFBooleanTrue
101 | ]
102 | var results: AnyObject?
103 | let status = SecItemCopyMatching(query as CFDictionary, &results)
104 | if status == errSecSuccess {
105 | let data = results as! Data
106 | let password = String(data: data, encoding: String.Encoding.utf8)!
107 | return password
108 | }
109 |
110 | return nil
111 | }
112 |
113 |
114 | public static func getJWKSHeader() throws ->[String:Any] {
115 |
116 | let publicKey = try? SecurityUtils.getKeyBitsFromKeyChain(AppIDConstants.publicKeyIdentifier)
117 |
118 |
119 | guard let unWrappedPublicKey = publicKey, let pkModulus : Data = getPublicKeyMod(unWrappedPublicKey), let pkExponent : Data = getPublicKeyExp(unWrappedPublicKey) else {
120 | throw AppIDError.generalError
121 | }
122 |
123 | let mod:String = Utils.base64StringFromData(pkModulus, isSafeUrl: true)
124 |
125 | let exp:String = Utils.base64StringFromData(pkExponent, isSafeUrl: true)
126 |
127 | let publicKeyJSON : [String:Any] = [
128 | "e" : exp as AnyObject,
129 | "n" : mod as AnyObject,
130 | "kty" : AppIDConstants.JSON_RSA_VALUE
131 | ]
132 | return publicKeyJSON
133 |
134 | }
135 |
136 | private static func getPublicKeyMod(_ publicKeyBits: Data) -> Data? {
137 | var iterator : Int = 0
138 | iterator += 1 // TYPE - bit stream - mod + exp
139 | _ = derEncodingGetSizeFrom(publicKeyBits, at:&iterator) // Total size
140 | iterator += 1 // TYPE - bit stream mod
141 | let mod_size : Int = derEncodingGetSizeFrom(publicKeyBits, at:&iterator)
142 |
143 | // Ensure we got an exponent size
144 | guard mod_size != -1, let range = Range(NSMakeRange(iterator, mod_size)) else {
145 | return nil
146 | }
147 |
148 | return publicKeyBits.subdata(in: range)
149 | }
150 |
151 | //Return public key exponent
152 | private static func getPublicKeyExp(_ publicKeyBits: Data) -> Data? {
153 | var iterator : Int = 0
154 | iterator += 1 // TYPE - bit stream - mod + exp
155 | _ = derEncodingGetSizeFrom(publicKeyBits, at:&iterator) // Total size
156 | iterator += 1// TYPE - bit stream mod
157 | let mod_size : Int = derEncodingGetSizeFrom(publicKeyBits, at:&iterator)
158 | iterator += mod_size
159 |
160 | iterator += 1 // TYPE - bit stream exp
161 | let exp_size : Int = derEncodingGetSizeFrom(publicKeyBits, at:&iterator)
162 |
163 | //Ensure we got an exponent size
164 | guard exp_size != -1, let range = Range(NSMakeRange(iterator, exp_size)) else {
165 | return nil
166 | }
167 | return publicKeyBits.subdata(in: range)
168 | }
169 |
170 | private static func derEncodingGetSizeFrom(_ buf : Data, at iterator: inout Int) -> Int{
171 |
172 | // Have to cast the pointer to the right size
173 | //let pointer = UnsafePointer((buf as NSData).bytes)
174 | //let count = buf.count
175 |
176 | // Get our buffer pointer and make an array out of it
177 | //let buffer = UnsafeBufferPointer(start:pointer, count:count)
178 | let data = buf//[UInt8](buffer)
179 |
180 | var itr : Int = iterator
181 | var num_bytes :UInt8 = 1
182 | var ret : Int = 0
183 | if (data[itr] > 0x80) {
184 | num_bytes = data[itr] - 0x80
185 | itr += 1
186 | }
187 |
188 | for i in 0 ..< Int(num_bytes) {
189 | ret = (ret * 0x100) + Int(data[itr + i])
190 | }
191 |
192 | iterator = itr + Int(num_bytes)
193 |
194 | return ret
195 | }
196 |
197 |
198 | internal static func signString(_ payloadString:String, keyIds ids:(publicKey: String, privateKey: String), keySize: Int) throws -> String {
199 | do {
200 | let privateKeySec = try getKeyRefFromKeyChain(ids.privateKey)
201 |
202 | guard let payloadData : Data = payloadString.data(using: String.Encoding.utf8) else {
203 | throw AppIDError.generalError
204 | }
205 | let signedData = try signData(payloadData, privateKey:privateKeySec)
206 |
207 | //return signedData.base64EncodedString()
208 | return Utils.base64StringFromData(signedData, isSafeUrl: true)
209 | }
210 | catch {
211 | throw AppIDError.generalError
212 | }
213 | }
214 |
215 | private static func signData(_ data:Data, privateKey:SecKey) throws -> Data {
216 | func doSha256(_ dataIn:Data) throws -> Data {
217 | var hash = [UInt8](repeating: 0, count: Int(CC_SHA256_DIGEST_LENGTH))
218 | dataIn.withUnsafeBytes {
219 | _ = CC_SHA256($0, CC_LONG(dataIn.count), &hash)
220 | }
221 | return Data(bytes: hash)
222 | }
223 | guard let digest:Data = try? doSha256(data), let signedData: NSMutableData = NSMutableData(length: SecKeyGetBlockSize(privateKey)) else {
224 | throw AppIDError.generalError
225 | }
226 |
227 | var signedDataLength: Int = signedData.length
228 |
229 | let digestBytes: UnsafePointer = ((digest as NSData).bytes).bindMemory(to: UInt8.self, capacity: digest.count)
230 | let digestlen = digest.count
231 | let mutableBytes: UnsafeMutablePointer = signedData.mutableBytes.assumingMemoryBound(to: UInt8.self)
232 |
233 | let signStatus:OSStatus = SecKeyRawSign(privateKey, SecPadding.PKCS1SHA256, digestBytes, digestlen,
234 | mutableBytes, &signedDataLength)
235 |
236 | guard signStatus == errSecSuccess else {
237 | throw AppIDError.generalError
238 | }
239 |
240 | return signedData as Data
241 | }
242 |
243 | internal static func saveItemToKeyChain(_ data:String, label: String) -> Bool{
244 | guard let stringData = data.data(using: String.Encoding.utf8) else {
245 | return false
246 | }
247 | let key: [NSString: AnyObject] = [
248 | kSecClass: kSecClassGenericPassword,
249 | kSecAttrService: label as AnyObject,
250 | kSecValueData: stringData as AnyObject
251 | ]
252 | var status = SecItemAdd(key as CFDictionary, nil)
253 | if(status != errSecSuccess){
254 | if(SecurityUtils.removeItemFromKeyChain(label) == true) {
255 | status = SecItemAdd(key as CFDictionary, nil)
256 | }
257 | }
258 | return status == errSecSuccess
259 | }
260 |
261 | internal static func removeItemFromKeyChain(_ label: String) -> Bool{
262 |
263 | let delQuery : [NSString:AnyObject] = [
264 | kSecClass: kSecClassGenericPassword,
265 | kSecAttrService: label as AnyObject
266 | ]
267 | let delStatus:OSStatus = SecItemDelete(delQuery as CFDictionary)
268 | return delStatus == errSecSuccess
269 |
270 | }
271 |
272 | internal static func deleteKeyFromKeyChain(_ tag:String) -> Bool{
273 | let delQuery : [NSString:AnyObject] = [
274 | kSecClass : kSecClassKey,
275 | kSecAttrApplicationTag : tag as AnyObject
276 | ]
277 | let delStatus:OSStatus = SecItemDelete(delQuery as CFDictionary)
278 | return delStatus == errSecSuccess
279 | }
280 | }
281 |
--------------------------------------------------------------------------------
/Source/IBMCloudAppID/internal/StringPreference.swift:
--------------------------------------------------------------------------------
1 | /* * Copyright 2016, 2017 IBM Corp.
2 | * Licensed under the Apache License, Version 2.0 (the "License");
3 | * you may not use this file except in compliance with the License.
4 | * You may obtain a copy of the License at
5 | * http://www.apache.org/licenses/LICENSE-2.0
6 | * Unless required by applicable law or agreed to in writing, software
7 | * distributed under the License is distributed on an "AS IS" BASIS,
8 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9 | * See the License for the specific language governing permissions and
10 | * limitations under the License.
11 | */
12 |
13 |
14 | import Foundation
15 |
16 | /**
17 | * Holds single string preference value
18 | */
19 | internal class StringPreference {
20 | private(set) final var sharedPreferences:UserDefaults
21 | private final var name:String
22 |
23 | // TODO: should these be syncronized?
24 |
25 | init(name:String, sharedPreferences: UserDefaults) {
26 | self.name = name
27 | self.sharedPreferences = sharedPreferences
28 | }
29 |
30 | public func get() -> String? {
31 | return self.sharedPreferences.value(forKey: name) as? String
32 | }
33 |
34 |
35 | public func set(_ value:String?) {
36 | self.sharedPreferences.setValue(value, forKey: name)
37 | self.sharedPreferences.synchronize()
38 | }
39 |
40 | public func clear() {
41 | self.set(nil)
42 | }
43 |
44 | }
45 |
--------------------------------------------------------------------------------
/Source/IBMCloudAppID/internal/UserProfileManagerImpl.swift:
--------------------------------------------------------------------------------
1 | /* * Copyright 2016, 2017 IBM Corp.
2 | * Licensed under the Apache License, Version 2.0 (the "License");
3 | * you may not use this file except in compliance with the License.
4 | * You may obtain a copy of the License at
5 | * http://www.apache.org/licenses/LICENSE-2.0
6 | * Unless required by applicable law or agreed to in writing, software
7 | * distributed under the License is distributed on an "AS IS" BASIS,
8 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9 | * See the License for the specific language governing permissions and
10 | * limitations under the License.
11 | */
12 |
13 | import Foundation
14 | import BMSCore
15 |
16 | public class UserProfileManagerImpl: UserProfileManager {
17 |
18 | static var logger = Logger.logger(name: AppIDConstants.UserProfileManagerLoggerName)
19 |
20 | private var appId: AppID
21 |
22 | init(appId: AppID) {
23 | self.appId = appId
24 | }
25 |
26 | public func setAttribute(key: String, value: String, completionHandler: @escaping (Error?, [String: Any]?) -> Void) {
27 | sendAttributeRequest(method: HttpMethod.PUT, key: key, value: value, accessTokenString: getLatestAccessToken(), completionHandler: completionHandler)
28 | }
29 |
30 | public func setAttribute(key: String, value: String, accessTokenString: String, completionHandler: @escaping (Error?, [String: Any]?) -> Void) {
31 | sendAttributeRequest(method: HttpMethod.PUT, key: key, value: value, accessTokenString: accessTokenString, completionHandler: completionHandler)
32 | }
33 |
34 | public func getAttribute(key: String, completionHandler: @escaping (Error?, [String: Any]?) -> Void) {
35 | sendAttributeRequest(method: HttpMethod.GET, key: key, value: nil, accessTokenString: getLatestAccessToken(), completionHandler: completionHandler)
36 | }
37 |
38 | public func getAttribute(key: String, accessTokenString: String, completionHandler: @escaping (Error?, [String: Any]?) -> Void) {
39 | sendAttributeRequest(method: HttpMethod.GET, key: key, value: nil, accessTokenString: accessTokenString, completionHandler: completionHandler)
40 | }
41 |
42 | public func deleteAttribute(key: String, completionHandler: @escaping (Error?, [String: Any]?) -> Void) {
43 | sendAttributeRequest(method: HttpMethod.DELETE, key: key, value: nil, accessTokenString: getLatestAccessToken(), completionHandler: completionHandler)
44 | }
45 |
46 | public func deleteAttribute(key: String, accessTokenString: String, completionHandler: @escaping (Error?, [String: Any]?) -> Void) {
47 | sendAttributeRequest(method: HttpMethod.DELETE, key: key, value: nil, accessTokenString: accessTokenString, completionHandler: completionHandler)
48 | }
49 |
50 | public func getAttributes(completionHandler: @escaping (Error?, [String: Any]?) -> Void) {
51 | sendAttributeRequest(method: HttpMethod.GET, key: nil, value: nil, accessTokenString: getLatestAccessToken(), completionHandler: completionHandler)
52 | }
53 |
54 | public func getAttributes(accessTokenString: String, completionHandler: @escaping (Error?, [String: Any]?) -> Void) {
55 | sendAttributeRequest(method: HttpMethod.GET, key: nil, value: nil, accessTokenString: accessTokenString, completionHandler: completionHandler)
56 | }
57 |
58 | ///
59 | /// Retrieves user info using the latest access and identity tokens
60 | ///
61 | /// - Parameter completionHandler {(Error?, [String: Any]?) -> Void}: result handler
62 | ///
63 | public func getUserInfo(completionHandler: @escaping (Error?, [String: Any]?) -> Void) {
64 |
65 | guard let accessToken = getLatestAccessToken() else {
66 | return logAndFail(error: .missingAccessToken, completionHandler: completionHandler)
67 | }
68 |
69 | let sub = getLatestIdentityTokenSubject()
70 |
71 | sendUserInfoRequest(accessToken: accessToken, idTokenSub: sub, completionHandler: completionHandler)
72 | }
73 |
74 | ///
75 | /// Retrives user info using the provided tokens
76 | ///
77 | /// - Parameter accessToken {String}: the access token used for authorization
78 | /// - Parameter idToken {String}: an optional identity token to use for validation
79 | /// - Parameter completionHandler {(Error?, [String: Any]?) -> Void}: result handler
80 | ///
81 | public func getUserInfo(accessTokenString accessToken: String, identityTokenString idToken: String? = nil, completionHandler: @escaping (Error?, [String: Any]?) -> Void) {
82 |
83 | // If provided an identityToken, we should validate user info response if possible
84 | if let idToken = idToken {
85 |
86 | guard let identityToken = IdentityTokenImpl(with: idToken) else {
87 | return logAndFail(error: .missingOrMalformedIdToken, completionHandler: completionHandler)
88 | }
89 |
90 | // If subject exists, use for validation
91 | if let sub = identityToken.subject {
92 | return sendUserInfoRequest(accessToken: accessToken, idTokenSub: sub, completionHandler: completionHandler)
93 | }
94 | }
95 |
96 | sendUserInfoRequest(accessToken: accessToken, idTokenSub: nil, completionHandler: completionHandler)
97 | }
98 |
99 | ///
100 | /// Retrives user info using the provided access token and validates if data provided
101 | ///
102 | /// - Parameter accessToken {String}: the access token used for authorization
103 | /// - Parameter idTokenSub {String}: the subject field from the identity token used for validation
104 | /// - Parameter completionHandler {(Error?, [String: Any]?) -> Void}: result handler
105 | ///
106 | private func sendUserInfoRequest(accessToken: String, idTokenSub: String? = nil, completionHandler: @escaping (Error?, [String: Any]?) -> Void) {
107 |
108 | let url = Config.getServerUrl(appId: appId) + "/" + AppIDConstants.userInfoEndPoint
109 |
110 | sendRequest(url: url, method: HttpMethod.GET, accessToken: accessToken) { (error, info) in
111 |
112 | guard error == nil else {
113 | return completionHandler(error, nil)
114 | }
115 |
116 | // Validate reponse received and contains a subject
117 | guard let info = info, let subject = info["sub"] as? String else {
118 | return self.logAndFail(error: .invalidUserInfoResponse, completionHandler: completionHandler)
119 | }
120 |
121 | // If a subject was provided, attempt validation
122 | if let idTokenSub = idTokenSub, subject != idTokenSub {
123 | return self.logAndFail(error: .responseValidationError, completionHandler: completionHandler)
124 | }
125 |
126 | completionHandler(nil, info)
127 | }
128 | }
129 |
130 | ///
131 | /// Handler for an attribute request
132 | ///
133 | /// - Parameter method {HttpMethod}: the Http method to make the request with
134 | /// - Parameter key {String?}: the optional attribute name to target
135 | /// - Parameter value {String?}: the optional attribute value to set
136 | /// - Parameter accessTokenString {String?}: the access token to authorize request
137 | /// - Parameter completionHandler {(Error?, [String: Any]?) -> Void}: result handler
138 | ///
139 | internal func sendAttributeRequest(method: HttpMethod, key: String?, value: String?, accessTokenString: String?, completionHandler: @escaping (Error?, [String: Any]?) -> Void) {
140 |
141 | var urlString = Config.getAttributesUrl(appId: appId) + AppIDConstants.attibutesEndpoint
142 |
143 | if let key = key {
144 | urlString = urlString + "/" + Utils.urlEncode(key)
145 | }
146 |
147 | guard let accessToken = accessTokenString else {
148 | return completionHandler(UserProfileError.missingAccessToken, nil)
149 | }
150 |
151 | sendRequest(url: urlString, method: method, body: value, accessToken: accessToken, completionHandler: completionHandler)
152 |
153 | }
154 |
155 | ///
156 | /// Constructs a url request
157 | ///
158 | /// - Parameter url {String}: the url to make the request to
159 | /// - Parameter method {HTTPMethod}: the request method
160 | /// - Parameter body {String}: the value to add to the request body
161 | /// - Parameter accessToken {String}: access token used for authorization
162 | /// - Parameter completionHandler {(Error?, [String: Any]?) -> Void}: result handler
163 | ///
164 | private func sendRequest(url: String, method: HttpMethod, body: String? = nil, accessToken: String, completionHandler: @escaping (Error?, [String: Any]?) -> Void) {
165 |
166 | guard let url = URL(string: url) else {
167 | return self.logAndFail(error: "Failed to parse URL string", completionHandler: completionHandler)
168 | }
169 |
170 | var req = URLRequest(url: url)
171 | req.httpMethod = method.rawValue
172 | req.timeoutInterval = BMSClient.sharedInstance.requestTimeout
173 |
174 | req.setValue("application/json", forHTTPHeaderField: "Content-Type")
175 | req.setValue("Bearer " + accessToken, forHTTPHeaderField: "Authorization")
176 |
177 | if let value = body {
178 | req.httpBody = value.data(using: .utf8)
179 | }
180 |
181 | send(request: req) { (data, response, error) in
182 |
183 | guard error == nil else {
184 | let errString = error?.localizedDescription ?? "Encountered an error"
185 | return self.logAndFail(level: "error", error: errString, completionHandler: completionHandler)
186 | }
187 |
188 | guard let resp = response, let response = resp as? HTTPURLResponse else {
189 | return self.logAndFail(error: "Did not receive a response", completionHandler: completionHandler)
190 | }
191 |
192 | guard response.statusCode >= 200 && response.statusCode < 300 else {
193 | if response.statusCode == 401 {
194 | UserProfileManagerImpl.logger.warn(message: "Ensure user profiles feature is enabled in the App ID dashboard.")
195 | return self.logAndFail(error: .unauthorized, completionHandler: completionHandler)
196 | } else if response.statusCode == 404 {
197 | return self.logAndFail(error: .notFound, completionHandler: completionHandler)
198 | } else {
199 | return self.logAndFail(error: "Unexpected response from server. Status Code:" + String(response.statusCode), completionHandler: completionHandler)
200 | }
201 | }
202 |
203 | if response.statusCode == 204 {
204 | return completionHandler(nil, [:])
205 | }
206 |
207 | guard let responseData = data else {
208 | return self.logAndFail(error: "Failed to parse server response - no response text", completionHandler: completionHandler)
209 | }
210 |
211 | guard let respString = String(data: responseData, encoding: .utf8),
212 | let json = try? Utils.parseJsonStringtoDictionary(respString) else {
213 | return self.logAndFail(error: .bodyParsingError, completionHandler: completionHandler)
214 | }
215 |
216 | completionHandler(nil, json)
217 | }
218 | }
219 |
220 | ///
221 | /// Error Handler
222 | ///
223 | /// - Parameter error {String}: the error to log
224 | /// - Parameter completionHandler {String}: the callback handler
225 | private func logAndFail(level: String = "debug", error: String, completionHandler: @escaping (Error?, [String:Any]?) -> Void) {
226 | logAndFail(error: UserProfileError.general(error), completionHandler: completionHandler)
227 | }
228 |
229 | ///
230 | /// Error Handler
231 | ///
232 | /// - Parameter error {UserManagerError}: the error to log
233 | /// - Parameter completionHandler {String}: the callback handler
234 | private func logAndFail(level: String = "debug", error: UserProfileError, completionHandler: @escaping (Error?, [String: Any]?) -> Void) {
235 | log(level: level, msg: error.description)
236 | completionHandler(error, nil)
237 | }
238 |
239 | ///
240 | /// Logging Helper
241 | ///
242 | private func log(level: String, msg: String) {
243 | switch level {
244 | case "warn" : UserProfileManagerImpl.logger.warn(message: msg)
245 | case "error" : UserProfileManagerImpl.logger.error(message: msg)
246 | default: UserProfileManagerImpl.logger.debug(message: msg)
247 | }
248 | }
249 |
250 | ///
251 | /// Send URLRequest Executorg
252 | ///
253 | internal func send(request : URLRequest, handler: @escaping (Data?, URLResponse?, Error?) -> Void) {
254 | URLSession.shared.dataTask(with: request, completionHandler: handler).resume()
255 | }
256 |
257 | ///
258 | /// Retrieves the latest access token
259 | ///
260 | /// - Returns: the raw access token
261 | internal func getLatestAccessToken() -> String? {
262 | return appId.oauthManager?.tokenManager?.latestAccessToken?.raw
263 | }
264 |
265 | ///
266 | /// Retrieves the latest identity token subject field
267 | ///
268 | /// - Returns: the subject field from the latest identity token
269 | internal func getLatestIdentityTokenSubject() -> String? {
270 | return appId.oauthManager?.tokenManager?.latestIdentityToken?.subject
271 | }
272 |
273 | }
274 |
--------------------------------------------------------------------------------
/Source/IBMCloudAppID/internal/Utils.swift:
--------------------------------------------------------------------------------
1 | /* * Copyright 2016, 2017 IBM Corp.
2 | * Licensed under the Apache License, Version 2.0 (the "License");
3 | * you may not use this file except in compliance with the License.
4 | * You may obtain a copy of the License at
5 | * http://www.apache.org/licenses/LICENSE-2.0
6 | * Unless required by applicable law or agreed to in writing, software
7 | * distributed under the License is distributed on an "AS IS" BASIS,
8 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9 | * See the License for the specific language governing permissions and
10 | * limitations under the License.
11 | */
12 |
13 | import Foundation
14 | import BMSCore
15 |
16 |
17 | public class Utils {
18 |
19 | public static func base64urlToBase64(base64url: String) -> String {
20 | var base64 = base64url
21 | .replacingOccurrences(of: "-", with: "+")
22 | .replacingOccurrences(of: "_", with: "/")
23 | if base64.count % 4 != 0 {
24 | base64.append(String(repeating: "=", count: 4 - base64.count % 4))
25 | }
26 | return base64
27 | }
28 |
29 | public static func JSONStringify(_ value: AnyObject, prettyPrinted:Bool = false) throws -> String{
30 |
31 | let options = prettyPrinted ? JSONSerialization.WritingOptions.prettyPrinted : JSONSerialization.WritingOptions(rawValue: 0)
32 |
33 |
34 | if JSONSerialization.isValidJSONObject(value) {
35 | do {
36 | let data = try JSONSerialization.data(withJSONObject: value, options: options)
37 | guard let string = String(data: data, encoding: String.Encoding.utf8) else {
38 | throw AppIDError.jsonUtilsError(msg: "Json is malformed")
39 | }
40 | return string
41 | } catch {
42 | throw AppIDError.jsonUtilsError(msg: "Json is malformed")
43 | }
44 | }
45 | return ""
46 | }
47 |
48 | public static func parseJsonStringtoDictionary(_ jsonString:String) throws ->[String:Any] {
49 | do {
50 | guard let data = jsonString.data(using: String.Encoding.utf8), let responseJson = try JSONSerialization.jsonObject(with: data, options: []) as? [String:Any] else {
51 | throw AppIDError.jsonUtilsError(msg: "Json is malformed")
52 | }
53 | return responseJson as [String:Any]
54 | }
55 | }
56 |
57 | // TODO: did not delete this method as it is used in appidconstants
58 |
59 | //Return the App Name and Version
60 | internal static func getApplicationDetails() -> (name:String, version:String) {
61 | var version = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String
62 | var name = Bundle.main.bundleIdentifier
63 | if name == nil {
64 | name = "nil"
65 | }
66 | if version == nil {
67 | version = "nil"
68 | }
69 | return (name!, version!)
70 |
71 | }
72 |
73 |
74 | /**
75 | Decode base64 code
76 |
77 | - parameter strBase64: strBase64 the String to decode
78 |
79 | - returns: return decoded String
80 | */
81 |
82 | public static func decodeBase64WithString(_ strBase64:String, isSafeUrl:Bool) -> Data? {
83 |
84 | guard let objPointerHelper = strBase64.cString(using: String.Encoding.ascii), let objPointer = String(validatingUTF8: objPointerHelper) else {
85 | return nil
86 | }
87 |
88 | let intLengthFixed:Int = objPointer.count
89 | var result:[Int8] = [Int8](repeating: 1, count: intLengthFixed)
90 |
91 | var i:Int=0, j:Int=0, k:Int
92 | var count = 0
93 | var intLengthMutated:Int = objPointer.count
94 | var current:Character = objPointer[objPointer.index(objPointer.startIndex, offsetBy: count)]
95 |
96 | while (current != "\0" && intLengthMutated > 0) {
97 | intLengthMutated-=1
98 |
99 | if current == "=" {
100 | if count < intLengthFixed && objPointer[objPointer.index(objPointer.startIndex, offsetBy: count)] != "=" && i%4 == 1 {
101 | return nil
102 | }
103 | if count == intLengthFixed {
104 | break
105 | }
106 | current = objPointer[objPointer.index(objPointer.startIndex, offsetBy: count)]
107 | count+=1
108 | continue
109 | }
110 | let stringCurrent = String(current)
111 | let singleValueArrayCurrent: [UInt8] = Array(stringCurrent.utf8)
112 | let intCurrent:Int = Int(singleValueArrayCurrent[0])
113 | let int8Current = isSafeUrl ? AppIDConstants.base64DecodingTableUrlSafe[intCurrent] :AppIDConstants.base64DecodingTable[intCurrent]
114 |
115 | if int8Current == -1 {
116 | current = objPointer[objPointer.index(objPointer.startIndex, offsetBy: count)]
117 | count+=1
118 | continue
119 | } else if int8Current == -2 {
120 | return nil
121 | }
122 |
123 | switch (i % 4) {
124 | case 0:
125 | result[j] = int8Current << 2
126 | case 1:
127 | result[j] |= int8Current >> 4
128 | j+=1
129 | result[j] = (int8Current & 0x0f) << 4
130 | case 2:
131 | result[j] |= int8Current >> 2
132 | j+=1
133 | result[j] = (int8Current & 0x03) << 6
134 | case 3:
135 | result[j] |= int8Current
136 | j+=1
137 | default: break
138 | }
139 |
140 | i+=1
141 |
142 | if count == intLengthFixed - 1 {
143 | break
144 | }
145 | count+=1
146 | current = objPointer[objPointer.index(objPointer.startIndex, offsetBy: count)]
147 | }
148 |
149 | // mop things up if we ended on a boundary
150 | k = j
151 | if current == "=" {
152 | switch (i % 4) {
153 | case 1:
154 | // Invalid state
155 | return nil
156 | case 2:
157 | k += 1
158 | result[k] = 0
159 | case 3:
160 | result[k] = 0
161 | default:
162 | break
163 | }
164 | }
165 |
166 | // Setup the return NSData
167 | return Data(bytes: UnsafeRawPointer(result), count: j)
168 | }
169 |
170 | internal static func base64StringFromData(_ data:Data, length:Int, isSafeUrl:Bool) -> String {
171 | var ixtext:Int = 0
172 | var ctremaining:Int
173 | var input:[Int] = [Int](repeating: 0, count: 3)
174 | var output:[Int] = [Int](repeating: 0, count: 4)
175 | var charsonline:Int = 0, ctcopy:Int
176 | guard data.count >= 1 else {
177 | return ""
178 | }
179 | var result:String = ""
180 | let count = data.count / MemoryLayout.size
181 | var raw = [Int8](repeating: 0, count: count)
182 | (data as NSData).getBytes(&raw, length:count * MemoryLayout.size)
183 | while (true) {
184 | ctremaining = data.count - ixtext
185 | if ctremaining <= 0 {
186 | break
187 | }
188 | for i in 0..<3 {
189 | let ix:Int = ixtext + i
190 | if ix < data.count {
191 | input[i] = Int(raw[ix])
192 | } else {
193 | input[i] = 0
194 | }
195 | }
196 | output[0] = (input[0] & 0xFC) >> 2
197 | output[1] = ((input[0] & 0x03) << 4) | ((input[1] & 0xF0) >> 4)
198 | output[2] = ((input[1] & 0x0F) << 2) | ((input[2] & 0xC0) >> 6)
199 | output[3] = input[2] & 0x3F
200 | ctcopy = 4
201 | switch ctremaining {
202 | case 1:
203 | ctcopy = 2
204 | case 2:
205 | ctcopy = 3
206 | default: break
207 | }
208 |
209 | for i in 0.. 0) && (charsonline >= length) {
222 | charsonline = 0
223 | }
224 |
225 | }
226 |
227 | return result
228 | }
229 |
230 | internal static func base64StringFromData(_ data:Data, isSafeUrl:Bool) -> String {
231 | let length = data.count
232 | return base64StringFromData(data, length: length, isSafeUrl: isSafeUrl)
233 | }
234 |
235 | internal static func urlEncode(_ str:String) -> String{
236 | var encodedString = ""
237 | var unchangedCharacters = ""
238 | let FORM_ENCODE_SET = " \"':;<=>@[]^`{}|/\\?#&!$(),~%"
239 |
240 | for element: Int in 0x20..<0x7f {
241 | if !FORM_ENCODE_SET.contains(String(describing: UnicodeScalar(element))) {
242 | unchangedCharacters += String(Character(UnicodeScalar(element)!))
243 | }
244 | }
245 |
246 | encodedString = str.trimmingCharacters(in: CharacterSet(charactersIn: "\n\r\t"))
247 | let charactersToRemove = ["\n", "\r", "\t"]
248 | for char in charactersToRemove {
249 | encodedString = encodedString.replacingOccurrences(of: char, with: "")
250 | }
251 | if let encodedString = encodedString.addingPercentEncoding(withAllowedCharacters: CharacterSet(charactersIn: unchangedCharacters)) {
252 | return encodedString
253 | }
254 | else {
255 | return "nil"
256 | }
257 | }
258 |
259 |
260 | public static func getParamFromQuery(url:URL, paramName: String) -> String? {
261 | return url.query?.components(separatedBy: "&").filter({(item) in item.hasPrefix(paramName)}).first?.components(separatedBy: "=")[1]
262 | }
263 |
264 | public static func generateStateParameter(of length: Int) -> String? {
265 | var bytes = [UInt8](repeating: 0, count: length)
266 | let result = SecRandomCopyBytes(kSecRandomDefault, bytes.count, &bytes)
267 |
268 | guard result == errSecSuccess else {
269 | return nil
270 | }
271 | /// This Base64 url encodes the state parameter and removes extra padding
272 | /// When the urlencode utility method is cleaned up this can be replaced.
273 | return Data(bytes: bytes)
274 | .base64EncodedString()
275 | .replacingOccurrences(of: "+", with: "-")
276 | .replacingOccurrences(of: "/", with: "_")
277 | .trimmingCharacters(in: CharacterSet(charactersIn: "="))
278 | }
279 |
280 | }
281 |
--------------------------------------------------------------------------------
/Source/IBMCloudAppID/internal/safariView.swift:
--------------------------------------------------------------------------------
1 | /* * Copyright 2016, 2017 IBM Corp.
2 | * Licensed under the Apache License, Version 2.0 (the "License");
3 | * you may not use this file except in compliance with the License.
4 | * You may obtain a copy of the License at
5 | * http://www.apache.org/licenses/LICENSE-2.0
6 | * Unless required by applicable law or agreed to in writing, software
7 | * distributed under the License is distributed on an "AS IS" BASIS,
8 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9 | * See the License for the specific language governing permissions and
10 | * limitations under the License.
11 | */
12 |
13 | import Foundation
14 | import SafariServices
15 | import BMSCore
16 |
17 | internal class safariView : SFSafariViewController, SFSafariViewControllerDelegate {
18 |
19 | var authorizationDelegate:AuthorizationDelegate?
20 |
21 | public init(url URL: URL) {
22 | super.init(url: URL, entersReaderIfAvailable: false)
23 | self.delegate = self
24 | }
25 |
26 | public func safariViewControllerDidFinish(_ controller: SFSafariViewController) {
27 | authorizationDelegate?.onAuthorizationCanceled()
28 | }
29 |
30 | }
31 |
--------------------------------------------------------------------------------
/Source/IBMCloudAppID/internal/tokens/AbstractToken.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | public protocol Token {
4 |
5 | var raw: String {get}
6 | var header: Dictionary {get}
7 | var payload: Dictionary {get}
8 | var signature: String {get}
9 |
10 | var issuer: String? {get}
11 | var subject: String? {get}
12 | var audience: [String]? {get}
13 | var expiration: Date? {get}
14 | var issuedAt: Date? {get}
15 | var tenant: String? {get}
16 | var authenticationMethods: [String]? {get}
17 | var isExpired: Bool {get}
18 | var isAnonymous: Bool {get}
19 | }
20 |
21 | internal class AbstractToken: Token {
22 |
23 | private static let ISSUER = "iss"
24 | private static let SUBJECT = "sub"
25 | private static let AUDIENCE = "aud"
26 | private static let EXPIRATION = "exp"
27 | private static let ISSUED_AT = "iat"
28 | private static let TENANT = "tenant"
29 | private static let AUTH_METHODS = "amr"
30 |
31 | var raw: String
32 | var header: Dictionary
33 | var payload: Dictionary
34 | var signature: String
35 |
36 | internal init? (with raw: String) {
37 | self.raw = raw
38 | let tokenComponents = self.raw.components(separatedBy: ".")
39 | guard tokenComponents.count==3 else {
40 | return nil
41 | }
42 |
43 | let headerComponent = tokenComponents[0]
44 | let payloadComponent = tokenComponents[1]
45 | self.signature = tokenComponents[2]
46 |
47 | guard
48 | let headerDecodedData = Data(base64Encoded: Utils.base64urlToBase64(base64url: headerComponent)),
49 | let payloadDecodedData = Data(base64Encoded: Utils.base64urlToBase64(base64url: payloadComponent))
50 | else {
51 | return nil
52 | }
53 |
54 | guard
55 | let headerDecodedString = String(data: headerDecodedData, encoding: String.Encoding.utf8),
56 | let payloadDecodedString = String(data: payloadDecodedData, encoding: String.Encoding.utf8)
57 | else {
58 | return nil
59 | }
60 |
61 | guard
62 | let headerDictionary = try? Utils.parseJsonStringtoDictionary(headerDecodedString),
63 | let payloadDictionary = try? Utils.parseJsonStringtoDictionary(payloadDecodedString)
64 | else {
65 | return nil
66 | }
67 |
68 | self.header = headerDictionary
69 | self.payload = payloadDictionary
70 | }
71 |
72 | var issuer: String? {
73 | return payload[AbstractToken.ISSUER] as? String
74 | }
75 |
76 | var subject: String? {
77 | return payload[AbstractToken.SUBJECT] as? String
78 | }
79 |
80 | var audience: [String]? {
81 | return payload[AbstractToken.AUDIENCE] as? [String]
82 | }
83 |
84 | var expiration: Date? {
85 | guard let exp = payload[AbstractToken.EXPIRATION] as? Double else {
86 | return nil
87 | }
88 | return Date(timeIntervalSince1970: exp)
89 | }
90 |
91 | var issuedAt: Date? {
92 | guard let iat = payload[AbstractToken.ISSUED_AT] as? Double else {
93 | return nil
94 | }
95 | return Date(timeIntervalSince1970: iat)
96 | }
97 | var tenant: String? {
98 | return payload[AbstractToken.TENANT] as? String
99 | }
100 |
101 | var authenticationMethods: [String]? {
102 | return payload[AbstractToken.AUTH_METHODS] as? [String]
103 | }
104 |
105 | var isExpired: Bool {
106 | guard let exp = self.expiration else {
107 | return true
108 | }
109 | return exp < Date()
110 | }
111 |
112 | var isAnonymous: Bool {
113 | // TODO: complete this
114 | guard let amr = payload[AbstractToken.AUTH_METHODS] as? Array else {
115 | return false
116 | }
117 | return amr.contains("appid_anon")
118 | }
119 |
120 | }
121 |
--------------------------------------------------------------------------------
/Source/IBMCloudAppID/internal/tokens/AccessTokenImpl.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | internal class AccessTokenImpl: AbstractToken, AccessToken {
4 | private static let SCOPE = "scope"
5 |
6 | var scope: String? {
7 | return payload[AccessTokenImpl.SCOPE] as? String
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/Source/IBMCloudAppID/internal/tokens/IdentityTokenImpl.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | internal class IdentityTokenImpl: AbstractToken, IdentityToken {
4 |
5 | private static let NAME = "name"
6 | private static let EMAIL = "email"
7 | private static let GENDER = "gender"
8 | private static let LOCALE = "locale"
9 | private static let PICTURE = "picture"
10 | private static let IDENTITIES = "identities"
11 |
12 | var name: String? {
13 | return payload[IdentityTokenImpl.NAME] as? String
14 | }
15 |
16 | var email: String? {
17 | return payload[IdentityTokenImpl.EMAIL] as? String
18 | }
19 |
20 | var gender: String? {
21 | return payload[IdentityTokenImpl.GENDER] as? String
22 | }
23 |
24 | var locale: String? {
25 | return payload[IdentityTokenImpl.LOCALE] as? String
26 | }
27 |
28 | var picture: String? {
29 | return payload[IdentityTokenImpl.PICTURE] as? String
30 | }
31 |
32 | var identities: Array>? {
33 | return payload[IdentityTokenImpl.IDENTITIES] as? Array>
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/Source/IBMCloudAppID/internal/tokens/RefreshTokenImpl.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | internal class RefreshTokenImpl: RefreshToken {
4 | private var rawData = ""
5 |
6 | var raw: String? {
7 | return rawData
8 | }
9 |
10 | internal init? (with raw: String) {
11 | self.rawData = raw
12 | }
13 |
14 | }
15 |
--------------------------------------------------------------------------------
/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 | FMWK
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | $(CURRENT_PROJECT_VERSION)
21 | NSPrincipalClass
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/Source/Resources/IBMCloudAppID.h:
--------------------------------------------------------------------------------
1 | /* * Copyright 2016, 2017 IBM Corp.
2 | * Licensed under the Apache License, Version 2.0 (the "License");
3 | * you may not use this file except in compliance with the License.
4 | * You may obtain a copy of the License at
5 | * http://www.apache.org/licenses/LICENSE-2.0
6 | * Unless required by applicable law or agreed to in writing, software
7 | * distributed under the License is distributed on an "AS IS" BASIS,
8 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9 | * See the License for the specific language governing permissions and
10 | * limitations under the License.
11 | */
12 |
13 | #import
14 |
15 | //! Project version number for BMSSecurity.
16 | FOUNDATION_EXPORT double BMSSecurityVersionNumber;
17 |
18 | //! Project version string for BMSSecurity.
19 | FOUNDATION_EXPORT const unsigned char BMSSecurityVersionString[];
20 |
21 | // In this header, you should import all the public headers of your framework using statements like #import
22 |
23 | #if defined(__cplusplus)
24 | extern "C" {
25 | #endif
26 | typedef uint32_t CC_LONG; /* 32 bit unsigned integer */
27 |
28 | #define CC_SHA256_DIGEST_LENGTH 32 /* digest length in bytes */
29 | #define CC_SHA256_BLOCK_BYTES 64 /* block size in bytes */
30 | extern unsigned char *CC_SHA256(const void *data, CC_LONG len, unsigned char *md)
31 | __OSX_AVAILABLE_STARTING(__MAC_10_4, __IPHONE_2_0);
32 |
33 | #if defined(__cplusplus)
34 | }
35 | #endif
36 |
--------------------------------------------------------------------------------
/dummyAppForKeyChain/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | // dummyAppForKeyChain
4 | //
5 | // Created by Oded Betzalel on 16/02/2017.
6 | // Copyright © 2017 Oded Betzalel. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | @UIApplicationMain
12 | class AppDelegate: UIResponder, UIApplicationDelegate {
13 |
14 | var window: UIWindow?
15 |
16 |
17 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
18 | // Override point for customization after application launch.
19 | return true
20 | }
21 |
22 | func applicationWillResignActive(_ application: UIApplication) {
23 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
24 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game.
25 | }
26 |
27 | func applicationDidEnterBackground(_ application: UIApplication) {
28 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
29 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
30 | }
31 |
32 | func applicationWillEnterForeground(_ application: UIApplication) {
33 | // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background.
34 | }
35 |
36 | func applicationDidBecomeActive(_ application: UIApplication) {
37 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
38 | }
39 |
40 | func applicationWillTerminate(_ application: UIApplication) {
41 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
42 | }
43 |
44 |
45 | }
46 |
--------------------------------------------------------------------------------
/dummyAppForKeyChain/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "iphone",
5 | "size" : "20x20",
6 | "scale" : "2x"
7 | },
8 | {
9 | "idiom" : "iphone",
10 | "size" : "20x20",
11 | "scale" : "3x"
12 | },
13 | {
14 | "idiom" : "iphone",
15 | "size" : "29x29",
16 | "scale" : "2x"
17 | },
18 | {
19 | "idiom" : "iphone",
20 | "size" : "29x29",
21 | "scale" : "3x"
22 | },
23 | {
24 | "idiom" : "iphone",
25 | "size" : "40x40",
26 | "scale" : "2x"
27 | },
28 | {
29 | "idiom" : "iphone",
30 | "size" : "40x40",
31 | "scale" : "3x"
32 | },
33 | {
34 | "idiom" : "iphone",
35 | "size" : "60x60",
36 | "scale" : "2x"
37 | },
38 | {
39 | "idiom" : "iphone",
40 | "size" : "60x60",
41 | "scale" : "3x"
42 | },
43 | {
44 | "idiom" : "ios-marketing",
45 | "size" : "1024x1024",
46 | "scale" : "1x"
47 | }
48 | ],
49 | "info" : {
50 | "version" : 1,
51 | "author" : "xcode"
52 | }
53 | }
--------------------------------------------------------------------------------
/dummyAppForKeyChain/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 |
--------------------------------------------------------------------------------
/dummyAppForKeyChain/Base.lproj/Main.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 |
--------------------------------------------------------------------------------
/dummyAppForKeyChain/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 | CFBundleVersion
20 | 1
21 | LSRequiresIPhoneOS
22 |
23 | UILaunchStoryboardName
24 | LaunchScreen
25 | UIMainStoryboardFile
26 | Main
27 | UIRequiredDeviceCapabilities
28 |
29 | armv7
30 |
31 | UISupportedInterfaceOrientations
32 |
33 | UIInterfaceOrientationPortrait
34 | UIInterfaceOrientationLandscapeLeft
35 | UIInterfaceOrientationLandscapeRight
36 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/dummyAppForKeyChain/ViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ViewController.swift
3 | // dummyAppForKeyChain
4 | //
5 | // Created by Oded Betzalel on 16/02/2017.
6 | // Copyright © 2017 Oded Betzalel. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | class ViewController: UIViewController {
12 |
13 | override func viewDidLoad() {
14 | super.viewDidLoad()
15 | // Do any additional setup after loading the view, typically from a nib.
16 | }
17 |
18 | override func didReceiveMemoryWarning() {
19 | super.didReceiveMemoryWarning()
20 | // Dispose of any resources that can be recreated.
21 | }
22 |
23 |
24 | }
25 |
26 |
--------------------------------------------------------------------------------
/dummyAppForKeyChain/dummyAppForKeyChain.entitlements:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | keychain-access-groups
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/scripts/release.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # This script is used by Travis-CI to automatically release new versions of App ID.
4 | # First, we check if the version specified in the .podspec does not already exist as a git tag.
5 | # If the version does not exist yet, we add a git tag for this new version and publish to Cocoapods.
6 |
7 | set -ev
8 | cd ~/Documents
9 | # GITHUB_TOKEN required for Travis to have permissions to push to the App ID repository
10 | git clone https://ibm-bluemix-mobile-services:${GITHUB_TOKEN}@github.com/ibm-bluemix-mobile-services/appid-clientsdk-swift.git
11 | cd appid-clientsdk-swift
12 | git remote rm origin
13 | git remote add origin https://ibm-bluemix-mobile-services:${GITHUB_TOKEN}@github.com/ibm-bluemix-mobile-services/appid-clientsdk-swift.git
14 | version=$(grep -o 'version.*=.*[0-9]' IBMCloudAppID.podspec | cut -f 2 -d "'")
15 | git fetch --tags
16 | if [[ ! "$(git tag)" =~ "${version}" ]]; then
17 | echo "Publishing new version ${version} ";
18 | git tag $version;
19 | git push origin --tags;
20 | pod trunk push --allow-warnings;
21 | fi
22 |
--------------------------------------------------------------------------------