├── .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 | --------------------------------------------------------------------------------