From e2b6c07ba92c3b45c9da5b429a398f15676274ca Mon Sep 17 00:00:00 2001 From: Todd Scharien Date: Wed, 30 Nov 2022 10:30:33 -0800 Subject: [PATCH 01/30] [Automatic] Change case --- Podfile.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Podfile.lock b/Podfile.lock index bba6114..157f11b 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -7,7 +7,7 @@ DEPENDENCIES: - SwiftKeychainWrapper SPEC REPOS: - https://github.com/cocoapods/specs.git: + https://github.com/CocoaPods/Specs.git: - Alamofire - SwiftKeychainWrapper @@ -17,4 +17,4 @@ SPEC CHECKSUMS: PODFILE CHECKSUM: 5b9df332a135fcc090d04042e541929ed53aa5d4 -COCOAPODS: 1.5.3 +COCOAPODS: 1.11.3 From fe89fecc1381be66b7fa4b7f3aebc5ed7784e36a Mon Sep 17 00:00:00 2001 From: Todd Scharien Date: Wed, 30 Nov 2022 10:31:07 -0800 Subject: [PATCH 02/30] Change IOS deployment target to 12.0 --- SingleSignOn.xcodeproj/project.pbxproj | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/SingleSignOn.xcodeproj/project.pbxproj b/SingleSignOn.xcodeproj/project.pbxproj index 83ffce7..a425b97 100644 --- a/SingleSignOn.xcodeproj/project.pbxproj +++ b/SingleSignOn.xcodeproj/project.pbxproj @@ -46,7 +46,7 @@ 5610F06E202D13E7004CD2AC /* SingleSignOnTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SingleSignOnTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 5610F073202D13E7004CD2AC /* SingleSignOnTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SingleSignOnTests.swift; sourceTree = ""; }; 5610F075202D13E7004CD2AC /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - 5610F07F202D14E3004CD2AC /* Podfile */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = Podfile; sourceTree = SOURCE_ROOT; }; + 5610F07F202D14E3004CD2AC /* Podfile */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = Podfile; sourceTree = SOURCE_ROOT; xcLanguageSpecificationIdentifier = xcode.lang.ruby; }; 56FC88CB202D1845008F7642 /* KeycloakAPI.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeycloakAPI.swift; sourceTree = ""; }; 56FC88CC202D1845008F7642 /* Constants.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = ""; }; 56FC88CE202D1845008F7642 /* AuthViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AuthViewController.swift; sourceTree = ""; }; @@ -417,7 +417,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 11.2; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -473,7 +473,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 11.2; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; From 902e914a98e10f1251dcc356cf6359301edf3f57 Mon Sep 17 00:00:00 2001 From: Todd Scharien Date: Mon, 5 Dec 2022 10:21:45 -0800 Subject: [PATCH 03/30] Replace Alamofire with AppAuth library --- Podfile | 2 +- Podfile.lock | 15 ++++++--- SingleSignOn/API/KeycloakAPI.swift | 49 ++---------------------------- 3 files changed, 13 insertions(+), 53 deletions(-) diff --git a/Podfile b/Podfile index 444d2bc..fb92c19 100644 --- a/Podfile +++ b/Podfile @@ -5,6 +5,6 @@ platform :ios, '9.0' use_frameworks! target 'SingleSignOn' do - pod 'Alamofire' + pod 'AppAuth', '1.6.0' pod 'SwiftKeychainWrapper' end diff --git a/Podfile.lock b/Podfile.lock index 157f11b..4c88ca2 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -1,20 +1,25 @@ PODS: - - Alamofire (4.7.3) + - AppAuth (1.6.0): + - AppAuth/Core (= 1.6.0) + - AppAuth/ExternalUserAgent (= 1.6.0) + - AppAuth/Core (1.6.0) + - AppAuth/ExternalUserAgent (1.6.0): + - AppAuth/Core - SwiftKeychainWrapper (3.0.1) DEPENDENCIES: - - Alamofire + - AppAuth (= 1.6.0) - SwiftKeychainWrapper SPEC REPOS: https://github.com/CocoaPods/Specs.git: - - Alamofire + - AppAuth - SwiftKeychainWrapper SPEC CHECKSUMS: - Alamofire: c7287b6e5d7da964a70935e5db17046b7fde6568 + AppAuth: 8fca6b5563a5baef2c04bee27538025e4ceb2add SwiftKeychainWrapper: 38952a3636320ae61bad3513cadd870929de7a4a -PODFILE CHECKSUM: 5b9df332a135fcc090d04042e541929ed53aa5d4 +PODFILE CHECKSUM: 7312ac5c30bb9e5d65fc2d0f592bba66de343fc6 COCOAPODS: 1.11.3 diff --git a/SingleSignOn/API/KeycloakAPI.swift b/SingleSignOn/API/KeycloakAPI.swift index a26d0c9..bb5029f 100644 --- a/SingleSignOn/API/KeycloakAPI.swift +++ b/SingleSignOn/API/KeycloakAPI.swift @@ -19,7 +19,6 @@ // import Foundation -import Alamofire class KeycloakAPI { @@ -29,57 +28,13 @@ class KeycloakAPI { let params = ["grant_type": grantType, "redirect_uri": redirectUri, "client_id": clientId, "code": code] - Alamofire.request(url, method: .post, parameters: params, encoding: URLEncoding.default) - .responseJSON { response in - - guard response.result.isSuccess else { - completionHandler(nil, AuthenticationError.unknownError) - return - } - - guard let json = response.result.value as? [String: Any] else { - print("No JSON returned in response.") - print("Error: \(String(describing: response.result.error))") - - completionHandler(nil, AuthenticationError.unknownError) - return - } - - let model = Credentials(withJSON: json) - completionHandler(model, nil) - } + // TODO: implement AppAuth } class func refresh(credentials: Credentials, url: URL, grantType: String, redirectUri: String, clientId: String, completionHandler: @escaping (_ response: Credentials?, _ error: Error?) -> ()) { let params = ["grant_type": grantType, "redirect_uri": redirectUri, "client_id": clientId, "refresh_token": credentials.refreshToken] - Alamofire.request(url, method: .post, parameters: params, encoding: URLEncoding.default) - .responseJSON { response in - - guard response.result.isSuccess else { - completionHandler(nil, AuthenticationError.unknownError) - return - } - - guard let json = response.result.value as? [String: Any], json["error"] == nil else { - print("result error: \(String(describing: response.result.error))") - if let json = response.result.value as? [String: Any] { - let errorMessage = String(describing: json["error_description"] ?? "No message supplied") - print("result message: \(errorMessage)") - - if errorMessage == KeycloakAPI.refreshTokenExpiredMessage { - completionHandler(nil, AuthenticationError.expired) - return - } - } - - completionHandler(nil, AuthenticationError.unknownError) - return - } - - let model = Credentials(withJSON: json) - completionHandler(model, nil) - } + // TODO: implement AppAuth } } From 13c91d9121893af2cc8f6d85437cacc28b5a5898 Mon Sep 17 00:00:00 2001 From: Todd Scharien Date: Mon, 5 Dec 2022 10:24:24 -0800 Subject: [PATCH 04/30] Explicitly set SwiftKeychainWrapper version --- Podfile | 2 +- Podfile.lock | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Podfile b/Podfile index fb92c19..cce30cf 100644 --- a/Podfile +++ b/Podfile @@ -6,5 +6,5 @@ use_frameworks! target 'SingleSignOn' do pod 'AppAuth', '1.6.0' - pod 'SwiftKeychainWrapper' + pod 'SwiftKeychainWrapper', '3.0.1' end diff --git a/Podfile.lock b/Podfile.lock index 4c88ca2..820d336 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -9,7 +9,7 @@ PODS: DEPENDENCIES: - AppAuth (= 1.6.0) - - SwiftKeychainWrapper + - SwiftKeychainWrapper (= 3.0.1) SPEC REPOS: https://github.com/CocoaPods/Specs.git: @@ -20,6 +20,6 @@ SPEC CHECKSUMS: AppAuth: 8fca6b5563a5baef2c04bee27538025e4ceb2add SwiftKeychainWrapper: 38952a3636320ae61bad3513cadd870929de7a4a -PODFILE CHECKSUM: 7312ac5c30bb9e5d65fc2d0f592bba66de343fc6 +PODFILE CHECKSUM: 5b873ee44bd67706d8f252c520a9c79759b9f275 COCOAPODS: 1.11.3 From d2d6cf877990d6b285f7a5e2174fc51af8d13e1d Mon Sep 17 00:00:00 2001 From: Todd Scharien Date: Mon, 5 Dec 2022 10:26:25 -0800 Subject: [PATCH 05/30] Use explicit .self reference --- SingleSignOn/UI/AuthViewController.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SingleSignOn/UI/AuthViewController.swift b/SingleSignOn/UI/AuthViewController.swift index 7b154dc..2a00d2a 100644 --- a/SingleSignOn/UI/AuthViewController.swift +++ b/SingleSignOn/UI/AuthViewController.swift @@ -39,7 +39,7 @@ public class AuthViewController: UIViewController { }() private let headerView: WebHeaderView = { let bundle = Bundle(for: WebHeaderView.self) - let v = bundle.loadNibNamed("WebHeaderView", owner: self, options: nil)?.first as! WebHeaderView + let v = bundle.loadNibNamed("WebHeaderView", owner: AuthViewController.self, options: nil)?.first as! WebHeaderView v.translatesAutoresizingMaskIntoConstraints = false return v From 3454d297a89e2aa1d217a9b5e561c0e810f49152 Mon Sep 17 00:00:00 2001 From: Todd Scharien Date: Tue, 6 Dec 2022 09:40:32 -0800 Subject: [PATCH 06/30] Add whitespace to function calls --- SingleSignOn/API/KeycloakAPI.swift | 17 +++++++++++++++-- SingleSignOn/Services/AuthServices.swift | 14 ++++++++++++-- 2 files changed, 27 insertions(+), 4 deletions(-) diff --git a/SingleSignOn/API/KeycloakAPI.swift b/SingleSignOn/API/KeycloakAPI.swift index bb5029f..c0a3395 100644 --- a/SingleSignOn/API/KeycloakAPI.swift +++ b/SingleSignOn/API/KeycloakAPI.swift @@ -19,19 +19,32 @@ // import Foundation +import AppAuth class KeycloakAPI { static let refreshTokenExpiredMessage = "Refresh token expired" - class func exchange(oneTimeCode code: String, url: URL, grantType: String, redirectUri: String, clientId: String, completionHandler: @escaping (_ response: Credentials?, _ error: Error?) -> ()) { + class func exchange(oneTimeCode code: String, + url: URL, + grantType: String, + redirectUri: String, + clientId: String, + completionHandler: @escaping (_ response: Credentials?, _ error: Error?) -> ()) + { let params = ["grant_type": grantType, "redirect_uri": redirectUri, "client_id": clientId, "code": code] // TODO: implement AppAuth } - class func refresh(credentials: Credentials, url: URL, grantType: String, redirectUri: String, clientId: String, completionHandler: @escaping (_ response: Credentials?, _ error: Error?) -> ()) { + class func refresh(credentials: Credentials, + url: URL, + grantType: String, + redirectUri: String, + clientId: String, + completionHandler: @escaping (_ response: Credentials?, _ error: Error?) -> ()) + { let params = ["grant_type": grantType, "redirect_uri": redirectUri, "client_id": clientId, "refresh_token": credentials.refreshToken] diff --git a/SingleSignOn/Services/AuthServices.swift b/SingleSignOn/Services/AuthServices.swift index 5a887b7..66bda93 100644 --- a/SingleSignOn/Services/AuthServices.swift +++ b/SingleSignOn/Services/AuthServices.swift @@ -69,7 +69,12 @@ public class AuthServices: NSObject { let endpoint = Constants.API.token.replacingOccurrences(of: Constants.API.realmToken, with: realm) let url = baseUrl.appendingPathComponent(endpoint) - KeycloakAPI.exchange(oneTimeCode: oneTimeCode, url: url, grantType: Constants.GrantType.authorizationCode.rawValue, redirectUri: redirectUri, clientId: clientId) { (credentials: Credentials?, error: Error?) in + KeycloakAPI.exchange(oneTimeCode: oneTimeCode, + url: url, + grantType: Constants.GrantType.authorizationCode.rawValue, + redirectUri: redirectUri, + clientId: clientId) + { (credentials: Credentials?, error: Error?) in self.credentials = credentials completion(credentials, error) @@ -90,7 +95,12 @@ public class AuthServices: NSObject { let endpoint = Constants.API.token.replacingOccurrences(of: Constants.API.realmToken, with: realm) let url = baseUrl.appendingPathComponent(endpoint) - KeycloakAPI.refresh(credentials: credentials, url: url, grantType: Constants.GrantType.refreshToken.rawValue, redirectUri: redirectUri, clientId: clientId) { (credentials: Credentials?, error: Error?) in + KeycloakAPI.refresh(credentials: credentials, + url: url, + grantType: Constants.GrantType.refreshToken.rawValue, + redirectUri: redirectUri, + clientId: clientId) + { (credentials: Credentials?, error: Error?) in self.credentials = credentials completion(credentials, error) From fd0ebb1f1d9a85f6059bac3105352769eb7eb60d Mon Sep 17 00:00:00 2001 From: Todd Scharien Date: Wed, 7 Dec 2022 13:07:48 -0800 Subject: [PATCH 07/30] Replace Alamofire with AppAuth in .podspec --- SingleSignOn.podspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SingleSignOn.podspec b/SingleSignOn.podspec index c44094a..3655d60 100644 --- a/SingleSignOn.podspec +++ b/SingleSignOn.podspec @@ -12,5 +12,5 @@ Pod::Spec.new do |s| s.resources = 'SingleSignOn/**/*.{storyboard,xib,xcassets}' s.requires_arc = true s.dependency 'SwiftKeychainWrapper', '~> 3.0.1' - s.dependency 'Alamofire', '~> 4.7.3' + s.dependency 'AppAuth', '1.6.0' end From c165e723a305a6165596754b5636e7ad8d24796e Mon Sep 17 00:00:00 2001 From: Todd Scharien Date: Wed, 4 Jan 2023 09:28:06 -0800 Subject: [PATCH 08/30] Format multiline statements for readability --- SingleSignOn/Model/Credentials.swift | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/SingleSignOn/Model/Credentials.swift b/SingleSignOn/Model/Credentials.swift index 2a7df2d..4af48cf 100644 --- a/SingleSignOn/Model/Credentials.swift +++ b/SingleSignOn/Model/Credentials.swift @@ -75,7 +75,11 @@ public struct Credentials { // If we are loading credentials from the keychain we will have two additional fields representing when the // tokens will expire. Otherwise they need to be created - if let refreshExpiresAtString = data["refreshExpiresAt"] as? String, let refreshExpiresAt = Credentials.toDate(string: refreshExpiresAtString), let expiresAtString = data["expiresAt"] as? String, let expiresAt = Credentials.toDate(string: expiresAtString) { + if let refreshExpiresAtString = data["refreshExpiresAt"] as? String, + let refreshExpiresAt = Credentials.toDate(string: refreshExpiresAtString), + let expiresAtString = data["expiresAt"] as? String, + let expiresAt = Credentials.toDate(string: expiresAtString) { + self.refreshExpiresAt = refreshExpiresAt self.expiresAt = expiresAt } else { @@ -84,7 +88,17 @@ public struct Credentials { } // Used to serialize this object so it can be stored in the keychian - props = ["token_type": tokenType, "refresh_token": refreshToken, "access_token": accessToken, "session_state": sessionState, "refresh_expires_in": refreshExpiresIn, "not-before-policy": notBeforePolicy, "expires_in": expiresIn, "refreshExpiresAt": Credentials.dateToString(date: refreshExpiresAt), "expiresAt": Credentials.dateToString(date: expiresAt)] + props = [ + "token_type": tokenType, + "refresh_token": refreshToken, + "access_token": accessToken, + "session_state": sessionState, + "refresh_expires_in": refreshExpiresIn, + "not-before-policy": notBeforePolicy, + "expires_in": expiresIn, + "refreshExpiresAt": Credentials.dateToString(date: refreshExpiresAt), + "expiresAt": Credentials.dateToString(date: expiresAt) + ] save() } From 8fbf1a473c675a9680e87fd3bee0eacd091f08e7 Mon Sep 17 00:00:00 2001 From: Todd Scharien Date: Wed, 4 Jan 2023 10:30:57 -0800 Subject: [PATCH 09/30] Replace literals with constants --- SingleSignOn/Model/Credentials.swift | 48 +++++++++++++++++----------- 1 file changed, 30 insertions(+), 18 deletions(-) diff --git a/SingleSignOn/Model/Credentials.swift b/SingleSignOn/Model/Credentials.swift index 4af48cf..a16aa28 100644 --- a/SingleSignOn/Model/Credentials.swift +++ b/SingleSignOn/Model/Credentials.swift @@ -24,6 +24,18 @@ import SwiftKeychainWrapper public struct Credentials { + public struct Key { + public static let AccessToken = "access_token" + public static let TokenType = "token_type" + public static let RefreshToken = "refresh_token" + public static let SessionState = "session_state" + public static let RefreshExpiresIn = "refresh_expires_in" + public static let RefreshExpiresAt = "refreshExpiresAt" + public static let NotBeforePolicy = "not-before-policy" + public static let ExpiresIn = "expires_in" + public static let ExpiresAt = "expiresAt" + } + public let accessToken: String internal let tokenType: String internal let refreshToken: String @@ -65,19 +77,19 @@ public struct Credentials { init(withJSON data: [String: Any]) { - tokenType = data["token_type"] as! String - refreshToken = data["refresh_token"] as! String - accessToken = data["access_token"] as! String - sessionState = data["session_state"] as! String - refreshExpiresIn = data["refresh_expires_in"] as! Int // in sec - notBeforePolicy = data["not-before-policy"] as! Int - expiresIn = data["expires_in"] as! Int // in sec + tokenType = data[Key.TokenType] as! String + refreshToken = data[Key.RefreshToken] as! String + accessToken = data[Key.AccessToken] as! String + sessionState = data[Key.SessionState] as! String + refreshExpiresIn = data[Key.RefreshExpiresIn] as! Int // in sec + notBeforePolicy = data[Key.NotBeforePolicy] as! Int + expiresIn = data[Key.ExpiresIn] as! Int // in sec // If we are loading credentials from the keychain we will have two additional fields representing when the // tokens will expire. Otherwise they need to be created - if let refreshExpiresAtString = data["refreshExpiresAt"] as? String, + if let refreshExpiresAtString = data[Key.RefreshExpiresAt] as? String, let refreshExpiresAt = Credentials.toDate(string: refreshExpiresAtString), - let expiresAtString = data["expiresAt"] as? String, + let expiresAtString = data[Key.ExpiresAt] as? String, let expiresAt = Credentials.toDate(string: expiresAtString) { self.refreshExpiresAt = refreshExpiresAt @@ -89,15 +101,15 @@ public struct Credentials { // Used to serialize this object so it can be stored in the keychian props = [ - "token_type": tokenType, - "refresh_token": refreshToken, - "access_token": accessToken, - "session_state": sessionState, - "refresh_expires_in": refreshExpiresIn, - "not-before-policy": notBeforePolicy, - "expires_in": expiresIn, - "refreshExpiresAt": Credentials.dateToString(date: refreshExpiresAt), - "expiresAt": Credentials.dateToString(date: expiresAt) + Key.TokenType: tokenType, + Key.RefreshToken: refreshToken, + Key.AccessToken: accessToken, + Key.SessionState: sessionState, + Key.RefreshExpiresIn: refreshExpiresIn, + Key.NotBeforePolicy: notBeforePolicy, + Key.ExpiresIn: expiresIn, + Key.RefreshExpiresAt: Credentials.dateToString(date: refreshExpiresAt), + Key.ExpiresAt: Credentials.dateToString(date: expiresAt) ] save() From a77c2911278ba3bf3805253e3b008dacc3e55e8b Mon Sep 17 00:00:00 2001 From: Todd Scharien Date: Fri, 13 Jan 2023 09:36:46 -0800 Subject: [PATCH 10/30] Update pbxproj [automated change] --- SingleSignOn.xcodeproj/project.pbxproj | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/SingleSignOn.xcodeproj/project.pbxproj b/SingleSignOn.xcodeproj/project.pbxproj index a425b97..29f0190 100644 --- a/SingleSignOn.xcodeproj/project.pbxproj +++ b/SingleSignOn.xcodeproj/project.pbxproj @@ -26,6 +26,7 @@ 56FC88E8202D1871008F7642 /* README.md in Resources */ = {isa = PBXBuildFile; fileRef = 56FC88E7202D1871008F7642 /* README.md */; }; 56FC88EA202E0E04008F7642 /* SingleSignOn.podspec in Resources */ = {isa = PBXBuildFile; fileRef = 56FC88E9202E0E04008F7642 /* SingleSignOn.podspec */; }; BBF776BE94BDB4E00ABA2247 /* Pods_SingleSignOn.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 133211EAD64B6E7010491C5B /* Pods_SingleSignOn.framework */; }; + DA2D1D8B2965E3FC000FC010 /* OIDTokenResponseExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA2D1D8A2965E3FC000FC010 /* OIDTokenResponseExtension.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -62,6 +63,7 @@ 56FC88E7202D1871008F7642 /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; 56FC88E9202E0E04008F7642 /* SingleSignOn.podspec */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = SingleSignOn.podspec; sourceTree = SOURCE_ROOT; }; 9DBB25529445D12D3C9FEEA5 /* Pods-SingleSignOn.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SingleSignOn.debug.xcconfig"; path = "Pods/Target Support Files/Pods-SingleSignOn/Pods-SingleSignOn.debug.xcconfig"; sourceTree = ""; }; + DA2D1D8A2965E3FC000FC010 /* OIDTokenResponseExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OIDTokenResponseExtension.swift; sourceTree = ""; }; EBE0D3BF6EC9DFD049322169 /* Pods-SingleSignOn.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SingleSignOn.release.xcconfig"; path = "Pods/Target Support Files/Pods-SingleSignOn/Pods-SingleSignOn.release.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ @@ -125,6 +127,7 @@ 5610F07F202D14E3004CD2AC /* Podfile */, 56FC88E9202E0E04008F7642 /* SingleSignOn.podspec */, 56FC88CA202D1845008F7642 /* API */, + DA2D1D8E2971CE68000FC010 /* Extensions */, 56FC88D6202D1845008F7642 /* Model */, 56FC88D8202D1845008F7642 /* Services */, 56FC88CD202D1845008F7642 /* UI */, @@ -195,6 +198,14 @@ name = Frameworks; sourceTree = ""; }; + DA2D1D8E2971CE68000FC010 /* Extensions */ = { + isa = PBXGroup; + children = ( + DA2D1D8A2965E3FC000FC010 /* OIDTokenResponseExtension.swift */, + ); + path = Extensions; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXHeadersBuildPhase section */ @@ -340,6 +351,7 @@ 56FC88DD202D1846008F7642 /* AuthViewController.swift in Sources */, 56FC88E1202D1846008F7642 /* AuthenticationDelegate.swift in Sources */, 56FC88E5202D1846008F7642 /* AuthServices.swift in Sources */, + DA2D1D8B2965E3FC000FC010 /* OIDTokenResponseExtension.swift in Sources */, 56FC88E4202D1846008F7642 /* Credentials.swift in Sources */, 56FC88DF202D1846008F7642 /* AuthenticationError.swift in Sources */, 56FC88E2202D1846008F7642 /* WebHeaderView.swift in Sources */, From 64e1f89362e783ebe499b7775bb785f44c0eb8e5 Mon Sep 17 00:00:00 2001 From: Todd Scharien Date: Fri, 13 Jan 2023 09:45:35 -0800 Subject: [PATCH 11/30] Add extension method for token conversion --- .../OIDTokenResponseExtension.swift | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 SingleSignOn/Extensions/OIDTokenResponseExtension.swift diff --git a/SingleSignOn/Extensions/OIDTokenResponseExtension.swift b/SingleSignOn/Extensions/OIDTokenResponseExtension.swift new file mode 100644 index 0000000..31731e9 --- /dev/null +++ b/SingleSignOn/Extensions/OIDTokenResponseExtension.swift @@ -0,0 +1,30 @@ +// +// OIDTokenResponseExtension.swift +// SingleSignOn +// +// Created by Scharien, Todd SDPR:EX on 2023-01-04. +// Copyright © 2023 Jason Leach. All rights reserved. +// + +import Foundation +import AppAuth + +extension OIDTokenResponse { + + func toCredentials() -> Credentials { + + return Credentials(withJSON: [ + Credentials.Key.TokenType: tokenType!, + Credentials.Key.RefreshToken: refreshToken!, + Credentials.Key.AccessToken: accessToken!, + Credentials.Key.SessionState: value(forKey: Credentials.Key.SessionState) as Any, + Credentials.Key.RefreshExpiresIn: value(forKey: Credentials.Key.RefreshExpiresIn) as Any, + Credentials.Key.RefreshExpiresAt: value(forKey: Credentials.Key.RefreshExpiresAt) as Any, + Credentials.Key.NotBeforePolicy: value(forKey: Credentials.Key.NotBeforePolicy) as Any, + Credentials.Key.ExpiresIn: value(forKey: Credentials.Key.ExpiresIn) as Any, + Credentials.Key.ExpiresAt: accessTokenExpirationDate! + ]) + + } + +} From 62e2929625663f197d1edd4a332ac3a6aa12eabf Mon Sep 17 00:00:00 2001 From: Todd Scharien Date: Fri, 13 Jan 2023 09:49:32 -0800 Subject: [PATCH 12/30] Remove KeycloakAPI implementation --- SingleSignOn.xcodeproj/project.pbxproj | 12 ------ SingleSignOn/API/KeycloakAPI.swift | 53 -------------------------- 2 files changed, 65 deletions(-) delete mode 100644 SingleSignOn/API/KeycloakAPI.swift diff --git a/SingleSignOn.xcodeproj/project.pbxproj b/SingleSignOn.xcodeproj/project.pbxproj index 29f0190..c6dd43d 100644 --- a/SingleSignOn.xcodeproj/project.pbxproj +++ b/SingleSignOn.xcodeproj/project.pbxproj @@ -11,7 +11,6 @@ 5610F074202D13E7004CD2AC /* SingleSignOnTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5610F073202D13E7004CD2AC /* SingleSignOnTests.swift */; }; 5610F076202D13E7004CD2AC /* SingleSignOn.h in Headers */ = {isa = PBXBuildFile; fileRef = 5610F068202D13E7004CD2AC /* SingleSignOn.h */; settings = {ATTRIBUTES = (Public, ); }; }; 5610F080202D14E3004CD2AC /* Podfile in Resources */ = {isa = PBXBuildFile; fileRef = 5610F07F202D14E3004CD2AC /* Podfile */; }; - 56FC88DB202D1846008F7642 /* KeycloakAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56FC88CB202D1845008F7642 /* KeycloakAPI.swift */; }; 56FC88DC202D1846008F7642 /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56FC88CC202D1845008F7642 /* Constants.swift */; }; 56FC88DD202D1846008F7642 /* AuthViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56FC88CE202D1845008F7642 /* AuthViewController.swift */; }; 56FC88DE202D1846008F7642 /* Media.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 56FC88CF202D1845008F7642 /* Media.xcassets */; }; @@ -48,7 +47,6 @@ 5610F073202D13E7004CD2AC /* SingleSignOnTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SingleSignOnTests.swift; sourceTree = ""; }; 5610F075202D13E7004CD2AC /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 5610F07F202D14E3004CD2AC /* Podfile */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = Podfile; sourceTree = SOURCE_ROOT; xcLanguageSpecificationIdentifier = xcode.lang.ruby; }; - 56FC88CB202D1845008F7642 /* KeycloakAPI.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeycloakAPI.swift; sourceTree = ""; }; 56FC88CC202D1845008F7642 /* Constants.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = ""; }; 56FC88CE202D1845008F7642 /* AuthViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AuthViewController.swift; sourceTree = ""; }; 56FC88CF202D1845008F7642 /* Media.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Media.xcassets; sourceTree = ""; }; @@ -126,7 +124,6 @@ 5610F069202D13E7004CD2AC /* Info.plist */, 5610F07F202D14E3004CD2AC /* Podfile */, 56FC88E9202E0E04008F7642 /* SingleSignOn.podspec */, - 56FC88CA202D1845008F7642 /* API */, DA2D1D8E2971CE68000FC010 /* Extensions */, 56FC88D6202D1845008F7642 /* Model */, 56FC88D8202D1845008F7642 /* Services */, @@ -144,14 +141,6 @@ path = SingleSignOnTests; sourceTree = ""; }; - 56FC88CA202D1845008F7642 /* API */ = { - isa = PBXGroup; - children = ( - 56FC88CB202D1845008F7642 /* KeycloakAPI.swift */, - ); - path = API; - sourceTree = ""; - }; 56FC88CD202D1845008F7642 /* UI */ = { isa = PBXGroup; children = ( @@ -347,7 +336,6 @@ buildActionMask = 2147483647; files = ( 56FC88E6202D1846008F7642 /* Theme.swift in Sources */, - 56FC88DB202D1846008F7642 /* KeycloakAPI.swift in Sources */, 56FC88DD202D1846008F7642 /* AuthViewController.swift in Sources */, 56FC88E1202D1846008F7642 /* AuthenticationDelegate.swift in Sources */, 56FC88E5202D1846008F7642 /* AuthServices.swift in Sources */, diff --git a/SingleSignOn/API/KeycloakAPI.swift b/SingleSignOn/API/KeycloakAPI.swift deleted file mode 100644 index c0a3395..0000000 --- a/SingleSignOn/API/KeycloakAPI.swift +++ /dev/null @@ -1,53 +0,0 @@ -// -// SecureImage -// -// Copyright © 2018 Province of British Columbia -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -// Created by Jason Leach on 2018-02-01. -// - -import Foundation -import AppAuth - -class KeycloakAPI { - - static let refreshTokenExpiredMessage = "Refresh token expired" - - class func exchange(oneTimeCode code: String, - url: URL, - grantType: String, - redirectUri: String, - clientId: String, - completionHandler: @escaping (_ response: Credentials?, _ error: Error?) -> ()) - { - - let params = ["grant_type": grantType, "redirect_uri": redirectUri, "client_id": clientId, "code": code] - - // TODO: implement AppAuth - } - - class func refresh(credentials: Credentials, - url: URL, - grantType: String, - redirectUri: String, - clientId: String, - completionHandler: @escaping (_ response: Credentials?, _ error: Error?) -> ()) - { - - let params = ["grant_type": grantType, "redirect_uri": redirectUri, "client_id": clientId, "refresh_token": credentials.refreshToken] - - // TODO: implement AppAuth - } -} From 4dfacd3517bd0c64b576834e4f3d449a318a8541 Mon Sep 17 00:00:00 2001 From: Todd Scharien Date: Fri, 13 Jan 2023 10:25:45 -0800 Subject: [PATCH 13/30] Create Endpoint class to handle URL manipulation --- SingleSignOn.xcodeproj/project.pbxproj | 12 +++++ SingleSignOn/API/Endpoint.swift | 61 ++++++++++++++++++++++++++ SingleSignOn/Constants.swift | 6 --- 3 files changed, 73 insertions(+), 6 deletions(-) create mode 100644 SingleSignOn/API/Endpoint.swift diff --git a/SingleSignOn.xcodeproj/project.pbxproj b/SingleSignOn.xcodeproj/project.pbxproj index c6dd43d..b7bebb1 100644 --- a/SingleSignOn.xcodeproj/project.pbxproj +++ b/SingleSignOn.xcodeproj/project.pbxproj @@ -26,6 +26,7 @@ 56FC88EA202E0E04008F7642 /* SingleSignOn.podspec in Resources */ = {isa = PBXBuildFile; fileRef = 56FC88E9202E0E04008F7642 /* SingleSignOn.podspec */; }; BBF776BE94BDB4E00ABA2247 /* Pods_SingleSignOn.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 133211EAD64B6E7010491C5B /* Pods_SingleSignOn.framework */; }; DA2D1D8B2965E3FC000FC010 /* OIDTokenResponseExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA2D1D8A2965E3FC000FC010 /* OIDTokenResponseExtension.swift */; }; + DA2D1D912971D219000FC010 /* Endpoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA2D1D902971D219000FC010 /* Endpoint.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -62,6 +63,7 @@ 56FC88E9202E0E04008F7642 /* SingleSignOn.podspec */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = SingleSignOn.podspec; sourceTree = SOURCE_ROOT; }; 9DBB25529445D12D3C9FEEA5 /* Pods-SingleSignOn.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SingleSignOn.debug.xcconfig"; path = "Pods/Target Support Files/Pods-SingleSignOn/Pods-SingleSignOn.debug.xcconfig"; sourceTree = ""; }; DA2D1D8A2965E3FC000FC010 /* OIDTokenResponseExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OIDTokenResponseExtension.swift; sourceTree = ""; }; + DA2D1D902971D219000FC010 /* Endpoint.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Endpoint.swift; sourceTree = ""; }; EBE0D3BF6EC9DFD049322169 /* Pods-SingleSignOn.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SingleSignOn.release.xcconfig"; path = "Pods/Target Support Files/Pods-SingleSignOn/Pods-SingleSignOn.release.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ @@ -124,6 +126,7 @@ 5610F069202D13E7004CD2AC /* Info.plist */, 5610F07F202D14E3004CD2AC /* Podfile */, 56FC88E9202E0E04008F7642 /* SingleSignOn.podspec */, + DA2D1D8F2971D1E2000FC010 /* API */, DA2D1D8E2971CE68000FC010 /* Extensions */, 56FC88D6202D1845008F7642 /* Model */, 56FC88D8202D1845008F7642 /* Services */, @@ -195,6 +198,14 @@ path = Extensions; sourceTree = ""; }; + DA2D1D8F2971D1E2000FC010 /* API */ = { + isa = PBXGroup; + children = ( + DA2D1D902971D219000FC010 /* Endpoint.swift */, + ); + path = API; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXHeadersBuildPhase section */ @@ -337,6 +348,7 @@ files = ( 56FC88E6202D1846008F7642 /* Theme.swift in Sources */, 56FC88DD202D1846008F7642 /* AuthViewController.swift in Sources */, + DA2D1D912971D219000FC010 /* Endpoint.swift in Sources */, 56FC88E1202D1846008F7642 /* AuthenticationDelegate.swift in Sources */, 56FC88E5202D1846008F7642 /* AuthServices.swift in Sources */, DA2D1D8B2965E3FC000FC010 /* OIDTokenResponseExtension.swift in Sources */, diff --git a/SingleSignOn/API/Endpoint.swift b/SingleSignOn/API/Endpoint.swift new file mode 100644 index 0000000..b85c084 --- /dev/null +++ b/SingleSignOn/API/Endpoint.swift @@ -0,0 +1,61 @@ +// +// EndpointInfo.swift +// SingleSignOn +// +// Created by Scharien, Todd SDPR:EX on 2023-01-13. +// Copyright © 2023 Jason Leach. All rights reserved. +// + +import Foundation + +public struct Endpoint { + public let realmName: String + public let clientId: String + public let redirectUri: String + public let baseUrl: String + public let responseType: String + + public let hint: String? + + var baseOidcUrl: String { + return baseUrl + "/auth/realms/\(realmName)/protocol/openid-connect" + } + + public var authUrl: String { + return baseOidcUrl + "/auth" + } + + public var tokenUrl: String { + return baseOidcUrl + "/token" + } + + public var logoutUrl: String { + return baseOidcUrl + "/logout" + } + + public var oidcQuery: String { + var query = "response_type=\(responseType)&client_id=\(clientId)&redirect_uri=\(redirectUri)" + + if let hint = hint { + query += "&kc_idp_hint=\(hint)" + } + + return query + } + + init(realmName: String, + clientId: String, + redirectUri: String, + baseUrl: String, + responseType: String = Constants.API.authenticationResponseType, + hint: String? = nil) { + + self.realmName = realmName + self.clientId = clientId + self.redirectUri = redirectUri + self.baseUrl = baseUrl + self.responseType = responseType + + self.hint = hint + } +} diff --git a/SingleSignOn/Constants.swift b/SingleSignOn/Constants.swift index d30b1a3..78774cb 100644 --- a/SingleSignOn/Constants.swift +++ b/SingleSignOn/Constants.swift @@ -32,15 +32,9 @@ struct Constants { } struct API { - // The token {{REALM_NAME}} will be replaced with the correct value - // as needed. - static let auth = "/auth/realms/{{REALM_NAME}}/protocol/openid-connect/auth" - static let token = "/auth/realms/{{REALM_NAME}}/protocol/openid-connect/token" - static let logout = "/auth/realms/{{REALM_NAME}}/protocol/openid-connect/logout" static let authenticationResponseType = "code" static let allowedWebDomain = "gov.bc.ca" static let secureScheme = "https" - static let realmToken = "{{REALM_NAME}}" } enum GrantType: String { From 13f57842ef896b85318018b157de421a57a0fea6 Mon Sep 17 00:00:00 2001 From: Todd Scharien Date: Fri, 13 Jan 2023 10:32:42 -0800 Subject: [PATCH 14/30] Refactor AuthViewController to use Endpoint object --- SingleSignOn/UI/AuthViewController.swift | 27 ++++++------------------ 1 file changed, 6 insertions(+), 21 deletions(-) diff --git a/SingleSignOn/UI/AuthViewController.swift b/SingleSignOn/UI/AuthViewController.swift index 2a00d2a..69a5fb5 100644 --- a/SingleSignOn/UI/AuthViewController.swift +++ b/SingleSignOn/UI/AuthViewController.swift @@ -23,11 +23,7 @@ import WebKit public class AuthViewController: UIViewController { - private var authUrl: URL - private var redirectUri: String - private var clientId: String - private var responseType: String - private var idpHint: String? + private var endpoint: Endpoint private let headerViewHeight: CGFloat = { return 88.0 }() @@ -47,13 +43,8 @@ public class AuthViewController: UIViewController { private var recievedCustomRedirectUrl = false public weak var delegate: AuthenticationDelegate? - public init(authUrl: URL, redirectUri: String, clientId: String, responseType: String, idpHint: String? = nil) { - - self.redirectUri = redirectUri - self.clientId = clientId - self.authUrl = authUrl - self.responseType = responseType - self.idpHint = idpHint + public init(endpoint: Endpoint) { + self.endpoint = endpoint super.init(nibName: nil, bundle: nil) @@ -100,14 +91,8 @@ public class AuthViewController: UIViewController { private func buildAuthenticationURL() -> URL? { - var components = URLComponents(url: authUrl, resolvingAgainstBaseURL: true) - var query = "response_type=\(responseType)&client_id=\(clientId)&redirect_uri=\(redirectUri)" - if let idpHint = idpHint { - query = query + "&kc_idp_hint=\(idpHint)" - } - - components?.query = query - + var components = URLComponents(url: URL(string: endpoint.authUrl)!, resolvingAgainstBaseURL: true) + components?.query = endpoint.oidcQuery return components?.url } @@ -149,7 +134,7 @@ public class AuthViewController: UIViewController { // Only the scheme is important in determining if the URL is our // custom redirect URL. let redirComponents = URLComponents(url: url, resolvingAgainstBaseURL: true) - let customComponents = URLComponents(string: redirectUri) + let customComponents = URLComponents(string: endpoint.redirectUri) return redirComponents?.scheme == customComponents?.scheme } From e310ac6e622f39586848014fd0b0bdf19bb6ddab Mon Sep 17 00:00:00 2001 From: Todd Scharien Date: Mon, 16 Jan 2023 09:42:26 -0800 Subject: [PATCH 15/30] Use Endpoint obj in AuthServices --- SingleSignOn/Services/AuthServices.swift | 41 ++++++++++-------------- 1 file changed, 17 insertions(+), 24 deletions(-) diff --git a/SingleSignOn/Services/AuthServices.swift b/SingleSignOn/Services/AuthServices.swift index 66bda93..3eb4202 100644 --- a/SingleSignOn/Services/AuthServices.swift +++ b/SingleSignOn/Services/AuthServices.swift @@ -24,11 +24,8 @@ public typealias AuthenticationCompleted = (_ credentials: Credentials?, _ error public class AuthServices: NSObject { - private var baseUrl: URL - private var redirectUri: String - private var clientId: String - private var realm: String - private var idpHint: String? + private var endpoint: Endpoint + public private(set) var credentials: Credentials? = { return Credentials.loadFromStoredCredentials() }() @@ -36,12 +33,14 @@ public class AuthServices: NSObject { public init(baseUrl: URL, redirectUri: String, clientId: String, realm: String, idpHint: String? = nil) { - self.baseUrl = baseUrl - self.redirectUri = redirectUri - self.clientId = clientId - self.realm = realm - self.idpHint = idpHint - + endpoint = Endpoint( + realmName: realm, + clientId: clientId, + redirectUri: redirectUri, + baseUrl: baseUrl.absoluteString, + hint: idpHint + ) + super.init() } @@ -56,9 +55,7 @@ public class AuthServices: NSObject { public func viewController(completion: AuthenticationCompleted? = nil) -> AuthViewController { - let endpoint = Constants.API.auth.replacingOccurrences(of: Constants.API.realmToken, with: realm) - let url = baseUrl.appendingPathComponent(endpoint) - let avc = AuthViewController(authUrl: url, redirectUri: redirectUri, clientId: clientId, responseType: Constants.API.authenticationResponseType, idpHint: idpHint) + let avc = AuthViewController(endpoint: endpoint) avc.delegate = self onAuthenticationCompleted = completion @@ -67,13 +64,11 @@ public class AuthServices: NSObject { public func exchange(_ oneTimeCode: String, completion: @escaping (Credentials?, Error?) -> Void) { - let endpoint = Constants.API.token.replacingOccurrences(of: Constants.API.realmToken, with: realm) - let url = baseUrl.appendingPathComponent(endpoint) KeycloakAPI.exchange(oneTimeCode: oneTimeCode, - url: url, + url: endpoint.tokenUrl, grantType: Constants.GrantType.authorizationCode.rawValue, - redirectUri: redirectUri, - clientId: clientId) + redirectUri: endpoint.redirectUri, + clientId: endpoint.clientId) { (credentials: Credentials?, error: Error?) in self.credentials = credentials @@ -93,13 +88,11 @@ public class AuthServices: NSObject { return } - let endpoint = Constants.API.token.replacingOccurrences(of: Constants.API.realmToken, with: realm) - let url = baseUrl.appendingPathComponent(endpoint) KeycloakAPI.refresh(credentials: credentials, - url: url, + url: endpoint.tokenUrl, grantType: Constants.GrantType.refreshToken.rawValue, - redirectUri: redirectUri, - clientId: clientId) + redirectUri: endpoint.redirectUri, + clientId: endpoint.clientId) { (credentials: Credentials?, error: Error?) in self.credentials = credentials From 1b69d942bc5d463416010fc3cfc5f7c268635881 Mon Sep 17 00:00:00 2001 From: Todd Scharien Date: Mon, 16 Jan 2023 10:26:57 -0800 Subject: [PATCH 16/30] Use Endpoint obj in AuthServices --- SingleSignOn/Services/AuthServices.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SingleSignOn/Services/AuthServices.swift b/SingleSignOn/Services/AuthServices.swift index 3eb4202..e91d341 100644 --- a/SingleSignOn/Services/AuthServices.swift +++ b/SingleSignOn/Services/AuthServices.swift @@ -24,7 +24,7 @@ public typealias AuthenticationCompleted = (_ credentials: Credentials?, _ error public class AuthServices: NSObject { - private var endpoint: Endpoint + private let endpoint: Endpoint public private(set) var credentials: Credentials? = { return Credentials.loadFromStoredCredentials() From 85ff0f7285c20c089fd1ead130a4bf9a79aa518d Mon Sep 17 00:00:00 2001 From: Todd Scharien Date: Mon, 16 Jan 2023 11:51:14 -0800 Subject: [PATCH 17/30] Increment build number --- SingleSignOn.xcodeproj/project.pbxproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SingleSignOn.xcodeproj/project.pbxproj b/SingleSignOn.xcodeproj/project.pbxproj index b7bebb1..7e0a8ee 100644 --- a/SingleSignOn.xcodeproj/project.pbxproj +++ b/SingleSignOn.xcodeproj/project.pbxproj @@ -411,7 +411,7 @@ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CODE_SIGN_IDENTITY = "iPhone Developer"; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 2; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; @@ -473,7 +473,7 @@ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CODE_SIGN_IDENTITY = "iPhone Developer"; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 2; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; From a122bcfb61124489a0c0ac28d613535756a79ab0 Mon Sep 17 00:00:00 2001 From: Todd Scharien Date: Mon, 16 Jan 2023 11:51:47 -0800 Subject: [PATCH 18/30] Increment visible version numbers --- SingleSignOn.podspec | 2 +- SingleSignOn/Info.plist | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/SingleSignOn.podspec b/SingleSignOn.podspec index 3655d60..fedb35b 100644 --- a/SingleSignOn.podspec +++ b/SingleSignOn.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "SingleSignOn" - s.version = "1.0.6" + s.version = "1.1.0" s.summary = "Library to interface with RedHat SSO" s.description = "This pod contains various components to support authentication and credential managment" s.homepage = "http://pathfinder.gov.bc.ca" diff --git a/SingleSignOn/Info.plist b/SingleSignOn/Info.plist index 1007fd9..a5ae6f9 100644 --- a/SingleSignOn/Info.plist +++ b/SingleSignOn/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 1.0 + 1.1.0 CFBundleVersion $(CURRENT_PROJECT_VERSION) NSPrincipalClass From c8f773a981d765fc9fbc8b711f9c425b78ef3f66 Mon Sep 17 00:00:00 2001 From: Todd Scharien Date: Mon, 16 Jan 2023 14:51:13 -0800 Subject: [PATCH 19/30] Move comments --- SingleSignOn/Model/Credentials.swift | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/SingleSignOn/Model/Credentials.swift b/SingleSignOn/Model/Credentials.swift index a16aa28..fe89e1f 100644 --- a/SingleSignOn/Model/Credentials.swift +++ b/SingleSignOn/Model/Credentials.swift @@ -40,10 +40,10 @@ public struct Credentials { internal let tokenType: String internal let refreshToken: String internal let sessionState: String - internal let refreshExpiresIn: Int + internal let refreshExpiresIn: Int // in seconds internal let refreshExpiresAt: Date internal let notBeforePolicy: Int - internal let expiresIn: Int + internal let expiresIn: Int // in seconds internal let expiresAt: Date internal let props: [String : Any] @@ -81,9 +81,9 @@ public struct Credentials { refreshToken = data[Key.RefreshToken] as! String accessToken = data[Key.AccessToken] as! String sessionState = data[Key.SessionState] as! String - refreshExpiresIn = data[Key.RefreshExpiresIn] as! Int // in sec + refreshExpiresIn = data[Key.RefreshExpiresIn] as! Int notBeforePolicy = data[Key.NotBeforePolicy] as! Int - expiresIn = data[Key.ExpiresIn] as! Int // in sec + expiresIn = data[Key.ExpiresIn] as! Int // If we are loading credentials from the keychain we will have two additional fields representing when the // tokens will expire. Otherwise they need to be created From 7c69da3184f504006ef8775285d633ba6f568942 Mon Sep 17 00:00:00 2001 From: Todd Scharien Date: Tue, 17 Jan 2023 15:08:36 -0800 Subject: [PATCH 20/30] Rework .additionalParameters data collection --- .../Extensions/OIDTokenResponseExtension.swift | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/SingleSignOn/Extensions/OIDTokenResponseExtension.swift b/SingleSignOn/Extensions/OIDTokenResponseExtension.swift index 31731e9..648e69a 100644 --- a/SingleSignOn/Extensions/OIDTokenResponseExtension.swift +++ b/SingleSignOn/Extensions/OIDTokenResponseExtension.swift @@ -12,16 +12,20 @@ import AppAuth extension OIDTokenResponse { func toCredentials() -> Credentials { + let currentDate = Date() + let expiresIn = accessTokenExpirationDate!.timeIntervalSince(currentDate) // in seconds + let refreshExpiresIn = additionalParameters?[Credentials.Key.RefreshExpiresIn] as! Double // in seconds + let refreshExpiresAt = currentDate.addingTimeInterval(refreshExpiresIn) return Credentials(withJSON: [ Credentials.Key.TokenType: tokenType!, Credentials.Key.RefreshToken: refreshToken!, Credentials.Key.AccessToken: accessToken!, - Credentials.Key.SessionState: value(forKey: Credentials.Key.SessionState) as Any, - Credentials.Key.RefreshExpiresIn: value(forKey: Credentials.Key.RefreshExpiresIn) as Any, - Credentials.Key.RefreshExpiresAt: value(forKey: Credentials.Key.RefreshExpiresAt) as Any, - Credentials.Key.NotBeforePolicy: value(forKey: Credentials.Key.NotBeforePolicy) as Any, - Credentials.Key.ExpiresIn: value(forKey: Credentials.Key.ExpiresIn) as Any, + Credentials.Key.SessionState: String(describing: additionalParameters?[Credentials.Key.SessionState]), + Credentials.Key.RefreshExpiresIn: Int(refreshExpiresIn), + Credentials.Key.RefreshExpiresAt: refreshExpiresAt, + Credentials.Key.NotBeforePolicy: additionalParameters?[Credentials.Key.NotBeforePolicy] as! Int, + Credentials.Key.ExpiresIn: Int(expiresIn), Credentials.Key.ExpiresAt: accessTokenExpirationDate! ]) From 9de49eea9a9f927f4c985b5ac2cd0ee865812bbb Mon Sep 17 00:00:00 2001 From: Todd Scharien Date: Tue, 17 Jan 2023 15:12:48 -0800 Subject: [PATCH 21/30] Rename extensions file --- ...TokenResponseExtension.swift => CredentialsExtensions.swift} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename SingleSignOn/Extensions/{OIDTokenResponseExtension.swift => CredentialsExtensions.swift} (97%) diff --git a/SingleSignOn/Extensions/OIDTokenResponseExtension.swift b/SingleSignOn/Extensions/CredentialsExtensions.swift similarity index 97% rename from SingleSignOn/Extensions/OIDTokenResponseExtension.swift rename to SingleSignOn/Extensions/CredentialsExtensions.swift index 648e69a..257c6e6 100644 --- a/SingleSignOn/Extensions/OIDTokenResponseExtension.swift +++ b/SingleSignOn/Extensions/CredentialsExtensions.swift @@ -1,5 +1,5 @@ // -// OIDTokenResponseExtension.swift +// CredentialsExtensions.swift // SingleSignOn // // Created by Scharien, Todd SDPR:EX on 2023-01-04. From 2fa4b55614f98c9fec1ea05cc66fdaef1495ac0e Mon Sep 17 00:00:00 2001 From: Todd Scharien Date: Tue, 17 Jan 2023 16:14:22 -0800 Subject: [PATCH 22/30] Remove duplicate import --- SingleSignOn/Model/Credentials.swift | 1 - 1 file changed, 1 deletion(-) diff --git a/SingleSignOn/Model/Credentials.swift b/SingleSignOn/Model/Credentials.swift index fe89e1f..3ffa32c 100644 --- a/SingleSignOn/Model/Credentials.swift +++ b/SingleSignOn/Model/Credentials.swift @@ -20,7 +20,6 @@ import Foundation import SwiftKeychainWrapper -import SwiftKeychainWrapper public struct Credentials { From 1753d72804120fbce69a255d358b1eabca813c7f Mon Sep 17 00:00:00 2001 From: Todd Scharien Date: Tue, 17 Jan 2023 16:15:09 -0800 Subject: [PATCH 23/30] Implement canRefresh function --- SingleSignOn/Model/Credentials.swift | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/SingleSignOn/Model/Credentials.swift b/SingleSignOn/Model/Credentials.swift index 3ffa32c..c6120cc 100644 --- a/SingleSignOn/Model/Credentials.swift +++ b/SingleSignOn/Model/Credentials.swift @@ -124,6 +124,11 @@ public struct Credentials { return isAuthTokenExpired() && isRefreshTokenExpired() } + public func canRefresh() -> Bool { + + return isAuthTokenExpired() && !isRefreshTokenExpired() + } + public func isAuthTokenExpired() -> Bool { return Date() > expiresAt From 822982b84e270e7e7215541b1508ec4af2cf7f48 Mon Sep 17 00:00:00 2001 From: Todd Scharien Date: Tue, 17 Jan 2023 16:16:39 -0800 Subject: [PATCH 24/30] Remove whitespace --- SingleSignOn/Services/AuthServices.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SingleSignOn/Services/AuthServices.swift b/SingleSignOn/Services/AuthServices.swift index e91d341..0ae83f6 100644 --- a/SingleSignOn/Services/AuthServices.swift +++ b/SingleSignOn/Services/AuthServices.swift @@ -5,7 +5,7 @@ // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. -// You may obtain a copy of the License at +// You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // From aed31df5529d2600a2de384265dd145d3d6f952b Mon Sep 17 00:00:00 2001 From: Todd Scharien Date: Wed, 18 Jan 2023 15:19:57 -0800 Subject: [PATCH 25/30] Change isExpired to isValid --- SingleSignOn/Model/Credentials.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/SingleSignOn/Model/Credentials.swift b/SingleSignOn/Model/Credentials.swift index c6120cc..7a9d814 100644 --- a/SingleSignOn/Model/Credentials.swift +++ b/SingleSignOn/Model/Credentials.swift @@ -119,9 +119,9 @@ public struct Credentials { KeychainWrapper.standard.removeObject(forKey: Constants.Keychain.KeycloakCredentials) } - public func isExpired() -> Bool { - - return isAuthTokenExpired() && isRefreshTokenExpired() + public func isValid() -> Bool { + + return !isAuthTokenExpired() && !isRefreshTokenExpired() } public func canRefresh() -> Bool { From 4134730724dd063e6038a29683b3461e960be466 Mon Sep 17 00:00:00 2001 From: Todd Scharien Date: Wed, 18 Jan 2023 15:20:59 -0800 Subject: [PATCH 26/30] Fix typos --- SingleSignOn/Model/Credentials.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/SingleSignOn/Model/Credentials.swift b/SingleSignOn/Model/Credentials.swift index 7a9d814..eee7342 100644 --- a/SingleSignOn/Model/Credentials.swift +++ b/SingleSignOn/Model/Credentials.swift @@ -145,7 +145,7 @@ public struct Credentials { do { return try JSONSerialization.jsonObject(with: data, options: .allowFragments) as? [String: Any] } catch let error { - print("error converting to json: \(error)") + print("Error converting to json: \(error)") } } @@ -158,10 +158,10 @@ public struct Credentials { let data = try JSONSerialization.data(withJSONObject: props, options: .prettyPrinted) // Securley store the credentials guard KeychainWrapper.standard.set(data.base64EncodedString(), forKey: Constants.Keychain.KeycloakCredentials) else { - fatalError("Unalbe to store auth credentials") + fatalError("Unable to store auth credentials") } } catch let error { - print("error converting to json: \(error)") + print("Error converting to json: \(error)") } } From 2ba8b00138189eb44554654dc3ceb39ec6ae45c1 Mon Sep 17 00:00:00 2001 From: Todd Scharien Date: Wed, 18 Jan 2023 15:21:26 -0800 Subject: [PATCH 27/30] Add AuthenticationError case --- SingleSignOn/UI/AuthenticationError.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/SingleSignOn/UI/AuthenticationError.swift b/SingleSignOn/UI/AuthenticationError.swift index 45456d6..cda913a 100644 --- a/SingleSignOn/UI/AuthenticationError.swift +++ b/SingleSignOn/UI/AuthenticationError.swift @@ -29,4 +29,5 @@ public enum AuthenticationError: Error { case credentialsUnavailable case expired case webRequestFailed(error: Error) + case unableToCreateTokenRefreshRequest } From f2ce45461337be618d5514eebed1b6511605397c Mon Sep 17 00:00:00 2001 From: Todd Scharien Date: Wed, 18 Jan 2023 15:57:42 -0800 Subject: [PATCH 28/30] Add extensions for persisting OIDAuthState --- .../Extensions/OIDAuthStateExtensions.swift | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 SingleSignOn/Extensions/OIDAuthStateExtensions.swift diff --git a/SingleSignOn/Extensions/OIDAuthStateExtensions.swift b/SingleSignOn/Extensions/OIDAuthStateExtensions.swift new file mode 100644 index 0000000..050f0b6 --- /dev/null +++ b/SingleSignOn/Extensions/OIDAuthStateExtensions.swift @@ -0,0 +1,41 @@ +// +// OIDAuthStateExtensions.swift +// SingleSignOn +// +// Created by Scharien, Todd SDPR:EX on 2023-01-18. +// + +import Foundation +import AppAuth +import SwiftKeychainWrapper + +internal extension OIDAuthState { + + static let AuthStateKey = "Serialized.AppAuth.OIDAuthState" + + private func saveToStorage(data: Data) { + KeychainWrapper.standard.set(data, forKey: OIDAuthState.AuthStateKey) + } + + private static func loadFromStorage() -> Data? { + return KeychainWrapper.standard.data(forKey: OIDAuthState.AuthStateKey) + } + + static func removeFromStorage() { + KeychainWrapper.standard.remove(key: OIDAuthState.AuthStateKey) + } + + func saveAsSerialized() throws { + let data = try NSKeyedArchiver.archivedData(withRootObject: self, requiringSecureCoding: true) + saveToStorage(data: data) + } + + static func loadFromSerialized() throws -> OIDAuthState { + if let data = loadFromStorage() { + return try NSKeyedUnarchiver.unarchivedObject(ofClass: OIDAuthState.self, from: data)! + } else { + throw AuthenticationError.credentialsUnavailable + } + } + +} From e44c359151516e039fa908b8890b663d1e679a9c Mon Sep 17 00:00:00 2001 From: Todd Scharien Date: Thu, 19 Jan 2023 11:37:35 -0800 Subject: [PATCH 29/30] Implement AppAuth in AuthServices --- SingleSignOn/Services/AuthServices.swift | 164 +++++++++++++++-------- 1 file changed, 106 insertions(+), 58 deletions(-) diff --git a/SingleSignOn/Services/AuthServices.swift b/SingleSignOn/Services/AuthServices.swift index 0ae83f6..d8fee12 100644 --- a/SingleSignOn/Services/AuthServices.swift +++ b/SingleSignOn/Services/AuthServices.swift @@ -19,18 +19,50 @@ // import Foundation +import AppAuth public typealias AuthenticationCompleted = (_ credentials: Credentials?, _ error: Error?) -> Void public class AuthServices: NSObject { private let endpoint: Endpoint + private let authConfig: OIDServiceConfiguration + + private var authRequest: OIDAuthorizationRequest { + return OIDAuthorizationRequest( + configuration: authConfig, + clientId: endpoint.clientId, + scopes: [OIDScopeOpenID, OIDScopeProfile], + redirectURL: URL(string: endpoint.redirectUri)!, + responseType: OIDResponseTypeCode, + additionalParameters: nil + ) + } public private(set) var credentials: Credentials? = { return Credentials.loadFromStoredCredentials() }() + public var onAuthenticationCompleted: AuthenticationCompleted? + private var _authState: OIDAuthState? + var authState: OIDAuthState? { + get { + if _authState == nil { + do { + _authState = try OIDAuthState.loadFromSerialized() + } catch { + _authState = nil + } + } + return _authState + } + set { + _authState = newValue + } + } + var currentAuthorizationFlow: OIDExternalUserAgentSession? + public init(baseUrl: URL, redirectUri: String, clientId: String, realm: String, idpHint: String? = nil) { endpoint = Endpoint( @@ -41,44 +73,66 @@ public class AuthServices: NSObject { hint: idpHint ) + authConfig = OIDServiceConfiguration( + authorizationEndpoint: URL(string: endpoint.authUrl)!, + tokenEndpoint: URL(string: endpoint.tokenUrl)! + ) + super.init() } public func isAuthenticated() -> Bool { - - guard let credentials = credentials, !credentials.isExpired() else { - return false + if let credentials { + return credentials.isValid() } - - return true + return false } - public func viewController(completion: AuthenticationCompleted? = nil) -> AuthViewController { - - let avc = AuthViewController(endpoint: endpoint) - avc.delegate = self - onAuthenticationCompleted = completion - - return avc + public func canRefresh() -> Bool { + if let credentials { + return credentials.canRefresh() && authState != nil + } + return false } - - public func exchange(_ oneTimeCode: String, completion: @escaping (Credentials?, Error?) -> Void) { + + public func doWithAuthentication(presenting: UIViewController, completion: @escaping (Credentials?, Error?) -> Void) { + if isAuthenticated() { + completion(credentials, nil) + } else if canRefresh() { + refreshCredientials(completion: completion) + } else { + // no credentials or all tokens expired + authenticate(presenting: presenting, completion: completion) + } + } + + private func authenticate(presenting: UIViewController, completion: @escaping (Credentials?, Error?) -> Void) { - KeycloakAPI.exchange(oneTimeCode: oneTimeCode, - url: endpoint.tokenUrl, - grantType: Constants.GrantType.authorizationCode.rawValue, - redirectUri: endpoint.redirectUri, - clientId: endpoint.clientId) - { (credentials: Credentials?, error: Error?) in - - self.credentials = credentials - completion(credentials, error) + OIDAuthState.removeFromStorage() + + currentAuthorizationFlow = OIDAuthState.authState(byPresenting: authRequest, presenting: presenting) + { authState, error in + + self.authState = authState ?? nil + self.credentials = authState?.lastTokenResponse?.toCredentials() + + if let authState, error == nil { + do { + try authState.saveAsSerialized() + } catch let savingError { + self.logout() + completion(nil, savingError) + return + } + } + + completion(self.credentials, error) } } - public func refreshCredientials(completion: @escaping (Credentials?, Error?) -> Void) { + private func refreshCredientials(completion: @escaping (Credentials?, Error?) -> Void) { - guard let credentials = credentials else { + guard let credentials else { completion(nil, AuthenticationError.credentialsUnavailable) return } @@ -87,48 +141,42 @@ public class AuthServices: NSObject { completion(nil, AuthenticationError.expired) return } - - KeycloakAPI.refresh(credentials: credentials, - url: endpoint.tokenUrl, - grantType: Constants.GrantType.refreshToken.rawValue, - redirectUri: endpoint.redirectUri, - clientId: endpoint.clientId) - { (credentials: Credentials?, error: Error?) in - - self.credentials = credentials - completion(credentials, error) + + guard let authState else { + completion(nil, AuthenticationError.credentialsUnavailable) + return } - } - - public func logout() { - guard let credentials = credentials else { + guard let tokenRefreshRequest = authState.tokenRefreshRequest() else { + completion(nil, AuthenticationError.unableToCreateTokenRefreshRequest) return } - - credentials.remove(); - self.credentials = nil - } -} - -// MARK: AuthenticationDelegate -extension AuthServices: AuthenticationDelegate { - - public func authenticationSucceded(oneTimeCode: String) { - exchange(oneTimeCode) { (credentials: Credentials?, error: Error?) in - - guard let credentials = credentials else { - - self.onAuthenticationCompleted?(nil, AuthenticationError.unableToExchangeOneTimeCodeForToken) - return + OIDAuthorizationService.perform(tokenRefreshRequest) { tokenResponse, error in + let credentials = tokenResponse?.toCredentials() + + if let _ = credentials, error == nil { + do { + try authState.saveAsSerialized() + } catch let savingError { + self.logout() + completion(nil, savingError) + return + } } - self.onAuthenticationCompleted?(credentials, nil) + self.credentials = credentials + completion(credentials, error) } } - public func authenticationFailed(error: Error) { - onAuthenticationCompleted?(nil, error) + public func logout() { + + if let credentials { + credentials.remove(); + self.credentials = nil + } + + OIDAuthState.removeFromStorage() } } From 78715c5cb13b366efc2fb530470bf1ca920585c6 Mon Sep 17 00:00:00 2001 From: Todd Scharien Date: Thu, 19 Jan 2023 11:46:42 -0800 Subject: [PATCH 30/30] Remove unused fields --- SingleSignOn/Services/AuthServices.swift | 4 ---- 1 file changed, 4 deletions(-) diff --git a/SingleSignOn/Services/AuthServices.swift b/SingleSignOn/Services/AuthServices.swift index d8fee12..9b0dc34 100644 --- a/SingleSignOn/Services/AuthServices.swift +++ b/SingleSignOn/Services/AuthServices.swift @@ -21,8 +21,6 @@ import Foundation import AppAuth -public typealias AuthenticationCompleted = (_ credentials: Credentials?, _ error: Error?) -> Void - public class AuthServices: NSObject { private let endpoint: Endpoint @@ -43,8 +41,6 @@ public class AuthServices: NSObject { return Credentials.loadFromStoredCredentials() }() - public var onAuthenticationCompleted: AuthenticationCompleted? - private var _authState: OIDAuthState? var authState: OIDAuthState? { get {