From 59ed080e9dbf38c109149368eaa32b1248912d47 Mon Sep 17 00:00:00 2001 From: Hernan Zalazar Date: Thu, 1 Sep 2016 22:15:48 -0300 Subject: [PATCH 01/11] Clean project & organize files --- Lock.xcodeproj/project.pbxproj | 168 +++++++++++------- .../Auth0OAuth2Interactor.swift | 0 Lock/{Components => }/AuthButton.swift | 0 Lock/{Views => }/AuthCollectionView.swift | 0 Lock/{Presenters => }/AuthPresenter.swift | 0 Lock/{Models => }/AuthStyle.swift | 0 Lock/{Utils => }/Colors.swift | 0 Lock/ConnectionBuildable.swift | 48 +++++ Lock/Connections.swift | 43 +++++ .../CredentialAttribute.swift | 0 Lock/{Components => }/CredentialView.swift | 0 .../DatabaseAuthenticable.swift | 0 .../DatabaseForgotPasswordPresenter.swift | 0 .../DatabaseForgotPasswordView.swift | 0 .../DatabaseInteractor.swift | 0 .../DatabaseModeSwitcher.swift | 0 Lock/{Components => }/DatabaseModes.swift | 0 Lock/{Views => }/DatabaseOnlyView.swift | 0 .../DatabasePasswordInteractor.swift | 0 Lock/{Presenters => }/DatabasePresenter.swift | 0 .../DatabaseUserCreator.swift | 0 Lock/{Views => }/DatabaseView.swift | 0 Lock/{Components => }/Form.swift | 0 Lock/{Components => }/HeaderView.swift | 0 Lock/{Components => }/InputField.swift | 0 Lock/{Utils => }/Layout.swift | 0 Lock/{Utils => }/LazyImage.swift | 0 Lock/Lock.swift | 144 +-------------- Lock/LockOptions.swift | 37 ++++ .../LockViewController.swift | 0 Lock/{Utils => }/Logger.swift | 0 Lock/{Presenters => }/MessagePresenter.swift | 0 Lock/{Components => }/MessageView.swift | 0 .../MultifactorAuthenticatable.swift | 0 Lock/{Views => }/MultifactorCodeView.swift | 0 .../MultifactorInteractor.swift | 0 .../MultifactorPresenter.swift | 0 .../OAuth2Authenticatable.swift | 0 Lock/OfflineConnections.swift | 38 ++++ Lock/{Utils => }/Operations.swift | 0 Lock/OptionBuildable.swift | 72 ++++++++ Lock/Options.swift | 32 ++++ .../PasswordRecoverable.swift | 0 Lock/{Presenters => }/Presentable.swift | 0 Lock/{Components => }/PrimaryButton.swift | 0 Lock/{Utils => }/Queue.swift | 0 Lock/{Utils => }/Resources.swift | 0 Lock/{Router => }/Router.swift | 0 Lock/{Router => }/Routes.swift | 0 Lock/{Components => }/SecondaryButton.swift | 0 Lock/{Components => }/SignUpView.swift | 0 Lock/{Components => }/SingleInputView.swift | 0 Lock/{Models => }/User.swift | 0 Lock/{Utils => }/Validators.swift | 0 Lock/{Views => }/View.swift | 0 Lock/{Utils => }/i18n.swift | 0 56 files changed, 385 insertions(+), 197 deletions(-) rename Lock/{Interactors => }/Auth0OAuth2Interactor.swift (100%) rename Lock/{Components => }/AuthButton.swift (100%) rename Lock/{Views => }/AuthCollectionView.swift (100%) rename Lock/{Presenters => }/AuthPresenter.swift (100%) rename Lock/{Models => }/AuthStyle.swift (100%) rename Lock/{Utils => }/Colors.swift (100%) create mode 100644 Lock/ConnectionBuildable.swift create mode 100644 Lock/Connections.swift rename Lock/{Interactors => }/CredentialAttribute.swift (100%) rename Lock/{Components => }/CredentialView.swift (100%) rename Lock/{Interactors => }/DatabaseAuthenticable.swift (100%) rename Lock/{Presenters => }/DatabaseForgotPasswordPresenter.swift (100%) rename Lock/{Views => }/DatabaseForgotPasswordView.swift (100%) rename Lock/{Interactors => }/DatabaseInteractor.swift (100%) rename Lock/{Components => }/DatabaseModeSwitcher.swift (100%) rename Lock/{Components => }/DatabaseModes.swift (100%) rename Lock/{Views => }/DatabaseOnlyView.swift (100%) rename Lock/{Interactors => }/DatabasePasswordInteractor.swift (100%) rename Lock/{Presenters => }/DatabasePresenter.swift (100%) rename Lock/{Interactors => }/DatabaseUserCreator.swift (100%) rename Lock/{Views => }/DatabaseView.swift (100%) rename Lock/{Components => }/Form.swift (100%) rename Lock/{Components => }/HeaderView.swift (100%) rename Lock/{Components => }/InputField.swift (100%) rename Lock/{Utils => }/Layout.swift (100%) rename Lock/{Utils => }/LazyImage.swift (100%) create mode 100644 Lock/LockOptions.swift rename Lock/{Controllers => }/LockViewController.swift (100%) rename Lock/{Utils => }/Logger.swift (100%) rename Lock/{Presenters => }/MessagePresenter.swift (100%) rename Lock/{Components => }/MessageView.swift (100%) rename Lock/{Interactors => }/MultifactorAuthenticatable.swift (100%) rename Lock/{Views => }/MultifactorCodeView.swift (100%) rename Lock/{Interactors => }/MultifactorInteractor.swift (100%) rename Lock/{Presenters => }/MultifactorPresenter.swift (100%) rename Lock/{Interactors => }/OAuth2Authenticatable.swift (100%) create mode 100644 Lock/OfflineConnections.swift rename Lock/{Utils => }/Operations.swift (100%) create mode 100644 Lock/OptionBuildable.swift create mode 100644 Lock/Options.swift rename Lock/{Interactors => }/PasswordRecoverable.swift (100%) rename Lock/{Presenters => }/Presentable.swift (100%) rename Lock/{Components => }/PrimaryButton.swift (100%) rename Lock/{Utils => }/Queue.swift (100%) rename Lock/{Utils => }/Resources.swift (100%) rename Lock/{Router => }/Router.swift (100%) rename Lock/{Router => }/Routes.swift (100%) rename Lock/{Components => }/SecondaryButton.swift (100%) rename Lock/{Components => }/SignUpView.swift (100%) rename Lock/{Components => }/SingleInputView.swift (100%) rename Lock/{Models => }/User.swift (100%) rename Lock/{Utils => }/Validators.swift (100%) rename Lock/{Views => }/View.swift (100%) rename Lock/{Utils => }/i18n.swift (100%) diff --git a/Lock.xcodeproj/project.pbxproj b/Lock.xcodeproj/project.pbxproj index 4b4f435b4..d4d06f4b9 100644 --- a/Lock.xcodeproj/project.pbxproj +++ b/Lock.xcodeproj/project.pbxproj @@ -33,7 +33,6 @@ 5F50900E1D1DF40400EAA650 /* DatabaseOnlyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F50900D1D1DF40400EAA650 /* DatabaseOnlyView.swift */; }; 5F51EE681D1C88FC0024BCD6 /* SignUpView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F51EE671D1C88FC0024BCD6 /* SignUpView.swift */; }; 5F51EE6A1D1CBC830024BCD6 /* SingleInputView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F51EE691D1CBC830024BCD6 /* SingleInputView.swift */; }; - 5F51EE6E1D1CE6210024BCD6 /* LockViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F51EE6D1D1CE6210024BCD6 /* LockViewController.swift */; }; 5F57DFC61D4F79DD00C54DA8 /* AuthStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F57DFC51D4F79DD00C54DA8 /* AuthStyle.swift */; }; 5F57DFC71D4F79DD00C54DA8 /* AuthStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F57DFC51D4F79DD00C54DA8 /* AuthStyle.swift */; }; 5F57DFC91D4F87CE00C54DA8 /* AuthCollectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F57DFC81D4F87CE00C54DA8 /* AuthCollectionView.swift */; }; @@ -45,6 +44,13 @@ 5F5F98D81D22ED120016FC22 /* Presentable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F5F98D71D22ED120016FC22 /* Presentable.swift */; }; 5F5F98DA1D22EF490016FC22 /* Router.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F5F98D91D22EF490016FC22 /* Router.swift */; }; 5F5F98DC1D22F0B40016FC22 /* RouterSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F5F98DB1D22F0B40016FC22 /* RouterSpec.swift */; }; + 5F70F1DF1D7904A3004698DA /* ConnectionBuildable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F70F1DE1D7904A3004698DA /* ConnectionBuildable.swift */; }; + 5F70F1E11D790500004698DA /* Connections.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F70F1E01D790500004698DA /* Connections.swift */; }; + 5F70F1E31D79057A004698DA /* OfflineConnections.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F70F1E21D79057A004698DA /* OfflineConnections.swift */; }; + 5F70F1E51D790735004698DA /* Options.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F70F1E41D790735004698DA /* Options.swift */; }; + 5F70F1E71D790773004698DA /* OptionBuildable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F70F1E61D790773004698DA /* OptionBuildable.swift */; }; + 5F70F1E91D7907D5004698DA /* LockOptions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F70F1E81D7907D5004698DA /* LockOptions.swift */; }; + 5F70F1EE1D790BBE004698DA /* LockViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F70F1ED1D790BBE004698DA /* LockViewController.swift */; }; 5F73CDD01D30250900D8D8D1 /* MessagePresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F73CDCF1D30250900D8D8D1 /* MessagePresenter.swift */; }; 5F73CDD41D3073BE00D8D8D1 /* DatabaseForgotPasswordView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F73CDD31D3073BE00D8D8D1 /* DatabaseForgotPasswordView.swift */; }; 5F73CDD61D30790500D8D8D1 /* DatabaseForgotPasswordPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F73CDD51D30790500D8D8D1 /* DatabaseForgotPasswordPresenter.swift */; }; @@ -175,43 +181,49 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ - 5F1456591D5130E80085DF9C /* Colors.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Colors.swift; sourceTree = ""; }; - 5F14565D1D5237820085DF9C /* DatabaseView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DatabaseView.swift; sourceTree = ""; }; + 5F1456591D5130E80085DF9C /* Colors.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Colors.swift; path = Lock/Colors.swift; sourceTree = SOURCE_ROOT; }; + 5F14565D1D5237820085DF9C /* DatabaseView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = DatabaseView.swift; path = Lock/DatabaseView.swift; sourceTree = SOURCE_ROOT; }; 5F2037C11D5D02880005D2E2 /* Matchers.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Matchers.swift; sourceTree = ""; }; 5F2496AE1D66210500A1C6E2 /* LockViewControllerSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LockViewControllerSpec.swift; sourceTree = ""; }; - 5F2496B21D665A5600A1C6E2 /* DatabaseUserCreator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DatabaseUserCreator.swift; sourceTree = ""; }; - 5F2496B51D665AA800A1C6E2 /* InputValidationError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InputValidationError.swift; sourceTree = ""; }; - 5F2496B71D665AC500A1C6E2 /* CredentialAttribute.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CredentialAttribute.swift; sourceTree = ""; }; - 5F2496B91D665AE900A1C6E2 /* DatabaseAuthenticatableError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DatabaseAuthenticatableError.swift; sourceTree = ""; }; - 5F2496BB1D665AF700A1C6E2 /* DatabaseUserCreatorError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DatabaseUserCreatorError.swift; sourceTree = ""; }; + 5F2496B21D665A5600A1C6E2 /* DatabaseUserCreator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = DatabaseUserCreator.swift; path = Lock/DatabaseUserCreator.swift; sourceTree = SOURCE_ROOT; }; + 5F2496B51D665AA800A1C6E2 /* InputValidationError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = InputValidationError.swift; path = Lock/InputValidationError.swift; sourceTree = SOURCE_ROOT; }; + 5F2496B71D665AC500A1C6E2 /* CredentialAttribute.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = CredentialAttribute.swift; path = Lock/CredentialAttribute.swift; sourceTree = SOURCE_ROOT; }; + 5F2496B91D665AE900A1C6E2 /* DatabaseAuthenticatableError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = DatabaseAuthenticatableError.swift; path = Lock/DatabaseAuthenticatableError.swift; sourceTree = SOURCE_ROOT; }; + 5F2496BB1D665AF700A1C6E2 /* DatabaseUserCreatorError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = DatabaseUserCreatorError.swift; path = Lock/DatabaseUserCreatorError.swift; sourceTree = SOURCE_ROOT; }; 5F2496BD1D67ADB300A1C6E2 /* ValidatorSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ValidatorSpec.swift; sourceTree = ""; }; - 5F390E861D638A6D00FC549C /* Logger.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Logger.swift; sourceTree = ""; }; + 5F390E861D638A6D00FC549C /* Logger.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Logger.swift; path = Lock/Logger.swift; sourceTree = SOURCE_ROOT; }; 5F390E881D63A5B800FC549C /* CleanroomLogger.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CleanroomLogger.framework; path = Carthage/Build/iOS/CleanroomLogger.framework; sourceTree = ""; }; 5F390E891D63A5B800FC549C /* CleanroomASL.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CleanroomASL.framework; path = Carthage/Build/iOS/CleanroomASL.framework; sourceTree = ""; }; 5F390E8C1D63B99300FC549C /* LoggerSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoggerSpec.swift; sourceTree = ""; }; - 5F508FF61D1D868900EAA650 /* DatabaseInteractor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DatabaseInteractor.swift; sourceTree = ""; }; + 5F508FF61D1D868900EAA650 /* DatabaseInteractor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = DatabaseInteractor.swift; path = Lock/DatabaseInteractor.swift; sourceTree = SOURCE_ROOT; }; 5F508FF91D1DB1C200EAA650 /* DatabaseInteractorSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DatabaseInteractorSpec.swift; sourceTree = ""; }; 5F5090071D1DE7BA00EAA650 /* NetworkStub.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NetworkStub.swift; sourceTree = ""; }; 5F5090091D1DEE9A00EAA650 /* Constants.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = ""; }; - 5F50900D1D1DF40400EAA650 /* DatabaseOnlyView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DatabaseOnlyView.swift; sourceTree = ""; }; - 5F51EE671D1C88FC0024BCD6 /* SignUpView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SignUpView.swift; sourceTree = ""; }; - 5F51EE691D1CBC830024BCD6 /* SingleInputView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SingleInputView.swift; sourceTree = ""; }; - 5F51EE6D1D1CE6210024BCD6 /* LockViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LockViewController.swift; sourceTree = ""; }; - 5F57DFC51D4F79DD00C54DA8 /* AuthStyle.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AuthStyle.swift; sourceTree = ""; }; - 5F57DFC81D4F87CE00C54DA8 /* AuthCollectionView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AuthCollectionView.swift; sourceTree = ""; }; - 5F57DFCB1D4F93A000C54DA8 /* AuthPresenter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AuthPresenter.swift; sourceTree = ""; }; + 5F50900D1D1DF40400EAA650 /* DatabaseOnlyView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = DatabaseOnlyView.swift; path = Lock/DatabaseOnlyView.swift; sourceTree = SOURCE_ROOT; }; + 5F51EE671D1C88FC0024BCD6 /* SignUpView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = SignUpView.swift; path = Lock/SignUpView.swift; sourceTree = SOURCE_ROOT; }; + 5F51EE691D1CBC830024BCD6 /* SingleInputView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = SingleInputView.swift; path = Lock/SingleInputView.swift; sourceTree = SOURCE_ROOT; }; + 5F57DFC51D4F79DD00C54DA8 /* AuthStyle.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = AuthStyle.swift; path = Lock/AuthStyle.swift; sourceTree = SOURCE_ROOT; }; + 5F57DFC81D4F87CE00C54DA8 /* AuthCollectionView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = AuthCollectionView.swift; path = Lock/AuthCollectionView.swift; sourceTree = SOURCE_ROOT; }; + 5F57DFCB1D4F93A000C54DA8 /* AuthPresenter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = AuthPresenter.swift; path = Lock/AuthPresenter.swift; sourceTree = SOURCE_ROOT; }; 5F57DFCD1D4FBE5A00C54DA8 /* AuthPresenterSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AuthPresenterSpec.swift; sourceTree = ""; }; - 5F57DFD11D4FE59800C54DA8 /* OAuth2Authenticatable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OAuth2Authenticatable.swift; sourceTree = ""; }; - 5F57DFD31D4FE64700C54DA8 /* Auth0OAuth2Interactor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Auth0OAuth2Interactor.swift; sourceTree = ""; }; + 5F57DFD11D4FE59800C54DA8 /* OAuth2Authenticatable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = OAuth2Authenticatable.swift; path = Lock/OAuth2Authenticatable.swift; sourceTree = SOURCE_ROOT; }; + 5F57DFD31D4FE64700C54DA8 /* Auth0OAuth2Interactor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Auth0OAuth2Interactor.swift; path = Lock/Auth0OAuth2Interactor.swift; sourceTree = SOURCE_ROOT; }; 5F5F98D31D21E3890016FC22 /* DatabasePresenterSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DatabasePresenterSpec.swift; sourceTree = ""; }; - 5F5F98D71D22ED120016FC22 /* Presentable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Presentable.swift; sourceTree = ""; }; - 5F5F98D91D22EF490016FC22 /* Router.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Router.swift; sourceTree = ""; }; + 5F5F98D71D22ED120016FC22 /* Presentable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Presentable.swift; path = Lock/Presentable.swift; sourceTree = SOURCE_ROOT; }; + 5F5F98D91D22EF490016FC22 /* Router.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Router.swift; path = Lock/Router.swift; sourceTree = SOURCE_ROOT; }; 5F5F98DB1D22F0B40016FC22 /* RouterSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RouterSpec.swift; sourceTree = ""; }; - 5F73CDCF1D30250900D8D8D1 /* MessagePresenter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessagePresenter.swift; sourceTree = ""; }; - 5F73CDD31D3073BE00D8D8D1 /* DatabaseForgotPasswordView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DatabaseForgotPasswordView.swift; sourceTree = ""; }; - 5F73CDD51D30790500D8D8D1 /* DatabaseForgotPasswordPresenter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DatabaseForgotPasswordPresenter.swift; sourceTree = ""; }; - 5F73CDD71D3093BF00D8D8D1 /* PasswordRecoverable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PasswordRecoverable.swift; sourceTree = ""; }; - 5F73CDD91D30957900D8D8D1 /* DatabasePasswordInteractor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DatabasePasswordInteractor.swift; sourceTree = ""; }; + 5F70F1DE1D7904A3004698DA /* ConnectionBuildable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ConnectionBuildable.swift; path = Lock/ConnectionBuildable.swift; sourceTree = SOURCE_ROOT; }; + 5F70F1E01D790500004698DA /* Connections.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Connections.swift; path = Lock/Connections.swift; sourceTree = SOURCE_ROOT; }; + 5F70F1E21D79057A004698DA /* OfflineConnections.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = OfflineConnections.swift; path = Lock/OfflineConnections.swift; sourceTree = SOURCE_ROOT; }; + 5F70F1E41D790735004698DA /* Options.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Options.swift; path = Lock/Options.swift; sourceTree = SOURCE_ROOT; }; + 5F70F1E61D790773004698DA /* OptionBuildable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = OptionBuildable.swift; path = Lock/OptionBuildable.swift; sourceTree = SOURCE_ROOT; }; + 5F70F1E81D7907D5004698DA /* LockOptions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = LockOptions.swift; path = Lock/LockOptions.swift; sourceTree = SOURCE_ROOT; }; + 5F70F1ED1D790BBE004698DA /* LockViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = LockViewController.swift; path = Lock/LockViewController.swift; sourceTree = SOURCE_ROOT; }; + 5F73CDCF1D30250900D8D8D1 /* MessagePresenter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = MessagePresenter.swift; path = Lock/MessagePresenter.swift; sourceTree = SOURCE_ROOT; }; + 5F73CDD31D3073BE00D8D8D1 /* DatabaseForgotPasswordView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = DatabaseForgotPasswordView.swift; path = Lock/DatabaseForgotPasswordView.swift; sourceTree = SOURCE_ROOT; }; + 5F73CDD51D30790500D8D8D1 /* DatabaseForgotPasswordPresenter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = DatabaseForgotPasswordPresenter.swift; path = Lock/DatabaseForgotPasswordPresenter.swift; sourceTree = SOURCE_ROOT; }; + 5F73CDD71D3093BF00D8D8D1 /* PasswordRecoverable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = PasswordRecoverable.swift; path = Lock/PasswordRecoverable.swift; sourceTree = SOURCE_ROOT; }; + 5F73CDD91D30957900D8D8D1 /* DatabasePasswordInteractor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = DatabasePasswordInteractor.swift; path = Lock/DatabasePasswordInteractor.swift; sourceTree = SOURCE_ROOT; }; 5F73CDDB1D309BE900D8D8D1 /* DatabasePasswordInteractorSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DatabasePasswordInteractorSpec.swift; sourceTree = ""; }; 5F73CDDD1D30B16900D8D8D1 /* DatabaseForgotPasswordPresenterSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DatabaseForgotPasswordPresenterSpec.swift; sourceTree = ""; }; 5F814FD01D1A7294003670A4 /* Quick.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Quick.framework; path = Carthage/Build/iOS/Quick.framework; sourceTree = ""; }; @@ -221,40 +233,40 @@ 5F9231D41D5B6C5E00D92580 /* AuthCollectionViewSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AuthCollectionViewSpec.swift; sourceTree = ""; }; 5F92C68A1D4FE90F00CCE6C0 /* Auth0OAuth2InteractorSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Auth0OAuth2InteractorSpec.swift; sourceTree = ""; }; 5F92C68C1D50E47100CCE6C0 /* AuthStyleSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AuthStyleSpec.swift; sourceTree = ""; }; - 5F92C68E1D50EAC200CCE6C0 /* LazyImage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LazyImage.swift; sourceTree = ""; }; + 5F92C68E1D50EAC200CCE6C0 /* LazyImage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = LazyImage.swift; path = Lock/LazyImage.swift; sourceTree = SOURCE_ROOT; }; 5F92C6901D510AFE00CCE6C0 /* LazyImageSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LazyImageSpec.swift; sourceTree = ""; }; 5F99AA791D1B031500D27842 /* Lock.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Lock.xcassets; sourceTree = ""; }; - 5F99AA811D1B0A3900D27842 /* i18n.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = i18n.swift; sourceTree = ""; }; + 5F99AA811D1B0A3900D27842 /* i18n.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = i18n.swift; path = Lock/i18n.swift; sourceTree = SOURCE_ROOT; }; 5F99AA831D1B0BCE00D27842 /* Lock.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Lock.swift; sourceTree = ""; }; - 5F99AA851D1B0BF100D27842 /* Resources.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Resources.swift; sourceTree = ""; }; - 5F99AA871D1B0E2500D27842 /* Layout.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Layout.swift; sourceTree = ""; }; - 5F99AA891D1B360C00D27842 /* PrimaryButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PrimaryButton.swift; sourceTree = ""; }; - 5F99AA8B1D1B3F1300D27842 /* InputField.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InputField.swift; sourceTree = ""; }; - 5F99AA8F1D1B9E7100D27842 /* DatabaseModeSwitcher.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DatabaseModeSwitcher.swift; sourceTree = ""; }; - 5F99AA911D1BA5CA00D27842 /* DatabaseModes.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DatabaseModes.swift; sourceTree = ""; }; - 5F99AA931D1BABFC00D27842 /* SecondaryButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SecondaryButton.swift; sourceTree = ""; }; - 5F99AA951D1C4AF400D27842 /* CredentialView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CredentialView.swift; sourceTree = ""; }; + 5F99AA851D1B0BF100D27842 /* Resources.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Resources.swift; path = Lock/Resources.swift; sourceTree = SOURCE_ROOT; }; + 5F99AA871D1B0E2500D27842 /* Layout.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Layout.swift; path = Lock/Layout.swift; sourceTree = SOURCE_ROOT; }; + 5F99AA891D1B360C00D27842 /* PrimaryButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = PrimaryButton.swift; path = Lock/PrimaryButton.swift; sourceTree = SOURCE_ROOT; }; + 5F99AA8B1D1B3F1300D27842 /* InputField.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = InputField.swift; path = Lock/InputField.swift; sourceTree = SOURCE_ROOT; }; + 5F99AA8F1D1B9E7100D27842 /* DatabaseModeSwitcher.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = DatabaseModeSwitcher.swift; path = Lock/DatabaseModeSwitcher.swift; sourceTree = SOURCE_ROOT; }; + 5F99AA911D1BA5CA00D27842 /* DatabaseModes.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = DatabaseModes.swift; path = Lock/DatabaseModes.swift; sourceTree = SOURCE_ROOT; }; + 5F99AA931D1BABFC00D27842 /* SecondaryButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = SecondaryButton.swift; path = Lock/SecondaryButton.swift; sourceTree = SOURCE_ROOT; }; + 5F99AA951D1C4AF400D27842 /* CredentialView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = CredentialView.swift; path = Lock/CredentialView.swift; sourceTree = SOURCE_ROOT; }; 5FA2504F1D48E2A200C544FA /* OptionsSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OptionsSpec.swift; sourceTree = ""; }; 5FA250511D48F08200C544FA /* LockSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LockSpec.swift; sourceTree = ""; }; 5FBE5CB31D3D52EE0038536D /* Auth0.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Auth0.plist; sourceTree = SOURCE_ROOT; }; - 5FBE5CB71D3D8F030038536D /* User.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = User.swift; sourceTree = ""; }; - 5FBE5CB91D3E59B90038536D /* MultifactorCodeView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MultifactorCodeView.swift; sourceTree = ""; }; - 5FBE5CBD1D3E5C7B0038536D /* MultifactorAuthenticatable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MultifactorAuthenticatable.swift; sourceTree = ""; }; - 5FBE5CBF1D3E5E0A0038536D /* MultifactorInteractor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MultifactorInteractor.swift; sourceTree = ""; }; + 5FBE5CB71D3D8F030038536D /* User.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = User.swift; path = Lock/User.swift; sourceTree = SOURCE_ROOT; }; + 5FBE5CB91D3E59B90038536D /* MultifactorCodeView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = MultifactorCodeView.swift; path = Lock/MultifactorCodeView.swift; sourceTree = SOURCE_ROOT; }; + 5FBE5CBD1D3E5C7B0038536D /* MultifactorAuthenticatable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = MultifactorAuthenticatable.swift; path = Lock/MultifactorAuthenticatable.swift; sourceTree = SOURCE_ROOT; }; + 5FBE5CBF1D3E5E0A0038536D /* MultifactorInteractor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = MultifactorInteractor.swift; path = Lock/MultifactorInteractor.swift; sourceTree = SOURCE_ROOT; }; 5FBE5CC11D3E5EF50038536D /* MultifactorInteractorSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MultifactorInteractorSpec.swift; sourceTree = ""; }; - 5FBE5CC31D3E67320038536D /* Validators.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Validators.swift; sourceTree = ""; }; - 5FBE5CC51D3E7F9D0038536D /* MultifactorPresenter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MultifactorPresenter.swift; sourceTree = ""; }; + 5FBE5CC31D3E67320038536D /* Validators.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Validators.swift; path = Lock/Validators.swift; sourceTree = SOURCE_ROOT; }; + 5FBE5CC51D3E7F9D0038536D /* MultifactorPresenter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = MultifactorPresenter.swift; path = Lock/MultifactorPresenter.swift; sourceTree = SOURCE_ROOT; }; 5FBE5CC71D3EA0EA0038536D /* Mocks.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Mocks.swift; sourceTree = ""; }; 5FBE5CC91D3EA1380038536D /* MultifactorPresenterSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MultifactorPresenterSpec.swift; sourceTree = ""; }; 5FBE5CCC1D3EDF960038536D /* UserSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserSpec.swift; sourceTree = ""; }; - 5FC434851D1DF769005188BC /* View.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = View.swift; sourceTree = ""; }; - 5FC434891D1DF82A005188BC /* DatabasePresenter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DatabasePresenter.swift; sourceTree = ""; }; - 5FC4348B1D1DFC5A005188BC /* Form.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Form.swift; sourceTree = ""; }; - 5FC4348E1D1E0A57005188BC /* DatabaseAuthenticable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DatabaseAuthenticable.swift; sourceTree = ""; }; - 5FD6772B1D4C303C004B87C4 /* AuthButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AuthButton.swift; sourceTree = ""; }; - 5FDB41CD1D2C79FD00166B67 /* Operations.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Operations.swift; sourceTree = ""; }; - 5FDB41CF1D2C95B100166B67 /* MessageView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageView.swift; sourceTree = ""; }; - 5FDC876C1D46DAF200D28596 /* Queue.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Queue.swift; sourceTree = ""; }; + 5FC434851D1DF769005188BC /* View.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = View.swift; path = Lock/View.swift; sourceTree = SOURCE_ROOT; }; + 5FC434891D1DF82A005188BC /* DatabasePresenter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = DatabasePresenter.swift; path = Lock/DatabasePresenter.swift; sourceTree = SOURCE_ROOT; }; + 5FC4348B1D1DFC5A005188BC /* Form.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Form.swift; path = Lock/Form.swift; sourceTree = SOURCE_ROOT; }; + 5FC4348E1D1E0A57005188BC /* DatabaseAuthenticable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = DatabaseAuthenticable.swift; path = Lock/DatabaseAuthenticable.swift; sourceTree = SOURCE_ROOT; }; + 5FD6772B1D4C303C004B87C4 /* AuthButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = AuthButton.swift; path = Lock/AuthButton.swift; sourceTree = SOURCE_ROOT; }; + 5FDB41CD1D2C79FD00166B67 /* Operations.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Operations.swift; path = Lock/Operations.swift; sourceTree = SOURCE_ROOT; }; + 5FDB41CF1D2C95B100166B67 /* MessageView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = MessageView.swift; path = Lock/MessageView.swift; sourceTree = SOURCE_ROOT; }; + 5FDC876C1D46DAF200D28596 /* Queue.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Queue.swift; path = Lock/Queue.swift; sourceTree = SOURCE_ROOT; }; 5FEADCF41D1A7EBC0032D810 /* LockApp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = LockApp.app; sourceTree = BUILT_PRODUCTS_DIR; }; 5FEADCF61D1A7EBC0032D810 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 5FEADCF81D1A7EBC0032D810 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; @@ -267,10 +279,10 @@ 5FEAE1CB1D1A5154005C0028 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 5FEAE1D01D1A5154005C0028 /* LockTests.iOS.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = LockTests.iOS.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 5FEAE1D71D1A5154005C0028 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - 5FEAE20F1D1A5691005C0028 /* HeaderView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HeaderView.swift; sourceTree = ""; }; + 5FEAE20F1D1A5691005C0028 /* HeaderView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = HeaderView.swift; path = Lock/HeaderView.swift; sourceTree = SOURCE_ROOT; }; 5FF1FA321D4C12DF00158CD1 /* LockUI.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = LockUI.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 5FF1FA341D4C12DF00158CD1 /* LockUI.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = LockUI.h; path = Lock/LockUI.h; sourceTree = SOURCE_ROOT; }; - 5FFC54FD1D37E3F700579581 /* Routes.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Routes.swift; sourceTree = ""; }; + 5FFC54FD1D37E3F700579581 /* Routes.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Routes.swift; path = Lock/Routes.swift; sourceTree = SOURCE_ROOT; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -379,7 +391,7 @@ 5F51EE6B1D1CE6110024BCD6 /* Controllers */ = { isa = PBXGroup; children = ( - 5F51EE6D1D1CE6210024BCD6 /* LockViewController.swift */, + 5F70F1ED1D790BBE004698DA /* LockViewController.swift */, ); path = Controllers; sourceTree = ""; @@ -395,6 +407,37 @@ path = Presenters; sourceTree = ""; }; + 5F70F1EA1D7909C4004698DA /* Connections */ = { + isa = PBXGroup; + children = ( + 5F70F1DE1D7904A3004698DA /* ConnectionBuildable.swift */, + 5F70F1E01D790500004698DA /* Connections.swift */, + 5F70F1E21D79057A004698DA /* OfflineConnections.swift */, + ); + name = Connections; + sourceTree = ""; + }; + 5F70F1EB1D7909DC004698DA /* Options */ = { + isa = PBXGroup; + children = ( + 5F70F1E41D790735004698DA /* Options.swift */, + 5F70F1E61D790773004698DA /* OptionBuildable.swift */, + 5F70F1E81D7907D5004698DA /* LockOptions.swift */, + ); + name = Options; + sourceTree = ""; + }; + 5F70F1EC1D790A6D004698DA /* Supporting Files */ = { + isa = PBXGroup; + children = ( + 5FEAE1CB1D1A5154005C0028 /* Info.plist */, + 5FEAE1C91D1A5154005C0028 /* Lock.h */, + 5FF1FA341D4C12DF00158CD1 /* LockUI.h */, + 5F99AA791D1B031500D27842 /* Lock.xcassets */, + ); + name = "Supporting Files"; + sourceTree = ""; + }; 5F9231D31D5B6C2800D92580 /* Views */ = { isa = PBXGroup; children = ( @@ -424,6 +467,8 @@ children = ( 5FBE5CB71D3D8F030038536D /* User.swift */, 5F57DFC51D4F79DD00C54DA8 /* AuthStyle.swift */, + 5F70F1EA1D7909C4004698DA /* Connections */, + 5F70F1EB1D7909DC004698DA /* Options */, ); path = Models; sourceTree = ""; @@ -467,10 +512,10 @@ 5FEAE1BC1D1A5154005C0028 = { isa = PBXGroup; children = ( - 5FEADCF51D1A7EBC0032D810 /* App */, - 5FEAE2111D1A5716005C0028 /* Frameworks */, 5FEAE1C81D1A5154005C0028 /* Lock */, 5FEAE1D41D1A5154005C0028 /* LockTests */, + 5FEADCF51D1A7EBC0032D810 /* App */, + 5FEAE2111D1A5716005C0028 /* Frameworks */, 5FEAE1C71D1A5154005C0028 /* Products */, ); sourceTree = ""; @@ -498,11 +543,8 @@ 5FFC54FC1D37E3E400579581 /* Router */, 5F99AA801D1B0A2B00D27842 /* Utils */, 5F50900C1D1DF3ED00EAA650 /* Views */, - 5FEAE1CB1D1A5154005C0028 /* Info.plist */, - 5FEAE1C91D1A5154005C0028 /* Lock.h */, - 5FF1FA341D4C12DF00158CD1 /* LockUI.h */, 5F99AA831D1B0BCE00D27842 /* Lock.swift */, - 5F99AA791D1B031500D27842 /* Lock.xcassets */, + 5F70F1EC1D790A6D004698DA /* Supporting Files */, ); path = Lock; sourceTree = ""; @@ -813,28 +855,32 @@ 5F99AA941D1BABFC00D27842 /* SecondaryButton.swift in Sources */, 5F73CDD61D30790500D8D8D1 /* DatabaseForgotPasswordPresenter.swift in Sources */, 5F99AA901D1B9E7100D27842 /* DatabaseModeSwitcher.swift in Sources */, + 5F70F1E31D79057A004698DA /* OfflineConnections.swift in Sources */, 5F2496B31D665A5600A1C6E2 /* DatabaseUserCreator.swift in Sources */, + 5F70F1E71D790773004698DA /* OptionBuildable.swift in Sources */, 5FBE5CB81D3D8F030038536D /* User.swift in Sources */, 5F99AA861D1B0BF100D27842 /* Resources.swift in Sources */, 5F2496B81D665AC500A1C6E2 /* CredentialAttribute.swift in Sources */, + 5F70F1DF1D7904A3004698DA /* ConnectionBuildable.swift in Sources */, 5FC4348F1D1E0A57005188BC /* DatabaseAuthenticable.swift in Sources */, 5FD6772C1D4C303C004B87C4 /* AuthButton.swift in Sources */, 5F99AA8A1D1B360C00D27842 /* PrimaryButton.swift in Sources */, 5FBE5CC41D3E67320038536D /* Validators.swift in Sources */, 5FC4348C1D1DFC5A005188BC /* Form.swift in Sources */, 5F73CDD01D30250900D8D8D1 /* MessagePresenter.swift in Sources */, + 5F70F1E51D790735004698DA /* Options.swift in Sources */, 5FDB41D01D2C95B100166B67 /* MessageView.swift in Sources */, 5F2496B61D665AA800A1C6E2 /* InputValidationError.swift in Sources */, 5FFC54FE1D37E3F700579581 /* Routes.swift in Sources */, 5FC434861D1DF769005188BC /* View.swift in Sources */, 5FDB41CE1D2C79FD00166B67 /* Operations.swift in Sources */, 5F51EE681D1C88FC0024BCD6 /* SignUpView.swift in Sources */, - 5F51EE6E1D1CE6210024BCD6 /* LockViewController.swift in Sources */, 5F57DFD41D4FE64700C54DA8 /* Auth0OAuth2Interactor.swift in Sources */, 5F57DFD21D4FE59800C54DA8 /* OAuth2Authenticatable.swift in Sources */, 5F508FF71D1D868900EAA650 /* DatabaseInteractor.swift in Sources */, 5F99AA921D1BA5CA00D27842 /* DatabaseModes.swift in Sources */, 5FDC876D1D46DAF200D28596 /* Queue.swift in Sources */, + 5F70F1E11D790500004698DA /* Connections.swift in Sources */, 5F92C68F1D50EAC200CCE6C0 /* LazyImage.swift in Sources */, 5F2496BA1D665AE900A1C6E2 /* DatabaseAuthenticatableError.swift in Sources */, 5F57DFC61D4F79DD00C54DA8 /* AuthStyle.swift in Sources */, @@ -851,8 +897,10 @@ 5FBE5CBE1D3E5C7B0038536D /* MultifactorAuthenticatable.swift in Sources */, 5FBE5CC01D3E5E0A0038536D /* MultifactorInteractor.swift in Sources */, 5F50900E1D1DF40400EAA650 /* DatabaseOnlyView.swift in Sources */, + 5F70F1E91D7907D5004698DA /* LockOptions.swift in Sources */, 5FC4348A1D1DF82A005188BC /* DatabasePresenter.swift in Sources */, 5F14565E1D5237820085DF9C /* DatabaseView.swift in Sources */, + 5F70F1EE1D790BBE004698DA /* LockViewController.swift in Sources */, 5F99AA821D1B0A3900D27842 /* i18n.swift in Sources */, 5F73CDD41D3073BE00D8D8D1 /* DatabaseForgotPasswordView.swift in Sources */, 5F99AA961D1C4AF400D27842 /* CredentialView.swift in Sources */, diff --git a/Lock/Interactors/Auth0OAuth2Interactor.swift b/Lock/Auth0OAuth2Interactor.swift similarity index 100% rename from Lock/Interactors/Auth0OAuth2Interactor.swift rename to Lock/Auth0OAuth2Interactor.swift diff --git a/Lock/Components/AuthButton.swift b/Lock/AuthButton.swift similarity index 100% rename from Lock/Components/AuthButton.swift rename to Lock/AuthButton.swift diff --git a/Lock/Views/AuthCollectionView.swift b/Lock/AuthCollectionView.swift similarity index 100% rename from Lock/Views/AuthCollectionView.swift rename to Lock/AuthCollectionView.swift diff --git a/Lock/Presenters/AuthPresenter.swift b/Lock/AuthPresenter.swift similarity index 100% rename from Lock/Presenters/AuthPresenter.swift rename to Lock/AuthPresenter.swift diff --git a/Lock/Models/AuthStyle.swift b/Lock/AuthStyle.swift similarity index 100% rename from Lock/Models/AuthStyle.swift rename to Lock/AuthStyle.swift diff --git a/Lock/Utils/Colors.swift b/Lock/Colors.swift similarity index 100% rename from Lock/Utils/Colors.swift rename to Lock/Colors.swift diff --git a/Lock/ConnectionBuildable.swift b/Lock/ConnectionBuildable.swift new file mode 100644 index 000000000..6fc085650 --- /dev/null +++ b/Lock/ConnectionBuildable.swift @@ -0,0 +1,48 @@ +// ConnectionBuildable.swift +// +// Copyright (c) 2016 Auth0 (http://auth0.com) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import Foundation + + +/** + * Allows to specify Lock connections + */ +public protocol ConnectionBuildable: Connections { + + /** + Configure a database connection + + - parameter name: name of the database connection + - parameter requiresUsername: if the database connection requires username + - important: Only **ONE** database connection can be used so subsequent calls will override the previous value + */ + mutating func database(name name: String, requiresUsername: Bool) + + /** + Adds a new social connection + + - parameter name: name of the connection + - parameter style: style used for the button used to trigger authentication + - seeAlso: AuthStyle + */ + mutating func social(name name: String, style: AuthStyle) +} diff --git a/Lock/Connections.swift b/Lock/Connections.swift new file mode 100644 index 000000000..38c1d682b --- /dev/null +++ b/Lock/Connections.swift @@ -0,0 +1,43 @@ +// Connections.swift +// +// Copyright (c) 2016 Auth0 (http://auth0.com) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import Foundation + +public protocol Connections { + var database: DatabaseConnection? { get } + var oauth2: [OAuth2Connection] { get } +} + +public struct DatabaseConnection { + public let name: String + public let requiresUsername: Bool +} + +public protocol OAuth2Connection { + var name: String { get } + var style: AuthStyle { get } +} + +public struct SocialConnection: OAuth2Connection { + public let name: String + public let style: AuthStyle +} diff --git a/Lock/Interactors/CredentialAttribute.swift b/Lock/CredentialAttribute.swift similarity index 100% rename from Lock/Interactors/CredentialAttribute.swift rename to Lock/CredentialAttribute.swift diff --git a/Lock/Components/CredentialView.swift b/Lock/CredentialView.swift similarity index 100% rename from Lock/Components/CredentialView.swift rename to Lock/CredentialView.swift diff --git a/Lock/Interactors/DatabaseAuthenticable.swift b/Lock/DatabaseAuthenticable.swift similarity index 100% rename from Lock/Interactors/DatabaseAuthenticable.swift rename to Lock/DatabaseAuthenticable.swift diff --git a/Lock/Presenters/DatabaseForgotPasswordPresenter.swift b/Lock/DatabaseForgotPasswordPresenter.swift similarity index 100% rename from Lock/Presenters/DatabaseForgotPasswordPresenter.swift rename to Lock/DatabaseForgotPasswordPresenter.swift diff --git a/Lock/Views/DatabaseForgotPasswordView.swift b/Lock/DatabaseForgotPasswordView.swift similarity index 100% rename from Lock/Views/DatabaseForgotPasswordView.swift rename to Lock/DatabaseForgotPasswordView.swift diff --git a/Lock/Interactors/DatabaseInteractor.swift b/Lock/DatabaseInteractor.swift similarity index 100% rename from Lock/Interactors/DatabaseInteractor.swift rename to Lock/DatabaseInteractor.swift diff --git a/Lock/Components/DatabaseModeSwitcher.swift b/Lock/DatabaseModeSwitcher.swift similarity index 100% rename from Lock/Components/DatabaseModeSwitcher.swift rename to Lock/DatabaseModeSwitcher.swift diff --git a/Lock/Components/DatabaseModes.swift b/Lock/DatabaseModes.swift similarity index 100% rename from Lock/Components/DatabaseModes.swift rename to Lock/DatabaseModes.swift diff --git a/Lock/Views/DatabaseOnlyView.swift b/Lock/DatabaseOnlyView.swift similarity index 100% rename from Lock/Views/DatabaseOnlyView.swift rename to Lock/DatabaseOnlyView.swift diff --git a/Lock/Interactors/DatabasePasswordInteractor.swift b/Lock/DatabasePasswordInteractor.swift similarity index 100% rename from Lock/Interactors/DatabasePasswordInteractor.swift rename to Lock/DatabasePasswordInteractor.swift diff --git a/Lock/Presenters/DatabasePresenter.swift b/Lock/DatabasePresenter.swift similarity index 100% rename from Lock/Presenters/DatabasePresenter.swift rename to Lock/DatabasePresenter.swift diff --git a/Lock/Interactors/DatabaseUserCreator.swift b/Lock/DatabaseUserCreator.swift similarity index 100% rename from Lock/Interactors/DatabaseUserCreator.swift rename to Lock/DatabaseUserCreator.swift diff --git a/Lock/Views/DatabaseView.swift b/Lock/DatabaseView.swift similarity index 100% rename from Lock/Views/DatabaseView.swift rename to Lock/DatabaseView.swift diff --git a/Lock/Components/Form.swift b/Lock/Form.swift similarity index 100% rename from Lock/Components/Form.swift rename to Lock/Form.swift diff --git a/Lock/Components/HeaderView.swift b/Lock/HeaderView.swift similarity index 100% rename from Lock/Components/HeaderView.swift rename to Lock/HeaderView.swift diff --git a/Lock/Components/InputField.swift b/Lock/InputField.swift similarity index 100% rename from Lock/Components/InputField.swift rename to Lock/InputField.swift diff --git a/Lock/Utils/Layout.swift b/Lock/Layout.swift similarity index 100% rename from Lock/Utils/Layout.swift rename to Lock/Layout.swift diff --git a/Lock/Utils/LazyImage.swift b/Lock/LazyImage.swift similarity index 100% rename from Lock/Utils/LazyImage.swift rename to Lock/LazyImage.swift diff --git a/Lock/Lock.swift b/Lock/Lock.swift index 6e4528215..a6813fdeb 100644 --- a/Lock/Lock.swift +++ b/Lock/Lock.swift @@ -165,6 +165,12 @@ public class Lock: NSObject { } } +public enum Result { + case Success(Credentials) + case Failure(ErrorType) + case Cancelled +} + private func telemetryFor(authenticaction authentication: Authentication, webAuth: WebAuth) -> (Authentication, WebAuth) { var authentication = authentication var webAuth = webAuth @@ -176,140 +182,4 @@ private func telemetryFor(authenticaction authentication: Authentication, webAut authentication.using(inLibrary: name, version: version) webAuth.using(inLibrary: name, version: version) return (authentication, webAuth) -} - -public protocol Connections { - var database: DatabaseConnection? { get } - var oauth2: [OAuth2Connection] { get } -} - -/** - * Allows to specify Lock connections - */ -public protocol ConnectionBuildable: Connections { - - /** - Configure a database connection - - - parameter name: name of the database connection - - parameter requiresUsername: if the database connection requires username - - important: Only **ONE** database connection can be used so subsequent calls will override the previous value - */ - mutating func database(name name: String, requiresUsername: Bool) - - /** - Adds a new social connection - - - parameter name: name of the connection - - parameter style: style used for the button used to trigger authentication - - seeAlso: AuthStyle - */ - mutating func social(name name: String, style: AuthStyle) -} - -struct OfflineConnections: ConnectionBuildable { - - var database: DatabaseConnection? = nil - var oauth2: [OAuth2Connection] = [] - - mutating func database(name name: String, requiresUsername: Bool) { - self.database = DatabaseConnection(name: name, requiresUsername: requiresUsername) - } - - mutating func social(name name: String, style: AuthStyle) { - let social = SocialConnection(name: name, style: style) - self.oauth2.append(social) - } -} - -public protocol Options { - var closable: Bool { get } - var termsOfServiceURL: NSURL { get } - var privacyPolicyURL: NSURL { get } - var logLevel: LoggerLevel { get } - var loggerOutput: LoggerOutput? { get } - var logHttpRequest: Bool { get } -} - -/** - * Lock options - */ -public protocol OptionBuildable: Options { - - /// Allows Lock to be dismissed. By default is false - var closable: Bool { get set } - - /// ToS URL. By default is Auth0's - var termsOfServiceURL: NSURL { get set } - - /// Privacy Policy URL. By default is Auth0's - var privacyPolicyURL: NSURL { get set } - - /// Log level for Lock. By default is `Off` - var logLevel: LoggerLevel { get set } - - /// Log output used when Log is enabled. By default a simple `print` statement is used. - var loggerOutput: LoggerOutput? { get set } - - /// If request from Auth0.swift should be logged or not - var logHttpRequest: Bool { get set } -} - -extension OptionBuildable { - - /// ToS URL. By default is Auth0's - var termsOfService: String { - get { - return self.termsOfServiceURL.absoluteString - } - set { - guard let url = NSURL(string: newValue) else { return } // FIXME: log error - self.termsOfServiceURL = url - } - } - - /// Privacy Policy URL. By default is Auth0's - var privacyPolicy: String { - get { - return self.privacyPolicyURL.absoluteString - } - set { - guard let url = NSURL(string: newValue) else { return } // FIXME: log error - self.privacyPolicyURL = url - } - } -} - -struct LockOptions: OptionBuildable { - var closable: Bool = false - var termsOfServiceURL: NSURL = NSURL(string: "https://auth0.com/terms")! - var privacyPolicyURL: NSURL = NSURL(string: "https://auth0.com/privacy")! - var logLevel: LoggerLevel = .Off - var loggerOutput: LoggerOutput? = nil - var logHttpRequest: Bool = false { - didSet { - Auth0.enableLogging(enabled: self.logHttpRequest) - } - } -} - -public struct DatabaseConnection { - public let name: String - public let requiresUsername: Bool -} - -public protocol OAuth2Connection { - var name: String { get } - var style: AuthStyle { get } -} - -public struct SocialConnection: OAuth2Connection { - public let name: String - public let style: AuthStyle -} - -public enum Result { - case Success(Credentials) - case Failure(ErrorType) - case Cancelled -} +} \ No newline at end of file diff --git a/Lock/LockOptions.swift b/Lock/LockOptions.swift new file mode 100644 index 000000000..9867fcd06 --- /dev/null +++ b/Lock/LockOptions.swift @@ -0,0 +1,37 @@ +// LockOptions.swift +// +// Copyright (c) 2016 Auth0 (http://auth0.com) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import Foundation +import Auth0 + +struct LockOptions: OptionBuildable { + var closable: Bool = false + var termsOfServiceURL: NSURL = NSURL(string: "https://auth0.com/terms")! + var privacyPolicyURL: NSURL = NSURL(string: "https://auth0.com/privacy")! + var logLevel: LoggerLevel = .Off + var loggerOutput: LoggerOutput? = nil + var logHttpRequest: Bool = false { + didSet { + Auth0.enableLogging(enabled: self.logHttpRequest) + } + } +} diff --git a/Lock/Controllers/LockViewController.swift b/Lock/LockViewController.swift similarity index 100% rename from Lock/Controllers/LockViewController.swift rename to Lock/LockViewController.swift diff --git a/Lock/Utils/Logger.swift b/Lock/Logger.swift similarity index 100% rename from Lock/Utils/Logger.swift rename to Lock/Logger.swift diff --git a/Lock/Presenters/MessagePresenter.swift b/Lock/MessagePresenter.swift similarity index 100% rename from Lock/Presenters/MessagePresenter.swift rename to Lock/MessagePresenter.swift diff --git a/Lock/Components/MessageView.swift b/Lock/MessageView.swift similarity index 100% rename from Lock/Components/MessageView.swift rename to Lock/MessageView.swift diff --git a/Lock/Interactors/MultifactorAuthenticatable.swift b/Lock/MultifactorAuthenticatable.swift similarity index 100% rename from Lock/Interactors/MultifactorAuthenticatable.swift rename to Lock/MultifactorAuthenticatable.swift diff --git a/Lock/Views/MultifactorCodeView.swift b/Lock/MultifactorCodeView.swift similarity index 100% rename from Lock/Views/MultifactorCodeView.swift rename to Lock/MultifactorCodeView.swift diff --git a/Lock/Interactors/MultifactorInteractor.swift b/Lock/MultifactorInteractor.swift similarity index 100% rename from Lock/Interactors/MultifactorInteractor.swift rename to Lock/MultifactorInteractor.swift diff --git a/Lock/Presenters/MultifactorPresenter.swift b/Lock/MultifactorPresenter.swift similarity index 100% rename from Lock/Presenters/MultifactorPresenter.swift rename to Lock/MultifactorPresenter.swift diff --git a/Lock/Interactors/OAuth2Authenticatable.swift b/Lock/OAuth2Authenticatable.swift similarity index 100% rename from Lock/Interactors/OAuth2Authenticatable.swift rename to Lock/OAuth2Authenticatable.swift diff --git a/Lock/OfflineConnections.swift b/Lock/OfflineConnections.swift new file mode 100644 index 000000000..ea2586997 --- /dev/null +++ b/Lock/OfflineConnections.swift @@ -0,0 +1,38 @@ +// OfflineConnections.swift +// +// Copyright (c) 2016 Auth0 (http://auth0.com) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import Foundation + +struct OfflineConnections: ConnectionBuildable { + + var database: DatabaseConnection? = nil + var oauth2: [OAuth2Connection] = [] + + mutating func database(name name: String, requiresUsername: Bool) { + self.database = DatabaseConnection(name: name, requiresUsername: requiresUsername) + } + + mutating func social(name name: String, style: AuthStyle) { + let social = SocialConnection(name: name, style: style) + self.oauth2.append(social) + } +} \ No newline at end of file diff --git a/Lock/Utils/Operations.swift b/Lock/Operations.swift similarity index 100% rename from Lock/Utils/Operations.swift rename to Lock/Operations.swift diff --git a/Lock/OptionBuildable.swift b/Lock/OptionBuildable.swift new file mode 100644 index 000000000..a623aaebd --- /dev/null +++ b/Lock/OptionBuildable.swift @@ -0,0 +1,72 @@ +// OptionBuildable.swift +// +// Copyright (c) 2016 Auth0 (http://auth0.com) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import Foundation + +/** + * Lock options + */ +public protocol OptionBuildable: Options { + + /// Allows Lock to be dismissed. By default is false + var closable: Bool { get set } + + /// ToS URL. By default is Auth0's + var termsOfServiceURL: NSURL { get set } + + /// Privacy Policy URL. By default is Auth0's + var privacyPolicyURL: NSURL { get set } + + /// Log level for Lock. By default is `Off` + var logLevel: LoggerLevel { get set } + + /// Log output used when Log is enabled. By default a simple `print` statement is used. + var loggerOutput: LoggerOutput? { get set } + + /// If request from Auth0.swift should be logged or not + var logHttpRequest: Bool { get set } +} + +extension OptionBuildable { + + /// ToS URL. By default is Auth0's + var termsOfService: String { + get { + return self.termsOfServiceURL.absoluteString + } + set { + guard let url = NSURL(string: newValue) else { return } // FIXME: log error + self.termsOfServiceURL = url + } + } + + /// Privacy Policy URL. By default is Auth0's + var privacyPolicy: String { + get { + return self.privacyPolicyURL.absoluteString + } + set { + guard let url = NSURL(string: newValue) else { return } // FIXME: log error + self.privacyPolicyURL = url + } + } +} \ No newline at end of file diff --git a/Lock/Options.swift b/Lock/Options.swift new file mode 100644 index 000000000..37012882e --- /dev/null +++ b/Lock/Options.swift @@ -0,0 +1,32 @@ +// Options.swift +// +// Copyright (c) 2016 Auth0 (http://auth0.com) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import Foundation + +public protocol Options { + var closable: Bool { get } + var termsOfServiceURL: NSURL { get } + var privacyPolicyURL: NSURL { get } + var logLevel: LoggerLevel { get } + var loggerOutput: LoggerOutput? { get } + var logHttpRequest: Bool { get } +} diff --git a/Lock/Interactors/PasswordRecoverable.swift b/Lock/PasswordRecoverable.swift similarity index 100% rename from Lock/Interactors/PasswordRecoverable.swift rename to Lock/PasswordRecoverable.swift diff --git a/Lock/Presenters/Presentable.swift b/Lock/Presentable.swift similarity index 100% rename from Lock/Presenters/Presentable.swift rename to Lock/Presentable.swift diff --git a/Lock/Components/PrimaryButton.swift b/Lock/PrimaryButton.swift similarity index 100% rename from Lock/Components/PrimaryButton.swift rename to Lock/PrimaryButton.swift diff --git a/Lock/Utils/Queue.swift b/Lock/Queue.swift similarity index 100% rename from Lock/Utils/Queue.swift rename to Lock/Queue.swift diff --git a/Lock/Utils/Resources.swift b/Lock/Resources.swift similarity index 100% rename from Lock/Utils/Resources.swift rename to Lock/Resources.swift diff --git a/Lock/Router/Router.swift b/Lock/Router.swift similarity index 100% rename from Lock/Router/Router.swift rename to Lock/Router.swift diff --git a/Lock/Router/Routes.swift b/Lock/Routes.swift similarity index 100% rename from Lock/Router/Routes.swift rename to Lock/Routes.swift diff --git a/Lock/Components/SecondaryButton.swift b/Lock/SecondaryButton.swift similarity index 100% rename from Lock/Components/SecondaryButton.swift rename to Lock/SecondaryButton.swift diff --git a/Lock/Components/SignUpView.swift b/Lock/SignUpView.swift similarity index 100% rename from Lock/Components/SignUpView.swift rename to Lock/SignUpView.swift diff --git a/Lock/Components/SingleInputView.swift b/Lock/SingleInputView.swift similarity index 100% rename from Lock/Components/SingleInputView.swift rename to Lock/SingleInputView.swift diff --git a/Lock/Models/User.swift b/Lock/User.swift similarity index 100% rename from Lock/Models/User.swift rename to Lock/User.swift diff --git a/Lock/Utils/Validators.swift b/Lock/Validators.swift similarity index 100% rename from Lock/Utils/Validators.swift rename to Lock/Validators.swift diff --git a/Lock/Views/View.swift b/Lock/View.swift similarity index 100% rename from Lock/Views/View.swift rename to Lock/View.swift diff --git a/Lock/Utils/i18n.swift b/Lock/i18n.swift similarity index 100% rename from Lock/Utils/i18n.swift rename to Lock/i18n.swift From 6ff438ccc42d0c85196564a0b461ff5836bcd693 Mon Sep 17 00:00:00 2001 From: Hernan Zalazar Date: Thu, 1 Sep 2016 23:55:33 -0300 Subject: [PATCH 02/11] Rename connections -> withConections Avoid keeping two var with the same object --- App/ViewController.swift | 6 +++--- Lock/Lock.swift | 7 +++---- LockTests/LockSpec.swift | 10 +++++----- LockTests/LockViewControllerSpec.swift | 2 +- LockTests/Router/RouterSpec.swift | 8 ++++---- 5 files changed, 16 insertions(+), 17 deletions(-) diff --git a/App/ViewController.swift b/App/ViewController.swift index bbef6c111..8fd76d1fc 100644 --- a/App/ViewController.swift +++ b/App/ViewController.swift @@ -49,7 +49,7 @@ class ViewController: UIViewController { databaseOnly.onPress = { [weak self] _ in let lock = Lock .classic() - .connections { connections in + .withConnections { connections in connections.database(name: "Username-Password-Authentication", requiresUsername: true) } self?.showLock(lock) @@ -59,7 +59,7 @@ class ViewController: UIViewController { databaseAndSocial.onPress = { [weak self] _ in let lock = Lock .classic() - .connections { connections in + .withConnections { connections in connections.social(name: "facebook", style: .Facebook) connections.social(name: "google-oauth2", style: .Google) connections.database(name: "Username-Password-Authentication", requiresUsername: true) @@ -71,7 +71,7 @@ class ViewController: UIViewController { socialOnly.onPress = { [weak self] _ in let lock = Lock .classic() - .connections { connections in + .withConnections { connections in connections.social(name: "facebook", style: .Facebook) connections.social(name: "google-oauth2", style: .Google) connections.social(name: "instagram", style: .Instagram) diff --git a/Lock/Lock.swift b/Lock/Lock.swift index a6813fdeb..e2693ca78 100644 --- a/Lock/Lock.swift +++ b/Lock/Lock.swift @@ -34,8 +34,7 @@ public class Lock: NSObject { let authentication: Authentication let webAuth: WebAuth - private var connectionBuilder: ConnectionBuildable? = nil - var connections: Connections? { return self.connectionBuilder } + var connections: Connections? = nil private var optionsBuilder: OptionBuildable = LockOptions() var options: Options { return self.optionsBuilder } @@ -110,10 +109,10 @@ public class Lock: NSObject { - returns: Lock itself for chaining */ - public func connections(closure: (inout ConnectionBuildable) -> ()) -> Lock { + public func withConnections(closure: (inout ConnectionBuildable) -> ()) -> Lock { var connections: ConnectionBuildable = OfflineConnections() closure(&connections) - self.connectionBuilder = connections + self.connections = connections return self } diff --git a/LockTests/LockSpec.swift b/LockTests/LockSpec.swift index b20392b81..9b49af925 100644 --- a/LockTests/LockSpec.swift +++ b/LockTests/LockSpec.swift @@ -76,10 +76,10 @@ class LockSpec: QuickSpec { } } - describe("connections") { + describe("withConnections") { it("should allow settings connections") { - lock.connections { $0.database(name: "MyDB", requiresUsername: false) } + lock.withConnections { $0.database(name: "MyDB", requiresUsername: false) } expect(lock.connections?.database?.name) == "MyDB" } @@ -88,13 +88,13 @@ class LockSpec: QuickSpec { } it("should use the latest options") { - lock.connections { $0.database(name: "MyDB", requiresUsername: false) } - lock.connections { $0.database(name: "AnotherDB", requiresUsername: false) } + lock.withConnections { $0.database(name: "MyDB", requiresUsername: false) } + lock.withConnections { $0.database(name: "AnotherDB", requiresUsername: false) } expect(lock.connections?.database?.name) == "AnotherDB" } it("should return itself") { - expect(lock.connections { _ in } ) == lock + expect(lock.withConnections { _ in } ) == lock } } diff --git a/LockTests/LockViewControllerSpec.swift b/LockTests/LockViewControllerSpec.swift index 7aba2765f..bb9a91f3c 100644 --- a/LockTests/LockViewControllerSpec.swift +++ b/LockTests/LockViewControllerSpec.swift @@ -32,7 +32,7 @@ class LockViewControllerSpec: QuickSpec { var controller: LockViewController! beforeEach { - controller = Lock.classic(clientId: clientId, domain: domain).connections { $0.database(name: "db", requiresUsername: false) }.controller + controller = Lock.classic(clientId: clientId, domain: domain).withConnections { $0.database(name: "db", requiresUsername: false) }.controller } describe("message presenter") { diff --git a/LockTests/Router/RouterSpec.swift b/LockTests/Router/RouterSpec.swift index 9410dd099..bba170d79 100644 --- a/LockTests/Router/RouterSpec.swift +++ b/LockTests/Router/RouterSpec.swift @@ -36,7 +36,7 @@ class RouterSpec: QuickSpec { beforeEach { lock = Lock(authentication: Auth0.authentication(clientId: "CLIENT_ID", domain: "samples.auth0.com"), webAuth: MockWebAuth()) - lock.connections { $0.database(name: "connection", requiresUsername: false) } + lock.withConnections { $0.database(name: "connection", requiresUsername: false) } controller = MockLockController(lock: lock) router = Router(lock: lock, controller: controller) } @@ -54,14 +54,14 @@ class RouterSpec: QuickSpec { } it("should return root for single database connection") { - lock.connections { $0.database(name: connection, requiresUsername: true) } + lock.withConnections { $0.database(name: connection, requiresUsername: true) } let root = router.root as? DatabasePresenter expect(root).toNot(beNil()) expect(root?.authPresenter).to(beNil()) } it("should return root for single database connection and social") { - lock.connections { + lock.withConnections { $0.database(name: connection, requiresUsername: true) $0.social(name: "facebook", style: .Facebook) } @@ -71,7 +71,7 @@ class RouterSpec: QuickSpec { } it("should return root for only social connections") { - lock.connections { $0.social(name: "dropbox", style: AuthStyle(name: "Dropbox")) } + lock.withConnections { $0.social(name: "dropbox", style: AuthStyle(name: "Dropbox")) } expect(router.root as? AuthPresenter).toNot(beNil()) } From a9a354ca7826992b9c181c46b365ca4577e30c54 Mon Sep 17 00:00:00 2001 From: Hernan Zalazar Date: Fri, 2 Sep 2016 10:09:46 -0300 Subject: [PATCH 03/11] Show loading in absence of connections --- App/ViewController.swift | 8 +++- ConnectionLoadingPresenter.swift | 31 +++++++++++++++ LoadingView.swift | 66 +++++++++++++++++++++++++++++++ Lock.xcodeproj/project.pbxproj | 8 ++++ Lock/Router.swift | 2 +- LockTests/Router/RouterSpec.swift | 8 ++-- 6 files changed, 117 insertions(+), 6 deletions(-) create mode 100644 ConnectionLoadingPresenter.swift create mode 100644 LoadingView.swift diff --git a/App/ViewController.swift b/App/ViewController.swift index 8fd76d1fc..b6af2b981 100644 --- a/App/ViewController.swift +++ b/App/ViewController.swift @@ -44,6 +44,12 @@ class ViewController: UIViewController { ]) header.translatesAutoresizingMaskIntoConstraints = false + let cdnLoading = AuthButton(size: .Big) + cdnLoading.title = "LOGIN WITH CDN" + cdnLoading.onPress = { [weak self] _ in + let lock = Lock.classic() + self?.showLock(lock) + } let databaseOnly = AuthButton(size: .Big) databaseOnly.title = "LOGIN WITH DB" databaseOnly.onPress = { [weak self] _ in @@ -83,7 +89,7 @@ class ViewController: UIViewController { self?.showLock(lock) } - let stack = UIStackView(arrangedSubviews: [wrap(databaseOnly), wrap(socialOnly), wrap(databaseAndSocial)]) + let stack = UIStackView(arrangedSubviews: [wrap(cdnLoading), wrap(databaseOnly), wrap(socialOnly), wrap(databaseAndSocial)]) stack.axis = .Vertical stack.distribution = .FillProportionally stack.alignment = .Fill diff --git a/ConnectionLoadingPresenter.swift b/ConnectionLoadingPresenter.swift new file mode 100644 index 000000000..adcf4224c --- /dev/null +++ b/ConnectionLoadingPresenter.swift @@ -0,0 +1,31 @@ +// ConnectionLoadingPresenter.swift +// +// Copyright (c) 2016 Auth0 (http://auth0.com) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import Foundation + +class ConnectionLoadingPresenter: Presentable { + var messagePresenter: MessagePresenter? + + var view: View { + return LoadingView() + } +} \ No newline at end of file diff --git a/LoadingView.swift b/LoadingView.swift new file mode 100644 index 000000000..1ab2d291d --- /dev/null +++ b/LoadingView.swift @@ -0,0 +1,66 @@ +// LoadingView.swift +// +// Copyright (c) 2016 Auth0 (http://auth0.com) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import UIKit + +class LoadingView: UIView, View { + + weak var indicator: UIActivityIndicatorView? + + var inProgress: Bool { + get { + return self.indicator?.isAnimating() ?? false + } + set { + Queue.main.async { + if newValue { + self.indicator?.startAnimating() + } else { + self.indicator?.stopAnimating() + } + } + } + } + + // MARK:- Initialisers + + init() { + super.init(frame: CGRectZero) + self.backgroundColor = .whiteColor() + + let activityIndicator = UIActivityIndicatorView(activityIndicatorStyle: .WhiteLarge) + activityIndicator.color = UIColor ( red: 0.5725, green: 0.5804, blue: 0.5843, alpha: 1.0 ) + activityIndicator.translatesAutoresizingMaskIntoConstraints = false + self.addSubview(activityIndicator) + constraintEqual(anchor: activityIndicator.centerXAnchor, toAnchor: self.centerXAnchor) + constraintEqual(anchor: activityIndicator.centerYAnchor, toAnchor: self.centerYAnchor) + + self.indicator = activityIndicator + self.indicator?.startAnimating() + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + +} \ No newline at end of file diff --git a/Lock.xcodeproj/project.pbxproj b/Lock.xcodeproj/project.pbxproj index d4d06f4b9..b76f531ae 100644 --- a/Lock.xcodeproj/project.pbxproj +++ b/Lock.xcodeproj/project.pbxproj @@ -100,6 +100,8 @@ 5FDB41CE1D2C79FD00166B67 /* Operations.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5FDB41CD1D2C79FD00166B67 /* Operations.swift */; }; 5FDB41D01D2C95B100166B67 /* MessageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5FDB41CF1D2C95B100166B67 /* MessageView.swift */; }; 5FDC876D1D46DAF200D28596 /* Queue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5FDC876C1D46DAF200D28596 /* Queue.swift */; }; + 5FE50DB51D79ACCC00D82290 /* LoadingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5FE50DB41D79ACCC00D82290 /* LoadingView.swift */; }; + 5FE50DB71D79AF3000D82290 /* ConnectionLoadingPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5FE50DB61D79AF3000D82290 /* ConnectionLoadingPresenter.swift */; }; 5FEADCEF1D1A76610032D810 /* Auth0.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5F814FD21D1A7294003670A4 /* Auth0.framework */; }; 5FEADCF71D1A7EBC0032D810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5FEADCF61D1A7EBC0032D810 /* AppDelegate.swift */; }; 5FEADCF91D1A7EBC0032D810 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5FEADCF81D1A7EBC0032D810 /* ViewController.swift */; }; @@ -267,6 +269,8 @@ 5FDB41CD1D2C79FD00166B67 /* Operations.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Operations.swift; path = Lock/Operations.swift; sourceTree = SOURCE_ROOT; }; 5FDB41CF1D2C95B100166B67 /* MessageView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = MessageView.swift; path = Lock/MessageView.swift; sourceTree = SOURCE_ROOT; }; 5FDC876C1D46DAF200D28596 /* Queue.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Queue.swift; path = Lock/Queue.swift; sourceTree = SOURCE_ROOT; }; + 5FE50DB41D79ACCC00D82290 /* LoadingView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoadingView.swift; sourceTree = SOURCE_ROOT; }; + 5FE50DB61D79AF3000D82290 /* ConnectionLoadingPresenter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConnectionLoadingPresenter.swift; sourceTree = SOURCE_ROOT; }; 5FEADCF41D1A7EBC0032D810 /* LockApp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = LockApp.app; sourceTree = BUILT_PRODUCTS_DIR; }; 5FEADCF61D1A7EBC0032D810 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 5FEADCF81D1A7EBC0032D810 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; @@ -384,6 +388,7 @@ 5FC434851D1DF769005188BC /* View.swift */, 5F57DFC81D4F87CE00C54DA8 /* AuthCollectionView.swift */, 5F14565D1D5237820085DF9C /* DatabaseView.swift */, + 5FE50DB41D79ACCC00D82290 /* LoadingView.swift */, ); path = Views; sourceTree = ""; @@ -491,6 +496,7 @@ 5FBE5CC51D3E7F9D0038536D /* MultifactorPresenter.swift */, 5F5F98D71D22ED120016FC22 /* Presentable.swift */, 5F57DFCB1D4F93A000C54DA8 /* AuthPresenter.swift */, + 5FE50DB61D79AF3000D82290 /* ConnectionLoadingPresenter.swift */, ); path = Presenters; sourceTree = ""; @@ -849,12 +855,14 @@ files = ( 5F5F98DA1D22EF490016FC22 /* Router.swift in Sources */, 5F14565A1D5130E80085DF9C /* Colors.swift in Sources */, + 5FE50DB71D79AF3000D82290 /* ConnectionLoadingPresenter.swift in Sources */, 5F57DFC91D4F87CE00C54DA8 /* AuthCollectionView.swift in Sources */, 5F57DFCC1D4F93A000C54DA8 /* AuthPresenter.swift in Sources */, 5F390E871D638A6D00FC549C /* Logger.swift in Sources */, 5F99AA941D1BABFC00D27842 /* SecondaryButton.swift in Sources */, 5F73CDD61D30790500D8D8D1 /* DatabaseForgotPasswordPresenter.swift in Sources */, 5F99AA901D1B9E7100D27842 /* DatabaseModeSwitcher.swift in Sources */, + 5FE50DB51D79ACCC00D82290 /* LoadingView.swift in Sources */, 5F70F1E31D79057A004698DA /* OfflineConnections.swift in Sources */, 5F2496B31D665A5600A1C6E2 /* DatabaseUserCreator.swift in Sources */, 5F70F1E71D790773004698DA /* OptionBuildable.swift in Sources */, diff --git a/Lock/Router.swift b/Lock/Router.swift index c0ee80641..5d1d6cf36 100644 --- a/Lock/Router.swift +++ b/Lock/Router.swift @@ -73,7 +73,7 @@ struct Router: Navigable { } var root: Presentable? { - guard let connections = self.lock.connections else { return nil } // FIXME: show error screen + guard let connections = self.lock.connections else { return ConnectionLoadingPresenter() } if let database = connections.database { let authentication = self.lock.authentication diff --git a/LockTests/Router/RouterSpec.swift b/LockTests/Router/RouterSpec.swift index bba170d79..e14bd0739 100644 --- a/LockTests/Router/RouterSpec.swift +++ b/LockTests/Router/RouterSpec.swift @@ -49,10 +49,6 @@ class RouterSpec: QuickSpec { router = Router(lock: lock, controller: controller) } - it("should return no root if there are no connections") { - expect(router.root).to(beNil()) - } - it("should return root for single database connection") { lock.withConnections { $0.database(name: connection, requiresUsername: true) } let root = router.root as? DatabasePresenter @@ -75,6 +71,10 @@ class RouterSpec: QuickSpec { expect(router.root as? AuthPresenter).toNot(beNil()) } + it("should return root for loading connection from CDN") { + expect(router.root as? ConnectionLoadingPresenter).toNot(beNil()) + } + } describe("events") { From abf465d0671f17d08cb8325f6a30400f44e39ee9 Mon Sep 17 00:00:00 2001 From: Hernan Zalazar Date: Fri, 2 Sep 2016 10:25:44 -0300 Subject: [PATCH 04/11] Avoid navigating again to root from root --- Lock/Router.swift | 5 ++++- LockTests/Router/RouterSpec.swift | 6 ++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/Lock/Router.swift b/Lock/Router.swift index 5d1d6cf36..76281dcdf 100644 --- a/Lock/Router.swift +++ b/Lock/Router.swift @@ -123,12 +123,15 @@ struct Router: Navigable { func navigate(route: Route) { let presentable: Presentable? switch route { - case .Root: + case .Root where self.controller?.routes.current != .Root: presentable = self.root case .ForgotPassword: presentable = self.forgotPassword case .Multifactor: presentable = self.multifactor + default: + self.lock.logger.warn("Ignoring navigation \(route)") + return } self.lock.logger.debug("Navigating to \(route)") self.controller?.routes.go(route) diff --git a/LockTests/Router/RouterSpec.swift b/LockTests/Router/RouterSpec.swift index e14bd0739..21b8d4b2a 100644 --- a/LockTests/Router/RouterSpec.swift +++ b/LockTests/Router/RouterSpec.swift @@ -145,6 +145,12 @@ class RouterSpec: QuickSpec { } } + it("should not show root again") { + expect(controller.routes.current).toNot(beNil()) + router.navigate(.Root) + expect(controller.presentable).to(beNil()) + } + it("should show forgot pwd screen") { router.navigate(.ForgotPassword) expect(controller.presentable as? DatabaseForgotPasswordPresenter).toNot(beNil()) From 784a5c50939246619dc96343e4ab68f52f0a248d Mon Sep 17 00:00:00 2001 From: Hernan Zalazar Date: Fri, 2 Sep 2016 18:43:08 -0300 Subject: [PATCH 05/11] Load client info from CDN DB connection only so far --- CDNLoaderInteractor.swift | 102 ++++++++++++ ConnectionLoadingPresenter.swift | 16 +- Lock.xcodeproj/project.pbxproj | 16 ++ Lock/Lock.swift | 1 + Lock/Router.swift | 13 +- Lock/Routes.swift | 4 + .../Interactors/CDNLoaderInteractorSpec.swift | 153 ++++++++++++++++++ .../ConnectionLoadingPresenterSpec.swift | 57 +++++++ LockTests/Utils/Mocks.swift | 15 +- LockTests/Utils/NetworkStub.swift | 14 ++ RemoteConnectionLoader.swift | 29 ++++ 11 files changed, 417 insertions(+), 3 deletions(-) create mode 100644 CDNLoaderInteractor.swift create mode 100644 LockTests/Interactors/CDNLoaderInteractorSpec.swift create mode 100644 LockTests/Presenters/ConnectionLoadingPresenterSpec.swift create mode 100644 RemoteConnectionLoader.swift diff --git a/CDNLoaderInteractor.swift b/CDNLoaderInteractor.swift new file mode 100644 index 000000000..c85fe748b --- /dev/null +++ b/CDNLoaderInteractor.swift @@ -0,0 +1,102 @@ +// CDNLoaderInteractor.swift +// +// Copyright (c) 2016 Auth0 (http://auth0.com) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import Foundation + +typealias JSONObject = [String: AnyObject] +typealias JSONArray = [JSONObject] + +struct CDNLoaderInteractor: RemoteConnectionLoader, Loggable { + + let url: NSURL + + init(baseURL: NSURL, clientId: String) { + self.url = NSURL(string: "client/\(clientId).js", relativeToURL: cdnURL(from: baseURL))! + } + + func load(callback: Connections? -> ()) { + self.logger.info("Loading client info from \(self.url)") + let task = NSURLSession.sharedSession().dataTaskWithURL(self.url) { (data, response, error) in + guard error == nil else { + self.logger.error("Failed to load with error \(error!)") + callback(nil) + return + } + guard let response = response as? NSHTTPURLResponse else { + self.logger.error("Response was not NSHTTURLResponse") + return callback(nil) + } + + let payload: String? + if let data = data { + payload = String(data: data, encoding: NSUTF8StringEncoding) + } else { + payload = nil + } + guard 200...299 ~= response.statusCode else { + self.logger.error("HTTP response was not successful. HTTP \(response.statusCode) <\(payload ?? "No Body")>") + return callback(nil) + } + + guard var jsonp = payload else { + self.logger.error("HTTP response had no jsonp \(payload ?? "No Body")") + return callback(nil) + } + + self.logger.verbose("Received jsonp \(jsonp)") + + if let prefixRange = jsonp.rangeOfString("Auth0.setClient(") { + jsonp.removeRange(prefixRange) + } + if let suffixRange = jsonp.rangeOfString(");") { + jsonp.removeRange(suffixRange) + } + + do { + var connections = OfflineConnections() + let json = try NSJSONSerialization.JSONObjectWithData(jsonp.dataUsingEncoding(NSUTF8StringEncoding)!, options: []) + self.logger.debug("Client configuration is \(json)") + let strategies = json["strategies"] as? JSONArray ?? [] + if let auth0 = strategies.filter({ $0["name"] as? String == "auth0" }).first { + let databases = auth0["connections"] as? JSONArray ?? [] + if let connection = databases.first, let name = connection["name"] as? String { + let requiresUsername = connection["requires_username"] as? Bool ?? false + connections.database(name: name, requiresUsername: requiresUsername) + } + } + callback(connections) + } catch let e { + self.logger.error("Failed to parse \(jsonp) with error \(e)") + return callback(nil) + } + } + task.resume() + } +} + +private func cdnURL(from url: NSURL) -> NSURL { + guard let host = url.host where host.hasSuffix(".auth0.com") else { return url } + let components = host.componentsSeparatedByString(".") + guard components.count == 4 else { return NSURL(string: "https://cdn.auth0.com")! } + let region = components[1] + return NSURL(string: "https://cdn.\(region).auth0.com")! +} \ No newline at end of file diff --git a/ConnectionLoadingPresenter.swift b/ConnectionLoadingPresenter.swift index adcf4224c..9bbc91511 100644 --- a/ConnectionLoadingPresenter.swift +++ b/ConnectionLoadingPresenter.swift @@ -22,10 +22,24 @@ import Foundation -class ConnectionLoadingPresenter: Presentable { +class ConnectionLoadingPresenter: Presentable, Loggable { var messagePresenter: MessagePresenter? + let loader: RemoteConnectionLoader + let navigator: Navigable + + init(loader: RemoteConnectionLoader, navigator: Navigable) { + self.loader = loader + self.navigator = navigator + } var view: View { + self.loader.load { connections in + guard let connections = connections else { return } // FIXME: Show error + Queue.main.async { + self.logger.debug("Loaded connections. Moving to root view") + self.navigator.reload(withConnections: connections) + } + } return LoadingView() } } \ No newline at end of file diff --git a/Lock.xcodeproj/project.pbxproj b/Lock.xcodeproj/project.pbxproj index b76f531ae..1e994bdc3 100644 --- a/Lock.xcodeproj/project.pbxproj +++ b/Lock.xcodeproj/project.pbxproj @@ -102,6 +102,10 @@ 5FDC876D1D46DAF200D28596 /* Queue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5FDC876C1D46DAF200D28596 /* Queue.swift */; }; 5FE50DB51D79ACCC00D82290 /* LoadingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5FE50DB41D79ACCC00D82290 /* LoadingView.swift */; }; 5FE50DB71D79AF3000D82290 /* ConnectionLoadingPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5FE50DB61D79AF3000D82290 /* ConnectionLoadingPresenter.swift */; }; + 5FE50DB91D79B59900D82290 /* RemoteConnectionLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5FE50DB81D79B59900D82290 /* RemoteConnectionLoader.swift */; }; + 5FE50DBB1D79B7CD00D82290 /* CDNLoaderInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5FE50DBA1D79B7CD00D82290 /* CDNLoaderInteractor.swift */; }; + 5FE50DBD1D79B8AD00D82290 /* CDNLoaderInteractorSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5FE50DBC1D79B8AD00D82290 /* CDNLoaderInteractorSpec.swift */; }; + 5FE50DBF1D7A254000D82290 /* ConnectionLoadingPresenterSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5FE50DBE1D7A254000D82290 /* ConnectionLoadingPresenterSpec.swift */; }; 5FEADCEF1D1A76610032D810 /* Auth0.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5F814FD21D1A7294003670A4 /* Auth0.framework */; }; 5FEADCF71D1A7EBC0032D810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5FEADCF61D1A7EBC0032D810 /* AppDelegate.swift */; }; 5FEADCF91D1A7EBC0032D810 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5FEADCF81D1A7EBC0032D810 /* ViewController.swift */; }; @@ -271,6 +275,10 @@ 5FDC876C1D46DAF200D28596 /* Queue.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Queue.swift; path = Lock/Queue.swift; sourceTree = SOURCE_ROOT; }; 5FE50DB41D79ACCC00D82290 /* LoadingView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoadingView.swift; sourceTree = SOURCE_ROOT; }; 5FE50DB61D79AF3000D82290 /* ConnectionLoadingPresenter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConnectionLoadingPresenter.swift; sourceTree = SOURCE_ROOT; }; + 5FE50DB81D79B59900D82290 /* RemoteConnectionLoader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RemoteConnectionLoader.swift; sourceTree = SOURCE_ROOT; }; + 5FE50DBA1D79B7CD00D82290 /* CDNLoaderInteractor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CDNLoaderInteractor.swift; sourceTree = SOURCE_ROOT; }; + 5FE50DBC1D79B8AD00D82290 /* CDNLoaderInteractorSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CDNLoaderInteractorSpec.swift; sourceTree = ""; }; + 5FE50DBE1D7A254000D82290 /* ConnectionLoadingPresenterSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConnectionLoadingPresenterSpec.swift; sourceTree = ""; }; 5FEADCF41D1A7EBC0032D810 /* LockApp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = LockApp.app; sourceTree = BUILT_PRODUCTS_DIR; }; 5FEADCF61D1A7EBC0032D810 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 5FEADCF81D1A7EBC0032D810 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; @@ -353,6 +361,8 @@ 5F57DFD31D4FE64700C54DA8 /* Auth0OAuth2Interactor.swift */, 5F2496B21D665A5600A1C6E2 /* DatabaseUserCreator.swift */, 5F2496B71D665AC500A1C6E2 /* CredentialAttribute.swift */, + 5FE50DB81D79B59900D82290 /* RemoteConnectionLoader.swift */, + 5FE50DBA1D79B7CD00D82290 /* CDNLoaderInteractor.swift */, ); path = Interactors; sourceTree = ""; @@ -364,6 +374,7 @@ 5F73CDDB1D309BE900D8D8D1 /* DatabasePasswordInteractorSpec.swift */, 5FBE5CC11D3E5EF50038536D /* MultifactorInteractorSpec.swift */, 5F92C68A1D4FE90F00CCE6C0 /* Auth0OAuth2InteractorSpec.swift */, + 5FE50DBC1D79B8AD00D82290 /* CDNLoaderInteractorSpec.swift */, ); path = Interactors; sourceTree = ""; @@ -408,6 +419,7 @@ 5F5F98D31D21E3890016FC22 /* DatabasePresenterSpec.swift */, 5FBE5CC91D3EA1380038536D /* MultifactorPresenterSpec.swift */, 5F57DFCD1D4FBE5A00C54DA8 /* AuthPresenterSpec.swift */, + 5FE50DBE1D7A254000D82290 /* ConnectionLoadingPresenterSpec.swift */, ); path = Presenters; sourceTree = ""; @@ -856,6 +868,7 @@ 5F5F98DA1D22EF490016FC22 /* Router.swift in Sources */, 5F14565A1D5130E80085DF9C /* Colors.swift in Sources */, 5FE50DB71D79AF3000D82290 /* ConnectionLoadingPresenter.swift in Sources */, + 5FE50DBB1D79B7CD00D82290 /* CDNLoaderInteractor.swift in Sources */, 5F57DFC91D4F87CE00C54DA8 /* AuthCollectionView.swift in Sources */, 5F57DFCC1D4F93A000C54DA8 /* AuthPresenter.swift in Sources */, 5F390E871D638A6D00FC549C /* Logger.swift in Sources */, @@ -893,6 +906,7 @@ 5F2496BA1D665AE900A1C6E2 /* DatabaseAuthenticatableError.swift in Sources */, 5F57DFC61D4F79DD00C54DA8 /* AuthStyle.swift in Sources */, 5FBE5CC61D3E7F9D0038536D /* MultifactorPresenter.swift in Sources */, + 5FE50DB91D79B59900D82290 /* RemoteConnectionLoader.swift in Sources */, 5FEAE2101D1A5691005C0028 /* HeaderView.swift in Sources */, 5F99AA8C1D1B3F1300D27842 /* InputField.swift in Sources */, 5FBE5CBA1D3E59B90038536D /* MultifactorCodeView.swift in Sources */, @@ -920,6 +934,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 5FE50DBF1D7A254000D82290 /* ConnectionLoadingPresenterSpec.swift in Sources */, 5F2496BE1D67ADB300A1C6E2 /* ValidatorSpec.swift in Sources */, 5FBE5CCA1D3EA1380038536D /* MultifactorPresenterSpec.swift in Sources */, 5F92C68D1D50E47100CCE6C0 /* AuthStyleSpec.swift in Sources */, @@ -940,6 +955,7 @@ 5F57DFCE1D4FBE5A00C54DA8 /* AuthPresenterSpec.swift in Sources */, 5F92C6911D510AFE00CCE6C0 /* LazyImageSpec.swift in Sources */, 5F50900A1D1DEE9A00EAA650 /* Constants.swift in Sources */, + 5FE50DBD1D79B8AD00D82290 /* CDNLoaderInteractorSpec.swift in Sources */, 5F390E8D1D63B99300FC549C /* LoggerSpec.swift in Sources */, 5F2037C21D5D02880005D2E2 /* Matchers.swift in Sources */, ); diff --git a/Lock/Lock.swift b/Lock/Lock.swift index e2693ca78..d48a6a36d 100644 --- a/Lock/Lock.swift +++ b/Lock/Lock.swift @@ -127,6 +127,7 @@ public class Lock: NSObject { var options: OptionBuildable = LockOptions() closure(&options) self.optionsBuilder = options + logger.debug("Lock options overriden") return self } diff --git a/Lock/Router.swift b/Lock/Router.swift index 76281dcdf..ea4c103df 100644 --- a/Lock/Router.swift +++ b/Lock/Router.swift @@ -24,6 +24,7 @@ import Foundation import Auth0 protocol Navigable { + func reload(withConnections connections: Connections) func navigate(route: Route) func resetScroll(animated: Bool) func present(controller: UIViewController) @@ -73,7 +74,10 @@ struct Router: Navigable { } var root: Presentable? { - guard let connections = self.lock.connections else { return ConnectionLoadingPresenter() } + guard let connections = self.lock.connections else { + let interactor = CDNLoaderInteractor(baseURL: self.lock.authentication.url, clientId: self.lock.authentication.clientId) + return ConnectionLoadingPresenter(loader: interactor, navigator: self) + } if let database = connections.database { let authentication = self.lock.authentication @@ -120,6 +124,13 @@ struct Router: Navigable { var onBack: () -> () = {} + func reload(withConnections connections: Connections) { + self.lock.connections = connections + self.lock.logger.debug("Reloading Lock") + self.controller?.routes.reset() + self.controller?.present(self.root) + } + func navigate(route: Route) { let presentable: Presentable? switch route { diff --git a/Lock/Routes.swift b/Lock/Routes.swift index de037780a..f96fba4ff 100644 --- a/Lock/Routes.swift +++ b/Lock/Routes.swift @@ -38,6 +38,10 @@ struct Routes { mutating func go(route: Route) { self.history.append(route) } + + mutating func reset() { + self.history = [] + } } enum Route { diff --git a/LockTests/Interactors/CDNLoaderInteractorSpec.swift b/LockTests/Interactors/CDNLoaderInteractorSpec.swift new file mode 100644 index 000000000..27b378b52 --- /dev/null +++ b/LockTests/Interactors/CDNLoaderInteractorSpec.swift @@ -0,0 +1,153 @@ +// CDNLoaderInteractorSpec.swift +// +// Copyright (c) 2016 Auth0 (http://auth0.com) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import Quick +import Nimble +import OHHTTPStubs + +@testable import Lock + +class CDNLoaderInteractorSpec: QuickSpec { + + override func spec() { + + afterEach { + Auth0Stubs.cleanAll() + Auth0Stubs.failUnknown() + } + + describe("init") { + + it("should build url from non-auth0 domain") { + let loader = CDNLoaderInteractor(baseURL: NSURL(string: "https://somewhere.far.beyond")!, clientId: clientId) + expect(loader.url.absoluteString) == "https://somewhere.far.beyond/client/\(clientId).js" + } + + it("should build url from auth0 domain") { + let loader = CDNLoaderInteractor(baseURL: NSURL(string: "https://samples.auth0.com")!, clientId: clientId) + expect(loader.url.absoluteString) == "https://cdn.auth0.com/client/\(clientId).js" + } + + it("should build url from auth0 domain for eu region") { + let loader = CDNLoaderInteractor(baseURL: NSURL(string: "https://samples.eu.auth0.com")!, clientId: clientId) + expect(loader.url.absoluteString) == "https://cdn.eu.auth0.com/client/\(clientId).js" + } + + it("should build url from auth0 domain for au region") { + let loader = CDNLoaderInteractor(baseURL: NSURL(string: "https://samples.au.auth0.com")!, clientId: clientId) + expect(loader.url.absoluteString) == "https://cdn.au.auth0.com/client/\(clientId).js" + } + + } + + describe("load") { + + var loader: CDNLoaderInteractor! + var connections: Connections? + var callback: (Connections? -> ())! + + beforeEach { + loader = CDNLoaderInteractor(baseURL: NSURL(string: "https://overmind.auth0.com")!, clientId: clientId) + callback = { connections = $0 } + connections = nil + } + + context("failure") { + + beforeEach { + connections = OfflineConnections() + } + + it("should fail") { + loader.load(callback) + expect(connections).toEventually(beNil()) + } + + it("should fail for status code not in range 200...299") { + stub(isCDN(forClientId: clientId)) { _ in OHHTTPStubsResponse(data: NSData(), statusCode: 400, headers: [:]) } + loader.load(callback) + expect(connections).toEventually(beNil()) + } + + it("should fail when there is no body") { + stub(isCDN(forClientId: clientId)) { _ in OHHTTPStubsResponse(data: NSData(), statusCode: 200, headers: [:]) } + loader.load(callback) + expect(connections).toEventually(beNil()) + } + + it("should fail for invalid json") { + stub(isCDN(forClientId: clientId)) { _ in OHHTTPStubsResponse(data: "not a json object".dataUsingEncoding(NSUTF8StringEncoding)!, statusCode: 200, headers: [:]) } + loader.load(callback) + expect(connections).toEventually(beNil()) + } + } + + let databaseConnection = "DB Connection" + + it("should load empty strategies") { + stub(isCDN(forClientId: clientId)) { _ in Auth0Stubs.strategiesFromCDN([]) } + loader.load(callback) + expect(connections).toEventuallyNot(beNil()) + expect(connections?.database).toEventually(beNil()) + expect(connections?.oauth2).toEventually(beEmpty()) + } + + it("should load single database connection") { + stub(isCDN(forClientId: clientId)) { _ in return Auth0Stubs.strategiesFromCDN([mockStrategy("auth0", connections: [mockDatabaseConnection(databaseConnection)])]) } + loader.load(callback) + expect(connections?.database).toEventuallyNot(beNil()) + expect(connections?.database?.name).toEventually(equal(databaseConnection)) + expect(connections?.database?.requiresUsername).toEventually(beFalsy()) + } + + it("should load multiple database connections but pick the first") { + stub(isCDN(forClientId: clientId)) { _ in return Auth0Stubs.strategiesFromCDN([mockStrategy("auth0", connections: [mockDatabaseConnection(databaseConnection), mockDatabaseConnection("another one")])]) } + loader.load(callback) + expect(connections?.database).toEventuallyNot(beNil()) + expect(connections?.database?.name).toEventually(equal(databaseConnection)) + } + + it("should load single database connection with requires_username") { + stub(isCDN(forClientId: clientId)) { _ in return Auth0Stubs.strategiesFromCDN([mockStrategy("auth0", connections: [mockDatabaseConnection(databaseConnection, requiresUsername: true)])]) } + loader.load(callback) + expect(connections?.database).toEventuallyNot(beNil()) + expect(connections?.database?.name).toEventually(equal(databaseConnection)) + expect(connections?.database?.requiresUsername).toEventually(beTruthy()) + } + + } + + } + +} + +private func mockStrategy(name: String, connections: [JSONObject]) -> JSONObject { + return ["name": name, "connections": connections] +} + +private func mockDatabaseConnection(name: String, requiresUsername: Bool? = nil) -> JSONObject { + var json: JSONObject = ["name": name ] + if let requiresUsername = requiresUsername { + json["requires_username"] = requiresUsername + } + return json +} \ No newline at end of file diff --git a/LockTests/Presenters/ConnectionLoadingPresenterSpec.swift b/LockTests/Presenters/ConnectionLoadingPresenterSpec.swift new file mode 100644 index 000000000..cff54d022 --- /dev/null +++ b/LockTests/Presenters/ConnectionLoadingPresenterSpec.swift @@ -0,0 +1,57 @@ +// ConnectionLoadingPresenterSpec.swift +// +// Copyright (c) 2016 Auth0 (http://auth0.com) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import Quick +import Nimble + +@testable import Lock + +class ConnectionLoadingPresenterSpec: QuickSpec { + + override func spec() { + + var interactor: MockConnectionsLoader! + var presenter: ConnectionLoadingPresenter! + var messagePresenter: MockMessagePresenter! + var navigator: MockNavigator! + + beforeEach { + messagePresenter = MockMessagePresenter() + interactor = MockConnectionsLoader() + navigator = MockNavigator() + presenter = ConnectionLoadingPresenter(loader: interactor, navigator: navigator) + presenter.messagePresenter = messagePresenter + } + + it("should build LoadingView") { + expect(presenter.view as? LoadingView).toNot(beNil()) + } + + it("should reload when connections are obtained") { + let connections: Connections = OfflineConnections() + interactor.connections = connections + presenter.view + expect(navigator.connections).toEventuallyNot(beNil()) + } + + } +} diff --git a/LockTests/Utils/Mocks.swift b/LockTests/Utils/Mocks.swift index af82010d0..8a921b6cc 100644 --- a/LockTests/Utils/Mocks.swift +++ b/LockTests/Utils/Mocks.swift @@ -57,7 +57,7 @@ class MockNavigator: Navigable { var route: Route? var resetted: Bool = false var presented: UIViewController? = nil - + var connections: Connections? = nil func navigate(route: Route) { self.route = route @@ -70,6 +70,10 @@ class MockNavigator: Navigable { func present(controller: UIViewController) { self.presented = controller } + + func reload(withConnections connections: Connections) { + self.connections = connections + } } func mockInput(type: InputField.InputType, value: String? = nil) -> MockInputField { @@ -174,6 +178,15 @@ class MockDBInteractor: DatabaseAuthenticatable, DatabaseUserCreator { } } +class MockConnectionsLoader: RemoteConnectionLoader { + + var connections: Connections? = nil + + func load(callback: Connections? -> ()) { + callback(connections) + } +} + class MockWebAuth: WebAuth { var clientId: String = "CLIENT_ID" diff --git a/LockTests/Utils/NetworkStub.swift b/LockTests/Utils/NetworkStub.swift index f01131556..09f7e60be 100644 --- a/LockTests/Utils/NetworkStub.swift +++ b/LockTests/Utils/NetworkStub.swift @@ -104,6 +104,10 @@ func isOAuthAccessToken(domain: String) -> OHHTTPStubsTestBlock { return isMethodPOST() && isHost(domain) && isPath("/oauth/access_token") } +func isCDN(forClientId clientId: String) -> OHHTTPStubsTestBlock { + return isMethodGET() && isHost("cdn.auth0.com") && isPath("/client/\(clientId).js") +} + // MARK:- Response Stubs struct Auth0Stubs { @@ -139,4 +143,14 @@ struct Auth0Stubs { ] return OHHTTPStubsResponse(JSONObject: json, statusCode: 200, headers: ["Content-Type": "application/json"]) } + + static func strategiesFromCDN(strategies: [[String: AnyObject]]) -> OHHTTPStubsResponse { + let json = [ + "strategies": strategies + ] + let data = try! NSJSONSerialization.dataWithJSONObject(json, options: []) + let string = String(data: data, encoding: NSUTF8StringEncoding)! + let jsonp = "Auth0.setClient(\(string));" + return OHHTTPStubsResponse(data: jsonp.dataUsingEncoding(NSUTF8StringEncoding)!, statusCode: 200, headers: ["Content-Type": "application/x-javascript"]) + } } \ No newline at end of file diff --git a/RemoteConnectionLoader.swift b/RemoteConnectionLoader.swift new file mode 100644 index 000000000..a73d7a6d9 --- /dev/null +++ b/RemoteConnectionLoader.swift @@ -0,0 +1,29 @@ +// RemoteConnectionLoader.swift +// +// Copyright (c) 2016 Auth0 (http://auth0.com) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import Foundation + +protocol RemoteConnectionLoader { + + func load(callback: Connections? -> ()) + +} From ba2a940df8806abe9b01f3fdbcd851d350e6d9fb Mon Sep 17 00:00:00 2001 From: Hernan Zalazar Date: Fri, 2 Sep 2016 23:05:39 -0300 Subject: [PATCH 06/11] Load remaining strategies as oauth2 --- CDNLoaderInteractor.swift | 54 ++++++++++++++++--- .../Interactors/CDNLoaderInteractorSpec.swift | 40 ++++++++++++++ 2 files changed, 86 insertions(+), 8 deletions(-) diff --git a/CDNLoaderInteractor.swift b/CDNLoaderInteractor.swift index c85fe748b..4b3d2ebae 100644 --- a/CDNLoaderInteractor.swift +++ b/CDNLoaderInteractor.swift @@ -73,15 +73,16 @@ struct CDNLoaderInteractor: RemoteConnectionLoader, Loggable { do { var connections = OfflineConnections() - let json = try NSJSONSerialization.JSONObjectWithData(jsonp.dataUsingEncoding(NSUTF8StringEncoding)!, options: []) + let json = try NSJSONSerialization.JSONObjectWithData(jsonp.dataUsingEncoding(NSUTF8StringEncoding)!, options: []) as? JSONObject self.logger.debug("Client configuration is \(json)") - let strategies = json["strategies"] as? JSONArray ?? [] - if let auth0 = strategies.filter({ $0["name"] as? String == "auth0" }).first { - let databases = auth0["connections"] as? JSONArray ?? [] - if let connection = databases.first, let name = connection["name"] as? String { - let requiresUsername = connection["requires_username"] as? Bool ?? false - connections.database(name: name, requiresUsername: requiresUsername) - } + let info = ClientInfo(json: json) + if let auth0 = info.auth0, let connection = auth0.connections.first { + let requiresUsername = connection.booleanValue(forKey: "requires_username") + connections.database(name: connection.name, requiresUsername: requiresUsername) + } + info.oauth2.forEach { strategy in + let style = AuthStyle(name: strategy.name) + strategy.connections.forEach { connections.social(name: $0.name, style: style) } } callback(connections) } catch let e { @@ -93,6 +94,43 @@ struct CDNLoaderInteractor: RemoteConnectionLoader, Loggable { } } +private struct ClientInfo { + let json: JSONObject? + + var strategies: [StrategyInfo] { + let list = json?["strategies"] as? JSONArray ?? [] + return list + .filter { $0["name"] != nil } + .map { StrategyInfo(json: $0) } + } + + var auth0: StrategyInfo? { return strategies.filter({ $0.name == "auth0" }).first } + + var oauth2: [StrategyInfo] { return strategies.filter { $0.name != "auth0" } } +} + +private struct StrategyInfo { + let json: JSONObject + + var name: String { return json["name"] as! String } + + var connections: [ConnectionInfo] { + let list = json["connections"] as? JSONArray ?? [] + return list + .filter { $0["name"] != nil } + .map { ConnectionInfo(json: $0) } + } +} + +private struct ConnectionInfo { + + let json: JSONObject + + var name: String { return json["name"] as! String } + + func booleanValue(forKey key: String, defaultValue: Bool = false) -> Bool { return json[key] as? Bool ?? defaultValue } +} + private func cdnURL(from url: NSURL) -> NSURL { guard let host = url.host where host.hasSuffix(".auth0.com") else { return url } let components = host.componentsSeparatedByString(".") diff --git a/LockTests/Interactors/CDNLoaderInteractorSpec.swift b/LockTests/Interactors/CDNLoaderInteractorSpec.swift index 27b378b52..09bb94d99 100644 --- a/LockTests/Interactors/CDNLoaderInteractorSpec.swift +++ b/LockTests/Interactors/CDNLoaderInteractorSpec.swift @@ -111,6 +111,22 @@ class CDNLoaderInteractorSpec: QuickSpec { expect(connections?.oauth2).toEventually(beEmpty()) } + it("should not load strategies without name") { + stub(isCDN(forClientId: clientId)) { _ in Auth0Stubs.strategiesFromCDN([[:]]) } + loader.load(callback) + expect(connections).toEventuallyNot(beNil()) + expect(connections?.database).toEventually(beNil()) + expect(connections?.oauth2).toEventually(beEmpty()) + } + + it("should not load connection without name") { + stub(isCDN(forClientId: clientId)) { _ in Auth0Stubs.strategiesFromCDN([mockStrategy("auth0", connections: [[:]])]) } + loader.load(callback) + expect(connections).toEventuallyNot(beNil()) + expect(connections?.database).toEventually(beNil()) + expect(connections?.oauth2).toEventually(beEmpty()) + } + it("should load single database connection") { stub(isCDN(forClientId: clientId)) { _ in return Auth0Stubs.strategiesFromCDN([mockStrategy("auth0", connections: [mockDatabaseConnection(databaseConnection)])]) } loader.load(callback) @@ -134,6 +150,25 @@ class CDNLoaderInteractorSpec: QuickSpec { expect(connections?.database?.requiresUsername).toEventually(beTruthy()) } + it("should load oauth2 connections") { + stub(isCDN(forClientId: clientId)) { _ in return Auth0Stubs.strategiesFromCDN([mockStrategy("oauth2", connections: [mockOAuth2("steam")])]) } + loader.load(callback) + expect(connections?.oauth2).toEventuallyNot(beNil()) + let oauth2 = connections?.oauth2.first + expect(oauth2?.name) == "steam" + expect(oauth2?.style.name) == "oauth2" + expect(oauth2?.style.color) == .a0_orange + } + + it("should load multiple oauth2 connections") { + stub(isCDN(forClientId: clientId)) { _ in return Auth0Stubs.strategiesFromCDN([mockStrategy("facebook", connections: [mockOAuth2("facebook1"), mockOAuth2("facebook2")])]) } + loader.load(callback) + expect(connections?.oauth2).toEventuallyNot(beNil()) + expect(connections?.oauth2.count).toEventually(be(2)) + expect(connections?.oauth2[0].name) == "facebook1" + expect(connections?.oauth2[1].name) == "facebook2" + } + } } @@ -144,6 +179,11 @@ private func mockStrategy(name: String, connections: [JSONObject]) -> JSONObject return ["name": name, "connections": connections] } +private func mockOAuth2(name: String) -> JSONObject { + let json: JSONObject = ["name": name ] + return json +} + private func mockDatabaseConnection(name: String, requiresUsername: Bool? = nil) -> JSONObject { var json: JSONObject = ["name": name ] if let requiresUsername = requiresUsername { From ec032060875686cf3a6ee46089ed8b8036c5dd84 Mon Sep 17 00:00:00 2001 From: Hernan Zalazar Date: Sat, 3 Sep 2016 00:38:52 -0300 Subject: [PATCH 07/11] Map strategy to auth style --- CDNLoaderInteractor.swift | 3 +- Lock/AuthStyle.swift | 84 +++++++++++++++++++ .../Interactors/CDNLoaderInteractorSpec.swift | 11 ++- LockTests/Models/AuthStyleSpec.swift | 60 +++++++++++++ 4 files changed, 155 insertions(+), 3 deletions(-) diff --git a/CDNLoaderInteractor.swift b/CDNLoaderInteractor.swift index 4b3d2ebae..9ad546da3 100644 --- a/CDNLoaderInteractor.swift +++ b/CDNLoaderInteractor.swift @@ -81,8 +81,7 @@ struct CDNLoaderInteractor: RemoteConnectionLoader, Loggable { connections.database(name: connection.name, requiresUsername: requiresUsername) } info.oauth2.forEach { strategy in - let style = AuthStyle(name: strategy.name) - strategy.connections.forEach { connections.social(name: $0.name, style: style) } + strategy.connections.forEach { connections.social(name: $0.name, style: AuthStyle.style(forStrategy: strategy.name, connectionName: $0.name)) } } callback(connections) } catch let e { diff --git a/Lock/AuthStyle.swift b/Lock/AuthStyle.swift index 723d02a5e..164712038 100644 --- a/Lock/AuthStyle.swift +++ b/Lock/AuthStyle.swift @@ -195,4 +195,88 @@ public extension AuthStyle { public static var Weibo: AuthStyle { return AuthStyle(name: "新浪微博", color: .a0_fromRGB("#DD4B39"), withImage: "ic_auth_weibo") } +} + +// MARK:- AuthStyle from Strategy & Connection + +extension AuthStyle { + + static func style(forStrategy strategy: String, connectionName: String) -> AuthStyle { + switch strategy.lowercaseString { + case "amazon": + return .Amazon + case "aol": + return .Aol + case "baidu": + return .Baidu + case "bitbucket": + return .Bitbucket + case "dropbox": + return .Dropbox + case "dwolla": + return .Dwolla + case "ebay": + return .Ebay + case "evernote": + return .Evernote + case "evernote-sandbox": + return .EvernoteSandbox + case "exact": + return .Exact + case "facebook": + return .Facebook + case "fitbit": + return .Fitbit + case "github": + return .Github + case "google-oauth2": + return .Google + case "instagram": + return .Instagram + case "linkedin": + return .Linkedin + case "miicard": + return .Miicard + case "paypal": + return .Paypal + case "planningcenter": + return .PlanningCenter + case "renren": + return .RenRen + case "salesforce": + return .Salesforce + case "salesforce-community": + return .SalesforceCommunity + case "salesforce-sandbox": + return .SalesforceSandbox + case "shopify": + return .Shopify + case "soundcloud": + return .Soundcloud + case "thecity": + return .TheCity + case "thecity-sandbox": + return .TheCitySandbox + case "thirtysevensignals": + return .ThirtySevenSignals + case "twitter": + return .Twitter + case "vkontakte": + return .Vkontakte + case "windowslive": + return .Microsoft + case "wordpress": + return .Wordpress + case "yahoo": + return .Yahoo + case "yammer": + return .Yammer + case "yandex": + return .Yandex + case "weibo": + return .Weibo + default: + return AuthStyle(name: connectionName) + } + } } \ No newline at end of file diff --git a/LockTests/Interactors/CDNLoaderInteractorSpec.swift b/LockTests/Interactors/CDNLoaderInteractorSpec.swift index 09bb94d99..edd1852fb 100644 --- a/LockTests/Interactors/CDNLoaderInteractorSpec.swift +++ b/LockTests/Interactors/CDNLoaderInteractorSpec.swift @@ -156,10 +156,19 @@ class CDNLoaderInteractorSpec: QuickSpec { expect(connections?.oauth2).toEventuallyNot(beNil()) let oauth2 = connections?.oauth2.first expect(oauth2?.name) == "steam" - expect(oauth2?.style.name) == "oauth2" + expect(oauth2?.style.name) == "steam" expect(oauth2?.style.color) == .a0_orange } + it("should load first class social connections") { + stub(isCDN(forClientId: clientId)) { _ in return Auth0Stubs.strategiesFromCDN([mockStrategy("github", connections: [mockOAuth2("random")])]) } + loader.load(callback) + expect(connections?.oauth2).toEventuallyNot(beNil()) + let oauth2 = connections?.oauth2.first + expect(oauth2?.name) == "random" + expect(oauth2?.style) == .Github + } + it("should load multiple oauth2 connections") { stub(isCDN(forClientId: clientId)) { _ in return Auth0Stubs.strategiesFromCDN([mockStrategy("facebook", connections: [mockOAuth2("facebook1"), mockOAuth2("facebook2")])]) } loader.load(callback) diff --git a/LockTests/Models/AuthStyleSpec.swift b/LockTests/Models/AuthStyleSpec.swift index 3ba29d7ac..b8b7b6caa 100644 --- a/LockTests/Models/AuthStyleSpec.swift +++ b/LockTests/Models/AuthStyleSpec.swift @@ -142,6 +142,66 @@ class AuthStyleSpec: QuickSpec { itBehavesLike(FirstClassStyleExample) { return style } } } + + describe("style for strategy") { + + it("should default to auth0 style") { + let style = AuthStyle.style(forStrategy: "random", connectionName: "connection") + expect(style.name) == "connection" + expect(style.color) == UIColor.a0_orange + } + + [ + ("amazon", AuthStyle.Amazon), + ("aol", AuthStyle.Aol), + ("baidu", AuthStyle.Baidu), + ("bitbucket", AuthStyle.Bitbucket), + ("dropbox", AuthStyle.Dropbox), + ("dwolla", AuthStyle.Dwolla), + ("ebay", AuthStyle.Ebay), + ("evernote", AuthStyle.Evernote), + ("evernote-sandbox", AuthStyle.EvernoteSandbox), + ("exact", AuthStyle.Exact), + ("facebook", AuthStyle.Facebook), + ("fitbit", AuthStyle.Fitbit), + ("github", AuthStyle.Github), + ("google-oauth2", AuthStyle.Google), + ("instagram", AuthStyle.Instagram), + ("linkedin", AuthStyle.Linkedin), + ("miicard", AuthStyle.Miicard), + ("paypal", AuthStyle.Paypal), + ("planningcenter", AuthStyle.PlanningCenter), + ("renren", AuthStyle.RenRen), + ("salesforce", AuthStyle.Salesforce), + ("salesforce-community", AuthStyle.SalesforceCommunity), + ("salesforce-sandbox", AuthStyle.SalesforceSandbox), + ("shopify", AuthStyle.Shopify), + ("soundcloud", AuthStyle.Soundcloud), + ("thecity", AuthStyle.TheCity), + ("thecity-sandbox", AuthStyle.TheCitySandbox), + ("thirtysevensignals", AuthStyle.ThirtySevenSignals), + ("twitter", AuthStyle.Twitter), + ("vkontakte", AuthStyle.Vkontakte), + ("windowslive", AuthStyle.Microsoft), + ("wordpress", AuthStyle.Wordpress), + ("yahoo", AuthStyle.Yahoo), + ("yammer", AuthStyle.Yammer), + ("yandex", AuthStyle.Yandex), + ("weibo", AuthStyle.Weibo), + ].forEach { (strategy, expected) in + it("should match \(strategy) style") { + let style = AuthStyle.style(forStrategy: strategy, connectionName: "connection1") + expect(style) == expected + } + } + } } +} + +extension AuthStyle: Equatable, CustomStringConvertible { + public var description: String { return "AuthStyle(name=\(name))" } +} +public func ==(lhs: AuthStyle, rhs: AuthStyle) -> Bool { + return lhs.name == rhs.name && lhs.color == rhs.color && lhs.image.name == rhs.image.name } \ No newline at end of file From 3469ba5564685c1bbb9a284c5a6bc0734548e9dd Mon Sep 17 00:00:00 2001 From: Hernan Zalazar Date: Sat, 3 Sep 2016 01:05:11 -0300 Subject: [PATCH 08/11] blacklist enterprise & passwordless from oauth2 --- CDNLoaderInteractor.swift | 23 ++++++++++++++++++- .../Interactors/CDNLoaderInteractorSpec.swift | 12 ++++++++++ 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/CDNLoaderInteractor.swift b/CDNLoaderInteractor.swift index 9ad546da3..51637b90c 100644 --- a/CDNLoaderInteractor.swift +++ b/CDNLoaderInteractor.swift @@ -105,7 +105,28 @@ private struct ClientInfo { var auth0: StrategyInfo? { return strategies.filter({ $0.name == "auth0" }).first } - var oauth2: [StrategyInfo] { return strategies.filter { $0.name != "auth0" } } + var oauth2: [StrategyInfo] { return strategies.filter { $0.name != "auth0" && !passwordlessStrategyNames.contains($0.name) && !enterpriseStrategyNames.contains($0.name) } } + + let passwordlessStrategyNames = [ + "email", + "sms" + ] + + let enterpriseStrategyNames = [ + "google-apps", + "google-openid", + "office365", + "waad", + "adfs", + "ad", + "samlp", + "pingfederate", + "ip", + "mscrm", + "custom", + "sharepoint", + ] + } private struct StrategyInfo { diff --git a/LockTests/Interactors/CDNLoaderInteractorSpec.swift b/LockTests/Interactors/CDNLoaderInteractorSpec.swift index edd1852fb..e86eb97ea 100644 --- a/LockTests/Interactors/CDNLoaderInteractorSpec.swift +++ b/LockTests/Interactors/CDNLoaderInteractorSpec.swift @@ -178,6 +178,18 @@ class CDNLoaderInteractorSpec: QuickSpec { expect(connections?.oauth2[1].name) == "facebook2" } + it("should load database & oauth2 connection") { + stub(isCDN(forClientId: clientId)) { _ in + return Auth0Stubs.strategiesFromCDN([ + mockStrategy("auth0", connections: [mockDatabaseConnection(databaseConnection)]), + mockStrategy("facebook", connections: [mockOAuth2("facebook")]) + ]) + } + loader.load(callback) + expect(connections?.database?.name).toEventually(equal(databaseConnection)) + expect(connections?.oauth2).toEventually(haveCount(1)) + } + } } From 56e8f886fe50fc21c0c5167c28c8733c4d10c4e4 Mon Sep 17 00:00:00 2001 From: Hernan Zalazar Date: Mon, 5 Sep 2016 14:42:34 -0300 Subject: [PATCH 09/11] Yield error when cannot load client info --- ConnectionLoadingPresenter.swift | 2 +- Lock/Connections.swift | 2 + Lock/Lock.swift | 6 +++ Lock/OfflineConnections.swift | 4 ++ Lock/Router.swift | 26 +++++++-- LockTests/LockSpec.swift | 9 ---- .../ConnectionLoadingPresenterSpec.swift | 15 +++++- LockTests/Router/RouterSpec.swift | 54 ++++++++++++++----- LockTests/Utils/Mocks.swift | 24 +++++++++ 9 files changed, 115 insertions(+), 27 deletions(-) diff --git a/ConnectionLoadingPresenter.swift b/ConnectionLoadingPresenter.swift index 9bbc91511..231c16cbf 100644 --- a/ConnectionLoadingPresenter.swift +++ b/ConnectionLoadingPresenter.swift @@ -34,7 +34,7 @@ class ConnectionLoadingPresenter: Presentable, Loggable { var view: View { self.loader.load { connections in - guard let connections = connections else { return } // FIXME: Show error + guard let connections = connections where !connections.isEmpty else { return self.navigator.exit(withError: UnrecoverableError.ClientWithNoConnections) } Queue.main.async { self.logger.debug("Loaded connections. Moving to root view") self.navigator.reload(withConnections: connections) diff --git a/Lock/Connections.swift b/Lock/Connections.swift index 38c1d682b..d6f1e86fc 100644 --- a/Lock/Connections.swift +++ b/Lock/Connections.swift @@ -25,6 +25,8 @@ import Foundation public protocol Connections { var database: DatabaseConnection? { get } var oauth2: [OAuth2Connection] { get } + + var isEmpty: Bool { get } } public struct DatabaseConnection { diff --git a/Lock/Lock.swift b/Lock/Lock.swift index d48a6a36d..35fabf540 100644 --- a/Lock/Lock.swift +++ b/Lock/Lock.swift @@ -171,6 +171,12 @@ public enum Result { case Cancelled } +public enum UnrecoverableError: ErrorType { + case InvalidClientOrDomain + case ClientWithNoConnections + case MissingDatabaseConnection +} + private func telemetryFor(authenticaction authentication: Authentication, webAuth: WebAuth) -> (Authentication, WebAuth) { var authentication = authentication var webAuth = webAuth diff --git a/Lock/OfflineConnections.swift b/Lock/OfflineConnections.swift index ea2586997..500b12369 100644 --- a/Lock/OfflineConnections.swift +++ b/Lock/OfflineConnections.swift @@ -35,4 +35,8 @@ struct OfflineConnections: ConnectionBuildable { let social = SocialConnection(name: name, style: style) self.oauth2.append(social) } + + var isEmpty: Bool { + return self.database == nil && self.oauth2.isEmpty + } } \ No newline at end of file diff --git a/Lock/Router.swift b/Lock/Router.swift index ea4c103df..02509128e 100644 --- a/Lock/Router.swift +++ b/Lock/Router.swift @@ -28,6 +28,7 @@ protocol Navigable { func navigate(route: Route) func resetScroll(animated: Bool) func present(controller: UIViewController) + func exit(withError error: ErrorType) } struct Router: Navigable { @@ -43,14 +44,14 @@ struct Router: Navigable { self.lock = lock self.onDismiss = { [weak controller] in Queue.main.async { - controller?.dismissViewControllerAnimated(true, completion: { _ in + controller?.presentingViewController?.dismissViewControllerAnimated(true, completion: { _ in lock.callback(.Cancelled) }) } } self.onAuthentication = { [weak controller] credentials in Queue.main.async { - controller?.dismissViewControllerAnimated(true, completion: { _ in + controller?.presentingViewController?.dismissViewControllerAnimated(true, completion: { _ in lock.callback(.Success(credentials)) }) } @@ -101,7 +102,10 @@ struct Router: Navigable { } var forgotPassword: Presentable? { - guard let connections = self.lock.connections else { return nil } // FIXME: show error screen + guard let connections = self.lock.connections else { + exit(withError: UnrecoverableError.ClientWithNoConnections) + return nil + } let interactor = DatabasePasswordInteractor(connections: connections, authentication: self.lock.authentication, user: self.user) let presenter = DatabaseForgotPasswordPresenter(interactor: interactor, connections: connections) presenter.customLogger = self.lock.logger @@ -109,7 +113,10 @@ struct Router: Navigable { } var multifactor: Presentable? { - guard let connections = self.lock.connections, let database = connections.database else { return nil } // FIXME: show error screen + guard let connections = self.lock.connections, let database = connections.database else { + exit(withError: UnrecoverableError.MissingDatabaseConnection) + return nil + } let authentication = self.lock.authentication let interactor = MultifactorInteractor(user: self.user, authentication: authentication, connection: database, callback: self.onAuthentication) let presenter = MultifactorPresenter(interactor: interactor, connection: database) @@ -156,4 +163,15 @@ struct Router: Navigable { func resetScroll(animated: Bool) { self.controller?.scrollView.setContentOffset(CGPointZero, animated: animated) } + + func exit(withError error: ErrorType) { + let controller = self.controller?.presentingViewController + let lock = self.lock + self.lock.logger.debug("Dismissing Lock with error \(error)") + Queue.main.async { + controller?.dismissViewControllerAnimated(true, completion: { _ in + lock.callback(.Failure(error)) + }) + } + } } \ No newline at end of file diff --git a/LockTests/LockSpec.swift b/LockTests/LockSpec.swift index 9b49af925..7fc8e28fe 100644 --- a/LockTests/LockSpec.swift +++ b/LockTests/LockSpec.swift @@ -120,13 +120,4 @@ class LockSpec: QuickSpec { } -} - -class MockController: UIViewController { - - var presented: UIViewController? - - override func presentViewController(viewControllerToPresent: UIViewController, animated flag: Bool, completion: (() -> Void)?) { - self.presented = viewControllerToPresent - } } \ No newline at end of file diff --git a/LockTests/Presenters/ConnectionLoadingPresenterSpec.swift b/LockTests/Presenters/ConnectionLoadingPresenterSpec.swift index cff54d022..e3e28aada 100644 --- a/LockTests/Presenters/ConnectionLoadingPresenterSpec.swift +++ b/LockTests/Presenters/ConnectionLoadingPresenterSpec.swift @@ -47,11 +47,24 @@ class ConnectionLoadingPresenterSpec: QuickSpec { } it("should reload when connections are obtained") { - let connections: Connections = OfflineConnections() + var connections = OfflineConnections() + connections.database(name: connection, requiresUsername: false) interactor.connections = connections presenter.view expect(navigator.connections).toEventuallyNot(beNil()) } + it("should exit with error when failed to get connections") { + interactor.connections = nil + presenter.view + expect(navigator.unrecoverableError).toEventuallyNot(beNil()) + } + + it("should exit with error when there are no connections") { + interactor.connections = OfflineConnections() + presenter.view + expect(navigator.unrecoverableError).toEventuallyNot(beNil()) + } + } } diff --git a/LockTests/Router/RouterSpec.swift b/LockTests/Router/RouterSpec.swift index 21b8d4b2a..983b0728a 100644 --- a/LockTests/Router/RouterSpec.swift +++ b/LockTests/Router/RouterSpec.swift @@ -80,6 +80,7 @@ class RouterSpec: QuickSpec { describe("events") { it("should call callback with auth result") { + controller.presenting = MockController() let credentials = Credentials(accessToken: "ACCESS_TOKEN", tokenType: "bearer") waitUntil(timeout: 2) { done in lock.callback = { result in @@ -141,24 +142,52 @@ class RouterSpec: QuickSpec { router.onBack() expect(router.user.password).to(beNil()) } + } + + describe("exit") { + + var presenting: MockController! + beforeEach { + presenting = MockController() + presenting.presented = controller + controller.presenting = presenting + } + + it("should dismiss controller") { + router.exit(withError: UnrecoverableError.InvalidClientOrDomain) + expect(presenting.presented).toEventually(beNil()) + } + + it("should pass error in callback") { + waitUntil(timeout: 2) { done in + lock.callback = { result in + if case .Failure(let cause) = result, case UnrecoverableError.InvalidClientOrDomain = cause { + done() + } + } + router.exit(withError: UnrecoverableError.InvalidClientOrDomain) + } + } } } - it("should not show root again") { - expect(controller.routes.current).toNot(beNil()) - router.navigate(.Root) - expect(controller.presentable).to(beNil()) - } + describe("navigate") { + it("should not show root again") { + expect(controller.routes.current).toNot(beNil()) + router.navigate(.Root) + expect(controller.presentable).to(beNil()) + } - it("should show forgot pwd screen") { - router.navigate(.ForgotPassword) - expect(controller.presentable as? DatabaseForgotPasswordPresenter).toNot(beNil()) - } + it("should show forgot pwd screen") { + router.navigate(.ForgotPassword) + expect(controller.presentable as? DatabaseForgotPasswordPresenter).toNot(beNil()) + } - it("should show multifactor screen") { - router.navigate(.Multifactor) - expect(controller.presentable as? MultifactorPresenter).toNot(beNil()) + it("should show multifactor screen") { + router.navigate(.Multifactor) + expect(controller.presentable as? MultifactorPresenter).toNot(beNil()) + } } it("should present view controller") { @@ -166,6 +195,7 @@ class RouterSpec: QuickSpec { router.present(presented) expect(controller.presented) == presented } + } } \ No newline at end of file diff --git a/LockTests/Utils/Mocks.swift b/LockTests/Utils/Mocks.swift index 8a921b6cc..b44ef047d 100644 --- a/LockTests/Utils/Mocks.swift +++ b/LockTests/Utils/Mocks.swift @@ -26,6 +26,7 @@ import Auth0 class MockLockController: LockViewController { + var presenting: UIViewController? var presented: UIViewController? var presentable: Presentable? @@ -41,6 +42,10 @@ class MockLockController: LockViewController { override func present(presentable: Presentable?) { self.presentable = presentable } + + override var presentingViewController: UIViewController? { + return self.presenting + } } class MockAuthPresenter: AuthPresenter { @@ -58,6 +63,7 @@ class MockNavigator: Navigable { var resetted: Bool = false var presented: UIViewController? = nil var connections: Connections? = nil + var unrecoverableError: ErrorType? = nil func navigate(route: Route) { self.route = route @@ -74,6 +80,10 @@ class MockNavigator: Navigable { func reload(withConnections connections: Connections) { self.connections = connections } + + func exit(withError error: ErrorType) { + self.unrecoverableError = error + } } func mockInput(type: InputField.InputType, value: String? = nil) -> MockInputField { @@ -235,4 +245,18 @@ class MockOAuth2: OAuth2Authenticatable { self.connection = connection callback(self.onLogin()) } +} + +class MockController: UIViewController { + + var presented: UIViewController? + + override func presentViewController(viewControllerToPresent: UIViewController, animated flag: Bool, completion: (() -> Void)?) { + self.presented = viewControllerToPresent + } + + override func dismissViewControllerAnimated(flag: Bool, completion: (() -> Void)?) { + self.presented = nil + completion?() + } } \ No newline at end of file From 6e0823e2a21e4f39de9634a743082f3cc6a47ba2 Mon Sep 17 00:00:00 2001 From: Hernan Zalazar Date: Mon, 5 Sep 2016 15:40:14 -0300 Subject: [PATCH 10/11] Allow to filter connection by name --- Lock.xcodeproj/project.pbxproj | 4 + Lock/ConnectionBuildable.swift | 9 ++ Lock/OfflineConnections.swift | 21 +++- LockTests/Models/OfflineConnectionsSpec.swift | 108 ++++++++++++++++++ 4 files changed, 140 insertions(+), 2 deletions(-) create mode 100644 LockTests/Models/OfflineConnectionsSpec.swift diff --git a/Lock.xcodeproj/project.pbxproj b/Lock.xcodeproj/project.pbxproj index 1e994bdc3..86e98f616 100644 --- a/Lock.xcodeproj/project.pbxproj +++ b/Lock.xcodeproj/project.pbxproj @@ -106,6 +106,7 @@ 5FE50DBB1D79B7CD00D82290 /* CDNLoaderInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5FE50DBA1D79B7CD00D82290 /* CDNLoaderInteractor.swift */; }; 5FE50DBD1D79B8AD00D82290 /* CDNLoaderInteractorSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5FE50DBC1D79B8AD00D82290 /* CDNLoaderInteractorSpec.swift */; }; 5FE50DBF1D7A254000D82290 /* ConnectionLoadingPresenterSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5FE50DBE1D7A254000D82290 /* ConnectionLoadingPresenterSpec.swift */; }; + 5FE50DC11D7DED8C00D82290 /* OfflineConnectionsSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5FE50DC01D7DED8C00D82290 /* OfflineConnectionsSpec.swift */; }; 5FEADCEF1D1A76610032D810 /* Auth0.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5F814FD21D1A7294003670A4 /* Auth0.framework */; }; 5FEADCF71D1A7EBC0032D810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5FEADCF61D1A7EBC0032D810 /* AppDelegate.swift */; }; 5FEADCF91D1A7EBC0032D810 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5FEADCF81D1A7EBC0032D810 /* ViewController.swift */; }; @@ -279,6 +280,7 @@ 5FE50DBA1D79B7CD00D82290 /* CDNLoaderInteractor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CDNLoaderInteractor.swift; sourceTree = SOURCE_ROOT; }; 5FE50DBC1D79B8AD00D82290 /* CDNLoaderInteractorSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CDNLoaderInteractorSpec.swift; sourceTree = ""; }; 5FE50DBE1D7A254000D82290 /* ConnectionLoadingPresenterSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConnectionLoadingPresenterSpec.swift; sourceTree = ""; }; + 5FE50DC01D7DED8C00D82290 /* OfflineConnectionsSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OfflineConnectionsSpec.swift; sourceTree = ""; }; 5FEADCF41D1A7EBC0032D810 /* LockApp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = LockApp.app; sourceTree = BUILT_PRODUCTS_DIR; }; 5FEADCF61D1A7EBC0032D810 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 5FEADCF81D1A7EBC0032D810 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; @@ -495,6 +497,7 @@ children = ( 5FBE5CCC1D3EDF960038536D /* UserSpec.swift */, 5F92C68C1D50E47100CCE6C0 /* AuthStyleSpec.swift */, + 5FE50DC01D7DED8C00D82290 /* OfflineConnectionsSpec.swift */, ); path = Models; sourceTree = ""; @@ -958,6 +961,7 @@ 5FE50DBD1D79B8AD00D82290 /* CDNLoaderInteractorSpec.swift in Sources */, 5F390E8D1D63B99300FC549C /* LoggerSpec.swift in Sources */, 5F2037C21D5D02880005D2E2 /* Matchers.swift in Sources */, + 5FE50DC11D7DED8C00D82290 /* OfflineConnectionsSpec.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Lock/ConnectionBuildable.swift b/Lock/ConnectionBuildable.swift index 6fc085650..d7bcbc167 100644 --- a/Lock/ConnectionBuildable.swift +++ b/Lock/ConnectionBuildable.swift @@ -45,4 +45,13 @@ public protocol ConnectionBuildable: Connections { - seeAlso: AuthStyle */ mutating func social(name name: String, style: AuthStyle) + + /** + Adds a new oauth2 connection + + - parameter name: name of the connection + - parameter style: style used for the button used to trigger authentication + - seeAlso: AuthStyle + */ + mutating func oauth2(name name: String, style: AuthStyle) } diff --git a/Lock/OfflineConnections.swift b/Lock/OfflineConnections.swift index 500b12369..42176bad3 100644 --- a/Lock/OfflineConnections.swift +++ b/Lock/OfflineConnections.swift @@ -24,14 +24,27 @@ import Foundation struct OfflineConnections: ConnectionBuildable { - var database: DatabaseConnection? = nil - var oauth2: [OAuth2Connection] = [] + var database: DatabaseConnection? + var oauth2: [OAuth2Connection] + let allowedConnections: [String] + + init(database: DatabaseConnection? = nil, oauth2: [OAuth2Connection] = [], allowedConnections: [String] = []) { + self.database = database + self.oauth2 = oauth2 + self.allowedConnections = allowedConnections + } mutating func database(name name: String, requiresUsername: Bool) { + guard isAllowed(connectionName: name) else { return } self.database = DatabaseConnection(name: name, requiresUsername: requiresUsername) } mutating func social(name name: String, style: AuthStyle) { + self.oauth2(name: name, style: style) + } + + mutating func oauth2(name name: String, style: AuthStyle) { + guard isAllowed(connectionName: name) else { return } let social = SocialConnection(name: name, style: style) self.oauth2.append(social) } @@ -39,4 +52,8 @@ struct OfflineConnections: ConnectionBuildable { var isEmpty: Bool { return self.database == nil && self.oauth2.isEmpty } + + private func isAllowed(connectionName name: String) -> Bool { + return self.allowedConnections.isEmpty || !self.allowedConnections.contains(name) + } } \ No newline at end of file diff --git a/LockTests/Models/OfflineConnectionsSpec.swift b/LockTests/Models/OfflineConnectionsSpec.swift new file mode 100644 index 000000000..ba7e83e7c --- /dev/null +++ b/LockTests/Models/OfflineConnectionsSpec.swift @@ -0,0 +1,108 @@ +// OfflineConnectionsSpec.swift +// +// Copyright (c) 2016 Auth0 (http://auth0.com) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import Quick +import Nimble + +@testable import Lock + +class OfflineConnectionsSpec: QuickSpec { + + override func spec() { + + it("should report if there are no conenctions") { + let connections = OfflineConnections() + expect(connections.isEmpty) == true + } + + it("should add a database connection") { + var connections = OfflineConnections() + connections.database(name: connection, requiresUsername: false) + expect(connections.isEmpty) == false + expect(connections.database?.name) == connection + expect(connections.database?.requiresUsername) == false + } + + it("should add a social connection") { + var connections = OfflineConnections() + connections.social(name: connection, style: .Facebook) + expect(connections.isEmpty) == false + expect(connections.oauth2.first?.name) == connection + expect(connections.oauth2.first?.style) == .Facebook + } + + it("should add a oauth2 connection") { + var connections = OfflineConnections() + let style = AuthStyle(name: "Steam") + connections.oauth2(name: connection, style: style) + expect(connections.isEmpty) == false + expect(connections.oauth2.first?.name) == connection + expect(connections.oauth2.first?.style) == style + } + + describe("filter") { + + it("should not filter by default") { + var connections = OfflineConnections() + connections.database(name: connection, requiresUsername: false) + connections.social(name: "facebook", style: .Facebook) + expect(connections.database).toNot(beNil()) + expect(connections.oauth2).toNot(beEmpty()) + } + + it("should not filter with empty list") { + var connections = OfflineConnections(allowedConnections: []) + connections.database(name: connection, requiresUsername: false) + connections.social(name: "facebook", style: .Facebook) + expect(connections.database).toNot(beNil()) + expect(connections.oauth2).toNot(beEmpty()) + } + + it("should filter by name social") { + var connections = OfflineConnections(allowedConnections: ["facebook"]) + connections.database(name: connection, requiresUsername: false) + connections.social(name: "facebook", style: .Facebook) + expect(connections.database).toNot(beNil()) + expect(connections.oauth2).to(beEmpty()) + } + + it("should filter by name oauth2") { + var connections = OfflineConnections(allowedConnections: ["steam"]) + connections.database(name: connection, requiresUsername: false) + connections.oauth2(name: "steam", style: AuthStyle(name: "Steam")) + expect(connections.database).toNot(beNil()) + expect(connections.oauth2).to(beEmpty()) + } + + it("should filter by name database connection") { + var connections = OfflineConnections(allowedConnections: [connection]) + connections.database(name: connection, requiresUsername: false) + connections.social(name: "facebook", style: .Facebook) + expect(connections.database).to(beNil()) + expect(connections.oauth2).toNot(beEmpty()) + } + + } + + } + +} \ No newline at end of file From 337d003a05fdb07c6c6a3385401049fef46b4621 Mon Sep 17 00:00:00 2001 From: Hernan Zalazar Date: Mon, 5 Sep 2016 19:01:48 -0300 Subject: [PATCH 11/11] Allow to select only a set of connections to use --- App/ViewController.swift | 5 +- Lock/AuthPresenter.swift | 1 - Lock/Connections.swift | 9 ++++ Lock/DatabasePresenter.swift | 1 - Lock/Lock.swift | 27 +++++++++- Lock/OfflineConnections.swift | 28 +++++------ Lock/Router.swift | 18 ++++--- LockTests/LockSpec.swift | 6 +-- LockTests/Models/OfflineConnectionsSpec.swift | 44 ++++++++-------- LockTests/Router/RouterSpec.swift | 50 +++++++++++++++++++ 10 files changed, 136 insertions(+), 53 deletions(-) diff --git a/App/ViewController.swift b/App/ViewController.swift index b6af2b981..7ad1e95e1 100644 --- a/App/ViewController.swift +++ b/App/ViewController.swift @@ -47,7 +47,9 @@ class ViewController: UIViewController { let cdnLoading = AuthButton(size: .Big) cdnLoading.title = "LOGIN WITH CDN" cdnLoading.onPress = { [weak self] _ in - let lock = Lock.classic() + let lock = Lock + .classic() + .allowedConnections(["github", "instagram", "Username-Password-Authentication"]) self?.showLock(lock) } let databaseOnly = AuthButton(size: .Big) @@ -77,6 +79,7 @@ class ViewController: UIViewController { socialOnly.onPress = { [weak self] _ in let lock = Lock .classic() + .allowedConnections(["facebook", "google-oauth2", "twitter", "dropbox", "bitbucket"]) .withConnections { connections in connections.social(name: "facebook", style: .Facebook) connections.social(name: "google-oauth2", style: .Google) diff --git a/Lock/AuthPresenter.swift b/Lock/AuthPresenter.swift index 6c14db749..ca17361c5 100644 --- a/Lock/AuthPresenter.swift +++ b/Lock/AuthPresenter.swift @@ -29,7 +29,6 @@ class AuthPresenter: Presentable, Loggable { let interactor: OAuth2Authenticatable var messagePresenter: MessagePresenter? - var customLogger: Logger? init(connections: Connections, interactor: OAuth2Authenticatable) { self.connections = connections.oauth2 diff --git a/Lock/Connections.swift b/Lock/Connections.swift index d6f1e86fc..afdf69ea0 100644 --- a/Lock/Connections.swift +++ b/Lock/Connections.swift @@ -27,6 +27,15 @@ public protocol Connections { var oauth2: [OAuth2Connection] { get } var isEmpty: Bool { get } + + /** + Select only the connections whose names are in the array + + - parameter names: names of the connections to keep + + - returns: filtered connections + */ + func select(byNames names: [String]) -> Self } public struct DatabaseConnection { diff --git a/Lock/DatabasePresenter.swift b/Lock/DatabasePresenter.swift index 0b415ab42..9b2b9e19f 100644 --- a/Lock/DatabasePresenter.swift +++ b/Lock/DatabasePresenter.swift @@ -34,7 +34,6 @@ class DatabasePresenter: Presentable, Loggable { var messagePresenter: MessagePresenter? var authPresenter: AuthPresenter? - var customLogger: Logger? var initialEmail: String? { return self.authenticator.validEmail ? self.authenticator.email : nil } var initialUsername: String? { return self.authenticator.validUsername ? self.authenticator.username : nil } diff --git a/Lock/Lock.swift b/Lock/Lock.swift index 35fabf540..33826199a 100644 --- a/Lock/Lock.swift +++ b/Lock/Lock.swift @@ -34,7 +34,8 @@ public class Lock: NSObject { let authentication: Authentication let webAuth: WebAuth - var connections: Connections? = nil + var connectionProvider: ConnectionProvider = ConnectionProvider(local: OfflineConnections(), allowed: []) + var connections: Connections { return self.connectionProvider.connections } private var optionsBuilder: OptionBuildable = LockOptions() var options: Options { return self.optionsBuilder } @@ -112,7 +113,22 @@ public class Lock: NSObject { public func withConnections(closure: (inout ConnectionBuildable) -> ()) -> Lock { var connections: ConnectionBuildable = OfflineConnections() closure(&connections) - self.connections = connections + let allowed = self.connectionProvider.allowed + self.connectionProvider = ConnectionProvider(local: connections, allowed: allowed) + return self + } + + /** + Specify what connections should be used by Lock. + By default it will use all connections enabled or if an empty list is used + + - parameter allowedConnections: list of connection names to use + + - returns: Lock itself for chaining + */ + public func allowedConnections(allowedConnections: [String]) -> Lock { + let connections = self.connectionProvider.connections + self.connectionProvider = ConnectionProvider(local: connections, allowed: allowedConnections) return self } @@ -165,6 +181,13 @@ public class Lock: NSObject { } } +struct ConnectionProvider { + let local: Connections + let allowed: [String] + + var connections: Connections { return local.select(byNames: allowed) } +} + public enum Result { case Success(Credentials) case Failure(ErrorType) diff --git a/Lock/OfflineConnections.swift b/Lock/OfflineConnections.swift index 42176bad3..0f970ded3 100644 --- a/Lock/OfflineConnections.swift +++ b/Lock/OfflineConnections.swift @@ -24,18 +24,10 @@ import Foundation struct OfflineConnections: ConnectionBuildable { - var database: DatabaseConnection? - var oauth2: [OAuth2Connection] - let allowedConnections: [String] - - init(database: DatabaseConnection? = nil, oauth2: [OAuth2Connection] = [], allowedConnections: [String] = []) { - self.database = database - self.oauth2 = oauth2 - self.allowedConnections = allowedConnections - } + private (set) var database: DatabaseConnection? = nil + private (set) var oauth2: [OAuth2Connection] = [] mutating func database(name name: String, requiresUsername: Bool) { - guard isAllowed(connectionName: name) else { return } self.database = DatabaseConnection(name: name, requiresUsername: requiresUsername) } @@ -44,7 +36,6 @@ struct OfflineConnections: ConnectionBuildable { } mutating func oauth2(name name: String, style: AuthStyle) { - guard isAllowed(connectionName: name) else { return } let social = SocialConnection(name: name, style: style) self.oauth2.append(social) } @@ -53,7 +44,16 @@ struct OfflineConnections: ConnectionBuildable { return self.database == nil && self.oauth2.isEmpty } - private func isAllowed(connectionName name: String) -> Bool { - return self.allowedConnections.isEmpty || !self.allowedConnections.contains(name) + func select(byNames names: [String]) -> OfflineConnections { + var connections = OfflineConnections() + if let database = self.database where isWhitelisted(connectionName: database.name, inList: names) { + connections.database = database + } + connections.oauth2 = self.oauth2.filter { isWhitelisted(connectionName: $0.name, inList: names) } + return connections } -} \ No newline at end of file +} + +private func isWhitelisted(connectionName name: String, inList whitelist: [String]) -> Bool { + return whitelist.isEmpty || whitelist.contains(name) +} diff --git a/Lock/Router.swift b/Lock/Router.swift index 02509128e..d203c61ea 100644 --- a/Lock/Router.swift +++ b/Lock/Router.swift @@ -75,7 +75,9 @@ struct Router: Navigable { } var root: Presentable? { - guard let connections = self.lock.connections else { + let connections = self.lock.connections + guard !connections.isEmpty else { + self.lock.logger.debug("No connections configured. Loading client info from Auth0...") let interactor = CDNLoaderInteractor(baseURL: self.lock.authentication.url, clientId: self.lock.authentication.clientId) return ConnectionLoadingPresenter(loader: interactor, navigator: self) } @@ -88,21 +90,20 @@ struct Router: Navigable { let interactor = Auth0OAuth2Interactor(webAuth: self.lock.webAuth, onCredentials: self.onAuthentication) presenter.authPresenter = AuthPresenter(connections: connections, interactor: interactor) } - presenter.customLogger = self.lock.logger return presenter } if !connections.oauth2.isEmpty { let interactor = Auth0OAuth2Interactor(webAuth: self.lock.webAuth, onCredentials: self.onAuthentication) let presenter = AuthPresenter(connections: connections, interactor: interactor) - presenter.customLogger = self.lock.logger return presenter } return nil } var forgotPassword: Presentable? { - guard let connections = self.lock.connections else { + let connections = self.lock.connections + guard !connections.isEmpty else { exit(withError: UnrecoverableError.ClientWithNoConnections) return nil } @@ -113,7 +114,8 @@ struct Router: Navigable { } var multifactor: Presentable? { - guard let connections = self.lock.connections, let database = connections.database else { + let connections = self.lock.connections + guard let database = connections.database else { exit(withError: UnrecoverableError.MissingDatabaseConnection) return nil } @@ -132,8 +134,10 @@ struct Router: Navigable { var onBack: () -> () = {} func reload(withConnections connections: Connections) { - self.lock.connections = connections - self.lock.logger.debug("Reloading Lock") + self.lock.connectionProvider = ConnectionProvider(local: connections, allowed: self.lock.connectionProvider.allowed) + let connections = self.lock.connections + self.lock.logger.debug("Reloading Lock with connections \(connections).") + guard !connections.isEmpty else { return exit(withError: UnrecoverableError.ClientWithNoConnections) } self.controller?.routes.reset() self.controller?.present(self.root) } diff --git a/LockTests/LockSpec.swift b/LockTests/LockSpec.swift index 7fc8e28fe..0ccb44f86 100644 --- a/LockTests/LockSpec.swift +++ b/LockTests/LockSpec.swift @@ -80,17 +80,17 @@ class LockSpec: QuickSpec { it("should allow settings connections") { lock.withConnections { $0.database(name: "MyDB", requiresUsername: false) } - expect(lock.connections?.database?.name) == "MyDB" + expect(lock.connections.database?.name) == "MyDB" } it("should have defaults if never called") { - expect(lock.connections?.database).to(beNil()) + expect(lock.connections.database).to(beNil()) } it("should use the latest options") { lock.withConnections { $0.database(name: "MyDB", requiresUsername: false) } lock.withConnections { $0.database(name: "AnotherDB", requiresUsername: false) } - expect(lock.connections?.database?.name) == "AnotherDB" + expect(lock.connections.database?.name) == "AnotherDB" } it("should return itself") { diff --git a/LockTests/Models/OfflineConnectionsSpec.swift b/LockTests/Models/OfflineConnectionsSpec.swift index ba7e83e7c..c718c1c8f 100644 --- a/LockTests/Models/OfflineConnectionsSpec.swift +++ b/LockTests/Models/OfflineConnectionsSpec.swift @@ -59,46 +59,42 @@ class OfflineConnectionsSpec: QuickSpec { expect(connections.oauth2.first?.style) == style } - describe("filter") { + describe("select") { - it("should not filter by default") { + it("should do nothing with empty list") { var connections = OfflineConnections() connections.database(name: connection, requiresUsername: false) connections.social(name: "facebook", style: .Facebook) - expect(connections.database).toNot(beNil()) - expect(connections.oauth2).toNot(beEmpty()) + let filtered = connections.select(byNames: []) + expect(filtered.database).toNot(beNil()) + expect(filtered.oauth2).toNot(beEmpty()) } - it("should not filter with empty list") { - var connections = OfflineConnections(allowedConnections: []) - connections.database(name: connection, requiresUsername: false) - connections.social(name: "facebook", style: .Facebook) - expect(connections.database).toNot(beNil()) - expect(connections.oauth2).toNot(beEmpty()) - } - - it("should filter by name social") { - var connections = OfflineConnections(allowedConnections: ["facebook"]) + it("should select by name a social connection") { + var connections = OfflineConnections() connections.database(name: connection, requiresUsername: false) connections.social(name: "facebook", style: .Facebook) - expect(connections.database).toNot(beNil()) - expect(connections.oauth2).to(beEmpty()) + let filtered = connections.select(byNames: ["facebook"]) + expect(filtered.database).to(beNil()) + expect(filtered.oauth2).toNot(beEmpty()) } - it("should filter by name oauth2") { - var connections = OfflineConnections(allowedConnections: ["steam"]) + it("should select by name an oauth2 connection") { + var connections = OfflineConnections() connections.database(name: connection, requiresUsername: false) connections.oauth2(name: "steam", style: AuthStyle(name: "Steam")) - expect(connections.database).toNot(beNil()) - expect(connections.oauth2).to(beEmpty()) + let filtered = connections.select(byNames: ["steam"]) + expect(filtered.database).to(beNil()) + expect(filtered.oauth2).toNot(beEmpty()) } - it("should filter by name database connection") { - var connections = OfflineConnections(allowedConnections: [connection]) + it("should select by name database connection") { + var connections = OfflineConnections() connections.database(name: connection, requiresUsername: false) connections.social(name: "facebook", style: .Facebook) - expect(connections.database).to(beNil()) - expect(connections.oauth2).toNot(beEmpty()) + let filtered = connections.select(byNames: [connection]) + expect(filtered.database).toNot(beNil()) + expect(filtered.oauth2).to(beEmpty()) } } diff --git a/LockTests/Router/RouterSpec.swift b/LockTests/Router/RouterSpec.swift index 983b0728a..d25af18cb 100644 --- a/LockTests/Router/RouterSpec.swift +++ b/LockTests/Router/RouterSpec.swift @@ -196,6 +196,56 @@ class RouterSpec: QuickSpec { expect(controller.presented) == presented } + describe("reload") { + + beforeEach { + let presenting = MockController() + presenting.presented = controller + controller.presenting = presenting + } + + it("should override connections") { + var connections = OfflineConnections() + connections.social(name: "facebook", style: .Facebook) + router.reload(withConnections: connections) + let actual = router.lock.connectionProvider.connections + expect(actual.isEmpty) == false + expect(actual.oauth2.map { $0.name }).to(contain("facebook")) + } + + it("should show root") { + var connections = OfflineConnections() + connections.social(name: "facebook", style: .Facebook) + router.reload(withConnections: connections) + expect(controller.presentable).toNot(beNil()) + expect(controller.routes.history).to(beEmpty()) + } + + it("should select when overriding connections") { + lock = Lock(authentication: Auth0.authentication(clientId: "CLIENT_ID", domain: "samples.auth0.com"), webAuth: MockWebAuth()).allowedConnections(["facebook"]) + controller = MockLockController(lock: lock) + router = Router(lock: lock, controller: controller) + var connections = OfflineConnections() + connections.social(name: "facebook", style: .Facebook) + connections.social(name: "twitter", style: .Twitter) + router.reload(withConnections: connections) + let actual = router.lock.connectionProvider.connections + expect(actual.oauth2.map { $0.name }).toNot(contain("twitter")) + expect(actual.oauth2.map { $0.name }).to(contain("facebook")) + } + + it("should exit with error when connections are empty") { + waitUntil(timeout: 2) { done in + lock.callback = { result in + if case .Failure(let cause) = result, case UnrecoverableError.ClientWithNoConnections = cause { + done() + } + } + router.reload(withConnections: OfflineConnections()) + } + } + + } } } \ No newline at end of file