diff --git a/Authorization/Authorization.xcodeproj/project.pbxproj b/Authorization/Authorization.xcodeproj/project.pbxproj index ffebcdf02..2a9a60060 100644 --- a/Authorization/Authorization.xcodeproj/project.pbxproj +++ b/Authorization/Authorization.xcodeproj/project.pbxproj @@ -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 */; }; @@ -76,6 +80,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 = ""; }; 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 = ""; }; 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 = ""; }; + 99C1654A2C0C4F0600DC384D /* ContainerWebView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContainerWebView.swift; sourceTree = ""; }; + 99C1654C2C0C4F2F00DC384D /* SSOHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SSOHelper.swift; sourceTree = ""; }; + 99C1654E2C0C4F5900DC384D /* SSOWebView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SSOWebView.swift; sourceTree = ""; }; + 99C165502C0C4F7B00DC384D /* SSOWebViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SSOWebViewModel.swift; sourceTree = ""; }; 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 = ""; }; 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 = ""; }; BA8B3A312AD5487300D25EF5 /* SocialAuthView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SocialAuthView.swift; sourceTree = ""; }; @@ -146,6 +154,7 @@ 071009CC28D1E24000344290 /* Presentation */ = { isa = PBXGroup; children = ( + 99C165492C0C4EF000DC384D /* SSO */, BA8B3A302AD5485100D25EF5 /* SocialAuth */, E03261622AE6464A002CA7EB /* Startup */, 020C31BD290AADA700D6DEA2 /* Base */, @@ -267,6 +276,17 @@ path = ../Pods; sourceTree = ""; }; + 99C165492C0C4EF000DC384D /* SSO */ = { + isa = PBXGroup; + children = ( + 99C1654A2C0C4F0600DC384D /* ContainerWebView.swift */, + 99C1654C2C0C4F2F00DC384D /* SSOHelper.swift */, + 99C1654E2C0C4F5900DC384D /* SSOWebView.swift */, + 99C165502C0C4F7B00DC384D /* SSOWebViewModel.swift */, + ); + path = SSO; + sourceTree = ""; + }; BA8B3A302AD5485100D25EF5 /* SocialAuth */ = { isa = PBXGroup; children = ( @@ -501,13 +521,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; }; @@ -606,7 +630,7 @@ CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; - DEVELOPMENT_TEAM = L8PG7LC3Y3; + DEVELOPMENT_TEAM = ""; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; @@ -717,7 +741,7 @@ CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; - DEVELOPMENT_TEAM = L8PG7LC3Y3; + DEVELOPMENT_TEAM = ""; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; @@ -941,7 +965,7 @@ CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; - DEVELOPMENT_TEAM = L8PG7LC3Y3; + DEVELOPMENT_TEAM = ""; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; @@ -1034,7 +1058,7 @@ CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; - DEVELOPMENT_TEAM = L8PG7LC3Y3; + DEVELOPMENT_TEAM = ""; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; @@ -1132,7 +1156,7 @@ CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; - DEVELOPMENT_TEAM = L8PG7LC3Y3; + DEVELOPMENT_TEAM = ""; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; @@ -1225,7 +1249,7 @@ CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; - DEVELOPMENT_TEAM = L8PG7LC3Y3; + DEVELOPMENT_TEAM = ""; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; @@ -1381,7 +1405,7 @@ CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; - DEVELOPMENT_TEAM = L8PG7LC3Y3; + DEVELOPMENT_TEAM = ""; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; @@ -1416,7 +1440,7 @@ CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; - DEVELOPMENT_TEAM = L8PG7LC3Y3; + DEVELOPMENT_TEAM = ""; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; diff --git a/Authorization/Authorization/Presentation/AuthorizationAnalytics.swift b/Authorization/Authorization/Presentation/AuthorizationAnalytics.swift index 9fcd13b69..9371a1b40 100644 --- a/Authorization/Authorization/Presentation/AuthorizationAnalytics.swift +++ b/Authorization/Authorization/Presentation/AuthorizationAnalytics.swift @@ -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 } diff --git a/Authorization/Authorization/Presentation/Login/SignInView.swift b/Authorization/Authorization/Presentation/Login/SignInView.swift index b3f8ef784..104b506cd 100644 --- a/Authorization/Authorization/Presentation/Login/SignInView.swift +++ b/Authorization/Authorization/Presentation/Login/SignInView.swift @@ -61,100 +61,167 @@ public struct SignInView: View { ScrollView { VStack { VStack(alignment: .leading) { - Text(AuthLocalization.SignIn.logInTitle) - .font(Theme.Fonts.displaySmall) - .foregroundColor(Theme.Colors.textPrimary) - .padding(.bottom, 4) - .accessibilityIdentifier("signin_text") - Text(AuthLocalization.SignIn.welcomeBack) - .font(Theme.Fonts.titleSmall) - .foregroundColor(Theme.Colors.textPrimary) - .padding(.bottom, 20) - .accessibilityIdentifier("welcome_back_text") - Text(AuthLocalization.SignIn.emailOrUsername) - .font(Theme.Fonts.labelLarge) - .foregroundColor(Theme.Colors.textPrimary) - .accessibilityIdentifier("username_text") - TextField("", text: $email) - .font(Theme.Fonts.bodyLarge) - .foregroundColor(Theme.Colors.textInputTextColor) - .keyboardType(.emailAddress) - .textContentType(.emailAddress) - .autocapitalization(.none) - .autocorrectionDisabled() - .padding(.all, 14) - .background( - Theme.InputFieldBackground( - placeHolder: AuthLocalization.SignIn.emailOrUsername, - text: email, - padding: 15 + if viewModel.config.uiComponents.loginRegistrationEnabled { + Text(AuthLocalization.SignIn.logInTitle) + .font(Theme.Fonts.displaySmall) + .foregroundColor(Theme.Colors.textPrimary) + .padding(.bottom, 4) + .accessibilityIdentifier("signin_text") + Text(AuthLocalization.SignIn.welcomeBack) + .font(Theme.Fonts.titleSmall) + .foregroundColor(Theme.Colors.textPrimary) + .padding(.bottom, 20) + .accessibilityIdentifier("welcome_back_text") + Text(AuthLocalization.SignIn.emailOrUsername) + .font(Theme.Fonts.labelLarge) + .foregroundColor(Theme.Colors.textPrimary) + .accessibilityIdentifier("username_text") + TextField("", text: $email) + .font(Theme.Fonts.bodyLarge) + .foregroundColor(Theme.Colors.textInputTextColor) + .keyboardType(.emailAddress) + .textContentType(.emailAddress) + .autocapitalization(.none) + .autocorrectionDisabled() + .padding(.all, 14) + .background( + Theme.InputFieldBackground( + placeHolder: AuthLocalization.SignIn.emailOrUsername, + text: email, + padding: 15 + ) ) - ) - .overlay( - Theme.Shapes.textInputShape - .stroke(lineWidth: 1) - .fill(Theme.Colors.textInputStroke) - ) - .accessibilityIdentifier("username_textfield") - - Text(AuthLocalization.SignIn.password) - .font(Theme.Fonts.labelLarge) - .foregroundColor(Theme.Colors.textPrimary) - .padding(.top, 18) - .accessibilityIdentifier("password_text") - SecureField("", text: $password) - .font(Theme.Fonts.bodyLarge) - .foregroundColor(Theme.Colors.textInputTextColor) - .padding(.all, 14) - .background( - Theme.InputFieldBackground( - placeHolder: AuthLocalization.SignIn.password, - text: password, - padding: 15 + .overlay( + Theme.Shapes.textInputShape + .stroke(lineWidth: 1) + .fill(Theme.Colors.textInputStroke) ) - ) - .overlay( - Theme.Shapes.textInputShape - .stroke(lineWidth: 1) - .fill(Theme.Colors.textInputStroke) - ) - .accessibilityIdentifier("password_textfield") - HStack { - if !viewModel.config.features.startupScreenEnabled { - Button(CoreLocalization.register) { - viewModel.router.showRegisterScreen(sourceScreen: viewModel.sourceScreen) + .accessibilityIdentifier("username_textfield") + + Text(AuthLocalization.SignIn.password) + .font(Theme.Fonts.labelLarge) + .foregroundColor(Theme.Colors.textPrimary) + .padding(.top, 18) + .accessibilityIdentifier("password_text") + SecureField("", text: $password) + .font(Theme.Fonts.bodyLarge) + .foregroundColor(Theme.Colors.textInputTextColor) + .padding(.all, 14) + .background( + Theme.InputFieldBackground( + placeHolder: AuthLocalization.SignIn.password, + text: password, + padding: 15 + ) + ) + .overlay( + Theme.Shapes.textInputShape + .stroke(lineWidth: 1) + .fill(Theme.Colors.textInputStroke) + ) + .accessibilityIdentifier("password_textfield") + HStack { + if !viewModel.config.features.startupScreenEnabled { + Button(CoreLocalization.SignIn.registerBtn) { + viewModel.router.showRegisterScreen(sourceScreen: viewModel.sourceScreen) + } + .foregroundColor(Theme.Colors.accentColor) + .accessibilityIdentifier("register_button") + + Spacer() } - .foregroundColor(Theme.Colors.accentColor) - .accessibilityIdentifier("register_button") - Spacer() + Button(AuthLocalization.SignIn.forgotPassBtn) { + viewModel.trackForgotPasswordClicked() + viewModel.router.showForgotPasswordScreen() + } + .font(Theme.Fonts.bodyLarge) + .foregroundColor(Theme.Colors.infoColor) + .padding(.top, 0) + .accessibilityIdentifier("forgot_password_button") } - Button(AuthLocalization.SignIn.forgotPassBtn) { - viewModel.trackForgotPasswordClicked() - viewModel.router.showForgotPasswordScreen() + if viewModel.isShowProgress { + HStack(alignment: .center) { + ProgressBar(size: 40, lineWidth: 8) + .padding(20) + .accessibilityIdentifier("progress_bar") + }.frame(maxWidth: .infinity) + } else { + StyledButton(CoreLocalization.SignIn.logInBtn) { + Task { + await viewModel.login(username: email, password: password) + } + } + .frame(maxWidth: .infinity) + .padding(.top, 40) + .accessibilityIdentifier("signin_button") } - .font(Theme.Fonts.bodyLarge) - .foregroundColor(Theme.Colors.infoColor) - .padding(.top, 0) - .accessibilityIdentifier("forgot_password_button") } - - if viewModel.isShowProgress { - HStack(alignment: .center) { - ProgressBar(size: 40, lineWidth: 8) - .padding(20) - .accessibilityIdentifier("progress_bar") - }.frame(maxWidth: .infinity) - } else { - StyledButton(CoreLocalization.SignIn.logInBtn) { - Task { - await viewModel.login(username: email, password: password) + if viewModel.config.uiComponents.samlSSOLoginEnabled { + if !viewModel.config.uiComponents.loginRegistrationEnabled{ + VStack(alignment: .center) { + Text(AuthLocalization.SignIn.ssoHeading) + .font(Theme.Fonts.headlineSmall) + .multilineTextAlignment(.center) + .foregroundColor(Theme.Colors.textPrimary) + .padding(.bottom, 4) + .padding(.horizontal, 20) + .accessibilityIdentifier("signin_sso_heading") + } + + Divider() + + VStack(alignment: .center) { + Text(AuthLocalization.SignIn.ssoLogInTitle) + .font(Theme.Fonts.headlineSmall) + .multilineTextAlignment(.center) + .foregroundColor(Theme.Colors.textPrimary) + .padding(.bottom, 10) + .padding(.horizontal, 20) + .accessibilityIdentifier("signin_sso_login_title") + + Text(AuthLocalization.SignIn.ssoLogInSubtitle) + .font(Theme.Fonts.titleMedium) + .multilineTextAlignment(.center) + .foregroundColor(Theme.Colors.textSecondaryLight) + .padding(.bottom, 10) + .padding(.horizontal, 20) + .accessibilityIdentifier("signin_sso_login_subtitle") + } + } + + VStack(alignment: .center) { + + if viewModel.isShowProgress { + HStack(alignment: .center) { + ProgressBar(size: 40, lineWidth: 8) + .padding(20) + .accessibilityIdentifier("progressbar") + }.frame(maxWidth: .infinity) + } else { + let languageCode = Locale.current.language.languageCode?.identifier ?? "en" + if viewModel.config.uiComponents.samlSSODefaultLoginButton { + StyledButton(viewModel.config.ssoButtonTitle[languageCode] as! String, action: { + viewModel.router.showSSOWebBrowser(title: CoreLocalization.SignIn.logInBtn) + }) + .frame(maxWidth: .infinity) + .padding(.top, 20) + .accessibilityIdentifier("signin_SSO_button") + } else { + StyledButton(viewModel.config.ssoButtonTitle[languageCode] as! String, action: { + viewModel.router.showSSOWebBrowser(title: CoreLocalization.SignIn.logInBtn) + }, + color: .white, + textColor: Theme.Colors.accentColor, + borderColor: Theme.Colors.accentColor) + .frame(maxWidth: .infinity) + .padding(.top, 20) + .accessibilityIdentifier("signin_SSO_button") + } + } } - .frame(maxWidth: .infinity) - .padding(.top, 40) - .accessibilityIdentifier("signin_button") } } if viewModel.socialAuthEnabled { diff --git a/Authorization/Authorization/Presentation/Login/SignInViewModel.swift b/Authorization/Authorization/Presentation/Login/SignInViewModel.swift index 5a87151f5..22040cd4e 100644 --- a/Authorization/Authorization/Presentation/Login/SignInViewModel.swift +++ b/Authorization/Authorization/Presentation/Login/SignInViewModel.swift @@ -88,6 +88,20 @@ public class SignInViewModel: ObservableObject { } } + @MainActor + func ssoLogin(title: String) async { + analytics.userSignInClicked() + isShowProgress = true + do { + let user = try await interactor.login(ssoToken: "") + analytics.identify(id: "\(user.id)", username: user.username, email: user.email) + analytics.userLogin(method: .password) + router.showMainOrWhatsNewScreen(sourceScreen: sourceScreen) + } catch let error { + failure(error) + } + } + @MainActor func login(with result: Result) async { switch result { diff --git a/Authorization/Authorization/Presentation/Registration/SignUpView.swift b/Authorization/Authorization/Presentation/Registration/SignUpView.swift index d63a3b9ef..a4a869383 100644 --- a/Authorization/Authorization/Presentation/Registration/SignUpView.swift +++ b/Authorization/Authorization/Presentation/Registration/SignUpView.swift @@ -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") } @@ -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) diff --git a/Authorization/Authorization/Presentation/SSO/ContainerWebView.swift b/Authorization/Authorization/Presentation/SSO/ContainerWebView.swift new file mode 100644 index 000000000..476630cf6 --- /dev/null +++ b/Authorization/Authorization/Presentation/SSO/ContainerWebView.swift @@ -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") + } + } +} diff --git a/Authorization/Authorization/Presentation/SSO/SSOHelper.swift b/Authorization/Authorization/Presentation/SSO/SSOHelper.swift new file mode 100644 index 000000000..1f195a153 --- /dev/null +++ b/Authorization/Authorization/Presentation/SSO/SSOHelper.swift @@ -0,0 +1,80 @@ +// +// SSOHelper.swift +// Authorization +// +// Created by Rawan Matar on 02/06/2024. +// + +import Foundation +import KeychainSwift + +// 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 { + + private let keychain: KeychainSwift + public enum SSOHelperKeys: 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" + } + } + } + + public init(keychain: KeychainSwift) { + self.keychain = keychain + } + // MARK: - Public Properties + + /// Authentication + public var cookiePayload: String? { + get { + let defaults = UserDefaults.standard + return keychain.get(SSOHelperKeys.cookiePayload.rawValue) + } + set(newValue) { + if let newValue { + keychain.set(newValue, forKey: SSOHelperKeys.cookiePayload.rawValue) + } else { + keychain.delete(SSOHelperKeys.cookiePayload.rawValue) + } + } + } + + /// Authentication + public var cookieSignature: String? { + get { + let defaults = UserDefaults.standard + return keychain.get(SSOHelperKeys.cookieSignature.rawValue) + } + set(newValue) { + if let newValue { + keychain.set(newValue, forKey: SSOHelperKeys.cookieSignature.rawValue) + } else { + keychain.delete(SSOHelperKeys.cookieSignature.rawValue) + } + } + } + + // MARK: - Public Methods + + /// Checks if the user is login. + public func cleanAfterSuccesfulLogout() { + cookiePayload = nil + cookieSignature = nil + } +} + diff --git a/Authorization/Authorization/Presentation/SSO/SSOWebView.swift b/Authorization/Authorization/Presentation/SSO/SSOWebView.swift new file mode 100644 index 000000000..d7a89cf7b --- /dev/null +++ b/Authorization/Authorization/Presentation/SSO/SSOWebView.swift @@ -0,0 +1,91 @@ +// +// SSOWebView.swift +// Authorization +// +// Created by Rawan Matar on 02/06/2024. +// +import SwiftUI +@preconcurrency import WebKit +import Core + +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!) { + if webView.url?.absoluteString == nil { + 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(viewModel.config.ssoFinishedURL.absoluteString) { + webView.configuration.websiteDataStore.httpCookieStore.getAllCookies { cookies in + Task { + await self.viewModel.SSOLogin(cookies: cookies) + } + } + } + + decisionHandler(.allow) + } + } +} diff --git a/Authorization/Authorization/Presentation/SSO/SSOWebViewModel.swift b/Authorization/Authorization/Presentation/SSO/SSOWebViewModel.swift new file mode 100644 index 000000000..043a34060 --- /dev/null +++ b/Authorization/Authorization/Presentation/SSO/SSOWebViewModel.swift @@ -0,0 +1,119 @@ +// +// SSOWebViewModel.swift +// Authorization +// +// Created by Rawan Matar on 02/06/2024. +// + +import Foundation +import SwiftUI +import Core +import Alamofire +import AuthenticationServices +import FacebookLogin +import GoogleSignIn +import MSAL + +public class SSOWebViewModel: ObservableObject { + + @Published private(set) var isShowProgress = false + @Published private(set) var showError: Bool = false + @Published private(set) var showAlert: Bool = false + let sourceScreen: LogistrationSourceScreen = .default + + var errorMessage: String? { + didSet { + withAnimation { + showError = errorMessage != nil + } + } + } + var alertMessage: String? { + didSet { + withAnimation { + showAlert = alertMessage != nil + } + } + } + + let router: AuthorizationRouter + let config: ConfigProtocol + private let interactor: AuthInteractorProtocol + private let analytics: AuthorizationAnalytics + let ssoHelper: SSOHelper + + public init( + interactor: AuthInteractorProtocol, + router: AuthorizationRouter, + config: ConfigProtocol, + analytics: AuthorizationAnalytics, + ssoHelper: SSOHelper + ) { + self.interactor = interactor + self.router = router + self.config = config + self.analytics = analytics + self.ssoHelper = ssoHelper + } + + @MainActor + func SSOLogin(cookies: [HTTPCookie]) async { + guard !cookies.isEmpty else { + errorMessage = "COOKIES EMPTY" + return + } + + isShowProgress = true + for cookie in cookies { + /// Store cookies in UserDefaults + if cookie.name == SSOHelper.SSOHelperKeys.cookiePayload.description { + self.ssoHelper.cookiePayload = cookie.value + } + + if cookie.name == SSOHelper.SSOHelperKeys.cookieSignature.description { + self.ssoHelper.cookieSignature = cookie.value + } + if let signature = self.ssoHelper.cookieSignature, + let payload = self.ssoHelper.cookiePayload { + isShowProgress = true + do { + let user = try await interactor.login(ssoToken: "\(payload).\(signature)") + analytics.identify(id: "\(user.id)", username: user.username, email: user.email) + analytics.userLogin(method: .SSO) + router.showMainOrWhatsNewScreen(sourceScreen: sourceScreen) + } catch let error { + failure(error, authMethod: .SSO) + } + } + } + } + + @MainActor + private func failure(_ error: Error, authMethod: AuthMethod? = nil) { + isShowProgress = false + if let validationError = error.validationError, + let value = validationError.data?["error_description"] as? String { + if authMethod != .password, validationError.statusCode == 400, let authMethod = authMethod { + errorMessage = AuthLocalization.Error.accountNotRegistered( + authMethod.analyticsValue, + config.platformName + ) + } else if validationError.statusCode == 403 { + errorMessage = AuthLocalization.Error.disabledAccount + } else { + errorMessage = value + } + } else if case APIError.invalidGrant = error { + errorMessage = CoreLocalization.Error.invalidCredentials + } else if error.isInternetError { + errorMessage = CoreLocalization.Error.slowOrNoInternetConnection + } else { + errorMessage = CoreLocalization.Error.unknownError + } + } + + func trackForgotPasswordClicked() { + analytics.forgotPasswordClicked() + } + +} diff --git a/Authorization/Authorization/Presentation/Startup/StartupView.swift b/Authorization/Authorization/Presentation/Startup/StartupView.swift index 59d5772b4..5d762f98b 100644 --- a/Authorization/Authorization/Presentation/Startup/StartupView.swift +++ b/Authorization/Authorization/Presentation/Startup/StartupView.swift @@ -105,6 +105,8 @@ public struct StartupView: View { switch buttonAction { case .signIn: viewModel.router.showLoginScreen(sourceScreen: .startup) + case .signInWithSSO: + viewModel.router.showLoginScreen(sourceScreen: .startup) case .register: viewModel.router.showRegisterScreen(sourceScreen: .startup) } diff --git a/Authorization/Authorization/SwiftGen/Strings.swift b/Authorization/Authorization/SwiftGen/Strings.swift index d139b4a0e..e9358164c 100644 --- a/Authorization/Authorization/SwiftGen/Strings.swift +++ b/Authorization/Authorization/SwiftGen/Strings.swift @@ -69,6 +69,14 @@ public enum AuthLocalization { public static let logInTitle = AuthLocalization.tr("Localizable", "SIGN_IN.LOG_IN_TITLE", fallback: "Sign in") /// Password public static let password = AuthLocalization.tr("Localizable", "SIGN_IN.PASSWORD", fallback: "Password") + /// Start today to build your career with confidence + public static let ssoHeading = AuthLocalization.tr("Localizable", "SIGN_IN.SSO_HEADING", fallback: "Start today to build your career with confidence") + /// Log in through the national unified sign-on service + public static let ssoLogInSubtitle = AuthLocalization.tr("Localizable", "SIGN_IN.SSO_LOG_IN_SUBTITLE", fallback: "Log in through the national unified sign-on service") + /// Sign IN + public static let ssoLogInTitle = AuthLocalization.tr("Localizable", "SIGN_IN.SSO_LOG_IN_TITLE", fallback: "Sign IN") + /// An integrated set of knowledge and empowerment programs to develop the components of the endowment sector and its workers + public static let ssoSupportingText = AuthLocalization.tr("Localizable", "SIGN_IN.SSO_SUPPORTING_TEXT", fallback: "An integrated set of knowledge and empowerment programs to develop the components of the endowment sector and its workers") /// Welcome back! Sign in to access your courses. public static let welcomeBack = AuthLocalization.tr("Localizable", "SIGN_IN.WELCOME_BACK", fallback: "Welcome back! Sign in to access your courses.") } diff --git a/Authorization/Authorization/en.lproj/Localizable.strings b/Authorization/Authorization/en.lproj/Localizable.strings index f15da07ce..5285b05e6 100644 --- a/Authorization/Authorization/en.lproj/Localizable.strings +++ b/Authorization/Authorization/en.lproj/Localizable.strings @@ -14,7 +14,10 @@ "SIGN_IN.FORGOT_PASS_BTN" = "Forgot password?"; "SIGN_IN.AGREEMENT" = "By signing in to this app, you agree to the [%@ End User License Agreement](%@) and [%@ Terms of Service and Honor Code](%@) and you acknowledge that %@ and each Member process your personal data in accordance with the [Privacy Policy.](%@)"; - +"SIGN_IN.SSO_HEADING" = "Start today to build your career with confidence"; +"SIGN_IN.SSO_SUPPORTING_TEXT" = "An integrated set of knowledge and empowerment programs to develop the components of the endowment sector and its workers"; +"SIGN_IN.SSO_LOG_IN_TITLE" = "Sign in"; +"SIGN_IN.SSO_LOG_IN_SUBTITLE" = "Log in through the national unified sign-on service"; "ERROR.INVALID_EMAIL_ADDRESS" = "Invalid email address"; "ERROR.INVALID_PASSWORD_LENGHT" = "Invalid password lenght"; diff --git a/Authorization/AuthorizationTests/AuthorizationMock.generated.swift b/Authorization/AuthorizationTests/AuthorizationMock.generated.swift index a6520c298..b6106d4d0 100644 --- a/Authorization/AuthorizationTests/AuthorizationMock.generated.swift +++ b/Authorization/AuthorizationTests/AuthorizationMock.generated.swift @@ -93,6 +93,23 @@ open class AuthInteractorProtocolMock: AuthInteractorProtocol, Mock { return __value } + @discardableResult + open func login(ssoToken: String) async throws -> Core.User { + addInvocation(.m_login__SSO__username_password(Parameter.value(`ssoToken`))) + let perform = methodPerformValue(.m_login__SSO__username_password(Parameter.value(`ssoToken`))) as? (String) -> Void + perform?(`ssoToken`) + var __value: User + do { + __value = try methodReturnValue(.m_login__SSO__username_password(Parameter.value(ssoToken))).casted() + } catch MockError.notStubed { + onFatalFailure("Stub return value not specified for login(username: String, password: String). Use given") + Failure("Stub return value not specified for login(username: String, password: String). Use given") + } catch { + throw error + } + return __value + } + open func resetPassword(email: String) throws -> ResetPassword { addInvocation(.m_resetPassword__email_email(Parameter.value(`email`))) let perform = methodPerformValue(.m_resetPassword__email_email(Parameter.value(`email`))) as? (String) -> Void @@ -173,6 +190,7 @@ open class AuthInteractorProtocolMock: AuthInteractorProtocol, Mock { fileprivate enum MethodType { case m_login__username_usernamepassword_password(Parameter, Parameter) + case m_login__SSO__username_password(Parameter) case m_login__externalToken_externalTokenbackend_backend(Parameter, Parameter) case m_resetPassword__email_email(Parameter) case m_getCookies__force_force(Parameter) @@ -188,6 +206,11 @@ open class AuthInteractorProtocolMock: AuthInteractorProtocol, Mock { results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsPassword, rhs: rhsPassword, with: matcher), lhsPassword, rhsPassword, "password")) return Matcher.ComparisonResult(results) + case (.m_login__SSO__username_password(let lhsJwtToken), .m_login__SSO__username_password(let rhsJwtToken)): + var results: [Matcher.ParameterComparisonResult] = [] + results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsJwtToken, rhs: rhsJwtToken, with: matcher), lhsJwtToken, rhsJwtToken, "jwtToken")) + return Matcher.ComparisonResult(results) + case (.m_login__externalToken_externalTokenbackend_backend(let lhsExternaltoken, let lhsBackend), .m_login__externalToken_externalTokenbackend_backend(let rhsExternaltoken, let rhsBackend)): var results: [Matcher.ParameterComparisonResult] = [] results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsExternaltoken, rhs: rhsExternaltoken, with: matcher), lhsExternaltoken, rhsExternaltoken, "externalToken")) @@ -223,6 +246,7 @@ open class AuthInteractorProtocolMock: AuthInteractorProtocol, Mock { func intValue() -> Int { switch self { case let .m_login__username_usernamepassword_password(p0, p1): return p0.intValue + p1.intValue + case let .m_login__SSO__username_password(p0): return p0.intValue case let .m_login__externalToken_externalTokenbackend_backend(p0, p1): return p0.intValue + p1.intValue case let .m_resetPassword__email_email(p0): return p0.intValue case let .m_getCookies__force_force(p0): return p0.intValue @@ -234,6 +258,7 @@ open class AuthInteractorProtocolMock: AuthInteractorProtocol, Mock { func assertionName() -> String { switch self { case .m_login__username_usernamepassword_password: return ".login(username:password:)" + case .m_login__SSO__username_password: return ".loginSSO(username:password:)" case .m_login__externalToken_externalTokenbackend_backend: return ".login(externalToken:backend:)" case .m_resetPassword__email_email: return ".resetPassword(email:)" case .m_getCookies__force_force: return ".getCookies(force:)" @@ -261,6 +286,10 @@ open class AuthInteractorProtocolMock: AuthInteractorProtocol, Mock { public static func login(externalToken: Parameter, backend: Parameter, willReturn: User...) -> MethodStub { return Given(method: .m_login__externalToken_externalTokenbackend_backend(`externalToken`, `backend`), products: willReturn.map({ StubProduct.return($0 as Any) })) } + @discardableResult + public static func ssoLogin(title: Parameter, willReturn: User...) -> MethodStub { + return Given(method: .m_login__SSO__username_password(`title`), products: willReturn.map({ StubProduct.return($0 as Any) })) + } public static func resetPassword(email: Parameter, willReturn: ResetPassword...) -> MethodStub { return Given(method: .m_resetPassword__email_email(`email`), products: willReturn.map({ StubProduct.return($0 as Any) })) } @@ -353,7 +382,8 @@ open class AuthInteractorProtocolMock: AuthInteractorProtocol, Mock { fileprivate var method: MethodType @discardableResult - public static func login(username: Parameter, password: Parameter) -> Verify { return Verify(method: .m_login__username_usernamepassword_password(`username`, `password`))} + public static func login(username: Parameter, password: Parameter) -> Verify { return Verify(method: .m_login__username_usernamepassword_password(`username`, `password`))} + public static func ssoLogin(title: Parameter) -> Verify { return Verify(method: .m_login__SSO__username_password(`title`))} @discardableResult public static func login(externalToken: Parameter, backend: Parameter) -> Verify { return Verify(method: .m_login__externalToken_externalTokenbackend_backend(`externalToken`, `backend`))} public static func resetPassword(email: Parameter) -> Verify { return Verify(method: .m_resetPassword__email_email(`email`))} @@ -579,6 +609,7 @@ open class AuthorizationAnalyticsMock: AuthorizationAnalytics, Mock { fileprivate enum MethodType { case m_identify__id_idusername_usernameemail_email(Parameter, Parameter, Parameter) case m_userLogin__method_method(Parameter) + case m_ssoLogin__method_method(Parameter) case m_registerClicked case m_signInClicked case m_userSignInClicked @@ -638,6 +669,7 @@ open class AuthorizationAnalyticsMock: AuthorizationAnalytics, Mock { switch self { case let .m_identify__id_idusername_usernameemail_email(p0, p1, p2): return p0.intValue + p1.intValue + p2.intValue case let .m_userLogin__method_method(p0): return p0.intValue + case let .m_ssoLogin__method_method(p0): return p0.intValue case .m_registerClicked: return 0 case .m_signInClicked: return 0 case .m_userSignInClicked: return 0 @@ -653,6 +685,7 @@ open class AuthorizationAnalyticsMock: AuthorizationAnalytics, Mock { switch self { case .m_identify__id_idusername_usernameemail_email: return ".identify(id:username:email:)" case .m_userLogin__method_method: return ".userLogin(method:)" + case .m_ssoLogin__method_method: return ".ssoLogin(method:)" case .m_registerClicked: return ".registerClicked()" case .m_signInClicked: return ".signInClicked()" case .m_userSignInClicked: return ".userSignInClicked()" @@ -682,6 +715,7 @@ open class AuthorizationAnalyticsMock: AuthorizationAnalytics, Mock { public static func identify(id: Parameter, username: Parameter, email: Parameter) -> Verify { return Verify(method: .m_identify__id_idusername_usernameemail_email(`id`, `username`, `email`))} public static func userLogin(method: Parameter) -> Verify { return Verify(method: .m_userLogin__method_method(`method`))} + public static func ssoLogin(method: Parameter) -> Verify { return Verify(method: .m_ssoLogin__method_method(`method`))} public static func registerClicked() -> Verify { return Verify(method: .m_registerClicked)} public static func signInClicked() -> Verify { return Verify(method: .m_signInClicked)} public static func userSignInClicked() -> Verify { return Verify(method: .m_userSignInClicked)} @@ -927,6 +961,12 @@ open class AuthorizationRouterMock: AuthorizationRouter, Mock { perform?(`title`, `url`) } + open func showSSOWebBrowser(title: String) { + addInvocation(.m_showWebBrowser__SSO(Parameter.value(`title`))) + let perform = methodPerformValue(.m_showWebBrowser__SSO(Parameter.value(`title`))) as? (String) -> Void + perform?(`title`) + } + open func presentAlert(alertTitle: String, alertMessage: String, positiveAction: String, onCloseTapped: @escaping () -> Void, okTapped: @escaping () -> Void, type: AlertViewType) { addInvocation(.m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagepositiveAction_positiveActiononCloseTapped_onCloseTappedokTapped_okTappedtype_type(Parameter.value(`alertTitle`), Parameter.value(`alertMessage`), Parameter.value(`positiveAction`), Parameter<() -> Void>.value(`onCloseTapped`), Parameter<() -> Void>.value(`okTapped`), Parameter.value(`type`))) let perform = methodPerformValue(.m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagepositiveAction_positiveActiononCloseTapped_onCloseTappedokTapped_okTappedtype_type(Parameter.value(`alertTitle`), Parameter.value(`alertMessage`), Parameter.value(`positiveAction`), Parameter<() -> Void>.value(`onCloseTapped`), Parameter<() -> Void>.value(`okTapped`), Parameter.value(`type`))) as? (String, String, String, @escaping () -> Void, @escaping () -> Void, AlertViewType) -> Void @@ -966,6 +1006,7 @@ open class AuthorizationRouterMock: AuthorizationRouter, Mock { case m_showForgotPasswordScreen case m_showDiscoveryScreen__searchQuery_searchQuerysourceScreen_sourceScreen(Parameter, Parameter) case m_showWebBrowser__title_titleurl_url(Parameter, Parameter) + case m_showWebBrowser__SSO(Parameter) case m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagepositiveAction_positiveActiononCloseTapped_onCloseTappedokTapped_okTappedtype_type(Parameter, Parameter, Parameter, Parameter<() -> Void>, Parameter<() -> Void>, Parameter) case m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagenextSectionName_nextSectionNameaction_actionimage_imageonCloseTapped_onCloseTappedokTapped_okTappednextSectionTapped_nextSectionTapped(Parameter, Parameter, Parameter, Parameter, Parameter, Parameter<() -> Void>, Parameter<() -> Void>, Parameter<() -> Void>) case m_presentView__transitionStyle_transitionStyleview_viewcompletion_completion(Parameter, Parameter, Parameter<(() -> Void)?>) @@ -1085,6 +1126,7 @@ open class AuthorizationRouterMock: AuthorizationRouter, Mock { case .m_showForgotPasswordScreen: return 0 case let .m_showDiscoveryScreen__searchQuery_searchQuerysourceScreen_sourceScreen(p0, p1): return p0.intValue + p1.intValue case let .m_showWebBrowser__title_titleurl_url(p0, p1): return p0.intValue + p1.intValue + case let .m_showWebBrowser__SSO(p0): return p0.intValue case let .m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagepositiveAction_positiveActiononCloseTapped_onCloseTappedokTapped_okTappedtype_type(p0, p1, p2, p3, p4, p5): return p0.intValue + p1.intValue + p2.intValue + p3.intValue + p4.intValue + p5.intValue case let .m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagenextSectionName_nextSectionNameaction_actionimage_imageonCloseTapped_onCloseTappedokTapped_okTappednextSectionTapped_nextSectionTapped(p0, p1, p2, p3, p4, p5, p6, p7): return p0.intValue + p1.intValue + p2.intValue + p3.intValue + p4.intValue + p5.intValue + p6.intValue + p7.intValue case let .m_presentView__transitionStyle_transitionStyleview_viewcompletion_completion(p0, p1, p2): return p0.intValue + p1.intValue + p2.intValue @@ -1106,6 +1148,7 @@ open class AuthorizationRouterMock: AuthorizationRouter, Mock { case .m_showForgotPasswordScreen: return ".showForgotPasswordScreen()" case .m_showDiscoveryScreen__searchQuery_searchQuerysourceScreen_sourceScreen: return ".showDiscoveryScreen(searchQuery:sourceScreen:)" case .m_showWebBrowser__title_titleurl_url: return ".showWebBrowser(title:url:)" + case .m_showWebBrowser__SSO: return ".showSSOWebBrowser(title:)" case .m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagepositiveAction_positiveActiononCloseTapped_onCloseTappedokTapped_okTappedtype_type: return ".presentAlert(alertTitle:alertMessage:positiveAction:onCloseTapped:okTapped:type:)" case .m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagenextSectionName_nextSectionNameaction_actionimage_imageonCloseTapped_onCloseTappedokTapped_okTappednextSectionTapped_nextSectionTapped: return ".presentAlert(alertTitle:alertMessage:nextSectionName:action:image:onCloseTapped:okTapped:nextSectionTapped:)" case .m_presentView__transitionStyle_transitionStyleview_viewcompletion_completion: return ".presentView(transitionStyle:view:completion:)" @@ -1190,6 +1233,9 @@ open class AuthorizationRouterMock: AuthorizationRouter, Mock { public static func showWebBrowser(title: Parameter, url: Parameter, perform: @escaping (String, URL) -> Void) -> Perform { return Perform(method: .m_showWebBrowser__title_titleurl_url(`title`, `url`), performs: perform) } + public static func showSSOWebBrowser(title: Parameter, perform: @escaping (String, URL) -> Void) -> Perform { + return Perform(method: .m_showWebBrowser__SSO(`title`), performs: perform) + } public static func presentAlert(alertTitle: Parameter, alertMessage: Parameter, positiveAction: Parameter, onCloseTapped: Parameter<() -> Void>, okTapped: Parameter<() -> Void>, type: Parameter, perform: @escaping (String, String, String, @escaping () -> Void, @escaping () -> Void, AlertViewType) -> Void) -> Perform { return Perform(method: .m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagepositiveAction_positiveActiononCloseTapped_onCloseTappedokTapped_okTappedtype_type(`alertTitle`, `alertMessage`, `positiveAction`, `onCloseTapped`, `okTapped`, `type`), performs: perform) } @@ -1389,8 +1435,14 @@ open class BaseRouterMock: BaseRouter, Mock { open func showWebBrowser(title: String, url: URL) { addInvocation(.m_showWebBrowser__title_titleurl_url(Parameter.value(`title`), Parameter.value(`url`))) - let perform = methodPerformValue(.m_showWebBrowser__title_titleurl_url(Parameter.value(`title`), Parameter.value(`url`))) as? (String, URL) -> Void - perform?(`title`, `url`) + let perform = methodPerformValue(.m_showWebBrowser__title_titleurl_url(Parameter.value(`title`), Parameter.value(`url`))) as? (String, URL) -> Void + perform?(`title`, `url`) + } + + open func showSSOWebBrowser(title: String) { + addInvocation(.m_showWebBrowser__SSO(Parameter.value(`title`))) + let perform = methodPerformValue(.m_showWebBrowser__SSO(Parameter.value(`title`))) as? (String) -> Void + perform?(`title`) } open func presentAlert(alertTitle: String, alertMessage: String, positiveAction: String, onCloseTapped: @escaping () -> Void, okTapped: @escaping () -> Void, type: AlertViewType) { @@ -1431,6 +1483,7 @@ open class BaseRouterMock: BaseRouter, Mock { case m_showForgotPasswordScreen case m_showDiscoveryScreen__searchQuery_searchQuerysourceScreen_sourceScreen(Parameter, Parameter) case m_showWebBrowser__title_titleurl_url(Parameter, Parameter) + case m_showWebBrowser__SSO(Parameter) case m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagepositiveAction_positiveActiononCloseTapped_onCloseTappedokTapped_okTappedtype_type(Parameter, Parameter, Parameter, Parameter<() -> Void>, Parameter<() -> Void>, Parameter) case m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagenextSectionName_nextSectionNameaction_actionimage_imageonCloseTapped_onCloseTappedokTapped_okTappednextSectionTapped_nextSectionTapped(Parameter, Parameter, Parameter, Parameter, Parameter, Parameter<() -> Void>, Parameter<() -> Void>, Parameter<() -> Void>) case m_presentView__transitionStyle_transitionStyleview_viewcompletion_completion(Parameter, Parameter, Parameter<(() -> Void)?>) @@ -1544,6 +1597,7 @@ open class BaseRouterMock: BaseRouter, Mock { case .m_showForgotPasswordScreen: return 0 case let .m_showDiscoveryScreen__searchQuery_searchQuerysourceScreen_sourceScreen(p0, p1): return p0.intValue + p1.intValue case let .m_showWebBrowser__title_titleurl_url(p0, p1): return p0.intValue + p1.intValue + case let .m_showWebBrowser__SSO(p0): return p0.intValue case let .m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagepositiveAction_positiveActiononCloseTapped_onCloseTappedokTapped_okTappedtype_type(p0, p1, p2, p3, p4, p5): return p0.intValue + p1.intValue + p2.intValue + p3.intValue + p4.intValue + p5.intValue case let .m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagenextSectionName_nextSectionNameaction_actionimage_imageonCloseTapped_onCloseTappedokTapped_okTappednextSectionTapped_nextSectionTapped(p0, p1, p2, p3, p4, p5, p6, p7): return p0.intValue + p1.intValue + p2.intValue + p3.intValue + p4.intValue + p5.intValue + p6.intValue + p7.intValue case let .m_presentView__transitionStyle_transitionStyleview_viewcompletion_completion(p0, p1, p2): return p0.intValue + p1.intValue + p2.intValue @@ -1564,6 +1618,7 @@ open class BaseRouterMock: BaseRouter, Mock { case .m_showForgotPasswordScreen: return ".showForgotPasswordScreen()" case .m_showDiscoveryScreen__searchQuery_searchQuerysourceScreen_sourceScreen: return ".showDiscoveryScreen(searchQuery:sourceScreen:)" case .m_showWebBrowser__title_titleurl_url: return ".showWebBrowser(title:url:)" + case .m_showWebBrowser__SSO: return ".showSSOWebBrowser(title:)" case .m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagepositiveAction_positiveActiononCloseTapped_onCloseTappedokTapped_okTappedtype_type: return ".presentAlert(alertTitle:alertMessage:positiveAction:onCloseTapped:okTapped:type:)" case .m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagenextSectionName_nextSectionNameaction_actionimage_imageonCloseTapped_onCloseTappedokTapped_okTappednextSectionTapped_nextSectionTapped: return ".presentAlert(alertTitle:alertMessage:nextSectionName:action:image:onCloseTapped:okTapped:nextSectionTapped:)" case .m_presentView__transitionStyle_transitionStyleview_viewcompletion_completion: return ".presentView(transitionStyle:view:completion:)" diff --git a/Authorization/AuthorizationTests/Presentation/Login/SignInViewModelTests.swift b/Authorization/AuthorizationTests/Presentation/Login/SignInViewModelTests.swift index e824ad975..371ac62b7 100644 --- a/Authorization/AuthorizationTests/Presentation/Login/SignInViewModelTests.swift +++ b/Authorization/AuthorizationTests/Presentation/Login/SignInViewModelTests.swift @@ -93,7 +93,33 @@ final class SignInViewModelTests: XCTestCase { XCTAssertEqual(viewModel.errorMessage, nil) XCTAssertEqual(viewModel.isShowProgress, true) } - + + func testSSOLoginSuccess() async throws { + let interactor = AuthInteractorProtocolMock() + let router = AuthorizationRouterMock() + let validator = Validator() + let analytics = AuthorizationAnalyticsMock() + let viewModel = SignInViewModel( + interactor: interactor, + router: router, + config: ConfigMock(), + analytics: analytics, + validator: validator, + sourceScreen: .default + ) + let user = User(id: 1, username: "username", email: "edxUser@edx.com", name: "Name", userAvatar: "") + + Given(interactor, .ssoLogin(title: .any, willReturn: user)) + + await viewModel.ssoLogin(title: "Riyadah") + + Verify(interactor, 1, .ssoLogin(title: .any)) + Verify(router, 1, .showMainOrWhatsNewScreen(sourceScreen: .any)) + + XCTAssertEqual(viewModel.errorMessage, nil) + XCTAssertEqual(viewModel.isShowProgress, true) + } + func testSocialLoginSuccess() async throws { let interactor = AuthInteractorProtocolMock() let router = AuthorizationRouterMock() diff --git a/Core/Core.xcodeproj/project.pbxproj b/Core/Core.xcodeproj/project.pbxproj index e8b75069a..f1f58e740 100644 --- a/Core/Core.xcodeproj/project.pbxproj +++ b/Core/Core.xcodeproj/project.pbxproj @@ -1413,7 +1413,7 @@ CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; - DEVELOPMENT_TEAM = L8PG7LC3Y3; + DEVELOPMENT_TEAM = ""; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; @@ -1446,7 +1446,7 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = L8PG7LC3Y3; + DEVELOPMENT_TEAM = ""; GENERATE_INFOPLIST_FILE = YES; IPHONEOS_DEPLOYMENT_TARGET = 16.4; MARKETING_VERSION = 1.0; @@ -1528,7 +1528,7 @@ CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; - DEVELOPMENT_TEAM = L8PG7LC3Y3; + DEVELOPMENT_TEAM = ""; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; @@ -1560,7 +1560,7 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = L8PG7LC3Y3; + DEVELOPMENT_TEAM = ""; GENERATE_INFOPLIST_FILE = YES; IPHONEOS_DEPLOYMENT_TARGET = 16.4; MARKETING_VERSION = 1.0; @@ -1581,7 +1581,7 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = L8PG7LC3Y3; + DEVELOPMENT_TEAM = ""; GENERATE_INFOPLIST_FILE = YES; IPHONEOS_DEPLOYMENT_TARGET = 16.4; MARKETING_VERSION = 1.0; @@ -1603,7 +1603,7 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = L8PG7LC3Y3; + DEVELOPMENT_TEAM = ""; GENERATE_INFOPLIST_FILE = YES; IPHONEOS_DEPLOYMENT_TARGET = 16.4; MARKETING_VERSION = 1.0; @@ -1625,7 +1625,7 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = L8PG7LC3Y3; + DEVELOPMENT_TEAM = ""; GENERATE_INFOPLIST_FILE = YES; IPHONEOS_DEPLOYMENT_TARGET = 16.4; MARKETING_VERSION = 1.0; @@ -1647,7 +1647,7 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = L8PG7LC3Y3; + DEVELOPMENT_TEAM = ""; GENERATE_INFOPLIST_FILE = YES; IPHONEOS_DEPLOYMENT_TARGET = 16.4; MARKETING_VERSION = 1.0; @@ -1668,7 +1668,7 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = L8PG7LC3Y3; + DEVELOPMENT_TEAM = ""; GENERATE_INFOPLIST_FILE = YES; IPHONEOS_DEPLOYMENT_TARGET = 16.4; MARKETING_VERSION = 1.0; @@ -1689,7 +1689,7 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = L8PG7LC3Y3; + DEVELOPMENT_TEAM = ""; GENERATE_INFOPLIST_FILE = YES; IPHONEOS_DEPLOYMENT_TARGET = 16.4; MARKETING_VERSION = 1.0; @@ -1776,7 +1776,7 @@ CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; - DEVELOPMENT_TEAM = L8PG7LC3Y3; + DEVELOPMENT_TEAM = ""; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; @@ -1869,7 +1869,7 @@ CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; - DEVELOPMENT_TEAM = L8PG7LC3Y3; + DEVELOPMENT_TEAM = ""; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; @@ -1967,7 +1967,7 @@ CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; - DEVELOPMENT_TEAM = L8PG7LC3Y3; + DEVELOPMENT_TEAM = ""; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; @@ -2060,7 +2060,7 @@ CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; - DEVELOPMENT_TEAM = L8PG7LC3Y3; + DEVELOPMENT_TEAM = ""; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; @@ -2216,7 +2216,7 @@ CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; - DEVELOPMENT_TEAM = L8PG7LC3Y3; + DEVELOPMENT_TEAM = ""; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; @@ -2251,7 +2251,7 @@ CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; - DEVELOPMENT_TEAM = L8PG7LC3Y3; + DEVELOPMENT_TEAM = ""; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; diff --git a/Core/Core/Configuration/BaseRouter.swift b/Core/Core/Configuration/BaseRouter.swift index e2a2a714b..4f2ae5bba 100644 --- a/Core/Core/Configuration/BaseRouter.swift +++ b/Core/Core/Configuration/BaseRouter.swift @@ -34,6 +34,8 @@ public protocol BaseRouter { func showDiscoveryScreen(searchQuery: String?, sourceScreen: LogistrationSourceScreen) func showWebBrowser(title: String, url: URL) + + func showSSOWebBrowser(title: String) func presentAlert( alertTitle: String, @@ -100,6 +102,8 @@ open class BaseRouterMock: BaseRouter { public func removeLastView(controllers: Int) {} public func showWebBrowser(title: String, url: URL) {} + + public func showSSOWebBrowser(title: String) {} public func presentAlert( alertTitle: String, diff --git a/Core/Core/Configuration/Config/Config.swift b/Core/Core/Configuration/Config/Config.swift index be5fd1941..8c0cd2d18 100644 --- a/Core/Core/Configuration/Config/Config.swift +++ b/Core/Core/Configuration/Config/Config.swift @@ -9,6 +9,9 @@ import Foundation public protocol ConfigProtocol { var baseURL: URL { get } + var baseSSOURL: URL { get } + var ssoFinishedURL: URL { get } + var ssoButtonTitle: [String: Any] { get } var oAuthClientId: String { get } var tokenType: TokenType { get } var feedbackEmail: String { get } @@ -41,6 +44,9 @@ public enum TokenType: String { private enum ConfigKeys: String { case baseURL = "API_HOST_URL" + case ssoBaseURL = "SSO_URL" + case ssoFinishedURL = "SSO_FINISHED_URL" + case ssoButtonTitle = "SSO_BUTTON_TITLE" case oAuthClientID = "OAUTH_CLIENT_ID" case tokenType = "TOKEN_TYPE" case feedbackEmailAddress = "FEEDBACK_EMAIL_ADDRESS" @@ -120,6 +126,29 @@ extension Config: ConfigProtocol { return url } + public var baseSSOURL: URL { + guard let urlString = string(for: ConfigKeys.ssoBaseURL.rawValue), + let url = URL(string: urlString) else { + fatalError("Unable to find SSO base url in config.") + } + return url + } + + public var ssoFinishedURL: URL { + guard let urlString = string(for: ConfigKeys.ssoFinishedURL.rawValue), + let url = URL(string: urlString) else { + fatalError("Unable to find SSO successful login url in config.") + } + return url + } + + public var ssoButtonTitle: [String: Any] { + guard let ssoButtonTitle = dict(for: ConfigKeys.ssoButtonTitle.rawValue) else { + return ["en": CoreLocalization.SignIn.logInWithSsoBtn] + } + return ssoButtonTitle + } + public var oAuthClientId: String { guard let clientID = string(for: ConfigKeys.oAuthClientID.rawValue) else { fatalError("Unable to find OAuth ClientID in config.") @@ -168,6 +197,7 @@ extension Config: ConfigProtocol { public class ConfigMock: Config { private let config: [String: Any] = [ "API_HOST_URL": "https://www.example.com", + "SSO_URL" : "https://www.example.com", "OAUTH_CLIENT_ID": "oauth_client_id", "FEEDBACK_EMAIL_ADDRESS": "example@mail.com", "PLATFORM_NAME": "OpenEdx", diff --git a/Core/Core/Configuration/Config/UIComponentsConfig.swift b/Core/Core/Configuration/Config/UIComponentsConfig.swift index 1b8cf788e..16c42ad4c 100644 --- a/Core/Core/Configuration/Config/UIComponentsConfig.swift +++ b/Core/Core/Configuration/Config/UIComponentsConfig.swift @@ -10,15 +10,24 @@ import Foundation private enum Keys: String, RawStringExtractable { case courseDropDownNavigationEnabled = "COURSE_DROPDOWN_NAVIGATION_ENABLED" case courseUnitProgressEnabled = "COURSE_UNIT_PROGRESS_ENABLED" + case loginRegistrationEnabled = "LOGIN_REGISTRATION_ENABLED" + case samlSSOLoginEnabled = "SAML_SSO_LOGIN_ENABLED" + case samlSSODefaultLoginButton = "SAML_SSO_DEFAULT_LOGIN_BUTTON" } public class UIComponentsConfig: NSObject { public var courseDropDownNavigationEnabled: Bool public var courseUnitProgressEnabled: Bool + public var loginRegistrationEnabled: Bool + public var samlSSOLoginEnabled: Bool + public var samlSSODefaultLoginButton: Bool init(dictionary: [String: Any]) { courseDropDownNavigationEnabled = dictionary[Keys.courseDropDownNavigationEnabled] as? Bool ?? false courseUnitProgressEnabled = dictionary[Keys.courseUnitProgressEnabled] as? Bool ?? false + loginRegistrationEnabled = dictionary[Keys.loginRegistrationEnabled] as? Bool ?? true + samlSSOLoginEnabled = dictionary[Keys.samlSSOLoginEnabled] as? Bool ?? false + samlSSODefaultLoginButton = dictionary[Keys.samlSSODefaultLoginButton] as? Bool ?? false super.init() } } diff --git a/Core/Core/Data/Repository/AuthRepository.swift b/Core/Core/Data/Repository/AuthRepository.swift index d00441ee6..2c838e679 100644 --- a/Core/Core/Data/Repository/AuthRepository.swift +++ b/Core/Core/Data/Repository/AuthRepository.swift @@ -10,6 +10,7 @@ import Foundation public protocol AuthRepositoryProtocol { func login(username: String, password: String) async throws -> User func login(externalToken: String, backend: String) async throws -> User + func login(ssoToken: String) async throws -> User func getCookies(force: Bool) async throws func getRegistrationFields() async throws -> [PickerFields] func registerUser(fields: [String: String], isSocial: Bool) async throws -> User @@ -80,6 +81,16 @@ public class AuthRepository: AuthRepositoryProtocol { return user.domain } + public func login(ssoToken: String) async throws -> User { + if appStorage.accessToken == nil { + appStorage.accessToken = ssoToken + } + + let user = try await api.requestData(AuthEndpoint.getUserInfo).mapResponse(DataLayer.User.self) + appStorage.user = user + return user.domain + } + public func resetPassword(email: String) async throws -> ResetPassword { let response = try await api.requestData(AuthEndpoint.resetPassword(email: email)) .mapResponse(DataLayer.ResetPassword.self) @@ -137,6 +148,10 @@ class AuthRepositoryMock: AuthRepositoryProtocol { User(id: 1, username: "User", email: "email@gmail.com", name: "User Name", userAvatar: "") } + func login(ssoToken: String) async throws -> User { + return User(id: 1, username: "User", email: "email@gmail.com", name: "User Name", userAvatar: "") + } + func resetPassword(email: String) async throws -> ResetPassword { ResetPassword(success: true, responseText: "Success reset") } diff --git a/Core/Core/Domain/AuthInteractor.swift b/Core/Core/Domain/AuthInteractor.swift index 45868cbc9..6b3562f20 100644 --- a/Core/Core/Domain/AuthInteractor.swift +++ b/Core/Core/Domain/AuthInteractor.swift @@ -13,6 +13,7 @@ public protocol AuthInteractorProtocol { func login(username: String, password: String) async throws -> User @discardableResult func login(externalToken: String, backend: String) async throws -> User + func login(ssoToken: String) async throws -> User func resetPassword(email: String) async throws -> ResetPassword func getCookies(force: Bool) async throws func getRegistrationFields() async throws -> [PickerFields] @@ -37,6 +38,11 @@ public class AuthInteractor: AuthInteractorProtocol { return try await repository.login(externalToken: externalToken, backend: backend) } + @discardableResult + public func login(ssoToken: String) async throws -> User { + return try await repository.login(ssoToken: ssoToken) + } + public func resetPassword(email: String) async throws -> ResetPassword { try await repository.resetPassword(email: email) } diff --git a/Core/Core/SwiftGen/Strings.swift b/Core/Core/SwiftGen/Strings.swift index ed9aa825d..e42e1dd03 100644 --- a/Core/Core/SwiftGen/Strings.swift +++ b/Core/Core/SwiftGen/Strings.swift @@ -14,8 +14,6 @@ public enum CoreLocalization { public static let done = CoreLocalization.tr("Localizable", "DONE", fallback: "Done") /// View in Safari public static let openInBrowser = CoreLocalization.tr("Localizable", "OPEN_IN_BROWSER", fallback: "View in Safari") - /// Register - public static let register = CoreLocalization.tr("Localizable", "REGISTER", fallback: "Register") /// The user canceled the sign-in flow. public static let socialSignCanceled = CoreLocalization.tr("Localizable", "SOCIAL_SIGN_CANCELED", fallback: "The user canceled the sign-in flow.") /// Tomorrow @@ -290,6 +288,10 @@ public enum CoreLocalization { public enum SignIn { /// Sign in public static let logInBtn = CoreLocalization.tr("Localizable", "SIGN_IN.LOG_IN_BTN", fallback: "Sign in") + /// Sign in with SSO + public static let logInWithSsoBtn = CoreLocalization.tr("Localizable", "SIGN_IN.LOG_IN_WITH_SSO_BTN", fallback: "Sign in with SSO") + /// Register + public static let registerBtn = CoreLocalization.tr("Localizable", "SIGN_IN.REGISTER_BTN", fallback: "Register") } public enum View { public enum Snackbar { diff --git a/Core/Core/View/Base/LogistrationBottomView.swift b/Core/Core/View/Base/LogistrationBottomView.swift index fc0aa0ef4..faa3aaa35 100644 --- a/Core/Core/View/Base/LogistrationBottomView.swift +++ b/Core/Core/View/Base/LogistrationBottomView.swift @@ -15,14 +15,11 @@ public enum LogistrationSourceScreen: Equatable { case discovery case courseDetail(String, String) case programDetails(String) - - public var value: String? { - return String(describing: self).components(separatedBy: "(").first - } } public enum LogistrationAction { case signIn + case signInWithSSO case register } @@ -34,11 +31,11 @@ public struct LogistrationBottomView: View { public init(_ action: @escaping (LogistrationAction) -> Void) { self.action = action } - + public var body: some View { VStack(alignment: .leading) { HStack(spacing: 24) { - StyledButton(CoreLocalization.register) { + StyledButton(CoreLocalization.SignIn.registerBtn) { action(.register) } .accessibilityIdentifier("logistration_register_button") @@ -54,6 +51,18 @@ public struct LogistrationBottomView: View { ) .frame(width: 100) .accessibilityIdentifier("logistration_signin_button") + + StyledButton( + CoreLocalization.SignIn.logInWithSsoBtn, + action: { + action(.signInWithSSO) + }, + color: Theme.Colors.white, + textColor: Theme.Colors.secondaryButtonTextColor, + borderColor: Theme.Colors.secondaryButtonBorderColor + ) + .frame(width: 100) + .accessibilityIdentifier("logistration_signin_withsso_button") } .padding(.horizontal, isHorizontal ? 0 : 0) } diff --git a/Core/Core/en.lproj/Localizable.strings b/Core/Core/en.lproj/Localizable.strings index b7bc68aad..55f7e55dd 100644 --- a/Core/Core/en.lproj/Localizable.strings +++ b/Core/Core/en.lproj/Localizable.strings @@ -120,7 +120,8 @@ "SOCIAL_SIGN_CANCELED" = "The user canceled the sign-in flow."; "SIGN_IN.LOG_IN_BTN" = "Sign in"; -"REGISTER" = "Register"; +"SIGN_IN.REGISTER_BTN" = "Register"; +"SIGN_IN.LOG_IN_WITH_SSO_BTN" = "Sign in with SSO"; "TOMORROW" = "Tomorrow"; "YESTERDAY" = "Yesterday"; diff --git a/Course/Course.xcodeproj/project.pbxproj b/Course/Course.xcodeproj/project.pbxproj index a04ee6650..737558bc2 100644 --- a/Course/Course.xcodeproj/project.pbxproj +++ b/Course/Course.xcodeproj/project.pbxproj @@ -1258,7 +1258,7 @@ CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; - DEVELOPMENT_TEAM = L8PG7LC3Y3; + DEVELOPMENT_TEAM = ""; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; @@ -1293,7 +1293,7 @@ CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; - DEVELOPMENT_TEAM = L8PG7LC3Y3; + DEVELOPMENT_TEAM = ""; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; @@ -1391,7 +1391,7 @@ CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; - DEVELOPMENT_TEAM = L8PG7LC3Y3; + DEVELOPMENT_TEAM = ""; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; @@ -1490,7 +1490,7 @@ CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; - DEVELOPMENT_TEAM = L8PG7LC3Y3; + DEVELOPMENT_TEAM = ""; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; @@ -1583,7 +1583,7 @@ CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; - DEVELOPMENT_TEAM = L8PG7LC3Y3; + DEVELOPMENT_TEAM = ""; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; @@ -1675,7 +1675,7 @@ CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; - DEVELOPMENT_TEAM = L8PG7LC3Y3; + DEVELOPMENT_TEAM = ""; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; @@ -1773,7 +1773,7 @@ CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; - DEVELOPMENT_TEAM = L8PG7LC3Y3; + DEVELOPMENT_TEAM = ""; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; @@ -1887,7 +1887,7 @@ CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; - DEVELOPMENT_TEAM = L8PG7LC3Y3; + DEVELOPMENT_TEAM = ""; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; diff --git a/Course/CourseTests/CourseMock.generated.swift b/Course/CourseTests/CourseMock.generated.swift index f6bb64618..f3618b378 100644 --- a/Course/CourseTests/CourseMock.generated.swift +++ b/Course/CourseTests/CourseMock.generated.swift @@ -77,7 +77,24 @@ open class AuthInteractorProtocolMock: AuthInteractorProtocol, Mock { } @discardableResult - open func login(externalToken: String, backend: String) throws -> User { + open func login(ssoToken: String) async throws -> Core.User { + addInvocation(.m_login__SSO__username_password(Parameter.value(`ssoToken`))) + let perform = methodPerformValue(.m_login__SSO__username_password(Parameter.value(`ssoToken`))) as? (String) -> Void + perform?(`ssoToken`) + var __value: User + do { + __value = try methodReturnValue(.m_login__SSO__username_password(Parameter.value(ssoToken))).casted() + } catch MockError.notStubed { + onFatalFailure("Stub return value not specified for login(username: String, password: String). Use given") + Failure("Stub return value not specified for login(username: String, password: String). Use given") + } catch { + throw error + } + return __value + } + + @discardableResult + open func login(externalToken: String, backend: String) throws -> User { addInvocation(.m_login__externalToken_externalTokenbackend_backend(Parameter.value(`externalToken`), Parameter.value(`backend`))) let perform = methodPerformValue(.m_login__externalToken_externalTokenbackend_backend(Parameter.value(`externalToken`), Parameter.value(`backend`))) as? (String, String) -> Void perform?(`externalToken`, `backend`) @@ -173,6 +190,7 @@ open class AuthInteractorProtocolMock: AuthInteractorProtocol, Mock { fileprivate enum MethodType { case m_login__username_usernamepassword_password(Parameter, Parameter) + case m_login__SSO__username_password(Parameter) case m_login__externalToken_externalTokenbackend_backend(Parameter, Parameter) case m_resetPassword__email_email(Parameter) case m_getCookies__force_force(Parameter) @@ -223,6 +241,7 @@ open class AuthInteractorProtocolMock: AuthInteractorProtocol, Mock { func intValue() -> Int { switch self { case let .m_login__username_usernamepassword_password(p0, p1): return p0.intValue + p1.intValue + case let .m_login__SSO__username_password(p0): return p0.intValue case let .m_login__externalToken_externalTokenbackend_backend(p0, p1): return p0.intValue + p1.intValue case let .m_resetPassword__email_email(p0): return p0.intValue case let .m_getCookies__force_force(p0): return p0.intValue @@ -234,6 +253,7 @@ open class AuthInteractorProtocolMock: AuthInteractorProtocol, Mock { func assertionName() -> String { switch self { case .m_login__username_usernamepassword_password: return ".login(username:password:)" + case .m_login__SSO__username_password: return ".loginSSO(username:password:)" case .m_login__externalToken_externalTokenbackend_backend: return ".login(externalToken:backend:)" case .m_resetPassword__email_email: return ".resetPassword(email:)" case .m_getCookies__force_force: return ".getCookies(force:)" @@ -577,10 +597,16 @@ open class BaseRouterMock: BaseRouter, Mock { open func showWebBrowser(title: String, url: URL) { addInvocation(.m_showWebBrowser__title_titleurl_url(Parameter.value(`title`), Parameter.value(`url`))) - let perform = methodPerformValue(.m_showWebBrowser__title_titleurl_url(Parameter.value(`title`), Parameter.value(`url`))) as? (String, URL) -> Void - perform?(`title`, `url`) + let perform = methodPerformValue(.m_showWebBrowser__title_titleurl_url(Parameter.value(`title`), Parameter.value(`url`))) as? (String, URL) -> Void + perform?(`title`, `url`) } - + + open func showSSOWebBrowser(title: String) { + addInvocation(.m_showWebBrowser__SSO(Parameter.value(`title`))) + let perform = methodPerformValue(.m_showWebBrowser__SSO(Parameter.value(`title`))) as? (String) -> Void + perform?(`title`) + } + open func presentAlert(alertTitle: String, alertMessage: String, positiveAction: String, onCloseTapped: @escaping () -> Void, okTapped: @escaping () -> Void, type: AlertViewType) { addInvocation(.m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagepositiveAction_positiveActiononCloseTapped_onCloseTappedokTapped_okTappedtype_type(Parameter.value(`alertTitle`), Parameter.value(`alertMessage`), Parameter.value(`positiveAction`), Parameter<() -> Void>.value(`onCloseTapped`), Parameter<() -> Void>.value(`okTapped`), Parameter.value(`type`))) let perform = methodPerformValue(.m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagepositiveAction_positiveActiononCloseTapped_onCloseTappedokTapped_okTappedtype_type(Parameter.value(`alertTitle`), Parameter.value(`alertMessage`), Parameter.value(`positiveAction`), Parameter<() -> Void>.value(`onCloseTapped`), Parameter<() -> Void>.value(`okTapped`), Parameter.value(`type`))) as? (String, String, String, @escaping () -> Void, @escaping () -> Void, AlertViewType) -> Void @@ -619,6 +645,7 @@ open class BaseRouterMock: BaseRouter, Mock { case m_showForgotPasswordScreen case m_showDiscoveryScreen__searchQuery_searchQuerysourceScreen_sourceScreen(Parameter, Parameter) case m_showWebBrowser__title_titleurl_url(Parameter, Parameter) + case m_showWebBrowser__SSO(Parameter) case m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagepositiveAction_positiveActiononCloseTapped_onCloseTappedokTapped_okTappedtype_type(Parameter, Parameter, Parameter, Parameter<() -> Void>, Parameter<() -> Void>, Parameter) case m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagenextSectionName_nextSectionNameaction_actionimage_imageonCloseTapped_onCloseTappedokTapped_okTappednextSectionTapped_nextSectionTapped(Parameter, Parameter, Parameter, Parameter, Parameter, Parameter<() -> Void>, Parameter<() -> Void>, Parameter<() -> Void>) case m_presentView__transitionStyle_transitionStyleview_viewcompletion_completion(Parameter, Parameter, Parameter<(() -> Void)?>) @@ -732,6 +759,7 @@ open class BaseRouterMock: BaseRouter, Mock { case .m_showForgotPasswordScreen: return 0 case let .m_showDiscoveryScreen__searchQuery_searchQuerysourceScreen_sourceScreen(p0, p1): return p0.intValue + p1.intValue case let .m_showWebBrowser__title_titleurl_url(p0, p1): return p0.intValue + p1.intValue + case let .m_showWebBrowser__SSO(p0): return p0.intValue case let .m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagepositiveAction_positiveActiononCloseTapped_onCloseTappedokTapped_okTappedtype_type(p0, p1, p2, p3, p4, p5): return p0.intValue + p1.intValue + p2.intValue + p3.intValue + p4.intValue + p5.intValue case let .m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagenextSectionName_nextSectionNameaction_actionimage_imageonCloseTapped_onCloseTappedokTapped_okTappednextSectionTapped_nextSectionTapped(p0, p1, p2, p3, p4, p5, p6, p7): return p0.intValue + p1.intValue + p2.intValue + p3.intValue + p4.intValue + p5.intValue + p6.intValue + p7.intValue case let .m_presentView__transitionStyle_transitionStyleview_viewcompletion_completion(p0, p1, p2): return p0.intValue + p1.intValue + p2.intValue @@ -752,6 +780,7 @@ open class BaseRouterMock: BaseRouter, Mock { case .m_showForgotPasswordScreen: return ".showForgotPasswordScreen()" case .m_showDiscoveryScreen__searchQuery_searchQuerysourceScreen_sourceScreen: return ".showDiscoveryScreen(searchQuery:sourceScreen:)" case .m_showWebBrowser__title_titleurl_url: return ".showWebBrowser(title:url:)" + case .m_showWebBrowser__SSO: return ".showSSOWebBrowser(title:)" case .m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagepositiveAction_positiveActiononCloseTapped_onCloseTappedokTapped_okTappedtype_type: return ".presentAlert(alertTitle:alertMessage:positiveAction:onCloseTapped:okTapped:type:)" case .m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagenextSectionName_nextSectionNameaction_actionimage_imageonCloseTapped_onCloseTappedokTapped_okTappednextSectionTapped_nextSectionTapped: return ".presentAlert(alertTitle:alertMessage:nextSectionName:action:image:onCloseTapped:okTapped:nextSectionTapped:)" case .m_presentView__transitionStyle_transitionStyleview_viewcompletion_completion: return ".presentView(transitionStyle:view:completion:)" diff --git a/Dashboard/Dashboard.xcodeproj/project.pbxproj b/Dashboard/Dashboard.xcodeproj/project.pbxproj index 2ae0ee9bd..625c0d2f0 100644 --- a/Dashboard/Dashboard.xcodeproj/project.pbxproj +++ b/Dashboard/Dashboard.xcodeproj/project.pbxproj @@ -736,7 +736,7 @@ CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; - DEVELOPMENT_TEAM = L8PG7LC3Y3; + DEVELOPMENT_TEAM = ""; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; @@ -850,7 +850,7 @@ CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; - DEVELOPMENT_TEAM = L8PG7LC3Y3; + DEVELOPMENT_TEAM = ""; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; @@ -1027,7 +1027,7 @@ CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; - DEVELOPMENT_TEAM = L8PG7LC3Y3; + DEVELOPMENT_TEAM = ""; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; @@ -1062,7 +1062,7 @@ CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; - DEVELOPMENT_TEAM = L8PG7LC3Y3; + DEVELOPMENT_TEAM = ""; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; @@ -1160,7 +1160,7 @@ CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; - DEVELOPMENT_TEAM = L8PG7LC3Y3; + DEVELOPMENT_TEAM = ""; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; @@ -1253,7 +1253,7 @@ CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; - DEVELOPMENT_TEAM = L8PG7LC3Y3; + DEVELOPMENT_TEAM = ""; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; @@ -1351,7 +1351,7 @@ CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; - DEVELOPMENT_TEAM = L8PG7LC3Y3; + DEVELOPMENT_TEAM = ""; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; @@ -1444,7 +1444,7 @@ CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; - DEVELOPMENT_TEAM = L8PG7LC3Y3; + DEVELOPMENT_TEAM = ""; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; diff --git a/Dashboard/DashboardTests/DashboardMock.generated.swift b/Dashboard/DashboardTests/DashboardMock.generated.swift index 642eb04fd..a3ac7af27 100644 --- a/Dashboard/DashboardTests/DashboardMock.generated.swift +++ b/Dashboard/DashboardTests/DashboardMock.generated.swift @@ -93,6 +93,23 @@ open class AuthInteractorProtocolMock: AuthInteractorProtocol, Mock { return __value } + @discardableResult + open func login(ssoToken: String) async throws -> Core.User { + addInvocation(.m_login__SSO__username_password(Parameter.value(`ssoToken`))) + let perform = methodPerformValue(.m_login__SSO__username_password(Parameter.value(`ssoToken`))) as? (String) -> Void + perform?(`ssoToken`) + var __value: User + do { + __value = try methodReturnValue(.m_login__SSO__username_password(Parameter.value(ssoToken))).casted() + } catch MockError.notStubed { + onFatalFailure("Stub return value not specified for login(username: String, password: String). Use given") + Failure("Stub return value not specified for login(username: String, password: String). Use given") + } catch { + throw error + } + return __value + } + open func resetPassword(email: String) throws -> ResetPassword { addInvocation(.m_resetPassword__email_email(Parameter.value(`email`))) let perform = methodPerformValue(.m_resetPassword__email_email(Parameter.value(`email`))) as? (String) -> Void @@ -173,6 +190,7 @@ open class AuthInteractorProtocolMock: AuthInteractorProtocol, Mock { fileprivate enum MethodType { case m_login__username_usernamepassword_password(Parameter, Parameter) + case m_login__SSO__username_password(Parameter) case m_login__externalToken_externalTokenbackend_backend(Parameter, Parameter) case m_resetPassword__email_email(Parameter) case m_getCookies__force_force(Parameter) @@ -223,6 +241,7 @@ open class AuthInteractorProtocolMock: AuthInteractorProtocol, Mock { func intValue() -> Int { switch self { case let .m_login__username_usernamepassword_password(p0, p1): return p0.intValue + p1.intValue + case let .m_login__SSO__username_password(p0): return p0.intValue case let .m_login__externalToken_externalTokenbackend_backend(p0, p1): return p0.intValue + p1.intValue case let .m_resetPassword__email_email(p0): return p0.intValue case let .m_getCookies__force_force(p0): return p0.intValue @@ -234,6 +253,7 @@ open class AuthInteractorProtocolMock: AuthInteractorProtocol, Mock { func assertionName() -> String { switch self { case .m_login__username_usernamepassword_password: return ".login(username:password:)" + case .m_login__SSO__username_password: return ".loginSSO(username:password:)" case .m_login__externalToken_externalTokenbackend_backend: return ".login(externalToken:backend:)" case .m_resetPassword__email_email: return ".resetPassword(email:)" case .m_getCookies__force_force: return ".getCookies(force:)" @@ -581,6 +601,12 @@ open class BaseRouterMock: BaseRouter, Mock { perform?(`title`, `url`) } + open func showSSOWebBrowser(title: String) { + addInvocation(.m_showWebBrowser__SSO(Parameter.value(`title`))) + let perform = methodPerformValue(.m_showWebBrowser__SSO(Parameter.value(`title`))) as? (String) -> Void + perform?(`title`) + } + open func presentAlert(alertTitle: String, alertMessage: String, positiveAction: String, onCloseTapped: @escaping () -> Void, okTapped: @escaping () -> Void, type: AlertViewType) { addInvocation(.m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagepositiveAction_positiveActiononCloseTapped_onCloseTappedokTapped_okTappedtype_type(Parameter.value(`alertTitle`), Parameter.value(`alertMessage`), Parameter.value(`positiveAction`), Parameter<() -> Void>.value(`onCloseTapped`), Parameter<() -> Void>.value(`okTapped`), Parameter.value(`type`))) let perform = methodPerformValue(.m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagepositiveAction_positiveActiononCloseTapped_onCloseTappedokTapped_okTappedtype_type(Parameter.value(`alertTitle`), Parameter.value(`alertMessage`), Parameter.value(`positiveAction`), Parameter<() -> Void>.value(`onCloseTapped`), Parameter<() -> Void>.value(`okTapped`), Parameter.value(`type`))) as? (String, String, String, @escaping () -> Void, @escaping () -> Void, AlertViewType) -> Void @@ -619,6 +645,7 @@ open class BaseRouterMock: BaseRouter, Mock { case m_showForgotPasswordScreen case m_showDiscoveryScreen__searchQuery_searchQuerysourceScreen_sourceScreen(Parameter, Parameter) case m_showWebBrowser__title_titleurl_url(Parameter, Parameter) + case m_showWebBrowser__SSO(Parameter) case m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagepositiveAction_positiveActiononCloseTapped_onCloseTappedokTapped_okTappedtype_type(Parameter, Parameter, Parameter, Parameter<() -> Void>, Parameter<() -> Void>, Parameter) case m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagenextSectionName_nextSectionNameaction_actionimage_imageonCloseTapped_onCloseTappedokTapped_okTappednextSectionTapped_nextSectionTapped(Parameter, Parameter, Parameter, Parameter, Parameter, Parameter<() -> Void>, Parameter<() -> Void>, Parameter<() -> Void>) case m_presentView__transitionStyle_transitionStyleview_viewcompletion_completion(Parameter, Parameter, Parameter<(() -> Void)?>) @@ -732,6 +759,7 @@ open class BaseRouterMock: BaseRouter, Mock { case .m_showForgotPasswordScreen: return 0 case let .m_showDiscoveryScreen__searchQuery_searchQuerysourceScreen_sourceScreen(p0, p1): return p0.intValue + p1.intValue case let .m_showWebBrowser__title_titleurl_url(p0, p1): return p0.intValue + p1.intValue + case let .m_showWebBrowser__SSO(p0): return p0.intValue case let .m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagepositiveAction_positiveActiononCloseTapped_onCloseTappedokTapped_okTappedtype_type(p0, p1, p2, p3, p4, p5): return p0.intValue + p1.intValue + p2.intValue + p3.intValue + p4.intValue + p5.intValue case let .m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagenextSectionName_nextSectionNameaction_actionimage_imageonCloseTapped_onCloseTappedokTapped_okTappednextSectionTapped_nextSectionTapped(p0, p1, p2, p3, p4, p5, p6, p7): return p0.intValue + p1.intValue + p2.intValue + p3.intValue + p4.intValue + p5.intValue + p6.intValue + p7.intValue case let .m_presentView__transitionStyle_transitionStyleview_viewcompletion_completion(p0, p1, p2): return p0.intValue + p1.intValue + p2.intValue @@ -752,6 +780,7 @@ open class BaseRouterMock: BaseRouter, Mock { case .m_showForgotPasswordScreen: return ".showForgotPasswordScreen()" case .m_showDiscoveryScreen__searchQuery_searchQuerysourceScreen_sourceScreen: return ".showDiscoveryScreen(searchQuery:sourceScreen:)" case .m_showWebBrowser__title_titleurl_url: return ".showWebBrowser(title:url:)" + case .m_showWebBrowser__SSO: return ".showSSOWebBrowser(title:)" case .m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagepositiveAction_positiveActiononCloseTapped_onCloseTappedokTapped_okTappedtype_type: return ".presentAlert(alertTitle:alertMessage:positiveAction:onCloseTapped:okTapped:type:)" case .m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagenextSectionName_nextSectionNameaction_actionimage_imageonCloseTapped_onCloseTappedokTapped_okTappednextSectionTapped_nextSectionTapped: return ".presentAlert(alertTitle:alertMessage:nextSectionName:action:image:onCloseTapped:okTapped:nextSectionTapped:)" case .m_presentView__transitionStyle_transitionStyleview_viewcompletion_completion: return ".presentView(transitionStyle:view:completion:)" diff --git a/Discovery/Discovery.xcodeproj/project.pbxproj b/Discovery/Discovery.xcodeproj/project.pbxproj index 73f72e424..9ed79ac53 100644 --- a/Discovery/Discovery.xcodeproj/project.pbxproj +++ b/Discovery/Discovery.xcodeproj/project.pbxproj @@ -804,7 +804,7 @@ CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; - DEVELOPMENT_TEAM = L8PG7LC3Y3; + DEVELOPMENT_TEAM = ""; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; @@ -919,7 +919,7 @@ CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; - DEVELOPMENT_TEAM = L8PG7LC3Y3; + DEVELOPMENT_TEAM = ""; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; @@ -1097,7 +1097,7 @@ CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; - DEVELOPMENT_TEAM = L8PG7LC3Y3; + DEVELOPMENT_TEAM = ""; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; @@ -1133,7 +1133,7 @@ CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; - DEVELOPMENT_TEAM = L8PG7LC3Y3; + DEVELOPMENT_TEAM = ""; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; @@ -1232,7 +1232,7 @@ CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; - DEVELOPMENT_TEAM = L8PG7LC3Y3; + DEVELOPMENT_TEAM = ""; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; @@ -1332,7 +1332,7 @@ CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; - DEVELOPMENT_TEAM = L8PG7LC3Y3; + DEVELOPMENT_TEAM = ""; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; @@ -1426,7 +1426,7 @@ CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; - DEVELOPMENT_TEAM = L8PG7LC3Y3; + DEVELOPMENT_TEAM = ""; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; @@ -1519,7 +1519,7 @@ CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; - DEVELOPMENT_TEAM = L8PG7LC3Y3; + DEVELOPMENT_TEAM = ""; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; diff --git a/Discovery/Discovery/Info.plist b/Discovery/Discovery/Info.plist index f72a0f657..0c67376eb 100644 --- a/Discovery/Discovery/Info.plist +++ b/Discovery/Discovery/Info.plist @@ -1,12 +1,5 @@ - diff --git a/Discovery/Discovery/Presentation/NativeDiscovery/CourseDetailsView.swift b/Discovery/Discovery/Presentation/NativeDiscovery/CourseDetailsView.swift index 78b37f590..d5bc80704 100644 --- a/Discovery/Discovery/Presentation/NativeDiscovery/CourseDetailsView.swift +++ b/Discovery/Discovery/Presentation/NativeDiscovery/CourseDetailsView.swift @@ -165,6 +165,13 @@ public struct CourseDetailsView: View { viewModel.courseDetails?.courseTitle ?? "" ) ) + case .signInWithSSO: + viewModel.router.showLoginScreen( + sourceScreen: .courseDetail( + courseID, + viewModel.courseDetails?.courseTitle ?? "" + ) + ) } } } diff --git a/Discovery/Discovery/Presentation/NativeDiscovery/DiscoveryView.swift b/Discovery/Discovery/Presentation/NativeDiscovery/DiscoveryView.swift index d434dd05c..735abc4fb 100644 --- a/Discovery/Discovery/Presentation/NativeDiscovery/DiscoveryView.swift +++ b/Discovery/Discovery/Presentation/NativeDiscovery/DiscoveryView.swift @@ -146,7 +146,7 @@ public struct DiscoveryView: View { } } }.accessibilityAction {} - + if !viewModel.userloggedIn { LogistrationBottomView { buttonAction in switch buttonAction { @@ -154,18 +154,20 @@ public struct DiscoveryView: View { viewModel.router.showLoginScreen(sourceScreen: .discovery) case .register: viewModel.router.showRegisterScreen(sourceScreen: .discovery) + case .signInWithSSO: + viewModel.router.showLoginScreen(sourceScreen: .discovery) } } } }.padding(.top, 8) - + // MARK: - Offline mode SnackBar OfflineSnackBarView( connectivity: viewModel.connectivity, reloadAction: { await viewModel.discovery(page: 1, withProgress: false) }) - + // MARK: - Error Alert if viewModel.showError { VStack { diff --git a/Discovery/Discovery/Presentation/WebDiscovery/DiscoveryWebview.swift b/Discovery/Discovery/Presentation/WebDiscovery/DiscoveryWebview.swift index e4c23ff15..d67351713 100644 --- a/Discovery/Discovery/Presentation/WebDiscovery/DiscoveryWebview.swift +++ b/Discovery/Discovery/Presentation/WebDiscovery/DiscoveryWebview.swift @@ -14,7 +14,7 @@ public enum DiscoveryWebviewType: Equatable { case discovery case courseDetail(String) case programDetail(String) - + var rawValue: String { switch self { case .discovery: @@ -105,7 +105,7 @@ public struct DiscoveryWebview: View { webViewType: discoveryType.rawValue ) .accessibilityIdentifier("discovery_webview") - + if isLoading || viewModel.showProgress { HStack(alignment: .center) { ProgressBar( @@ -117,7 +117,7 @@ public struct DiscoveryWebview: View { } .frame(width: proxy.size.width, height: proxy.size.height) } - + // MARK: - Show Error if viewModel.showError { VStack { @@ -131,19 +131,21 @@ public struct DiscoveryWebview: View { } } } - + if !viewModel.userloggedIn, !isLoading { LogistrationBottomView { buttonAction in switch buttonAction { case .signIn: viewModel.router.showLoginScreen(sourceScreen: sourceScreen) + case .signInWithSSO: + viewModel.router.showLoginScreen(sourceScreen: sourceScreen) case .register: viewModel.router.showRegisterScreen(sourceScreen: sourceScreen) } } } } - + if viewModel.webViewError { FullScreenErrorView( type: viewModel.connectivity.isInternetAvaliable ? .generic : .noInternetWithReload diff --git a/Discovery/Discovery/Presentation/WebDiscovery/DiscoveryWebviewViewModel.swift b/Discovery/Discovery/Presentation/WebDiscovery/DiscoveryWebviewViewModel.swift index b9f2bb515..df2330abf 100644 --- a/Discovery/Discovery/Presentation/WebDiscovery/DiscoveryWebviewViewModel.swift +++ b/Discovery/Discovery/Presentation/WebDiscovery/DiscoveryWebviewViewModel.swift @@ -15,7 +15,7 @@ public class DiscoveryWebviewViewModel: ObservableObject { @Published private(set) var showProgress = false @Published var showError: Bool = false @Published var webViewError: Bool = false - + var errorMessage: String? { didSet { withAnimation { @@ -137,25 +137,14 @@ extension DiscoveryWebviewViewModel: WebViewNavigationDelegate { } if let url = request.url, outsideLink || capturedLink || externalLink, UIApplication.shared.canOpenURL(url) { - analytics.externalLinkOpen(url: url.absoluteString, screen: sourceScreen.value ?? "") router.presentAlert( alertTitle: DiscoveryLocalization.Alert.leavingAppTitle, alertMessage: DiscoveryLocalization.Alert.leavingAppMessage, positiveAction: CoreLocalization.Webview.Alert.continue, onCloseTapped: { [weak self] in self?.router.dismiss(animated: true) - self?.analytics.externalLinkOpenAction( - url: url.absoluteString, - screen: self?.sourceScreen.value ?? "", - action: "cancel" - ) - }, okTapped: { [weak self] in + }, okTapped: { UIApplication.shared.open(url, options: [:]) - self?.analytics.externalLinkOpenAction( - url: url.absoluteString, - screen: self?.sourceScreen.value ?? "", - action: "continue" - ) }, type: .default(positiveAction: CoreLocalization.Webview.Alert.continue, image: nil) ) return true @@ -249,7 +238,7 @@ extension DiscoveryWebviewViewModel: WebViewNavigationDelegate { private func isValidAppURLScheme(_ url: URL) -> Bool { return url.scheme ?? "" == config.URIScheme } - + public func showWebViewError() { self.webViewError = true } diff --git a/Discovery/DiscoveryTests/DiscoveryMock.generated.swift b/Discovery/DiscoveryTests/DiscoveryMock.generated.swift index 93a07e4e6..54425fc98 100644 --- a/Discovery/DiscoveryTests/DiscoveryMock.generated.swift +++ b/Discovery/DiscoveryTests/DiscoveryMock.generated.swift @@ -93,6 +93,23 @@ open class AuthInteractorProtocolMock: AuthInteractorProtocol, Mock { return __value } + @discardableResult + open func login(ssoToken: String) async throws -> Core.User { + addInvocation(.m_login__SSO__username_password(Parameter.value(`ssoToken`))) + let perform = methodPerformValue(.m_login__SSO__username_password(Parameter.value(`ssoToken`))) as? (String) -> Void + perform?(`ssoToken`) + var __value: User + do { + __value = try methodReturnValue(.m_login__SSO__username_password(Parameter.value(ssoToken))).casted() + } catch MockError.notStubed { + onFatalFailure("Stub return value not specified for login(username: String, password: String). Use given") + Failure("Stub return value not specified for login(username: String, password: String). Use given") + } catch { + throw error + } + return __value + } + open func resetPassword(email: String) throws -> ResetPassword { addInvocation(.m_resetPassword__email_email(Parameter.value(`email`))) let perform = methodPerformValue(.m_resetPassword__email_email(Parameter.value(`email`))) as? (String) -> Void @@ -173,6 +190,7 @@ open class AuthInteractorProtocolMock: AuthInteractorProtocol, Mock { fileprivate enum MethodType { case m_login__username_usernamepassword_password(Parameter, Parameter) + case m_login__SSO__username_password(Parameter) case m_login__externalToken_externalTokenbackend_backend(Parameter, Parameter) case m_resetPassword__email_email(Parameter) case m_getCookies__force_force(Parameter) @@ -188,6 +206,11 @@ open class AuthInteractorProtocolMock: AuthInteractorProtocol, Mock { results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsPassword, rhs: rhsPassword, with: matcher), lhsPassword, rhsPassword, "password")) return Matcher.ComparisonResult(results) + case (.m_login__SSO__username_password(let lhsJwtToken), .m_login__SSO__username_password(let rhsJwtToken)): + var results: [Matcher.ParameterComparisonResult] = [] + results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsJwtToken, rhs: rhsJwtToken, with: matcher), lhsJwtToken, rhsJwtToken, "jwtToken")) + return Matcher.ComparisonResult(results) + case (.m_login__externalToken_externalTokenbackend_backend(let lhsExternaltoken, let lhsBackend), .m_login__externalToken_externalTokenbackend_backend(let rhsExternaltoken, let rhsBackend)): var results: [Matcher.ParameterComparisonResult] = [] results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsExternaltoken, rhs: rhsExternaltoken, with: matcher), lhsExternaltoken, rhsExternaltoken, "externalToken")) @@ -223,6 +246,7 @@ open class AuthInteractorProtocolMock: AuthInteractorProtocol, Mock { func intValue() -> Int { switch self { case let .m_login__username_usernamepassword_password(p0, p1): return p0.intValue + p1.intValue + case let .m_login__SSO__username_password(p0): return p0.intValue case let .m_login__externalToken_externalTokenbackend_backend(p0, p1): return p0.intValue + p1.intValue case let .m_resetPassword__email_email(p0): return p0.intValue case let .m_getCookies__force_force(p0): return p0.intValue @@ -234,6 +258,7 @@ open class AuthInteractorProtocolMock: AuthInteractorProtocol, Mock { func assertionName() -> String { switch self { case .m_login__username_usernamepassword_password: return ".login(username:password:)" + case .m_login__SSO__username_password: return ".loginSSO(username:password:)" case .m_login__externalToken_externalTokenbackend_backend: return ".login(externalToken:backend:)" case .m_resetPassword__email_email: return ".resetPassword(email:)" case .m_getCookies__force_force: return ".getCookies(force:)" @@ -261,6 +286,10 @@ open class AuthInteractorProtocolMock: AuthInteractorProtocol, Mock { public static func login(externalToken: Parameter, backend: Parameter, willReturn: User...) -> MethodStub { return Given(method: .m_login__externalToken_externalTokenbackend_backend(`externalToken`, `backend`), products: willReturn.map({ StubProduct.return($0 as Any) })) } + @discardableResult + public static func ssoLogin(title: Parameter, willReturn: User...) -> MethodStub { + return Given(method: .m_login__SSO__username_password(`title`), products: willReturn.map({ StubProduct.return($0 as Any) })) + } public static func resetPassword(email: Parameter, willReturn: ResetPassword...) -> MethodStub { return Given(method: .m_resetPassword__email_email(`email`), products: willReturn.map({ StubProduct.return($0 as Any) })) } @@ -353,7 +382,8 @@ open class AuthInteractorProtocolMock: AuthInteractorProtocol, Mock { fileprivate var method: MethodType @discardableResult - public static func login(username: Parameter, password: Parameter) -> Verify { return Verify(method: .m_login__username_usernamepassword_password(`username`, `password`))} + public static func login(username: Parameter, password: Parameter) -> Verify { return Verify(method: .m_login__username_usernamepassword_password(`username`, `password`))} + public static func ssoLogin(title: Parameter) -> Verify { return Verify(method: .m_login__SSO__username_password(`title`))} @discardableResult public static func login(externalToken: Parameter, backend: Parameter) -> Verify { return Verify(method: .m_login__externalToken_externalTokenbackend_backend(`externalToken`, `backend`))} public static func resetPassword(email: Parameter) -> Verify { return Verify(method: .m_resetPassword__email_email(`email`))} @@ -581,6 +611,12 @@ open class BaseRouterMock: BaseRouter, Mock { perform?(`title`, `url`) } + open func showSSOWebBrowser(title: String) { + addInvocation(.m_showWebBrowser__SSO(Parameter.value(`title`))) + let perform = methodPerformValue(.m_showWebBrowser__SSO(Parameter.value(`title`))) as? (String) -> Void + perform?(`title`) + } + open func presentAlert(alertTitle: String, alertMessage: String, positiveAction: String, onCloseTapped: @escaping () -> Void, okTapped: @escaping () -> Void, type: AlertViewType) { addInvocation(.m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagepositiveAction_positiveActiononCloseTapped_onCloseTappedokTapped_okTappedtype_type(Parameter.value(`alertTitle`), Parameter.value(`alertMessage`), Parameter.value(`positiveAction`), Parameter<() -> Void>.value(`onCloseTapped`), Parameter<() -> Void>.value(`okTapped`), Parameter.value(`type`))) let perform = methodPerformValue(.m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagepositiveAction_positiveActiononCloseTapped_onCloseTappedokTapped_okTappedtype_type(Parameter.value(`alertTitle`), Parameter.value(`alertMessage`), Parameter.value(`positiveAction`), Parameter<() -> Void>.value(`onCloseTapped`), Parameter<() -> Void>.value(`okTapped`), Parameter.value(`type`))) as? (String, String, String, @escaping () -> Void, @escaping () -> Void, AlertViewType) -> Void @@ -619,6 +655,7 @@ open class BaseRouterMock: BaseRouter, Mock { case m_showForgotPasswordScreen case m_showDiscoveryScreen__searchQuery_searchQuerysourceScreen_sourceScreen(Parameter, Parameter) case m_showWebBrowser__title_titleurl_url(Parameter, Parameter) + case m_showWebBrowser__SSO(Parameter) case m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagepositiveAction_positiveActiononCloseTapped_onCloseTappedokTapped_okTappedtype_type(Parameter, Parameter, Parameter, Parameter<() -> Void>, Parameter<() -> Void>, Parameter) case m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagenextSectionName_nextSectionNameaction_actionimage_imageonCloseTapped_onCloseTappedokTapped_okTappednextSectionTapped_nextSectionTapped(Parameter, Parameter, Parameter, Parameter, Parameter, Parameter<() -> Void>, Parameter<() -> Void>, Parameter<() -> Void>) case m_presentView__transitionStyle_transitionStyleview_viewcompletion_completion(Parameter, Parameter, Parameter<(() -> Void)?>) @@ -732,6 +769,7 @@ open class BaseRouterMock: BaseRouter, Mock { case .m_showForgotPasswordScreen: return 0 case let .m_showDiscoveryScreen__searchQuery_searchQuerysourceScreen_sourceScreen(p0, p1): return p0.intValue + p1.intValue case let .m_showWebBrowser__title_titleurl_url(p0, p1): return p0.intValue + p1.intValue + case let .m_showWebBrowser__SSO(p0): return p0.intValue case let .m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagepositiveAction_positiveActiononCloseTapped_onCloseTappedokTapped_okTappedtype_type(p0, p1, p2, p3, p4, p5): return p0.intValue + p1.intValue + p2.intValue + p3.intValue + p4.intValue + p5.intValue case let .m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagenextSectionName_nextSectionNameaction_actionimage_imageonCloseTapped_onCloseTappedokTapped_okTappednextSectionTapped_nextSectionTapped(p0, p1, p2, p3, p4, p5, p6, p7): return p0.intValue + p1.intValue + p2.intValue + p3.intValue + p4.intValue + p5.intValue + p6.intValue + p7.intValue case let .m_presentView__transitionStyle_transitionStyleview_viewcompletion_completion(p0, p1, p2): return p0.intValue + p1.intValue + p2.intValue @@ -752,6 +790,7 @@ open class BaseRouterMock: BaseRouter, Mock { case .m_showForgotPasswordScreen: return ".showForgotPasswordScreen()" case .m_showDiscoveryScreen__searchQuery_searchQuerysourceScreen_sourceScreen: return ".showDiscoveryScreen(searchQuery:sourceScreen:)" case .m_showWebBrowser__title_titleurl_url: return ".showWebBrowser(title:url:)" + case .m_showWebBrowser__SSO: return ".showSSOWebBrowser(title:)" case .m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagepositiveAction_positiveActiononCloseTapped_onCloseTappedokTapped_okTappedtype_type: return ".presentAlert(alertTitle:alertMessage:positiveAction:onCloseTapped:okTapped:type:)" case .m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagenextSectionName_nextSectionNameaction_actionimage_imageonCloseTapped_onCloseTappedokTapped_okTappednextSectionTapped_nextSectionTapped: return ".presentAlert(alertTitle:alertMessage:nextSectionName:action:image:onCloseTapped:okTapped:nextSectionTapped:)" case .m_presentView__transitionStyle_transitionStyleview_viewcompletion_completion: return ".presentView(transitionStyle:view:completion:)" diff --git a/Discussion/DiscussionTests/DiscussionMock.generated.swift b/Discussion/DiscussionTests/DiscussionMock.generated.swift index 758b1cbdf..e443a5af5 100644 --- a/Discussion/DiscussionTests/DiscussionMock.generated.swift +++ b/Discussion/DiscussionTests/DiscussionMock.generated.swift @@ -76,6 +76,23 @@ open class AuthInteractorProtocolMock: AuthInteractorProtocol, Mock { return __value } + @discardableResult + open func login(ssoToken: String) async throws -> Core.User { + addInvocation(.m_login__SSO__username_password(Parameter.value(`ssoToken`))) + let perform = methodPerformValue(.m_login__SSO__username_password(Parameter.value(`ssoToken`))) as? (String) -> Void + perform?(`ssoToken`) + var __value: User + do { + __value = try methodReturnValue(.m_login__SSO__username_password(Parameter.value(ssoToken))).casted() + } catch MockError.notStubed { + onFatalFailure("Stub return value not specified for login(username: String, password: String). Use given") + Failure("Stub return value not specified for login(username: String, password: String). Use given") + } catch { + throw error + } + return __value + } + @discardableResult open func login(externalToken: String, backend: String) throws -> User { addInvocation(.m_login__externalToken_externalTokenbackend_backend(Parameter.value(`externalToken`), Parameter.value(`backend`))) @@ -173,6 +190,7 @@ open class AuthInteractorProtocolMock: AuthInteractorProtocol, Mock { fileprivate enum MethodType { case m_login__username_usernamepassword_password(Parameter, Parameter) + case m_login__SSO__username_password(Parameter) case m_login__externalToken_externalTokenbackend_backend(Parameter, Parameter) case m_resetPassword__email_email(Parameter) case m_getCookies__force_force(Parameter) @@ -223,6 +241,7 @@ open class AuthInteractorProtocolMock: AuthInteractorProtocol, Mock { func intValue() -> Int { switch self { case let .m_login__username_usernamepassword_password(p0, p1): return p0.intValue + p1.intValue + case let .m_login__SSO__username_password(p0): return p0.intValue case let .m_login__externalToken_externalTokenbackend_backend(p0, p1): return p0.intValue + p1.intValue case let .m_resetPassword__email_email(p0): return p0.intValue case let .m_getCookies__force_force(p0): return p0.intValue @@ -234,6 +253,7 @@ open class AuthInteractorProtocolMock: AuthInteractorProtocol, Mock { func assertionName() -> String { switch self { case .m_login__username_usernamepassword_password: return ".login(username:password:)" + case .m_login__SSO__username_password: return ".loginSSO(username:password:)" case .m_login__externalToken_externalTokenbackend_backend: return ".login(externalToken:backend:)" case .m_resetPassword__email_email: return ".resetPassword(email:)" case .m_getCookies__force_force: return ".getCookies(force:)" @@ -581,6 +601,12 @@ open class BaseRouterMock: BaseRouter, Mock { perform?(`title`, `url`) } + open func showSSOWebBrowser(title: String) { + addInvocation(.m_showWebBrowser__SSO(Parameter.value(`title`))) + let perform = methodPerformValue(.m_showWebBrowser__SSO(Parameter.value(`title`))) as? (String) -> Void + perform?(`title`) + } + open func presentAlert(alertTitle: String, alertMessage: String, positiveAction: String, onCloseTapped: @escaping () -> Void, okTapped: @escaping () -> Void, type: AlertViewType) { addInvocation(.m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagepositiveAction_positiveActiononCloseTapped_onCloseTappedokTapped_okTappedtype_type(Parameter.value(`alertTitle`), Parameter.value(`alertMessage`), Parameter.value(`positiveAction`), Parameter<() -> Void>.value(`onCloseTapped`), Parameter<() -> Void>.value(`okTapped`), Parameter.value(`type`))) let perform = methodPerformValue(.m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagepositiveAction_positiveActiononCloseTapped_onCloseTappedokTapped_okTappedtype_type(Parameter.value(`alertTitle`), Parameter.value(`alertMessage`), Parameter.value(`positiveAction`), Parameter<() -> Void>.value(`onCloseTapped`), Parameter<() -> Void>.value(`okTapped`), Parameter.value(`type`))) as? (String, String, String, @escaping () -> Void, @escaping () -> Void, AlertViewType) -> Void @@ -619,6 +645,7 @@ open class BaseRouterMock: BaseRouter, Mock { case m_showForgotPasswordScreen case m_showDiscoveryScreen__searchQuery_searchQuerysourceScreen_sourceScreen(Parameter, Parameter) case m_showWebBrowser__title_titleurl_url(Parameter, Parameter) + case m_showWebBrowser__SSO(Parameter) case m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagepositiveAction_positiveActiononCloseTapped_onCloseTappedokTapped_okTappedtype_type(Parameter, Parameter, Parameter, Parameter<() -> Void>, Parameter<() -> Void>, Parameter) case m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagenextSectionName_nextSectionNameaction_actionimage_imageonCloseTapped_onCloseTappedokTapped_okTappednextSectionTapped_nextSectionTapped(Parameter, Parameter, Parameter, Parameter, Parameter, Parameter<() -> Void>, Parameter<() -> Void>, Parameter<() -> Void>) case m_presentView__transitionStyle_transitionStyleview_viewcompletion_completion(Parameter, Parameter, Parameter<(() -> Void)?>) @@ -732,6 +759,7 @@ open class BaseRouterMock: BaseRouter, Mock { case .m_showForgotPasswordScreen: return 0 case let .m_showDiscoveryScreen__searchQuery_searchQuerysourceScreen_sourceScreen(p0, p1): return p0.intValue + p1.intValue case let .m_showWebBrowser__title_titleurl_url(p0, p1): return p0.intValue + p1.intValue + case let .m_showWebBrowser__SSO(p0): return p0.intValue case let .m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagepositiveAction_positiveActiononCloseTapped_onCloseTappedokTapped_okTappedtype_type(p0, p1, p2, p3, p4, p5): return p0.intValue + p1.intValue + p2.intValue + p3.intValue + p4.intValue + p5.intValue case let .m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagenextSectionName_nextSectionNameaction_actionimage_imageonCloseTapped_onCloseTappedokTapped_okTappednextSectionTapped_nextSectionTapped(p0, p1, p2, p3, p4, p5, p6, p7): return p0.intValue + p1.intValue + p2.intValue + p3.intValue + p4.intValue + p5.intValue + p6.intValue + p7.intValue case let .m_presentView__transitionStyle_transitionStyleview_viewcompletion_completion(p0, p1, p2): return p0.intValue + p1.intValue + p2.intValue @@ -752,6 +780,7 @@ open class BaseRouterMock: BaseRouter, Mock { case .m_showForgotPasswordScreen: return ".showForgotPasswordScreen()" case .m_showDiscoveryScreen__searchQuery_searchQuerysourceScreen_sourceScreen: return ".showDiscoveryScreen(searchQuery:sourceScreen:)" case .m_showWebBrowser__title_titleurl_url: return ".showWebBrowser(title:url:)" + case .m_showWebBrowser__SSO: return ".showSSOWebBrowser(title:)" case .m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagepositiveAction_positiveActiononCloseTapped_onCloseTappedokTapped_okTappedtype_type: return ".presentAlert(alertTitle:alertMessage:positiveAction:onCloseTapped:okTapped:type:)" case .m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagenextSectionName_nextSectionNameaction_actionimage_imageonCloseTapped_onCloseTappedokTapped_okTappednextSectionTapped_nextSectionTapped: return ".presentAlert(alertTitle:alertMessage:nextSectionName:action:image:onCloseTapped:okTapped:nextSectionTapped:)" case .m_presentView__transitionStyle_transitionStyleview_viewcompletion_completion: return ".presentView(transitionStyle:view:completion:)" @@ -3309,6 +3338,12 @@ open class DiscussionRouterMock: DiscussionRouter, Mock { perform?(`title`, `url`) } + open func showSSOWebBrowser(title: String) { + addInvocation(.m_showWebBrowser__SSO(Parameter.value(`title`))) + let perform = methodPerformValue(.m_showWebBrowser__SSO(Parameter.value(`title`))) as? (String) -> Void + perform?(`title`) + } + open func presentAlert(alertTitle: String, alertMessage: String, positiveAction: String, onCloseTapped: @escaping () -> Void, okTapped: @escaping () -> Void, type: AlertViewType) { addInvocation(.m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagepositiveAction_positiveActiononCloseTapped_onCloseTappedokTapped_okTappedtype_type(Parameter.value(`alertTitle`), Parameter.value(`alertMessage`), Parameter.value(`positiveAction`), Parameter<() -> Void>.value(`onCloseTapped`), Parameter<() -> Void>.value(`okTapped`), Parameter.value(`type`))) let perform = methodPerformValue(.m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagepositiveAction_positiveActiononCloseTapped_onCloseTappedokTapped_okTappedtype_type(Parameter.value(`alertTitle`), Parameter.value(`alertMessage`), Parameter.value(`positiveAction`), Parameter<() -> Void>.value(`onCloseTapped`), Parameter<() -> Void>.value(`okTapped`), Parameter.value(`type`))) as? (String, String, String, @escaping () -> Void, @escaping () -> Void, AlertViewType) -> Void @@ -3353,6 +3388,7 @@ open class DiscussionRouterMock: DiscussionRouter, Mock { case m_showForgotPasswordScreen case m_showDiscoveryScreen__searchQuery_searchQuerysourceScreen_sourceScreen(Parameter, Parameter) case m_showWebBrowser__title_titleurl_url(Parameter, Parameter) + case m_showWebBrowser__SSO(Parameter) case m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagepositiveAction_positiveActiononCloseTapped_onCloseTappedokTapped_okTappedtype_type(Parameter, Parameter, Parameter, Parameter<() -> Void>, Parameter<() -> Void>, Parameter) case m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagenextSectionName_nextSectionNameaction_actionimage_imageonCloseTapped_onCloseTappedokTapped_okTappednextSectionTapped_nextSectionTapped(Parameter, Parameter, Parameter, Parameter, Parameter, Parameter<() -> Void>, Parameter<() -> Void>, Parameter<() -> Void>) case m_presentView__transitionStyle_transitionStyleview_viewcompletion_completion(Parameter, Parameter, Parameter<(() -> Void)?>) @@ -3517,6 +3553,7 @@ open class DiscussionRouterMock: DiscussionRouter, Mock { case .m_showForgotPasswordScreen: return 0 case let .m_showDiscoveryScreen__searchQuery_searchQuerysourceScreen_sourceScreen(p0, p1): return p0.intValue + p1.intValue case let .m_showWebBrowser__title_titleurl_url(p0, p1): return p0.intValue + p1.intValue + case let .m_showWebBrowser__SSO(p0): return p0.intValue case let .m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagepositiveAction_positiveActiononCloseTapped_onCloseTappedokTapped_okTappedtype_type(p0, p1, p2, p3, p4, p5): return p0.intValue + p1.intValue + p2.intValue + p3.intValue + p4.intValue + p5.intValue case let .m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagenextSectionName_nextSectionNameaction_actionimage_imageonCloseTapped_onCloseTappedokTapped_okTappednextSectionTapped_nextSectionTapped(p0, p1, p2, p3, p4, p5, p6, p7): return p0.intValue + p1.intValue + p2.intValue + p3.intValue + p4.intValue + p5.intValue + p6.intValue + p7.intValue case let .m_presentView__transitionStyle_transitionStyleview_viewcompletion_completion(p0, p1, p2): return p0.intValue + p1.intValue + p2.intValue @@ -3543,6 +3580,7 @@ open class DiscussionRouterMock: DiscussionRouter, Mock { case .m_showForgotPasswordScreen: return ".showForgotPasswordScreen()" case .m_showDiscoveryScreen__searchQuery_searchQuerysourceScreen_sourceScreen: return ".showDiscoveryScreen(searchQuery:sourceScreen:)" case .m_showWebBrowser__title_titleurl_url: return ".showWebBrowser(title:url:)" + case .m_showWebBrowser__SSO: return ".showSSOWebBrowser(title:)" case .m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagepositiveAction_positiveActiononCloseTapped_onCloseTappedokTapped_okTappedtype_type: return ".presentAlert(alertTitle:alertMessage:positiveAction:onCloseTapped:okTapped:type:)" case .m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagenextSectionName_nextSectionNameaction_actionimage_imageonCloseTapped_onCloseTappedokTapped_okTappednextSectionTapped_nextSectionTapped: return ".presentAlert(alertTitle:alertMessage:nextSectionName:action:image:onCloseTapped:okTapped:nextSectionTapped:)" case .m_presentView__transitionStyle_transitionStyleview_viewcompletion_completion: return ".presentView(transitionStyle:view:completion:)" diff --git a/OpenEdX.xcodeproj/project.pbxproj b/OpenEdX.xcodeproj/project.pbxproj index 68414c4d2..0d103570a 100644 --- a/OpenEdX.xcodeproj/project.pbxproj +++ b/OpenEdX.xcodeproj/project.pbxproj @@ -758,13 +758,14 @@ CODE_SIGN_ENTITLEMENTS = OpenEdX/OpenEdX.entitlements; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = L8PG7LC3Y3; + DEVELOPMENT_TEAM = ""; FULLSTORY_ENABLED = NO; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = OpenEdX/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = ""; INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.education"; INFOPLIST_KEY_NSCalendarsUsageDescription = "We would like to utilize your calendar list to subscribe you to your personalized calendar for this course."; + INFOPLIST_KEY_NSPhotoLibraryAddUsageDescription = "Allow access to your photo library so you can save photos in your gallery."; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; INFOPLIST_KEY_UIStatusBarStyle = UIStatusBarStyleLightContent; @@ -849,13 +850,14 @@ CODE_SIGN_ENTITLEMENTS = OpenEdX/OpenEdX.entitlements; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = L8PG7LC3Y3; + DEVELOPMENT_TEAM = ""; FULLSTORY_ENABLED = NO; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = OpenEdX/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = ""; INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.education"; INFOPLIST_KEY_NSCalendarsUsageDescription = "We would like to utilize your calendar list to subscribe you to your personalized calendar for this course."; + INFOPLIST_KEY_NSPhotoLibraryAddUsageDescription = "Allow access to your photo library so you can save photos in your gallery."; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; INFOPLIST_KEY_UIStatusBarStyle = UIStatusBarStyleLightContent; @@ -946,13 +948,14 @@ CODE_SIGN_ENTITLEMENTS = OpenEdX/OpenEdX.entitlements; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = L8PG7LC3Y3; + DEVELOPMENT_TEAM = ""; FULLSTORY_ENABLED = NO; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = OpenEdX/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = ""; INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.education"; INFOPLIST_KEY_NSCalendarsUsageDescription = "We would like to utilize your calendar list to subscribe you to your personalized calendar for this course."; + INFOPLIST_KEY_NSPhotoLibraryAddUsageDescription = "Allow access to your photo library so you can save photos in your gallery."; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; INFOPLIST_KEY_UIStatusBarStyle = UIStatusBarStyleLightContent; @@ -1037,13 +1040,14 @@ CODE_SIGN_ENTITLEMENTS = OpenEdX/OpenEdX.entitlements; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = L8PG7LC3Y3; + DEVELOPMENT_TEAM = ""; FULLSTORY_ENABLED = NO; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = OpenEdX/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = ""; INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.education"; INFOPLIST_KEY_NSCalendarsUsageDescription = "We would like to utilize your calendar list to subscribe you to your personalized calendar for this course."; + INFOPLIST_KEY_NSPhotoLibraryAddUsageDescription = "Allow access to your photo library so you can save photos in your gallery."; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; INFOPLIST_KEY_UIStatusBarStyle = UIStatusBarStyleLightContent; @@ -1188,13 +1192,14 @@ CODE_SIGN_ENTITLEMENTS = OpenEdX/OpenEdX.entitlements; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = L8PG7LC3Y3; + DEVELOPMENT_TEAM = ""; FULLSTORY_ENABLED = NO; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = OpenEdX/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = ""; INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.education"; INFOPLIST_KEY_NSCalendarsUsageDescription = "We would like to utilize your calendar list to subscribe you to your personalized calendar for this course."; + INFOPLIST_KEY_NSPhotoLibraryAddUsageDescription = "Allow access to your photo library so you can save photos in your gallery."; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; INFOPLIST_KEY_UIStatusBarStyle = UIStatusBarStyleLightContent; @@ -1225,13 +1230,14 @@ CODE_SIGN_ENTITLEMENTS = OpenEdX/OpenEdX.entitlements; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = L8PG7LC3Y3; + DEVELOPMENT_TEAM = ""; FULLSTORY_ENABLED = NO; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = OpenEdX/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = ""; INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.education"; INFOPLIST_KEY_NSCalendarsUsageDescription = "We would like to utilize your calendar list to subscribe you to your personalized calendar for this course."; + INFOPLIST_KEY_NSPhotoLibraryAddUsageDescription = "Allow access to your photo library so you can save photos in your gallery."; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; INFOPLIST_KEY_UIStatusBarStyle = UIStatusBarStyleLightContent; diff --git a/OpenEdX/DI/AppAssembly.swift b/OpenEdX/DI/AppAssembly.swift index 5c4639e15..0ab06b29a 100644 --- a/OpenEdX/DI/AppAssembly.swift +++ b/OpenEdX/DI/AppAssembly.swift @@ -168,6 +168,12 @@ class AppAssembly: Assembly { r.resolve(AppStorage.self)! }.inObjectScope(.container) + container.register(SSOHelper.self){ r in + SSOHelper( + keychain: r.resolve(KeychainSwift.self)! + ) + } + container.register(Validator.self) { _ in Validator() }.inObjectScope(.container) diff --git a/OpenEdX/DI/ScreenAssembly.swift b/OpenEdX/DI/ScreenAssembly.swift index a65f25833..4ef5e7321 100644 --- a/OpenEdX/DI/ScreenAssembly.swift +++ b/OpenEdX/DI/ScreenAssembly.swift @@ -87,6 +87,15 @@ class ScreenAssembly: Assembly { sourceScreen: sourceScreen ) } + container.register(SSOWebViewModel.self) { r in + SSOWebViewModel( + interactor: r.resolve(AuthInteractorProtocol.self)!, + router: r.resolve(AuthorizationRouter.self)!, + config: r.resolve(ConfigProtocol.self)!, + analytics: r.resolve(AuthorizationAnalytics.self)!, + ssoHelper: r.resolve(SSOHelper.self)! + ) + } container.register(SignUpViewModel.self) { r, sourceScreen in SignUpViewModel( interactor: r.resolve(AuthInteractorProtocol.self)!, diff --git a/OpenEdX/Info.plist b/OpenEdX/Info.plist index 95202c02b..e9bd32e58 100644 --- a/OpenEdX/Info.plist +++ b/OpenEdX/Info.plist @@ -45,11 +45,5 @@ UIViewControllerBasedStatusBarAppearance - NSCalendarsUsageDescription - We would like to utilize your calendar list to subscribe you to your personalized calendar for this course. - NSCalendarsFullAccessUsageDescription - We would like to utilize your calendar list to subscribe you to your personalized calendar for this course. - NSPhotoLibraryAddUsageDescription - Allow access to your photo library so you can save photos in your gallery. diff --git a/OpenEdX/Router.swift b/OpenEdX/Router.swift index b90f405d9..0004ca0cf 100644 --- a/OpenEdX/Router.swift +++ b/OpenEdX/Router.swift @@ -848,6 +848,16 @@ public class Router: AuthorizationRouter, let controller = UIHostingController(rootView: webBrowser) navigationController.pushViewController(controller, animated: true) } + + public func showSSOWebBrowser(title: String) { + let config = Container.shared.resolve(ConfigProtocol.self)! + let webBrowser = ContainerWebView( + config.baseSSOURL.absoluteString, + title: title + ) + let controller = UIHostingController(rootView: webBrowser) + navigationController.pushViewController(controller, animated: true) + } } // MARK: BackNavigationProtocol diff --git a/Profile/Profile.xcodeproj/project.pbxproj b/Profile/Profile.xcodeproj/project.pbxproj index ae075f652..1562e19f0 100644 --- a/Profile/Profile.xcodeproj/project.pbxproj +++ b/Profile/Profile.xcodeproj/project.pbxproj @@ -864,7 +864,7 @@ CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; - DEVELOPMENT_TEAM = L8PG7LC3Y3; + DEVELOPMENT_TEAM = ""; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; @@ -899,7 +899,7 @@ CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; - DEVELOPMENT_TEAM = L8PG7LC3Y3; + DEVELOPMENT_TEAM = ""; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; @@ -996,7 +996,7 @@ CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; - DEVELOPMENT_TEAM = L8PG7LC3Y3; + DEVELOPMENT_TEAM = ""; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; @@ -1094,7 +1094,7 @@ CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; - DEVELOPMENT_TEAM = L8PG7LC3Y3; + DEVELOPMENT_TEAM = ""; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; @@ -1186,7 +1186,7 @@ CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; - DEVELOPMENT_TEAM = L8PG7LC3Y3; + DEVELOPMENT_TEAM = ""; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; @@ -1277,7 +1277,7 @@ CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; - DEVELOPMENT_TEAM = L8PG7LC3Y3; + DEVELOPMENT_TEAM = ""; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; @@ -1500,7 +1500,7 @@ CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; - DEVELOPMENT_TEAM = L8PG7LC3Y3; + DEVELOPMENT_TEAM = ""; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; @@ -1613,7 +1613,7 @@ CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; - DEVELOPMENT_TEAM = L8PG7LC3Y3; + DEVELOPMENT_TEAM = ""; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; diff --git a/Profile/ProfileTests/ProfileMock.generated.swift b/Profile/ProfileTests/ProfileMock.generated.swift index ec514f18d..6d51643fe 100644 --- a/Profile/ProfileTests/ProfileMock.generated.swift +++ b/Profile/ProfileTests/ProfileMock.generated.swift @@ -62,18 +62,35 @@ open class AuthInteractorProtocolMock: AuthInteractorProtocol, Mock { @discardableResult open func login(username: String, password: String) throws -> User { addInvocation(.m_login__username_usernamepassword_password(Parameter.value(`username`), Parameter.value(`password`))) - let perform = methodPerformValue(.m_login__username_usernamepassword_password(Parameter.value(`username`), Parameter.value(`password`))) as? (String, String) -> Void - perform?(`username`, `password`) - var __value: User - do { - __value = try methodReturnValue(.m_login__username_usernamepassword_password(Parameter.value(`username`), Parameter.value(`password`))).casted() - } catch MockError.notStubed { - onFatalFailure("Stub return value not specified for login(username: String, password: String). Use given") - Failure("Stub return value not specified for login(username: String, password: String). Use given") - } catch { - throw error - } - return __value + let perform = methodPerformValue(.m_login__username_usernamepassword_password(Parameter.value(`username`), Parameter.value(`password`))) as? (String, String) -> Void + perform?(`username`, `password`) + var __value: User + do { + __value = try methodReturnValue(.m_login__username_usernamepassword_password(Parameter.value(`username`), Parameter.value(`password`))).casted() + } catch MockError.notStubed { + onFatalFailure("Stub return value not specified for login(username: String, password: String). Use given") + Failure("Stub return value not specified for login(username: String, password: String). Use given") + } catch { + throw error + } + return __value + } + + @discardableResult + open func login(ssoToken: String) async throws -> Core.User { + addInvocation(.m_login__SSO__username_password(Parameter.value(`ssoToken`))) + let perform = methodPerformValue(.m_login__SSO__username_password(Parameter.value(`ssoToken`))) as? (String) -> Void + perform?(`ssoToken`) + var __value: User + do { + __value = try methodReturnValue(.m_login__SSO__username_password(Parameter.value(ssoToken))).casted() + } catch MockError.notStubed { + onFatalFailure("Stub return value not specified for login(username: String, password: String). Use given") + Failure("Stub return value not specified for login(username: String, password: String). Use given") + } catch { + throw error + } + return __value } @discardableResult @@ -173,6 +190,7 @@ open class AuthInteractorProtocolMock: AuthInteractorProtocol, Mock { fileprivate enum MethodType { case m_login__username_usernamepassword_password(Parameter, Parameter) + case m_login__SSO__username_password(Parameter) case m_login__externalToken_externalTokenbackend_backend(Parameter, Parameter) case m_resetPassword__email_email(Parameter) case m_getCookies__force_force(Parameter) @@ -223,6 +241,7 @@ open class AuthInteractorProtocolMock: AuthInteractorProtocol, Mock { func intValue() -> Int { switch self { case let .m_login__username_usernamepassword_password(p0, p1): return p0.intValue + p1.intValue + case let .m_login__SSO__username_password(p0): return p0.intValue case let .m_login__externalToken_externalTokenbackend_backend(p0, p1): return p0.intValue + p1.intValue case let .m_resetPassword__email_email(p0): return p0.intValue case let .m_getCookies__force_force(p0): return p0.intValue @@ -234,6 +253,7 @@ open class AuthInteractorProtocolMock: AuthInteractorProtocol, Mock { func assertionName() -> String { switch self { case .m_login__username_usernamepassword_password: return ".login(username:password:)" + case .m_login__SSO__username_password: return ".loginSSO(username:password:)" case .m_login__externalToken_externalTokenbackend_backend: return ".login(externalToken:backend:)" case .m_resetPassword__email_email: return ".resetPassword(email:)" case .m_getCookies__force_force: return ".getCookies(force:)" @@ -581,6 +601,12 @@ open class BaseRouterMock: BaseRouter, Mock { perform?(`title`, `url`) } + open func showSSOWebBrowser(title: String) { + addInvocation(.m_showWebBrowser__SSO(Parameter.value(`title`))) + let perform = methodPerformValue(.m_showWebBrowser__SSO(Parameter.value(`title`))) as? (String) -> Void + perform?(`title`) + } + open func presentAlert(alertTitle: String, alertMessage: String, positiveAction: String, onCloseTapped: @escaping () -> Void, okTapped: @escaping () -> Void, type: AlertViewType) { addInvocation(.m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagepositiveAction_positiveActiononCloseTapped_onCloseTappedokTapped_okTappedtype_type(Parameter.value(`alertTitle`), Parameter.value(`alertMessage`), Parameter.value(`positiveAction`), Parameter<() -> Void>.value(`onCloseTapped`), Parameter<() -> Void>.value(`okTapped`), Parameter.value(`type`))) let perform = methodPerformValue(.m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagepositiveAction_positiveActiononCloseTapped_onCloseTappedokTapped_okTappedtype_type(Parameter.value(`alertTitle`), Parameter.value(`alertMessage`), Parameter.value(`positiveAction`), Parameter<() -> Void>.value(`onCloseTapped`), Parameter<() -> Void>.value(`okTapped`), Parameter.value(`type`))) as? (String, String, String, @escaping () -> Void, @escaping () -> Void, AlertViewType) -> Void @@ -619,6 +645,7 @@ open class BaseRouterMock: BaseRouter, Mock { case m_showForgotPasswordScreen case m_showDiscoveryScreen__searchQuery_searchQuerysourceScreen_sourceScreen(Parameter, Parameter) case m_showWebBrowser__title_titleurl_url(Parameter, Parameter) + case m_showWebBrowser__SSO(Parameter) case m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagepositiveAction_positiveActiononCloseTapped_onCloseTappedokTapped_okTappedtype_type(Parameter, Parameter, Parameter, Parameter<() -> Void>, Parameter<() -> Void>, Parameter) case m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagenextSectionName_nextSectionNameaction_actionimage_imageonCloseTapped_onCloseTappedokTapped_okTappednextSectionTapped_nextSectionTapped(Parameter, Parameter, Parameter, Parameter, Parameter, Parameter<() -> Void>, Parameter<() -> Void>, Parameter<() -> Void>) case m_presentView__transitionStyle_transitionStyleview_viewcompletion_completion(Parameter, Parameter, Parameter<(() -> Void)?>) @@ -732,6 +759,7 @@ open class BaseRouterMock: BaseRouter, Mock { case .m_showForgotPasswordScreen: return 0 case let .m_showDiscoveryScreen__searchQuery_searchQuerysourceScreen_sourceScreen(p0, p1): return p0.intValue + p1.intValue case let .m_showWebBrowser__title_titleurl_url(p0, p1): return p0.intValue + p1.intValue + case let .m_showWebBrowser__SSO(p0): return p0.intValue case let .m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagepositiveAction_positiveActiononCloseTapped_onCloseTappedokTapped_okTappedtype_type(p0, p1, p2, p3, p4, p5): return p0.intValue + p1.intValue + p2.intValue + p3.intValue + p4.intValue + p5.intValue case let .m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagenextSectionName_nextSectionNameaction_actionimage_imageonCloseTapped_onCloseTappedokTapped_okTappednextSectionTapped_nextSectionTapped(p0, p1, p2, p3, p4, p5, p6, p7): return p0.intValue + p1.intValue + p2.intValue + p3.intValue + p4.intValue + p5.intValue + p6.intValue + p7.intValue case let .m_presentView__transitionStyle_transitionStyleview_viewcompletion_completion(p0, p1, p2): return p0.intValue + p1.intValue + p2.intValue @@ -752,6 +780,7 @@ open class BaseRouterMock: BaseRouter, Mock { case .m_showForgotPasswordScreen: return ".showForgotPasswordScreen()" case .m_showDiscoveryScreen__searchQuery_searchQuerysourceScreen_sourceScreen: return ".showDiscoveryScreen(searchQuery:sourceScreen:)" case .m_showWebBrowser__title_titleurl_url: return ".showWebBrowser(title:url:)" + case .m_showWebBrowser__SSO: return ".showSSOWebBrowser(title:)" case .m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagepositiveAction_positiveActiononCloseTapped_onCloseTappedokTapped_okTappedtype_type: return ".presentAlert(alertTitle:alertMessage:positiveAction:onCloseTapped:okTapped:type:)" case .m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagenextSectionName_nextSectionNameaction_actionimage_imageonCloseTapped_onCloseTappedokTapped_okTappednextSectionTapped_nextSectionTapped: return ".presentAlert(alertTitle:alertMessage:nextSectionName:action:image:onCloseTapped:okTapped:nextSectionTapped:)" case .m_presentView__transitionStyle_transitionStyleview_viewcompletion_completion: return ".presentView(transitionStyle:view:completion:)" @@ -4194,6 +4223,12 @@ open class ProfileRouterMock: ProfileRouter, Mock { perform?(`title`, `url`) } + open func showSSOWebBrowser(title: String) { + addInvocation(.m_showWebBrowser__SSO(Parameter.value(`title`))) + let perform = methodPerformValue(.m_showWebBrowser__SSO(Parameter.value(`title`))) as? (String) -> Void + perform?(`title`) + } + open func presentAlert(alertTitle: String, alertMessage: String, positiveAction: String, onCloseTapped: @escaping () -> Void, okTapped: @escaping () -> Void, type: AlertViewType) { addInvocation(.m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagepositiveAction_positiveActiononCloseTapped_onCloseTappedokTapped_okTappedtype_type(Parameter.value(`alertTitle`), Parameter.value(`alertMessage`), Parameter.value(`positiveAction`), Parameter<() -> Void>.value(`onCloseTapped`), Parameter<() -> Void>.value(`okTapped`), Parameter.value(`type`))) let perform = methodPerformValue(.m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagepositiveAction_positiveActiononCloseTapped_onCloseTappedokTapped_okTappedtype_type(Parameter.value(`alertTitle`), Parameter.value(`alertMessage`), Parameter.value(`positiveAction`), Parameter<() -> Void>.value(`onCloseTapped`), Parameter<() -> Void>.value(`okTapped`), Parameter.value(`type`))) as? (String, String, String, @escaping () -> Void, @escaping () -> Void, AlertViewType) -> Void @@ -4242,6 +4277,7 @@ open class ProfileRouterMock: ProfileRouter, Mock { case m_showForgotPasswordScreen case m_showDiscoveryScreen__searchQuery_searchQuerysourceScreen_sourceScreen(Parameter, Parameter) case m_showWebBrowser__title_titleurl_url(Parameter, Parameter) + case m_showWebBrowser__SSO(Parameter) case m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagepositiveAction_positiveActiononCloseTapped_onCloseTappedokTapped_okTappedtype_type(Parameter, Parameter, Parameter, Parameter<() -> Void>, Parameter<() -> Void>, Parameter) case m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagenextSectionName_nextSectionNameaction_actionimage_imageonCloseTapped_onCloseTappedokTapped_okTappednextSectionTapped_nextSectionTapped(Parameter, Parameter, Parameter, Parameter, Parameter, Parameter<() -> Void>, Parameter<() -> Void>, Parameter<() -> Void>) case m_presentView__transitionStyle_transitionStyleview_viewcompletion_completion(Parameter, Parameter, Parameter<(() -> Void)?>) @@ -4398,6 +4434,7 @@ open class ProfileRouterMock: ProfileRouter, Mock { case .m_showForgotPasswordScreen: return 0 case let .m_showDiscoveryScreen__searchQuery_searchQuerysourceScreen_sourceScreen(p0, p1): return p0.intValue + p1.intValue case let .m_showWebBrowser__title_titleurl_url(p0, p1): return p0.intValue + p1.intValue + case let .m_showWebBrowser__SSO(p0): return p0.intValue case let .m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagepositiveAction_positiveActiononCloseTapped_onCloseTappedokTapped_okTappedtype_type(p0, p1, p2, p3, p4, p5): return p0.intValue + p1.intValue + p2.intValue + p3.intValue + p4.intValue + p5.intValue case let .m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagenextSectionName_nextSectionNameaction_actionimage_imageonCloseTapped_onCloseTappedokTapped_okTappednextSectionTapped_nextSectionTapped(p0, p1, p2, p3, p4, p5, p6, p7): return p0.intValue + p1.intValue + p2.intValue + p3.intValue + p4.intValue + p5.intValue + p6.intValue + p7.intValue case let .m_presentView__transitionStyle_transitionStyleview_viewcompletion_completion(p0, p1, p2): return p0.intValue + p1.intValue + p2.intValue @@ -4428,6 +4465,7 @@ open class ProfileRouterMock: ProfileRouter, Mock { case .m_showForgotPasswordScreen: return ".showForgotPasswordScreen()" case .m_showDiscoveryScreen__searchQuery_searchQuerysourceScreen_sourceScreen: return ".showDiscoveryScreen(searchQuery:sourceScreen:)" case .m_showWebBrowser__title_titleurl_url: return ".showWebBrowser(title:url:)" + case .m_showWebBrowser__SSO: return ".showSSOWebBrowser(title:)" case .m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagepositiveAction_positiveActiononCloseTapped_onCloseTappedokTapped_okTappedtype_type: return ".presentAlert(alertTitle:alertMessage:positiveAction:onCloseTapped:okTapped:type:)" case .m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagenextSectionName_nextSectionNameaction_actionimage_imageonCloseTapped_onCloseTappedokTapped_okTappednextSectionTapped_nextSectionTapped: return ".presentAlert(alertTitle:alertMessage:nextSectionName:action:image:onCloseTapped:okTapped:nextSectionTapped:)" case .m_presentView__transitionStyle_transitionStyleview_viewcompletion_completion: return ".presentView(transitionStyle:view:completion:)" diff --git a/Theme/Theme.xcodeproj/project.pbxproj b/Theme/Theme.xcodeproj/project.pbxproj index ea888a71a..6178a2a2a 100644 --- a/Theme/Theme.xcodeproj/project.pbxproj +++ b/Theme/Theme.xcodeproj/project.pbxproj @@ -502,7 +502,7 @@ CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; - DEVELOPMENT_TEAM = L8PG7LC3Y3; + DEVELOPMENT_TEAM = ""; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; @@ -595,7 +595,7 @@ CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; - DEVELOPMENT_TEAM = L8PG7LC3Y3; + DEVELOPMENT_TEAM = ""; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; @@ -693,7 +693,7 @@ CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; - DEVELOPMENT_TEAM = L8PG7LC3Y3; + DEVELOPMENT_TEAM = ""; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; @@ -786,7 +786,7 @@ CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; - DEVELOPMENT_TEAM = L8PG7LC3Y3; + DEVELOPMENT_TEAM = ""; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; @@ -884,7 +884,7 @@ CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; - DEVELOPMENT_TEAM = L8PG7LC3Y3; + DEVELOPMENT_TEAM = ""; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; @@ -977,7 +977,7 @@ CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; - DEVELOPMENT_TEAM = L8PG7LC3Y3; + DEVELOPMENT_TEAM = ""; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; @@ -1133,7 +1133,7 @@ CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; - DEVELOPMENT_TEAM = L8PG7LC3Y3; + DEVELOPMENT_TEAM = ""; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; @@ -1168,7 +1168,7 @@ CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; - DEVELOPMENT_TEAM = L8PG7LC3Y3; + DEVELOPMENT_TEAM = ""; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; diff --git a/WhatsNew/WhatsNew.xcodeproj/project.pbxproj b/WhatsNew/WhatsNew.xcodeproj/project.pbxproj index 2cbc37bda..49ad542c3 100644 --- a/WhatsNew/WhatsNew.xcodeproj/project.pbxproj +++ b/WhatsNew/WhatsNew.xcodeproj/project.pbxproj @@ -612,7 +612,7 @@ CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; - DEVELOPMENT_TEAM = L8PG7LC3Y3; + DEVELOPMENT_TEAM = ""; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; @@ -651,7 +651,7 @@ CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; - DEVELOPMENT_TEAM = L8PG7LC3Y3; + DEVELOPMENT_TEAM = ""; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; @@ -797,7 +797,7 @@ CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; - DEVELOPMENT_TEAM = L8PG7LC3Y3; + DEVELOPMENT_TEAM = ""; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; @@ -923,7 +923,7 @@ CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; - DEVELOPMENT_TEAM = L8PG7LC3Y3; + DEVELOPMENT_TEAM = ""; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; @@ -1049,7 +1049,7 @@ CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; - DEVELOPMENT_TEAM = L8PG7LC3Y3; + DEVELOPMENT_TEAM = ""; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; @@ -1168,7 +1168,7 @@ CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; - DEVELOPMENT_TEAM = L8PG7LC3Y3; + DEVELOPMENT_TEAM = ""; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; @@ -1286,7 +1286,7 @@ CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; - DEVELOPMENT_TEAM = L8PG7LC3Y3; + DEVELOPMENT_TEAM = ""; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; @@ -1404,7 +1404,7 @@ CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; - DEVELOPMENT_TEAM = L8PG7LC3Y3; + DEVELOPMENT_TEAM = ""; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; diff --git a/WhatsNew/WhatsNew/Info.plist b/WhatsNew/WhatsNew/Info.plist index f72a0f657..0c67376eb 100644 --- a/WhatsNew/WhatsNew/Info.plist +++ b/WhatsNew/WhatsNew/Info.plist @@ -1,12 +1,5 @@ - diff --git a/default_config/dev/config.yaml b/default_config/dev/config.yaml index d7cec08d1..ffd91e5dc 100644 --- a/default_config/dev/config.yaml +++ b/default_config/dev/config.yaml @@ -1,8 +1,19 @@ API_HOST_URL: 'http://localhost:8000' +SSO_URL: 'http://localhost:8000' +SSO_FINISHED_URL: 'http://localhost:8000' ENVIRONMENT_DISPLAY_NAME: 'Localhost' FEEDBACK_EMAIL_ADDRESS: 'support@example.com' OAUTH_CLIENT_ID: '' +SSO_BUTTON_TITLE: + ar: "الدخول عبر SSO" + en: "Sign in with SSO" + + + UI_COMPONENTS: COURSE_UNIT_PROGRESS_ENABLED: false COURSE_NESTED_LIST_ENABLED: false + LOGIN_REGISTRATION_ENABLED: true + SAML_SSO_LOGIN_ENABLED: false + SAML_SSO_DEFAULT_LOGIN_BUTTON: false diff --git a/default_config/prod/config.yaml b/default_config/prod/config.yaml index d7cec08d1..0edb91b80 100644 --- a/default_config/prod/config.yaml +++ b/default_config/prod/config.yaml @@ -1,8 +1,18 @@ API_HOST_URL: 'http://localhost:8000' +SSO_URL: 'http://localhost:8000' +SSO_FINISHED_URL: 'http://localhost:8000' ENVIRONMENT_DISPLAY_NAME: 'Localhost' FEEDBACK_EMAIL_ADDRESS: 'support@example.com' OAUTH_CLIENT_ID: '' +SSO_BUTTON_TITLE: + ar: "الدخول عبر SSO" + en: "Sign in with SSO" + + UI_COMPONENTS: COURSE_UNIT_PROGRESS_ENABLED: false COURSE_NESTED_LIST_ENABLED: false + LOGIN_REGISTRATION_ENABLED: true + SAML_SSO_LOGIN_ENABLED: false + SAML_SSO_DEFAULT_LOGIN_BUTTON: false diff --git a/default_config/stage/config.yaml b/default_config/stage/config.yaml index d7cec08d1..0edb91b80 100644 --- a/default_config/stage/config.yaml +++ b/default_config/stage/config.yaml @@ -1,8 +1,18 @@ API_HOST_URL: 'http://localhost:8000' +SSO_URL: 'http://localhost:8000' +SSO_FINISHED_URL: 'http://localhost:8000' ENVIRONMENT_DISPLAY_NAME: 'Localhost' FEEDBACK_EMAIL_ADDRESS: 'support@example.com' OAUTH_CLIENT_ID: '' +SSO_BUTTON_TITLE: + ar: "الدخول عبر SSO" + en: "Sign in with SSO" + + UI_COMPONENTS: COURSE_UNIT_PROGRESS_ENABLED: false COURSE_NESTED_LIST_ENABLED: false + LOGIN_REGISTRATION_ENABLED: true + SAML_SSO_LOGIN_ENABLED: false + SAML_SSO_DEFAULT_LOGIN_BUTTON: false