Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Wk webview #95

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 5 additions & 38 deletions AeroGearOAuth2.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -240,7 +240,6 @@
4833045E19AF1635002F8DA9 /* Frameworks */,
4833045F19AF1635002F8DA9 /* Headers */,
4833046019AF1635002F8DA9 /* Resources */,
7BA5AFF5C6DA9134893CDFC3 /* [CP] Copy Pods Resources */,
);
buildRules = (
);
Expand All @@ -261,7 +260,6 @@
4833046B19AF1635002F8DA9 /* Resources */,
6F1432B31A16857C003BEE5B /* Copy Frameworks */,
81DD998A611C4EAF191D8BE0 /* [CP] Embed Pods Frameworks */,
8C9D4DEB6C7183EC37B53099 /* [CP] Copy Pods Resources */,
);
buildRules = (
);
Expand Down Expand Up @@ -298,6 +296,7 @@
developmentRegion = English;
hasScannedForEncodings = 0;
knownRegions = (
English,
en,
);
mainGroup = 4833045819AF1635002F8DA9;
Expand Down Expand Up @@ -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 */ = {
Expand Down Expand Up @@ -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;
};
Expand All @@ -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;
};
Expand Down
5 changes: 3 additions & 2 deletions AeroGearOAuth2/AccountManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
15 changes: 8 additions & 7 deletions AeroGearOAuth2/Config.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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.
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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)
}

Expand Down
26 changes: 17 additions & 9 deletions AeroGearOAuth2/OAuth2Module.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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
Expand Down
75 changes: 69 additions & 6 deletions AeroGearOAuth2/OAuth2WebViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand All @@ -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()
}
}

3 changes: 2 additions & 1 deletion Podfile
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
21 changes: 17 additions & 4 deletions Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -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