Skip to content

Commit

Permalink
feat: add single sign on by saml
Browse files Browse the repository at this point in the history
  • Loading branch information
RawanMatar89 committed Jul 9, 2024
1 parent 5f4268c commit 130dbf9
Show file tree
Hide file tree
Showing 23 changed files with 476 additions and 31 deletions.
24 changes: 24 additions & 0 deletions Authorization/Authorization.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@
0770DE6B28D0C035006D8A5D /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 0770DE6D28D0C035006D8A5D /* Localizable.strings */; };
0770DE7128D0C0E7006D8A5D /* Strings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0770DE7028D0C0E7006D8A5D /* Strings.swift */; };
5FB79D2802949372CDAF08D6 /* Pods_App_Authorization_AuthorizationTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4FAE9B7FD61FF88C9C4FE1E8 /* Pods_App_Authorization_AuthorizationTests.framework */; };
99C1654B2C0C4F0600DC384D /* ContainerWebView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 99C1654A2C0C4F0600DC384D /* ContainerWebView.swift */; };
99C1654D2C0C4F2F00DC384D /* SSOHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 99C1654C2C0C4F2F00DC384D /* SSOHelper.swift */; };
99C1654F2C0C4F5900DC384D /* SSOWebView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 99C1654E2C0C4F5900DC384D /* SSOWebView.swift */; };
99C165512C0C4F7B00DC384D /* SSOWebViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 99C165502C0C4F7B00DC384D /* SSOWebViewModel.swift */; };
BA8B3A322AD5487300D25EF5 /* SocialAuthView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA8B3A312AD5487300D25EF5 /* SocialAuthView.swift */; };
BADB3F552AD6DFC3004D5CFA /* SocialAuthViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = BADB3F542AD6DFC3004D5CFA /* SocialAuthViewModel.swift */; };
DE843D6BB1B9DDA398494890 /* Pods_App_Authorization.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 47BCFB7C19382EECF15131B6 /* Pods_App_Authorization.framework */; };
Expand Down Expand Up @@ -77,6 +81,10 @@
7A84BB166492D4E46FBCF01C /* Pods-App-Authorization-AuthorizationTests.debugdev.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-App-Authorization-AuthorizationTests.debugdev.xcconfig"; path = "Target Support Files/Pods-App-Authorization-AuthorizationTests/Pods-App-Authorization-AuthorizationTests.debugdev.xcconfig"; sourceTree = "<group>"; };
90DFBB75EF40580E180D71C8 /* Pods-App-Authorization.debugdev.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-App-Authorization.debugdev.xcconfig"; path = "Target Support Files/Pods-App-Authorization/Pods-App-Authorization.debugdev.xcconfig"; sourceTree = "<group>"; };
96C85172770225EB81A6D2DA /* Pods-App-Authorization.releasedev.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-App-Authorization.releasedev.xcconfig"; path = "Target Support Files/Pods-App-Authorization/Pods-App-Authorization.releasedev.xcconfig"; sourceTree = "<group>"; };
99C1654A2C0C4F0600DC384D /* ContainerWebView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContainerWebView.swift; sourceTree = "<group>"; };
99C1654C2C0C4F2F00DC384D /* SSOHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SSOHelper.swift; sourceTree = "<group>"; };
99C1654E2C0C4F5900DC384D /* SSOWebView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SSOWebView.swift; sourceTree = "<group>"; };
99C165502C0C4F7B00DC384D /* SSOWebViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SSOWebViewModel.swift; sourceTree = "<group>"; };
9BF6A1004A955E24527FCF0F /* Pods-App-Authorization-AuthorizationTests.releaseprod.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-App-Authorization-AuthorizationTests.releaseprod.xcconfig"; path = "Target Support Files/Pods-App-Authorization-AuthorizationTests/Pods-App-Authorization-AuthorizationTests.releaseprod.xcconfig"; sourceTree = "<group>"; };
A99D45203C981893C104053A /* Pods-App-Authorization-AuthorizationTests.releasestage.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-App-Authorization-AuthorizationTests.releasestage.xcconfig"; path = "Target Support Files/Pods-App-Authorization-AuthorizationTests/Pods-App-Authorization-AuthorizationTests.releasestage.xcconfig"; sourceTree = "<group>"; };
BA8B3A312AD5487300D25EF5 /* SocialAuthView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SocialAuthView.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -147,6 +155,7 @@
071009CC28D1E24000344290 /* Presentation */ = {
isa = PBXGroup;
children = (
99C165492C0C4EF000DC384D /* SSO */,
BA8B3A302AD5485100D25EF5 /* SocialAuth */,
E03261622AE6464A002CA7EB /* Startup */,
020C31BD290AADA700D6DEA2 /* Base */,
Expand Down Expand Up @@ -268,6 +277,17 @@
path = ../Pods;
sourceTree = "<group>";
};
99C165492C0C4EF000DC384D /* SSO */ = {
isa = PBXGroup;
children = (
99C1654A2C0C4F0600DC384D /* ContainerWebView.swift */,
99C1654C2C0C4F2F00DC384D /* SSOHelper.swift */,
99C1654E2C0C4F5900DC384D /* SSOWebView.swift */,
99C165502C0C4F7B00DC384D /* SSOWebViewModel.swift */,
);
path = SSO;
sourceTree = "<group>";
};
BA8B3A302AD5485100D25EF5 /* SocialAuth */ = {
isa = PBXGroup;
children = (
Expand Down Expand Up @@ -502,13 +522,17 @@
02066B462906D72F00F4307E /* SignUpViewModel.swift in Sources */,
E03261642AE64676002CA7EB /* StartupViewModel.swift in Sources */,
02A2ACDB2A4B016100FBBBBB /* AuthorizationAnalytics.swift in Sources */,
99C165512C0C4F7B00DC384D /* SSOWebViewModel.swift in Sources */,
99C1654B2C0C4F0600DC384D /* ContainerWebView.swift in Sources */,
025F40E029D1E2FC0064C183 /* ResetPasswordView.swift in Sources */,
020C31CB290BF49900D6DEA2 /* FieldsView.swift in Sources */,
0770DE4E28D0A677006D8A5D /* SignInView.swift in Sources */,
02F3BFE5292533720051930C /* AuthorizationRouter.swift in Sources */,
99C1654F2C0C4F5900DC384D /* SSOWebView.swift in Sources */,
E03261662AE64AF4002CA7EB /* StartupView.swift in Sources */,
071009C728D1DA4F00344290 /* SignInViewModel.swift in Sources */,
BA8B3A322AD5487300D25EF5 /* SocialAuthView.swift in Sources */,
99C1654D2C0C4F2F00DC384D /* SSOHelper.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,15 @@ import Core

public enum AuthMethod: Equatable {
case password
case SSO
case socailAuth(SocialAuthMethod)

public var analyticsValue: String {
switch self {
case .password:
"password"
case .SSO:
"SSO"
case .socailAuth(let socialAuthMethod):
socialAuthMethod.rawValue
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ public struct SignInView: View {
.accessibilityIdentifier("password_textfield")
HStack {
if !viewModel.config.features.startupScreenEnabled {
Button(CoreLocalization.register) {
Button(CoreLocalization.SignIn.registerBtn) {
viewModel.router.showRegisterScreen(sourceScreen: viewModel.sourceScreen)
}
.foregroundColor(Theme.Colors.accentColor)
Expand Down Expand Up @@ -155,6 +155,13 @@ public struct SignInView: View {
.frame(maxWidth: .infinity)
.padding(.top, 40)
.accessibilityIdentifier("signin_button")

StyledButton(CoreLocalization.SignIn.logInWithSsoBtn) {
viewModel.router.showSSOWebBrowser(title: CoreLocalization.SignIn.logInBtn)
}
.frame(maxWidth: .infinity)
.padding(.top, 20)
.accessibilityIdentifier("signin_button")
}
}
if viewModel.socialAuthEnabled {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ public struct SignUpView: View {
VStack(alignment: .center) {
ZStack {
HStack {
Text(CoreLocalization.register)
Text(CoreLocalization.SignIn.registerBtn)
.titleSettings(color: Theme.Colors.loginNavigationText)
.accessibilityIdentifier("register_text")
}
Expand All @@ -64,7 +64,7 @@ public struct SignUpView: View {
ScrollView {
VStack(alignment: .leading) {

Text(CoreLocalization.register)
Text(CoreLocalization.SignIn.registerBtn)
.font(Theme.Fonts.displaySmall)
.foregroundColor(Theme.Colors.textPrimary)
.padding(.bottom, 4)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
//
// ContainerWebView.swift
// Authorization
//
// Created by Rawan Matar on 02/06/2024.
//

import SwiftUI
import Core
import Swinject

public struct ContainerWebView: View {

// MARK: - Internal Properties

let url: String
private var pageTitle: String
@Environment(\.presentationMode) var presentationMode

// MARK: - Init

public init(_ url: String, title: String) {
self.url = url
self.pageTitle = title
}

// MARK: - UI

public var body: some View {
VStack(alignment: .center) {
NavigationBar(
title: pageTitle,
leftButtonAction: { presentationMode.wrappedValue.dismiss() }
)

ZStack {
if !url.isEmpty {
SSOWebView(url: URL(string: url), viewModel: Container.shared.resolve(SSOWebViewModel.self)!)
} else {
EmptyView()
}
}
.accessibilityIdentifier("web_browser")
}
}
}
73 changes: 73 additions & 0 deletions Authorization/Authorization/Presentation/SSO/SSOHelper.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
//
// SSOHelper.swift
// Authorization
//
// Created by Rawan Matar on 02/06/2024.
//

import Foundation

// https://developer.apple.com/documentation/ios-ipados-release-notes/foundation-release-notes

/**
A Helper for some of the SSO preferences.
Keeps data under the UserDefaults.
*/
public class SSOHelper: NSObject {

public enum UserDefaultKeys: String, CaseIterable {
case cookiePayload
case cookieSignature
case userInfo

var description: String {
switch self {
case .cookiePayload:
return "edx-jwt-cookie-header-payload"
case .cookieSignature:
return "edx-jwt-cookie-signature"
case .userInfo:
return "edx-user-info"
}
}
}

// MARK: - Singleton

public static let shared = SSOHelper()

// MARK: - Public Properties

/// Authentication
public var cookiePayload: String? {
get {
let defaults = UserDefaults.standard
return defaults.string(forKey: UserDefaultKeys.cookiePayload.rawValue)
}
set (newValue) {
let defaults = UserDefaults.standard
defaults.set(newValue, forKey: UserDefaultKeys.cookiePayload.rawValue)
}
}

/// Authentication
public var cookieSignature: String? {
get {
let defaults = UserDefaults.standard
return defaults.string(forKey: UserDefaultKeys.cookieSignature.rawValue)
}
set (newValue) {
let defaults = UserDefaults.standard
defaults.set(newValue, forKey: UserDefaultKeys.cookieSignature.rawValue)
}
}

// MARK: - Public Methods

/// Checks if the user is login.
public func cleanAfterSuccesfulLogout() {
cookiePayload = nil
cookieSignature = nil
}
}

104 changes: 104 additions & 0 deletions Authorization/Authorization/Presentation/SSO/SSOWebView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
//
// SSOWebView.swift
// Authorization
//
// Created by Rawan Matar on 02/06/2024.
//


import SwiftUI
import WebKit
import Core

public struct SAMLConstants {

public init() {}

public let samlLoginSuccess = URL(string: "https://blue.zeitlabs.com/auth/complete/tpa-saml")!
}

public struct SSOWebView: UIViewRepresentable {

let url: URL?

var viewModel: SSOWebViewModel

public init(url: URL?, viewModel: SSOWebViewModel) {
self.url = url
self.viewModel = viewModel
}

public func makeUIView(context: Context) -> WKWebView {
let coordinator = makeCoordinator()
let userContentController = WKUserContentController()
userContentController.add(coordinator, name: "bridge")

let prefs = WKWebpagePreferences()
let config = WKWebViewConfiguration()
prefs.allowsContentJavaScript = true

config.userContentController = userContentController
config.defaultWebpagePreferences = prefs
config.websiteDataStore = WKWebsiteDataStore.nonPersistent()

let wkWebView = WKWebView(frame: .zero, configuration: config)
wkWebView.navigationDelegate = coordinator

guard let currentURL = url else {
return wkWebView
}
let request = URLRequest(url: currentURL)
wkWebView.load(request)

return wkWebView
}

public func updateUIView(_ uiView: WKWebView, context: Context) {

}

public func makeCoordinator() -> Coordinator {
Coordinator(viewModel: self.viewModel)
}

public class Coordinator: NSObject, WKScriptMessageHandler, WKNavigationDelegate {
var viewModel: SSOWebViewModel

init(viewModel: SSOWebViewModel) {
self.viewModel = viewModel
super.init()
}

// WKScriptMessageHandler
public func userContentController(_ userContentController: WKUserContentController,
didReceive message: WKScriptMessage) {
}

// WKNavigationDelegate
public func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
guard let _ = webView.url?.absoluteString else {
return
}

}

public func webView(_ webView: WKWebView,
decidePolicyFor navigationAction: WKNavigationAction,
decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
guard let url = webView.url?.absoluteString else {
decisionHandler(.allow)
return
}

if url.contains(SAMLConstants().samlLoginSuccess.absoluteString) {
webView.configuration.websiteDataStore.httpCookieStore.getAllCookies { cookies in
Task {
await self.viewModel.SSOLogin(cookies: cookies)
}
}
}

decisionHandler(.allow)
}
}
}
Loading

0 comments on commit 130dbf9

Please sign in to comment.