From f360b849469714a10bea753f48d1419304bace61 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8rund=20B=2E=20Fagerjord?= Date: Wed, 18 Oct 2017 12:32:21 +0200 Subject: [PATCH] Fix merge conflict with master --- .gitignore | 5 +- Podfile | 4 +- Podfile.lock | 31 ++++----- TDConnectIosSdk.podspec | 2 +- TDConnectIosSdk.xcodeproj/project.pbxproj | 47 ++++++++++--- .../xcschemes/TDConnectIosSdk.xcscheme | 4 +- .../xcschemes/TDConnectIosSdkTests.xcscheme | 4 +- TDConnectIosSdk/AccountManager.swift | 32 +++++---- TDConnectIosSdk/Config.swift | 24 ++++++- TDConnectIosSdk/FacebookOAuth2Module.swift | 6 +- TDConnectIosSdk/KeycloakOAuth2Module.swift | 6 +- TDConnectIosSdk/OAuth2Module.swift | 4 +- TDConnectIosSdk/OAuth2Session.swift | 4 +- TDConnectIosSdk/OAuth2WebViewController.swift | 8 ++- TDConnectIosSdk/OpenIDClaim.swift | 6 +- .../TelenorConnectOAuth2Module.swift | 69 ++++++++++++++++--- .../TrustedPersistantOAuth2Session.swift | 29 ++++---- .../UntrustedMemoryOAuth2Session.swift | 15 ++-- TDConnectIosSdkTests/ConfigTest.swift | 2 +- .../FacebookOAuth2ModuleTest.swift | 2 +- TDConnectIosSdkTests/OAuth2ModuleMock.swift | 4 +- TDConnectIosSdkTests/OAuth2ModuleTest.swift | 32 +++++++-- ...penIDConnectFacebookOAuth2ModuleTest.swift | 2 +- .../OpenIDConnectGoogleOAuth2ModuleTest.swift | 4 +- ...penIDConnectKeycloakOAuth2ModuleTest.swift | 8 +-- 25 files changed, 250 insertions(+), 104 deletions(-) diff --git a/.gitignore b/.gitignore index 6db6e13..5c1d735 100644 --- a/.gitignore +++ b/.gitignore @@ -23,4 +23,7 @@ DerivedData Pods/ #Docset/AppleDoc -*/Docset/* \ No newline at end of file +*/Docset/* + +# rbenv +.ruby-version diff --git a/Podfile b/Podfile index 3ba6d72..78af305 100644 --- a/Podfile +++ b/Podfile @@ -6,11 +6,11 @@ use_frameworks! target 'TDConnectIosSdk' do pod 'AeroGearHttp', '1.0.0' - pod 'JSONWebToken', '2.0.2' + pod 'JSONWebToken', '2.2.0' target 'TDConnectIosSdkTests' do inherit! :search_paths - pod 'OHHTTPStubs', '5.2.3' + pod 'OHHTTPStubs', '6.0.0' end end diff --git a/Podfile.lock b/Podfile.lock index 6ad417e..5e2e9bc 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -1,33 +1,30 @@ PODS: - AeroGearHttp (1.0.0) - - CryptoSwift (0.6.7) - - JSONWebToken (2.0.2): - - CryptoSwift (~> 0.6.1) - - OHHTTPStubs (5.2.3): - - OHHTTPStubs/Default (= 5.2.3) - - OHHTTPStubs/Core (5.2.3) - - OHHTTPStubs/Default (5.2.3): + - JSONWebToken (2.2.0) + - OHHTTPStubs (6.0.0): + - OHHTTPStubs/Default (= 6.0.0) + - OHHTTPStubs/Core (6.0.0) + - OHHTTPStubs/Default (6.0.0): - OHHTTPStubs/Core - OHHTTPStubs/JSON - OHHTTPStubs/NSURLSession - OHHTTPStubs/OHPathHelpers - - OHHTTPStubs/JSON (5.2.3): + - OHHTTPStubs/JSON (6.0.0): - OHHTTPStubs/Core - - OHHTTPStubs/NSURLSession (5.2.3): + - OHHTTPStubs/NSURLSession (6.0.0): - OHHTTPStubs/Core - - OHHTTPStubs/OHPathHelpers (5.2.3) + - OHHTTPStubs/OHPathHelpers (6.0.0) DEPENDENCIES: - AeroGearHttp (= 1.0.0) - - JSONWebToken (= 2.0.2) - - OHHTTPStubs (= 5.2.3) + - JSONWebToken (= 2.2.0) + - OHHTTPStubs (= 6.0.0) SPEC CHECKSUMS: AeroGearHttp: 8c825d4563439f565196f52efe80e15f0055109a - CryptoSwift: 685ae257941e5447474348a2b545583e1a16b573 - JSONWebToken: ca86f02eeb3d1f58ba5197ec2cd53c7de915ecc1 - OHHTTPStubs: e238cd5b66d8efa51c861db45895de8fe079f4a7 + JSONWebToken: 6fae32489632658d563dd6952a5c34020ea813e4 + OHHTTPStubs: 752f9b11fd810a15162d50f11c06ff94f8e012eb -PODFILE CHECKSUM: cf7938e2f5f5d6ccc0c56922b4bca928dcd4aa2b +PODFILE CHECKSUM: 6b6c974f09234967cfab0f59df3c6cffcaa9869a -COCOAPODS: 1.2.0 +COCOAPODS: 1.3.1 diff --git a/TDConnectIosSdk.podspec b/TDConnectIosSdk.podspec index 175270a..a186c37 100644 --- a/TDConnectIosSdk.podspec +++ b/TDConnectIosSdk.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "TDConnectIosSdk" - s.version = "1.0.2" + s.version = "1.1.0" s.summary = "OAuth2 client library based on aerogear-ios-http" s.homepage = "https://github.com/telenordigital/connect-ios-sdk" s.license = 'Apache License, Version 2.0' diff --git a/TDConnectIosSdk.xcodeproj/project.pbxproj b/TDConnectIosSdk.xcodeproj/project.pbxproj index 8049fc4..7361681 100644 --- a/TDConnectIosSdk.xcodeproj/project.pbxproj +++ b/TDConnectIosSdk.xcodeproj/project.pbxproj @@ -310,17 +310,17 @@ isa = PBXProject; attributes = { LastSwiftUpdateCheck = 0700; - LastUpgradeCheck = 0820; + LastUpgradeCheck = 0900; ORGANIZATIONNAME = aerogear; TargetAttributes = { 4833046119AF1635002F8DA9 = { CreatedOnToolsVersion = 6.0; - LastSwiftMigration = 0830; + LastSwiftMigration = 0900; }; 4833046C19AF1635002F8DA9 = { CreatedOnToolsVersion = 6.0; DevelopmentTeam = M4Q75B4WS4; - LastSwiftMigration = 0820; + LastSwiftMigration = 0900; }; }; }; @@ -366,9 +366,16 @@ files = ( ); inputPaths = ( + "${SRCROOT}/Pods/Target Support Files/Pods-TDConnectIosSdkTests/Pods-TDConnectIosSdkTests-frameworks.sh", + "${BUILT_PRODUCTS_DIR}/OHHTTPStubs/OHHTTPStubs.framework", + "${BUILT_PRODUCTS_DIR}/AeroGearHttp/AeroGearHttp.framework", + "${BUILT_PRODUCTS_DIR}/JSONWebToken/JWT.framework", ); name = "[CP] Embed Pods Frameworks"; outputPaths = ( + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/OHHTTPStubs.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/AeroGearHttp.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/JWT.framework", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; @@ -396,13 +403,16 @@ files = ( ); inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", ); name = "[CP] Check Pods Manifest.lock"; outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-TDConnectIosSdkTests-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n"; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; 78AAE80F48F1E3DE0D894510 /* [CP] Check Pods Manifest.lock */ = { @@ -411,13 +421,16 @@ files = ( ); inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", ); name = "[CP] Check Pods Manifest.lock"; outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-TDConnectIosSdk-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n"; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; C7B14E91846F3174319DBEDD /* [CP] Copy Pods Resources */ = { @@ -498,14 +511,20 @@ CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; @@ -548,14 +567,20 @@ CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; @@ -605,7 +630,8 @@ PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 3.0; + SWIFT_SWIFT3_OBJC_INFERENCE = Default; + SWIFT_VERSION = 4.0; }; name = Debug; }; @@ -630,7 +656,8 @@ PRODUCT_BUNDLE_IDENTIFIER = "org.aerogear.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; - SWIFT_VERSION = 3.0; + SWIFT_SWIFT3_OBJC_INFERENCE = Default; + SWIFT_VERSION = 4.0; }; name = Release; }; @@ -651,7 +678,8 @@ PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = ""; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 3.0; + SWIFT_SWIFT3_OBJC_INFERENCE = Default; + SWIFT_VERSION = 4.0; }; name = Debug; }; @@ -667,7 +695,8 @@ PRODUCT_BUNDLE_IDENTIFIER = "org.aerogear.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = ""; - SWIFT_VERSION = 3.0; + SWIFT_SWIFT3_OBJC_INFERENCE = Default; + SWIFT_VERSION = 4.0; }; name = Release; }; diff --git a/TDConnectIosSdk.xcodeproj/xcshareddata/xcschemes/TDConnectIosSdk.xcscheme b/TDConnectIosSdk.xcodeproj/xcshareddata/xcschemes/TDConnectIosSdk.xcscheme index f8cdd7f..b08f687 100644 --- a/TDConnectIosSdk.xcodeproj/xcshareddata/xcschemes/TDConnectIosSdk.xcscheme +++ b/TDConnectIosSdk.xcodeproj/xcshareddata/xcschemes/TDConnectIosSdk.xcscheme @@ -1,6 +1,6 @@ @@ -56,6 +57,7 @@ buildConfiguration = "Debug" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" + language = "" launchStyle = "0" useCustomWorkingDirectory = "NO" ignoresPersistentStateOnLaunch = "NO" diff --git a/TDConnectIosSdk.xcodeproj/xcshareddata/xcschemes/TDConnectIosSdkTests.xcscheme b/TDConnectIosSdk.xcodeproj/xcshareddata/xcschemes/TDConnectIosSdkTests.xcscheme index aa7f368..750a28f 100644 --- a/TDConnectIosSdk.xcodeproj/xcshareddata/xcschemes/TDConnectIosSdkTests.xcscheme +++ b/TDConnectIosSdk.xcodeproj/xcshareddata/xcschemes/TDConnectIosSdkTests.xcscheme @@ -1,6 +1,6 @@ "scope1%20scope2" @@ -93,6 +98,11 @@ open class Config { */ open let clientSecret: String? + /** + Applies the "audience" obtained with the client registration process. + */ + public let audienceId: String? + /** Account id is used with AccountManager to store tokens. AccountId is defined by the end-user and can be any String. If AccountManager is not used, this field is optional. @@ -134,7 +144,15 @@ open class Config { */ public let isPublicClient: Bool - public init(base: String, authzEndpoint: String, redirectURL: String, accessTokenEndpoint: String, clientId: String, refreshTokenEndpoint: String? = nil, revokeTokenEndpoint: String? = nil, wellKnownConfigurationEndpoint: String? = nil, isOpenIDConnect: Bool = false, userInfoEndpoint: String? = nil, scopes: [String] = [], clientSecret: String? = nil, accountId: String? = nil, claims: Set? = nil, optionalParams: [String: String]? = nil, isWebView: Bool = false, isPublicClient: Bool = true) { + /** + A handler to allow the webview to be pushed onto the navigation controller + */ + open var webViewHandler: ((OAuth2WebViewController, _ completionHandler: (AnyObject?, NSError?) -> Void) -> ()) = { + (webView, completionHandler) in + UIApplication.shared.keyWindow?.rootViewController?.present(webView, animated: true, completion: nil) + } + + public init(base: String, authzEndpoint: String, redirectURL: String, accessTokenEndpoint: String, clientId: String, audienceId: String? = nil, refreshTokenEndpoint: String? = nil, revokeTokenEndpoint: String? = nil, wellKnownConfigurationEndpoint: String? = nil, isOpenIDConnect: Bool = false, userInfoEndpoint: String? = nil, logOutEndpoint: String? = nil, scopes: [String] = [], clientSecret: String? = nil, accountId: String? = nil, claims: Set? = nil, optionalParams: [String: String]? = nil, isWebView: Bool = false, isPublicClient: Bool = true) { self.baseURL = base self.authzEndpoint = authzEndpoint self.redirectURL = redirectURL @@ -144,9 +162,11 @@ open class Config { self.wellKnownConfigurationEndpoint = wellKnownConfigurationEndpoint self.isOpenIDConnect = isOpenIDConnect self.userInfoEndpoint = userInfoEndpoint + self.logOutEndpoint = logOutEndpoint self.scopes = scopes self.clientId = clientId self.clientSecret = clientSecret + self.audienceId = audienceId self.accountId = accountId self.claims = claims self.optionalParams = optionalParams diff --git a/TDConnectIosSdk/FacebookOAuth2Module.swift b/TDConnectIosSdk/FacebookOAuth2Module.swift index f589874..f778e2c 100644 --- a/TDConnectIosSdk/FacebookOAuth2Module.swift +++ b/TDConnectIosSdk/FacebookOAuth2Module.swift @@ -80,9 +80,13 @@ open class FacebookOAuth2Module: OAuth2Module { if (self.oauth2Session.accessToken == nil) { return } + guard let revokeTokenEndpoint = config.revokeTokenEndpoint else { + return + } + let paramDict: [String:String] = ["access_token":self.oauth2Session.accessToken!] - http.request(method: .delete, path: config.revokeTokenEndpoint!, parameters: paramDict as [String : AnyObject]?, completionHandler: { (response, error) in + http.request(method: .delete, path: revokeTokenEndpoint, parameters: paramDict as [String : AnyObject]?, completionHandler: { (response, error) in if (error != nil) { completionHandler(nil, error) diff --git a/TDConnectIosSdk/KeycloakOAuth2Module.swift b/TDConnectIosSdk/KeycloakOAuth2Module.swift index 01e0c4b..c5bda65 100644 --- a/TDConnectIosSdk/KeycloakOAuth2Module.swift +++ b/TDConnectIosSdk/KeycloakOAuth2Module.swift @@ -27,8 +27,12 @@ open class KeycloakOAuth2Module: OAuth2Module { if (self.oauth2Session.accessToken == nil) { return } + guard let revokeTokenEndpoint = config.revokeTokenEndpoint else { + return + } + let paramDict: [String:String] = [ "client_id": config.clientId, "refresh_token": self.oauth2Session.refreshToken!] - http.request(method: .post, path: config.revokeTokenEndpoint!, parameters: paramDict as [String : AnyObject]?, completionHandler: { (response, error) in + http.request(method: .post, path: revokeTokenEndpoint, parameters: paramDict as [String : AnyObject]?, completionHandler: { (response, error) in if (error != nil) { completionHandler(nil, error) return diff --git a/TDConnectIosSdk/OAuth2Module.swift b/TDConnectIosSdk/OAuth2Module.swift index 501092e..824e488 100644 --- a/TDConnectIosSdk/OAuth2Module.swift +++ b/TDConnectIosSdk/OAuth2Module.swift @@ -100,7 +100,7 @@ open class OAuth2Module: NSObject, AuthzModule, SFSafariViewControllerDelegate { config.accountId = "ACCOUNT_FOR_CLIENTID_\(config.clientId)" } if (session == nil) { - self.oauth2Session = TrustedPersistantOAuth2Session(accountId: config.accountId!) + self.oauth2Session = TrustedPersistentOAuth2Session(accountId: config.accountId!) } else { self.oauth2Session = session! } @@ -276,7 +276,7 @@ open class OAuth2Module: NSObject, AuthzModule, SFSafariViewControllerDelegate { } let jsonClaimsString = NSString(data: jsonClaims as Data, encoding: String.Encoding.utf8.rawValue) - let encodedJson = (jsonClaimsString as! String).urlEncode() + let encodedJson = (jsonClaimsString! as String).urlEncode() return "&claims=\(encodedJson)" } diff --git a/TDConnectIosSdk/OAuth2Session.swift b/TDConnectIosSdk/OAuth2Session.swift index 49d6e01..f3d158e 100644 --- a/TDConnectIosSdk/OAuth2Session.swift +++ b/TDConnectIosSdk/OAuth2Session.swift @@ -17,7 +17,7 @@ import Foundation /** -The protocol that an OAuth2 Session modules must adhere to and represent storage of oauth specific metadata. See TrustedPersistantOAuth2Session and UntrustedMemoryOAuth2Session as example implementations +The protocol that an OAuth2 Session modules must adhere to and represent storage of OAuth2 specific metadata. See TrustedPersistentOAuth2Session and UntrustedMemoryOAuth2Session as example implementations */ public protocol OAuth2Session { @@ -68,7 +68,7 @@ public protocol OAuth2Session { func clearTokens() /** - Save tokens information. Saving tokens allow you to refresh accesstoken transparently for the user without prompting + Save tokens information. Saving tokens allow you to refresh accessToken transparently for the user without prompting for grant access. :param: accessToken the access token. diff --git a/TDConnectIosSdk/OAuth2WebViewController.swift b/TDConnectIosSdk/OAuth2WebViewController.swift index 0e53085..f8da1e0 100644 --- a/TDConnectIosSdk/OAuth2WebViewController.swift +++ b/TDConnectIosSdk/OAuth2WebViewController.swift @@ -26,7 +26,7 @@ rather than an external browser approach. open class OAuth2WebViewController: UIViewController, UIWebViewDelegate { /// Login URL for OAuth. var targetURL: URL! - /// WebView intance used to load login page. + /// WebView instance used to load login page. var webView: UIWebView = UIWebView() /// Override of viewDidLoad to load the login page. @@ -42,7 +42,11 @@ open class OAuth2WebViewController: UIViewController, UIWebViewDelegate { super.viewDidLayoutSubviews() self.webView.frame = self.view.bounds } - + + override open func didReceiveMemoryWarning() { + super.didReceiveMemoryWarning() + } + func loadAddressURL() { let req = URLRequest(url: targetURL) webView.loadRequest(req) diff --git a/TDConnectIosSdk/OpenIDClaim.swift b/TDConnectIosSdk/OpenIDClaim.swift index d825b20..c1a91aa 100644 --- a/TDConnectIosSdk/OpenIDClaim.swift +++ b/TDConnectIosSdk/OpenIDClaim.swift @@ -66,9 +66,9 @@ open class OpenIdClaim: CustomStringConvertible { open var hd: String? /// Display all the claims. open var description: String { - return "sub: \(sub)\nname: \(name)\ngivenName: \(givenName)\nfamilyName: \(familyName)\nmiddleName: \(middleName)\n" + - "nickname: \(nickname)\npreferredUsername: \(preferredUsername)\nprofile: \(profile)\npicture: \(picture)\n" + - "website: \(website)\nemail: \(email)\nemailVerified: \(emailVerified)\ngender: \(gender)\nbirthdate: \(birthdate)\n" + return "sub: \(String(describing: sub))\nname: \(String(describing: name))\ngivenName: \(String(describing: givenName))\nfamilyName: \(String(describing: familyName))\nmiddleName: \(String(describing: middleName))\n" + + "nickname: \(String(describing: nickname))\npreferredUsername: \(String(describing: preferredUsername))\nprofile: \(String(describing: profile))\npicture: \(String(describing: picture))\n" + + "website: \(String(describing: website))\nemail: \(String(describing: email))\nemailVerified: \(String(describing: emailVerified))\ngender: \(String(describing: gender))\nbirthdate: \(String(describing: birthdate))\n" } /// Initialize an OpenIDClaim from a dictionary. all information not available are optional values set to .None. diff --git a/TDConnectIosSdk/TelenorConnectOAuth2Module.swift b/TDConnectIosSdk/TelenorConnectOAuth2Module.swift index 08426ff..4ac67a7 100644 --- a/TDConnectIosSdk/TelenorConnectOAuth2Module.swift +++ b/TDConnectIosSdk/TelenorConnectOAuth2Module.swift @@ -12,20 +12,69 @@ import AeroGearHttp public class TelenorConnectOAuth2Module: OAuth2Module { - override public func revokeAccess(completionHandler: @escaping (AnyObject?, NSError?) -> Void) { - // TODO: also revoke refreshToken - if (self.oauth2Session.accessToken == nil) { - return; + override open func revokeAccess(completionHandler: @escaping (AnyObject?, NSError?) -> Void) { + logOut(completionHandler: completionHandler) + } + + private func logOut(completionHandler: @escaping (AnyObject?, NSError?) -> Void) { + guard + let _ = self.config.logOutEndpoint, + let _ = self.oauth2Session.refreshToken, + let _ = self.oauth2Session.accessToken + else { + revokeAndClearTokens(completionHandler: completionHandler) + return } - let paramDict:[String:String] = [ "client_id": config.clientId, "token": self.oauth2Session.accessToken!] - http.request(method: .post, path: config.revokeTokenEndpoint!, parameters: paramDict, responseSerializer: StringResponseSerializer(), completionHandler: { (response, error) in - if (error != nil) { - completionHandler(nil, error) + + if (self.oauth2Session.tokenIsNotExpired()) { + callLogOutEndpoint(completionHandler: { (success, error) in + self.revokeAndClearTokens(completionHandler: completionHandler) + }) + return + } + + self.refreshAccessToken(completionHandler: { (successRefresh, errorRefresh) in + if (errorRefresh != nil) { + self.revokeAndClearTokens(completionHandler: completionHandler) return } - self.oauth2Session.clearTokens() - completionHandler(response as AnyObject?, nil) + self.callLogOutEndpoint(completionHandler: { (successLogOut, errorLogOut) in + self.revokeAndClearTokens(completionHandler: completionHandler) + }) + }) + } + + private func callLogOutEndpoint(completionHandler: @escaping (Any?, NSError?) -> Void) { + let http = Http(baseURL: self.config.baseURL) + http.authzModule = self + http.request(method: .post, path: config.logOutEndpoint!) { (response, error) in + completionHandler(response, error) + } + } + + private func revokeAndClearTokens(completionHandler: @escaping (AnyObject?, NSError?) -> Void) { + revokeAccessToken() + revokeRefreshToken() + self.oauth2Session.clearTokens() + completionHandler(nil, nil) + } + + private func revokeAccessToken() { + guard let accessToken = self.oauth2Session.accessToken else { + return + } + let paramDict:[String:String] = [ "client_id": config.clientId, "token": accessToken] + http.request(method: .post, path: config.revokeTokenEndpoint!, parameters: paramDict, responseSerializer: StringResponseSerializer(), completionHandler: { (response, error) in + }) + } + + private func revokeRefreshToken() { + guard let refreshToken = self.oauth2Session.refreshToken else { + return + } + let paramDict:[String:String] = [ "client_id": config.clientId, "token": refreshToken] + http.request(method: .post, path: config.revokeTokenEndpoint!, parameters: paramDict, responseSerializer: StringResponseSerializer(), completionHandler: { (response, error) in }) } } diff --git a/TDConnectIosSdk/TrustedPersistantOAuth2Session.swift b/TDConnectIosSdk/TrustedPersistantOAuth2Session.swift index d50892c..c0b4694 100644 --- a/TDConnectIosSdk/TrustedPersistantOAuth2Session.swift +++ b/TDConnectIosSdk/TrustedPersistantOAuth2Session.swift @@ -46,19 +46,19 @@ public class KeychainWrap { public var serviceIdentifier: String /** - The group id is Keychain access group which is used for sharing keychain content accross multiple apps issued from same developer. By default there is no access group. + The group id is Keychain access group which is used for sharing keychain content across multiple apps issued from same developer. By default there is no access group. */ public var groupId: String? /** Initialize KeychainWrapper setting default values. - :param: serviceId unique service, defulated to bundleId + :param: serviceId unique service, defaulted to bundleId :param: groupId used for SSO between app issued from same developer certificate. */ public init(serviceId: String? = Bundle.main.bundleIdentifier, groupId: String? = nil) { if serviceId == nil { - self.serviceIdentifier = "unkown" + self.serviceIdentifier = "unknown" } else { self.serviceIdentifier = serviceId! } @@ -68,7 +68,7 @@ public class KeychainWrap { /** Save tokens information in Keychain. - :param: key usually use accountId for oauth2 module, any unique string. + :param: key usually use accountId for OAuth2 module, any unique string. :param: tokenType type of token: access, refresh. :param: value string value of the token. */ @@ -202,7 +202,7 @@ public class KeychainWrap { /** An OAuth2Session implementation to store OAuth2 metadata using the Keychain. */ -public class TrustedPersistantOAuth2Session: OAuth2Session { +public class TrustedPersistentOAuth2Session: OAuth2Session { /** The account id. @@ -261,7 +261,7 @@ public class TrustedPersistantOAuth2Session: OAuth2Session { } } } - + /** The JWT. */ @@ -321,8 +321,9 @@ public class TrustedPersistantOAuth2Session: OAuth2Session { public func save(accessToken: String?, refreshToken: String?, accessTokenExpiration: String?, refreshTokenExpiration: String?, idToken: String?) { self.accessToken = accessToken self.refreshToken = refreshToken - - let now = NSDate() + self.idToken = idToken + + let now = Date() if let inter = accessTokenExpiration?.doubleValue { self.accessTokenExpirationDate = now.addingTimeInterval(inter) as Date } @@ -346,14 +347,14 @@ public class TrustedPersistantOAuth2Session: OAuth2Session { } /** - Initialize TrustedPersistantOAuth2Session using account id. Account id is the service id used for keychain storage. + Initialize TrustedPersistentOAuth2Session using account id. Account id is the service id used for keychain storage. - :param: accountId uniqueId to identify the oauth2module + :param: accountId uniqueId to identify the OAuth2Module :param: groupId used for SSO between app issued from same developer certificate. - :param: accessToken optional parameter to initilaize the storage with initial values - :param: accessTokenExpirationDate optional parameter to initilaize the storage with initial values - :param: refreshToken optional parameter to initilaize the storage with initial values - :param: refreshTokenExpirationDate optional parameter to initilaize the storage with initial values + :param: accessToken optional parameter to initialize the storage with initial values + :param: accessTokenExpirationDate optional parameter to initialize the storage with initial values + :param: refreshToken optional parameter to initialize the storage with initial values + :param: refreshTokenExpirationDate optional parameter to initialize the storage with initial values */ public init(accountId: String, groupId: String? = nil, diff --git a/TDConnectIosSdk/UntrustedMemoryOAuth2Session.swift b/TDConnectIosSdk/UntrustedMemoryOAuth2Session.swift index 3e64aaf..3b6bb94 100644 --- a/TDConnectIosSdk/UntrustedMemoryOAuth2Session.swift +++ b/TDConnectIosSdk/UntrustedMemoryOAuth2Session.swift @@ -46,7 +46,7 @@ open class UntrustedMemoryOAuth2Session: OAuth2Session { The refresh tokens. This toke does not expire and should be used to renew access token when expired. */ open var refreshToken: String? - + /** The refresh token's expiration date. */ @@ -72,7 +72,7 @@ open class UntrustedMemoryOAuth2Session: OAuth2Session { } /** - Save in memory tokens information. Saving tokens allow you to refresh accesstoken transparently for the user without prompting for grant access. + Save in memory tokens information. Saving tokens allow you to refresh accessToken transparently for the user without prompting for grant access. */ open func save(accessToken: String?, refreshToken: String?, accessTokenExpiration: String?, refreshTokenExpiration: String?, idToken: String? = nil) { self.accessToken = accessToken @@ -103,11 +103,12 @@ open class UntrustedMemoryOAuth2Session: OAuth2Session { /** Initialize session using account id. - :param: accountId uniqueId to identify the oauth2module. - :param: accessToken optional parameter to initilaize the storage with initial values. - :param: accessTokenExpirationDate optional parameter to initilaize the storage with initial values. - :param: refreshToken optional parameter to initilaize the storage with initial values. - :param: refreshTokenExpirationDate optional parameter to initilaize the storage with initial values. + :param: accountId uniqueId to identify the OAuth2Module. + :param: accessToken optional parameter to initialize the storage with initial values. + :param: accessTokenExpirationDate optional parameter to initialize the storage with initial values. + :param: refreshToken optional parameter to initialize the storage with initial values. + :param: refreshTokenExpirationDate optional parameter to initialize the storage with initial values. + :param: idToken optional parameter to initialize the storage with initial values. */ public init(accountId: String, accessToken: String? = nil, accessTokenExpirationDate: Date? = nil, refreshToken: String? = nil, refreshTokenExpirationDate: Date? = nil, idToken: String? = nil) { self.accessToken = accessToken diff --git a/TDConnectIosSdkTests/ConfigTest.swift b/TDConnectIosSdkTests/ConfigTest.swift index 37411dd..c45f67a 100644 --- a/TDConnectIosSdkTests/ConfigTest.swift +++ b/TDConnectIosSdkTests/ConfigTest.swift @@ -66,7 +66,7 @@ class ConfigTests: XCTestCase { scopes:["photo_upload, publish_actions"], isOpenIDConnect: true) print(facebookConfig.scopes) - XCTAssert(facebookConfig.scopes[0] == "photo_upload, publish_actions, public_profile", "public_profile defined for Open ID Connect config, facebook does not use openid") + XCTAssert(facebookConfig.scopes[0] == "photo_upload, publish_actions, public_profile", "public_profile defined for Open ID Connect config, Facebook does not use openid") } diff --git a/TDConnectIosSdkTests/FacebookOAuth2ModuleTest.swift b/TDConnectIosSdkTests/FacebookOAuth2ModuleTest.swift index 330b54a..453a6c1 100644 --- a/TDConnectIosSdkTests/FacebookOAuth2ModuleTest.swift +++ b/TDConnectIosSdkTests/FacebookOAuth2ModuleTest.swift @@ -77,7 +77,7 @@ class FacebookOAuth2ModuleTests: XCTestCase { let mockedSession = MockOAuth2SessionWithRefreshToken() let oauth2Module = FacebookOAuth2Module(config: facebookConfig, session: mockedSession, requestSerializer: JsonRequestSerializer(), responseSerializer: StringResponseSerializer()) oauth2Module.revokeAccess(completionHandler: {(response: AnyObject?, error: NSError?) -> Void in - XCTAssertTrue(mockedSession.initCalled == 1, "revoke token reset session") + XCTAssertTrue(mockedSession.clearTokensCalled, "revoke token reset session") expectation.fulfill() }) waitForExpectations(timeout: 10, handler: nil) diff --git a/TDConnectIosSdkTests/OAuth2ModuleMock.swift b/TDConnectIosSdkTests/OAuth2ModuleMock.swift index 1b11eed..4471c21 100644 --- a/TDConnectIosSdkTests/OAuth2ModuleMock.swift +++ b/TDConnectIosSdkTests/OAuth2ModuleMock.swift @@ -58,7 +58,7 @@ open class MockOAuth2SessionWithValidAccessTokenStored: OAuth2Session { open class MockOAuth2SessionWithRefreshToken: MockOAuth2SessionWithValidAccessTokenStored { open var savedRefreshedToken: String? - open var initCalled = 0 + open var clearTokensCalled = false open override var refreshToken: String? { get { return "REFRESH_TOKEN" @@ -85,7 +85,7 @@ open class MockOAuth2SessionWithRefreshToken: MockOAuth2SessionWithValidAccessTo savedRefreshedToken = refreshToken super.save(accessToken: accessToken, refreshToken: refreshToken, accessTokenExpiration: accessTokenExpiration, refreshTokenExpiration: refreshTokenExpiration, idToken: idToken) } - open override func clearTokens() {initCalled = 1} + open override func clearTokens() {clearTokensCalled = true} public override init() {} } diff --git a/TDConnectIosSdkTests/OAuth2ModuleTest.swift b/TDConnectIosSdkTests/OAuth2ModuleTest.swift index 0d21d26..95f53a4 100644 --- a/TDConnectIosSdkTests/OAuth2ModuleTest.swift +++ b/TDConnectIosSdkTests/OAuth2ModuleTest.swift @@ -37,15 +37,17 @@ func setupStubWithNSURLSessionDefaultConfiguration() { let string = "{\"id\":\"10204448880356292\",\"first_name\":\"Corinne\",\"gender\":\"female\",\"last_name\":\"Krych\",\"link\":\"https:\\/\\/www.facebook.com\\/app_scoped_user_id\\/10204448880356292\\/\",\"locale\":\"en_GB\",\"name\":\"Corinne Krych\",\"timezone\":1,\"updated_time\":\"2014-09-24T10:51:12+0000\",\"verified\":true}" let data = string.data(using: String.Encoding.utf8) return OHHTTPStubsResponse(data:data!, statusCode: 200, headers: ["Content-Type" : "text/json"]) - case "/o/oauth2/token": + case "/o/oauth2/token", + "/oauth/token": let string = "{\"access_token\":\"NEWLY_REFRESHED_ACCESS_TOKEN\", \"refresh_token\":\"REFRESH_TOKEN\",\"expires_in\":23, \"id_token\":\"NEW_ID_TOKEN\"}" let data = string.data(using: String.Encoding.utf8) return OHHTTPStubsResponse(data:data!, statusCode: 200, headers: ["Content-Type" : "text/json"]) - case "/o/oauth2/revoke": + case "/o/oauth2/revoke", + "/oauth/revoke", + "/oauth/logout": let string = "{}" let data = string.data(using: String.Encoding.utf8) return OHHTTPStubsResponse(data:data!, statusCode: 200, headers: ["Content-Type" : "text/json"]) - default: return OHHTTPStubsResponse(data:Data(), statusCode: 200, headers: ["Content-Type" : "text/json"]) } }) @@ -176,12 +178,34 @@ class OAuth2ModuleTests: XCTestCase { let mockedSession = MockOAuth2SessionWithRefreshToken() let oauth2Module = OAuth2Module(config: googleConfig, session: mockedSession) oauth2Module.revokeAccess(completionHandler: {(response: AnyObject?, error: NSError?) -> Void in - XCTAssertTrue(mockedSession.initCalled == 1, "revoke token reset session") + XCTAssertTrue(mockedSession.clearTokensCalled, "revoke token reset session") expectation.fulfill() }) waitForExpectations(timeout: 10, handler: nil) } + func testTelenorConnectOAuth2ModuleLogOutClearsTokens() { + setupStubWithNSURLSessionDefaultConfiguration() + let expectation = self.expectation(description: "logOut clears session tokens") + let config = TelenorConnectConfig( + clientId: "clientId", + redirectUrl: "redirectUrl", + useStaging: true, + scopes: ["scope1", "scope2"], + accountId: "accountId", + claims: ["claim1", "claim2"], + webView: false) + let mockedSession = MockOAuth2SessionWithRefreshToken() + let oauth2Module: OAuth2Module = TelenorConnectOAuth2Module(config: config, session: mockedSession) + oauth2Module.revokeAccess { (success, error) in + XCTAssertTrue(mockedSession.clearTokensCalled, "revoke token reset session") + XCTAssertNil(success) + XCTAssertNil(error) + expectation.fulfill() + } + waitForExpectations(timeout: 10, handler: nil) + } + func testGetClaimsParamFormatsCorrectly() { let claims: Set = ["email", "phone"] diff --git a/TDConnectIosSdkTests/OpenIDConnectFacebookOAuth2ModuleTest.swift b/TDConnectIosSdkTests/OpenIDConnectFacebookOAuth2ModuleTest.swift index f847a7a..fd28771 100644 --- a/TDConnectIosSdkTests/OpenIDConnectFacebookOAuth2ModuleTest.swift +++ b/TDConnectIosSdkTests/OpenIDConnectFacebookOAuth2ModuleTest.swift @@ -93,7 +93,7 @@ class OpenIDConnectFacebookOAuth2ModuleTests: XCTestCase { oauth2Module.login {(accessToken: AnyObject?, claims: OpenIdClaim?, error: NSError?) in var erroDict = (error?.userInfo)! let value = erroDict["OpenID Connect"] as! String - XCTAssertTrue( value == "No UserInfo endpoint available in config", "claim shoud be as mocked") + XCTAssertTrue( value == "No UserInfo endpoint available in config", "claim should be as mocked") loginExpectation.fulfill() } diff --git a/TDConnectIosSdkTests/OpenIDConnectGoogleOAuth2ModuleTest.swift b/TDConnectIosSdkTests/OpenIDConnectGoogleOAuth2ModuleTest.swift index 3859c18..bc46396 100644 --- a/TDConnectIosSdkTests/OpenIDConnectGoogleOAuth2ModuleTest.swift +++ b/TDConnectIosSdkTests/OpenIDConnectGoogleOAuth2ModuleTest.swift @@ -61,7 +61,7 @@ class OpenIDConnectGoogleOAuth2ModuleTests: XCTestCase { oauth2Module.login {(accessToken: AnyObject?, claims: OpenIdClaim?, error: NSError?) in - XCTAssertTrue("John" == claims?.name, "claim shoud be as mocked") + XCTAssertTrue("John" == claims?.name, "claim should be as mocked") loginExpectation.fulfill() } @@ -89,7 +89,7 @@ class OpenIDConnectGoogleOAuth2ModuleTests: XCTestCase { oauth2Module.login {(accessToken: AnyObject?, claims: OpenIdClaim?, error: NSError?) in var erroDict = (error?.userInfo)! let value = erroDict["OpenID Connect"] as! String - XCTAssertTrue( value == "No UserInfo endpoint available in config", "claim shoud be as mocked") + XCTAssertTrue( value == "No UserInfo endpoint available in config", "claim should be as mocked") loginExpectation.fulfill() } diff --git a/TDConnectIosSdkTests/OpenIDConnectKeycloakOAuth2ModuleTest.swift b/TDConnectIosSdkTests/OpenIDConnectKeycloakOAuth2ModuleTest.swift index c3e3457..1ff244e 100644 --- a/TDConnectIosSdkTests/OpenIDConnectKeycloakOAuth2ModuleTest.swift +++ b/TDConnectIosSdkTests/OpenIDConnectKeycloakOAuth2ModuleTest.swift @@ -59,10 +59,10 @@ class OpenIDConnectKeycloakOAuth2ModuleTests: XCTestCase { let oauth2Module = AccountManager.addAccountWith(config: keycloakConfig, moduleClass: MyKeycloakMockOAuth2ModuleSuccess.self) // no need of http stub as Keycloak does not provide a UserInfo endpoint but decode JWT token oauth2Module.login {(accessToken: AnyObject?, claims: OpenIdClaim?, error: NSError?) in - XCTAssertTrue("Sample User" == claims?.name, "name claim shoud be as defined in JWT token") - XCTAssertTrue("User" == claims?.familyName, "family name claim shoud be as defined in JWT token") - XCTAssertTrue("sample-user@example" == claims?.email, "email claim shoud be as defined in JWT token") - XCTAssertTrue("Sample" == claims?.givenName, "given name claim shoud be as defined in JWT token") + XCTAssertTrue("Sample User" == claims?.name, "name claim should be as defined in JWT token") + XCTAssertTrue("User" == claims?.familyName, "family name claim should be as defined in JWT token") + XCTAssertTrue("sample-user@example" == claims?.email, "email claim should be as defined in JWT token") + XCTAssertTrue("Sample" == claims?.givenName, "given name claim should be as defined in JWT token") loginExpectation.fulfill() }