diff --git a/AeroGearOAuth2.xcodeproj/project.pbxproj b/AeroGearOAuth2.xcodeproj/project.pbxproj index 4987dad..fd49682 100644 --- a/AeroGearOAuth2.xcodeproj/project.pbxproj +++ b/AeroGearOAuth2.xcodeproj/project.pbxproj @@ -240,7 +240,6 @@ 4833045E19AF1635002F8DA9 /* Frameworks */, 4833045F19AF1635002F8DA9 /* Headers */, 4833046019AF1635002F8DA9 /* Resources */, - 7BA5AFF5C6DA9134893CDFC3 /* [CP] Copy Pods Resources */, ); buildRules = ( ); @@ -261,7 +260,6 @@ 4833046B19AF1635002F8DA9 /* Resources */, 6F1432B31A16857C003BEE5B /* Copy Frameworks */, 81DD998A611C4EAF191D8BE0 /* [CP] Embed Pods Frameworks */, - 8C9D4DEB6C7183EC37B53099 /* [CP] Copy Pods Resources */, ); buildRules = ( ); @@ -298,6 +296,7 @@ developmentRegion = English; hasScannedForEncodings = 0; knownRegions = ( + English, en, ); mainGroup = 4833045819AF1635002F8DA9; @@ -347,54 +346,22 @@ 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; }; - 7BA5AFF5C6DA9134893CDFC3 /* [CP] Copy Pods Resources */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - ); - name = "[CP] Copy Pods Resources"; - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-AeroGearOAuth2/Pods-AeroGearOAuth2-resources.sh\"\n"; - showEnvVarsInLog = 0; - }; 81DD998A611C4EAF191D8BE0 /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( - "${SRCROOT}/Pods/Target Support Files/Pods-AeroGearOAuth2Tests/Pods-AeroGearOAuth2Tests-frameworks.sh", + "${PODS_ROOT}/Target Support Files/Pods-AeroGearOAuth2Tests/Pods-AeroGearOAuth2Tests-frameworks.sh", "${BUILT_PRODUCTS_DIR}/OHHTTPStubs/OHHTTPStubs.framework", - "${BUILT_PRODUCTS_DIR}/AeroGearHttp/AeroGearHttp.framework", ); name = "[CP] Embed Pods Frameworks"; outputPaths = ( "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/OHHTTPStubs.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/AeroGearHttp.framework", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-AeroGearOAuth2Tests/Pods-AeroGearOAuth2Tests-frameworks.sh\"\n"; - showEnvVarsInLog = 0; - }; - 8C9D4DEB6C7183EC37B53099 /* [CP] Copy Pods Resources */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - ); - name = "[CP] Copy Pods Resources"; - outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-AeroGearOAuth2Tests/Pods-AeroGearOAuth2Tests-resources.sh\"\n"; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-AeroGearOAuth2Tests/Pods-AeroGearOAuth2Tests-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; FE44ED274111AE8BBB6AE3E2 /* [CP] Check Pods Manifest.lock */ = { @@ -594,7 +561,7 @@ SKIP_INSTALL = YES; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_SWIFT3_OBJC_INFERENCE = Default; - SWIFT_VERSION = 4.0; + SWIFT_VERSION = 5.0; }; name = Debug; }; @@ -616,7 +583,7 @@ PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; SWIFT_SWIFT3_OBJC_INFERENCE = Default; - SWIFT_VERSION = 4.0; + SWIFT_VERSION = 5.0; }; name = Release; }; diff --git a/AeroGearOAuth2/AccountManager.swift b/AeroGearOAuth2/AccountManager.swift index ccf95d0..af67ff9 100644 --- a/AeroGearOAuth2/AccountManager.swift +++ b/AeroGearOAuth2/AccountManager.swift @@ -95,14 +95,15 @@ open class KeycloakConfig: Config { :param: realm to identify which realm to use. A realm group a set of application/OAuth2 client together. :param: isOpenIDConnect to identify if fetching id information is required. */ - public init(clientId: String, host: String, realm: String? = nil, isOpenIDConnect: Bool = false) { + public init(clientId: String, host: String, realm: String? = nil, isOpenIDConnect: Bool = false, redirectUrl: String? = nil) { let bundleString = Bundle.main.bundleIdentifier ?? "keycloak" let defaulRealmName = String(format: "%@-realm", clientId) let realm = realm ?? defaulRealmName + let redirectUrl = redirectUrl ?? "\(bundleString)://oauth2Callback" super.init( base: "\(host)/auth", authzEndpoint: "realms/\(realm)/protocol/openid-connect/auth", - redirectURL: "\(bundleString)://oauth2Callback", + redirectURL: redirectUrl, accessTokenEndpoint: "realms/\(realm)/protocol/openid-connect/token", clientId: clientId, refreshTokenEndpoint: "realms/\(realm)/protocol/openid-connect/token", diff --git a/AeroGearOAuth2/Config.swift b/AeroGearOAuth2/Config.swift index 3a3fdcc..4cab9d6 100644 --- a/AeroGearOAuth2/Config.swift +++ b/AeroGearOAuth2/Config.swift @@ -24,12 +24,12 @@ open class Config { /** Applies the baseURL to the configuration. */ - open let baseURL: String + public let baseURL: String /** Applies the "callback URL" once request token issued. */ - open let redirectURL: String + public let redirectURL: String /** Applies the "authorization endpoint" to the request token. @@ -44,17 +44,17 @@ open class Config { /** Endpoint for request to invalidate both accessToken and refreshToken. */ - open let revokeTokenEndpoint: String? + public let revokeTokenEndpoint: String? /** Endpoint for request a refreshToken. */ - open let refreshTokenEndpoint: String? + public let refreshTokenEndpoint: String? /** Endpoint for OpenID Connect to get user information. */ - open let userInfoEndpoint: String? + public let userInfoEndpoint: String? /** Boolean to indicate whether OpenID Connect on authorization code grant flow is used. @@ -84,12 +84,12 @@ open class Config { /** Applies the "client id" obtained with the client registration process. */ - open let clientId: String + public let clientId: String /** Applies the "client secret" obtained with the client registration process. */ - open let clientSecret: String? + public let clientSecret: String? /** Applies the "audience" obtained with the client registration process. @@ -121,6 +121,7 @@ open class Config { */ open var webViewHandler: ((UIViewController, _ completionHandler: (AnyObject?, NSError?) -> Void) -> ()) = { (webView, completionHandler) in + webView.modalPresentationStyle = .fullScreen UIApplication.shared.keyWindow?.rootViewController?.present(webView, animated: true, completion: nil) } diff --git a/AeroGearOAuth2/OAuth2Module.swift b/AeroGearOAuth2/OAuth2Module.swift index 469050b..4c23c98 100644 --- a/AeroGearOAuth2/OAuth2Module.swift +++ b/AeroGearOAuth2/OAuth2Module.swift @@ -356,13 +356,7 @@ open class OAuth2Module: AuthzModule { // MARK: Internal Methods - func extractCode(_ notification: Notification, completionHandler: @escaping (AnyObject?, NSError?) -> Void) { - let info = notification.userInfo! - let url: URL? = info[UIApplicationLaunchOptionsKey.url] as? URL - - // extract the code from the URL - let queryParamsDict = self.parametersFrom(queryString: url?.query) - let code = queryParamsDict["code"] + fileprivate func extractCodeFromQuery(_ code: String?, _ completionHandler: @escaping (AnyObject?, NSError?) -> Void, _ queryParamsDict: [String : String]) { // if exists perform the exchange if (code != nil) { self.exchangeAuthorizationCodeForAccessToken(code: code!, completionHandler: completionHandler) @@ -374,10 +368,24 @@ open class OAuth2Module: AuthzModule { completionHandler(nil, error) return } - + let errorDescription = queryParamsDict["error_description"] ?? "There was an error!" let error = NSError(domain: AGAuthzErrorDomain, code: 1, userInfo: ["error": errorName, "errorDescription": errorDescription]) - + + completionHandler(nil, error) + } + } + + func extractCode(_ notification: Notification, completionHandler: @escaping (AnyObject?, NSError?) -> Void) { + let info = notification.userInfo! + if let url = info[UIApplication.LaunchOptionsKey.url] as? URL { + //let url: URL? = info[UIApplication.LaunchOptionsKey.url] as? URL + // extract the code from the URL + let queryParamsDict = self.parametersFrom(queryString: url.query) + let code = queryParamsDict["code"] + extractCodeFromQuery(code, completionHandler, queryParamsDict) + } else { + let error = NSError(domain: AGAuthzErrorDomain, code: 1, userInfo: ["error": "Url Failed", "errorDescription": "error in fetching code"]) completionHandler(nil, error) } // finally, unregister diff --git a/AeroGearOAuth2/OAuth2WebViewController.swift b/AeroGearOAuth2/OAuth2WebViewController.swift index 81c0ad9..e1e71f5 100644 --- a/AeroGearOAuth2/OAuth2WebViewController.swift +++ b/AeroGearOAuth2/OAuth2WebViewController.swift @@ -18,24 +18,31 @@ import Foundation import UIKit +import WebKit + /** OAuth2WebViewController is a UIViewController to be used when the Oauth2 flow used an embedded view controller rather than an external browser approach. */ -open class OAuth2WebViewController: UIViewController, UIWebViewDelegate { +open class OAuth2WebViewController: UIViewController, WKNavigationDelegate, WKUIDelegate, UIScrollViewDelegate { /// Login URL for OAuth. var targetURL: URL! /// WebView instance used to load login page. - var webView: UIWebView = UIWebView() + var webView: WKWebView = WKWebView() + let SESSION_STATE: String = "session_state" + let CODE: String = "code" /// Override of viewDidLoad to load the login page. override open func viewDidLoad() { super.viewDidLoad() - webView.frame = UIScreen.main.bounds - webView.delegate = self - self.view.addSubview(webView) + intializeWKWebview() loadAddressURL() } + + override open func viewWillDisappear(_ animated: Bool) { + webView.cleanAllCookies() + webView.refreshCookies() + } override open func viewDidLayoutSubviews() { super.viewDidLayoutSubviews() @@ -45,9 +52,65 @@ open class OAuth2WebViewController: UIViewController, UIWebViewDelegate { override open func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() } + + fileprivate func intializeWKWebview() { + webView.frame = UIScreen.main.bounds + webView.navigationDelegate = self + let contentController = WKUserContentController() + //Script to disable zoomin and zoomout in webview + let source: String = "var meta = document.createElement('meta');" + + "meta.name = 'viewport';" + + "meta.content = 'width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no';" + + "var head = document.getElementsByTagName('head')[0];" + + "head.appendChild(meta);" + let script = WKUserScript(source: source, injectionTime: .atDocumentEnd, forMainFrameOnly: true) + contentController.addUserScript(script) + let config = WKWebViewConfiguration() + config.userContentController = contentController + webView = WKWebView(frame: .zero, configuration: config ) + webView.scrollView.delegate = self + webView.sizeToFit() + webView.navigationDelegate = self + webView.uiDelegate = self + view = webView + } func loadAddressURL() { let req = URLRequest(url: targetURL) - webView.loadRequest(req) + webView.load(req) + } + + /** + WKWebview delegate methods + */ + public func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping ((WKNavigationActionPolicy) -> Void)) { + if let url = navigationAction.request.url?.absoluteString { + if url.contains(SESSION_STATE) && url.contains(CODE) { + if let urlReq = URL(string: url) { + let notification = Notification(name: Notification.Name(AGAppLaunchedWithURLNotification), object: nil, userInfo: [UIApplication.LaunchOptionsKey.url: urlReq]) + NotificationCenter.default.post(notification) + } + decisionHandler(.cancel) + } else { + decisionHandler(.allow) + } + } + } + public func webView(_ webView: WKWebView, didFinish navigation: WKNavigation?) { + } } +extension WKWebView { + func cleanAllCookies() { + HTTPCookieStorage.shared.removeCookies(since: Date.distantPast) + WKWebsiteDataStore.default().fetchDataRecords(ofTypes: WKWebsiteDataStore.allWebsiteDataTypes()) { records in + records.forEach { record in + WKWebsiteDataStore.default().removeData(ofTypes: record.dataTypes, for: [record], completionHandler: {}) + } + } + } + func refreshCookies() { + self.configuration.processPool = WKProcessPool() + } +} + diff --git a/Podfile b/Podfile index acfba41..ce49f6d 100644 --- a/Podfile +++ b/Podfile @@ -5,7 +5,8 @@ platform :ios, '9.0' use_frameworks! target 'AeroGearOAuth2' do - pod 'AeroGearHttp', '2.0.0' + #pod 'AeroGearHttp', '2.0.0' + pod 'AeroGearHttp', :git => 'https://github.com/SiddharthMurugaiyan/aerogear-ios-http.git' target 'AeroGearOAuth2Tests' do inherit! :search_paths diff --git a/Podfile.lock b/Podfile.lock index bb0da8c..d544ac1 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -15,13 +15,26 @@ PODS: - OHHTTPStubs/OHPathHelpers (5.2.2) DEPENDENCIES: - - AeroGearHttp (= 2.0.0) + - AeroGearHttp (from `https://github.com/SiddharthMurugaiyan/aerogear-ios-http.git`) - OHHTTPStubs (= 5.2.2) +SPEC REPOS: + https://github.com/CocoaPods/Specs.git: + - OHHTTPStubs + +EXTERNAL SOURCES: + AeroGearHttp: + :git: https://github.com/SiddharthMurugaiyan/aerogear-ios-http.git + +CHECKOUT OPTIONS: + AeroGearHttp: + :commit: 75fcb49ed92eb81dedcf7a007ebb5d49c8d6cb66 + :git: https://github.com/SiddharthMurugaiyan/aerogear-ios-http.git + SPEC CHECKSUMS: - AeroGearHttp: 65bf374aefd3521e1d8d33547e4db96047b745aa + AeroGearHttp: ef1cdf2d14ba52bb927c42e6de6ccc26db9adf98 OHHTTPStubs: 34d9d0994e64fcf8552dbfade5ce82ada913ee31 -PODFILE CHECKSUM: 7b302d89355d58023f6fc008e933fbcf534556a8 +PODFILE CHECKSUM: e217247915055a5df60ec7c79ba313e6823f85d2 -COCOAPODS: 1.3.1 +COCOAPODS: 1.9.1