diff --git a/Cartfile b/Cartfile
index 76725caba..8070c9681 100644
--- a/Cartfile
+++ b/Cartfile
@@ -1,7 +1,7 @@
github "Quick/Quick"
github "Quick/Nimble"
github "ReactiveX/RxSwift"
-github "RxSwiftCommunity/RxDataSources" "026857261128bf552369a3b69b3ab56c3f85704d"
+github "RxSwiftCommunity/RxDataSources" "81b709c28b8ceb5cddc8c2262d009ea8735f0893"
github "RxSwiftCommunity/RxOptional" ~> 3.1.3
github "mozilla-mobile/telemetry-ios"
github "jrendel/SwiftKeychainWrapper" ~> 3.0
diff --git a/Cartfile.resolved b/Cartfile.resolved
index 2d36f2dc4..f242350d3 100644
--- a/Cartfile.resolved
+++ b/Cartfile.resolved
@@ -3,7 +3,7 @@ github "DaveWoodCom/XCGLogger" "Version_4.0.0"
github "Quick/Nimble" "v7.3.0"
github "Quick/Quick" "v1.3.1"
github "ReactiveX/RxSwift" "4.2.0"
-github "RxSwiftCommunity/RxDataSources" "026857261128bf552369a3b69b3ab56c3f85704d"
+github "RxSwiftCommunity/RxDataSources" "81b709c28b8ceb5cddc8c2262d009ea8735f0893"
github "RxSwiftCommunity/RxOptional" "3.5.0"
github "SwiftyJSON/SwiftyJSON" "3.1.4"
github "adjust/ios_sdk" "v4.15.0"
diff --git a/CredentialProvider/Info.plist b/CredentialProvider/Info.plist
index a5e08053e..752866b59 100644
--- a/CredentialProvider/Info.plist
+++ b/CredentialProvider/Info.plist
@@ -17,7 +17,7 @@
CFBundlePackageType
XPC!
CFBundleShortVersionString
- 1.3
+ 1.3.1
CFBundleVersion
1
MozDevelopmentTeam
diff --git a/Lockbox.xcodeproj/xcshareddata/xcschemes/uispecs.xcscheme b/Lockbox.xcodeproj/xcshareddata/xcschemes/uispecs.xcscheme
index 54a0af7af..4cb58824c 100644
--- a/Lockbox.xcodeproj/xcshareddata/xcschemes/uispecs.xcscheme
+++ b/Lockbox.xcodeproj/xcshareddata/xcschemes/uispecs.xcscheme
@@ -5,6 +5,16 @@
+
+
+
+
+
+
MMScr
app.switches["sendUsageData.switch"].tap()
}
+ screenState.gesture(forAction: Action.OpenDeviceSettings) { userState in
+ app.cells["autoFillSettingsOption"].tap()
+
+ }
+
screenState.gesture(forAction: Action.LockNow) { userState in
app.buttons["lockNow.button"].tap()
}
@@ -172,6 +178,19 @@ func createScreenGraph(for test: XCTestCase, with app: XCUIApplication) -> MMScr
extension BaseTestCase {
+ func disconnectAndConnectAccount() {
+ navigator.performAction(Action.DisconnectFirefoxLockbox)
+ // And, connect it again
+ waitforExistence(app.buttons["getStarted.button"])
+ app.buttons["getStarted.button"].tap()
+ userState.fxaUsername = emailTestAccountLogins
+ userState.fxaPassword = passwordTestAccountLogins
+ waitforExistence(app.webViews.textFields["Email"], timeout: 10)
+ navigator.nowAt(Screen.FxASigninScreenEmail)
+ navigator.performAction(Action.FxATypeEmail)
+ navigator.performAction(Action.FxATypePassword)
+ }
+
func checkIfAccountIsVerified() {
if (app.webViews.staticTexts["Confirm this sign-in"].exists) {
let group = DispatchGroup()
diff --git a/LockboxXCUITests/LockboxXCUITests.swift b/LockboxXCUITests/LockboxXCUITests.swift
index b4445ae17..758f84e8f 100644
--- a/LockboxXCUITests/LockboxXCUITests.swift
+++ b/LockboxXCUITests/LockboxXCUITests.swift
@@ -41,8 +41,9 @@ class LockboxXCUITests: BaseTestCase {
navigator.performAction(Action.FxATypeEmail)
waitforExistence(app.webViews.secureTextFields["Password"])
navigator.performAction(Action.FxATypePassword)
- // Lets try to remove this sleep(10) and see if the tests consistently pass
- // Check if the account is verified and if not, verify it
+ // When the account is unverified, it is necessary to wait here while finding a different workaround
+ sleep(5)
+ // Check if the account is verified and if not, verify it
checkIfAccountIsVerified()
if #available(iOS 12.0, *) {
@@ -134,17 +135,7 @@ class LockboxXCUITests: BaseTestCase {
waitforExistence(app.buttons["disconnectFirefoxLockbox.button"])
// Now disconnect the account
- navigator.performAction(Action.DisconnectFirefoxLockbox)
-
- // And, connect it again
- waitforExistence(app.buttons["getStarted.button"])
- app.buttons["getStarted.button"].tap()
- userState.fxaUsername = emailTestAccountLogins
- userState.fxaPassword = passwordTestAccountLogins
- waitforExistence(app.webViews.textFields["Email"], timeout: 10)
- navigator.nowAt(Screen.FxASigninScreenEmail)
- navigator.performAction(Action.FxATypeEmail)
- navigator.performAction(Action.FxATypePassword)
+ disconnectAndConnectAccount()
if #available(iOS 12.0, *) {
waitforExistence(app.buttons["setupAutofill.button"])
@@ -258,4 +249,62 @@ class LockboxXCUITests: BaseTestCase {
XCTAssertTrue(app.cells["appVersionSettingOption"].exists)
XCTAssertNotEqual(app.cells.staticTexts.element(boundBy: 2).label, "")
}
+
+ // Verify SetAutofillNow
+ func testSetAutofillNow() {
+ // Disconnect account
+ disconnectAndConnectAccount()
+ if #available(iOS 12.0, *) {
+ waitforExistence(app.buttons["setupAutofill.button"])
+ app.buttons["setupAutofill.button"].tap()
+ }
+ let settingsApp = XCUIApplication(bundleIdentifier: "com.apple.Preferences")
+ waitforExistence(settingsApp.cells.staticTexts["Passwords & Accounts"])
+ }
+
+ // Once app is open
+ func testSetAutofillSettings() {
+ // Open Setting to add Lockbox
+ navigator.goto(Screen.SettingsMenu)
+ navigator.performAction(Action.OpenDeviceSettings)
+ // Wait until settings app is open
+ let settingsApp = XCUIApplication(bundleIdentifier: "com.apple.Preferences")
+ waitforExistence(settingsApp.cells.staticTexts["Passwords & Accounts"])
+ // Configure Passwords & Accounts settings
+ settingsApp.cells.staticTexts["Passwords & Accounts"].tap()
+ waitforExistence(settingsApp.cells.staticTexts["AutoFill Passwords"])
+ settingsApp.cells.staticTexts["AutoFill Passwords"].tap()
+ waitforExistence(settingsApp.switches["AutoFill Passwords"])
+ settingsApp.switches["AutoFill Passwords"].tap()
+ waitforExistence(settingsApp.cells.staticTexts["Lockbox"])
+ settingsApp.cells.staticTexts["Lockbox"].tap()
+ // Wait until the app is updated
+ sleep(5)
+ settingsApp.terminate()
+
+ // Open Safari
+ let safari = XCUIApplication(bundleIdentifier: "com.apple.mobilesafari")
+ safari.launch()
+ waitforExistence(safari.buttons["URL"], timeout: 5)
+ safari.buttons["ReloadButton"].tap()
+ waitforExistence(safari.textFields["Email or phone"])
+ safari.textFields["Email or phone"].tap()
+
+ // Need to confirm what is shown here, different elements have appeared during the tests
+ if (safari.buttons["Other passwords"].exists) {
+ safari.buttons["Other passwords"].tap()
+ // Workaround for the test, the first time the password does not appear
+ safari.buttons["Cancel"].tap()
+ safari.buttons["Other passwords"].tap()
+ waitforExistence(safari.otherElements.staticTexts["Choose a saved password to use"])
+ print(safari.debugDescription)
+ XCTAssertTrue(safari.buttons.otherElements["iosmztest@gmail.com, for this website — Lockbox"].exists)
+ } else if (safari.otherElements["Password Auto-fill"].exists) {
+ safari.otherElements["Password Auto-fill"].tap()
+ XCTAssertTrue(safari.buttons["iosmztest@gmail.com, for this website — Lockbox"].exists)
+ } else {
+ XCTAssertTrue(safari.buttons["Use “iosmztest@gmail.com”"].exists)
+ }
+ safari.terminate()
+ }
}
diff --git a/LockboxXCUITests/SnapshotHelper.swift b/LockboxXCUITests/SnapshotHelper.swift
index 5b7ead01a..ad575130b 100644
--- a/LockboxXCUITests/SnapshotHelper.swift
+++ b/LockboxXCUITests/SnapshotHelper.swift
@@ -42,6 +42,7 @@ enum SnapshotError: Error, CustomDebugStringConvertible {
case cannotFindHomeDirectory
case cannotFindSimulatorHomeDirectory
case cannotAccessSimulatorHomeDirectory(String)
+ case cannotRunOnPhysicalDevice
var debugDescription: String {
switch self {
@@ -53,22 +54,27 @@ enum SnapshotError: Error, CustomDebugStringConvertible {
return "Couldn't find simulator home location. Please, check SIMULATOR_HOST_HOME env variable."
case .cannotAccessSimulatorHomeDirectory(let simulatorHostHome):
return "Can't prepare environment. Simulator home location is inaccessible. Does \(simulatorHostHome) exist?"
+ case .cannotRunOnPhysicalDevice:
+ return "Can't use Snapshot on a physical device."
}
}
}
+@objcMembers
open class Snapshot: NSObject {
- static var app: XCUIApplication!
- static var cacheDirectory: URL!
+ static var app: XCUIApplication?
+ static var cacheDirectory: URL?
static var screenshotsDirectory: URL? {
- return cacheDirectory.appendingPathComponent("screenshots", isDirectory: true)
+ return cacheDirectory?.appendingPathComponent("screenshots", isDirectory: true)
}
open class func setupSnapshot(_ app: XCUIApplication) {
+
+ Snapshot.app = app
+
do {
let cacheDir = try pathPrefix()
Snapshot.cacheDirectory = cacheDir
- Snapshot.app = app
setLanguage(app)
setLocale(app)
setLaunchArguments(app)
@@ -78,6 +84,11 @@ open class Snapshot: NSObject {
}
class func setLanguage(_ app: XCUIApplication) {
+ guard let cacheDirectory = self.cacheDirectory else {
+ print("CacheDirectory is not set - probably running on a physical device?")
+ return
+ }
+
let path = cacheDirectory.appendingPathComponent("language.txt")
do {
@@ -90,6 +101,11 @@ open class Snapshot: NSObject {
}
class func setLocale(_ app: XCUIApplication) {
+ guard let cacheDirectory = self.cacheDirectory else {
+ print("CacheDirectory is not set - probably running on a physical device?")
+ return
+ }
+
let path = cacheDirectory.appendingPathComponent("locale.txt")
do {
@@ -105,6 +121,11 @@ open class Snapshot: NSObject {
}
class func setLaunchArguments(_ app: XCUIApplication) {
+ guard let cacheDirectory = self.cacheDirectory else {
+ print("CacheDirectory is not set - probably running on a physical device?")
+ return
+ }
+
let path = cacheDirectory.appendingPathComponent("snapshot-launch_arguments.txt")
app.launchArguments += ["-FASTLANE_SNAPSHOT", "YES", "-ui_testing"]
@@ -126,14 +147,21 @@ open class Snapshot: NSObject {
waitForLoadingIndicatorToDisappear(within: timeout)
}
- print("snapshot: \(name)") // more information about this, check out https://github.com/fastlane/fastlane/tree/master/snapshot#how-does-it-work
+ print("snapshot: \(name)") // more information about this, check out https://docs.fastlane.tools/actions/snapshot/#how-does-it-work
sleep(1) // Waiting for the animation to be finished (kind of)
#if os(OSX)
XCUIApplication().typeKey(XCUIKeyboardKeySecondaryFn, modifierFlags: [])
#else
- let screenshot = app.windows.firstMatch.screenshot()
+
+ guard let app = self.app else {
+ print("XCUIApplication is not set. Please call setupSnapshot(app) before snapshot().")
+ return
+ }
+
+ let window = app.windows.firstMatch
+ let screenshot = window.screenshot()
guard let simulator = ProcessInfo().environment["SIMULATOR_DEVICE_NAME"], let screenshotsDir = screenshotsDirectory else { return }
let path = screenshotsDir.appendingPathComponent("\(simulator)-\(name).png")
do {
@@ -164,19 +192,23 @@ open class Snapshot: NSObject {
throw SnapshotError.cannotDetectUser
}
- guard let usersDir = FileManager.default.urls(for: .userDirectory, in: .localDomainMask).first else {
+ guard let usersDir = FileManager.default.urls(for: .userDirectory, in: .localDomainMask).first else {
throw SnapshotError.cannotFindHomeDirectory
}
homeDir = usersDir.appendingPathComponent(user)
#else
- guard let simulatorHostHome = ProcessInfo().environment["SIMULATOR_HOST_HOME"] else {
- throw SnapshotError.cannotFindSimulatorHomeDirectory
- }
- guard let homeDirUrl = URL(string: simulatorHostHome) else {
- throw SnapshotError.cannotAccessSimulatorHomeDirectory(simulatorHostHome)
- }
- homeDir = URL(fileURLWithPath: homeDirUrl.path)
+ #if arch(i386) || arch(x86_64)
+ guard let simulatorHostHome = ProcessInfo().environment["SIMULATOR_HOST_HOME"] else {
+ throw SnapshotError.cannotFindSimulatorHomeDirectory
+ }
+ guard let homeDirUrl = URL(string: simulatorHostHome) else {
+ throw SnapshotError.cannotAccessSimulatorHomeDirectory(simulatorHostHome)
+ }
+ homeDir = URL(fileURLWithPath: homeDirUrl.path)
+ #else
+ throw SnapshotError.cannotRunOnPhysicalDevice
+ #endif
#endif
return homeDir.appendingPathComponent("Library/Caches/tools.fastlane")
}
@@ -221,7 +253,7 @@ private extension XCUIElementQuery {
}
var deviceStatusBars: XCUIElementQuery {
- let deviceWidth = XCUIApplication().frame.width
+ let deviceWidth = XCUIApplication().windows.firstMatch.frame.width
let isStatusBar = NSPredicate { (evaluatedObject, _) in
guard let element = evaluatedObject as? XCUIElementAttributes else { return false }
@@ -241,4 +273,4 @@ private extension CGFloat {
// Please don't remove the lines below
// They are used to detect outdated configuration files
-// SnapshotHelperVersion [1.7]
+// SnapshotHelperVersion [1.12]
diff --git a/README.md b/README.md
index 9d3002493..3235b69e0 100644
--- a/README.md
+++ b/README.md
@@ -20,6 +20,9 @@ See the [guidelines][contributing-link] for contributing to this project.
This project is governed by a [Code Of Conduct][coc-link].
+To disclose potential a security vulnerability please see our
+[security][security-link] documentation.
+
## [License][license-link]
This module is licensed under the [Mozilla Public License,
@@ -37,4 +40,5 @@ Note that some test fixtures use source code from third-party services, and are
[org-website]: https://lockbox.firefox.com/
[contributing-link]: docs/contributing.md
[coc-link]: docs/code_of_conduct.md
+[security-link]: docs/SECURITY.md
[license-link]: /LICENSE
diff --git a/docs/SECURITY.md b/docs/SECURITY.md
new file mode 100644
index 000000000..19bea32e7
--- /dev/null
+++ b/docs/SECURITY.md
@@ -0,0 +1,5 @@
+# Mozilla Security #
+
+- Mozilla cares about privacy and security. For more information please see: https://www.mozilla.org/security/
+
+- If you believe that you've found a security vulnerability, please report it by sending email to the addresses: security@mozilla.org and lockbox-dev@mozilla.com
diff --git a/docs/contributing.md b/docs/contributing.md
index 996fff0d0..cfa4c75f4 100644
--- a/docs/contributing.md
+++ b/docs/contributing.md
@@ -18,6 +18,8 @@ Please open [a new issue in the GitHub repository](https://github.com/mozilla-lo
Be sure to include as much information including screenshots, text output, and both your expected and actual results.
+If you believe that you've found a security vulnerability, please report it by sending email to the addresses: security@mozilla.org and lockbox-dev@mozilla.com
+
## How to Request Enhancements
First, please refer to the applicable [GitHub repository](https://github.com/orgs/mozilla-lockbox/) and search [the repository's GitHub issues](https://github.com/mozilla-lockbox/lockbox-ios/issues) to make sure your idea has not been (or is not still) considered.
diff --git a/docs/release-notes.md b/docs/release-notes.md
index ad4407b2f..1cc77ea08 100644
--- a/docs/release-notes.md
+++ b/docs/release-notes.md
@@ -1,5 +1,22 @@
# Release Notes
+## 1.3.1 (Build 2428)
+
+We fixed some bugs related to "Disconnecting" (signing out) and the new AutoFill feature.
+
+Here's the full list of changes:
+
+- If you "Disconnect" then sign-in, you won't get stuck seeing the “No matching entries” error message
+- We also made it so if you sign-in with a different account, you don't accidentally see the items from the first account
+
+## 1.3 (Build 2362)
+
+_Date: 2018-09-15_
+
+With iOS 12 you can automatically fill your usernames and passwords from Firefox Lockbox into apps and websites.
+
+Be sure to enable AutoFill after updating to iOS 12 from within Settings under the "Passwords & Accounts" section.
+
## 1.3 (Build 2349)
_Date: 2018-09-14_
diff --git a/lockbox-ios/Common/Resources/Info.plist b/lockbox-ios/Common/Resources/Info.plist
index 6c38e24d6..67675590c 100644
--- a/lockbox-ios/Common/Resources/Info.plist
+++ b/lockbox-ios/Common/Resources/Info.plist
@@ -17,7 +17,7 @@
CFBundlePackageType
APPL
CFBundleShortVersionString
- 1.3
+ 1.3.1
CFBundleVersion
1
LSApplicationQueriesSchemes
diff --git a/lockbox-ios/Presenter/RootPresenter.swift b/lockbox-ios/Presenter/RootPresenter.swift
index 06195a839..bf71570f2 100644
--- a/lockbox-ios/Presenter/RootPresenter.swift
+++ b/lockbox-ios/Presenter/RootPresenter.swift
@@ -90,6 +90,8 @@ class RootPresenter {
} else if latest.oauthInfo == nil && latest.profile == nil {
self.dispatcher.dispatch(action: LoginRouteAction.welcome)
self.dispatcher.dispatch(action: DataStoreAction.reset)
+ self.dispatcher.dispatch(action: CredentialProviderAction.clear)
+ self.dispatcher.dispatch(action: AccountAction.clear)
}
}
.disposed(by: self.disposeBag)
diff --git a/lockbox-ios/Store/AccountStore.swift b/lockbox-ios/Store/AccountStore.swift
index 57cfd94d2..0820347aa 100644
--- a/lockbox-ios/Store/AccountStore.swift
+++ b/lockbox-ios/Store/AccountStore.swift
@@ -95,6 +95,12 @@ class AccountStore: BaseAccountStore {
})
.disposed(by: self.disposeBag)
+ initFxa()
+ }
+}
+
+extension AccountStore {
+ private func initFxa() {
if let accountJSON = self.storedAccountJSON {
self.fxa = try? FirefoxAccount.fromJSON(state: accountJSON)
self.generateLoginURL()
@@ -102,10 +108,10 @@ class AccountStore: BaseAccountStore {
} else {
FxAConfig.release { (config: FxAConfig?, _) in
if let config = config {
- self.fxa = try? FirefoxAccount(
- config: config,
- clientId: Constant.fxa.clientID,
- redirectUri: Constant.fxa.redirectURI)
+ self.fxa = try? FirefoxAccount(
+ config: config,
+ clientId: Constant.fxa.clientID,
+ redirectUri: Constant.fxa.redirectURI)
self.generateLoginURL()
}
@@ -115,9 +121,7 @@ class AccountStore: BaseAccountStore {
}
}
}
-}
-extension AccountStore {
private func generateLoginURL() {
self.fxa?.beginOAuthFlow(scopes: Constant.fxa.scopes, wantsKeys: true) { url, _ in
if let url = url {
@@ -139,6 +143,8 @@ extension AccountStore {
self._profile.onNext(nil)
self._oauthInfo.onNext(nil)
+
+ self.initFxa()
}
private func clearOldKeychainValues() {
diff --git a/lockbox-iosTests/RootPresenterSpec.swift b/lockbox-iosTests/RootPresenterSpec.swift
index 18571af86..0002e3bb9 100644
--- a/lockbox-iosTests/RootPresenterSpec.swift
+++ b/lockbox-iosTests/RootPresenterSpec.swift
@@ -228,11 +228,18 @@ class RootPresenterSpec: QuickSpec {
self.accountStore.profileInfoStub.onNext(nil)
}
- it("routes to the welcome view and resets the datastore") {
+ it("clears the account, credential provider, routes to the welcome view and resets the datastore") {
+ let arg = self.dispatcher.dispatchActionArgument.popLast() as! AccountAction
+ expect(arg).to(equal(AccountAction.clear))
+
+ let credentialProviderAction = self.dispatcher.dispatchActionArgument.popLast() as! CredentialProviderAction
+ expect(credentialProviderAction).to(equal(CredentialProviderAction.clear))
+
let dataStoreAction = self.dispatcher.dispatchActionArgument.popLast() as! DataStoreAction
expect(dataStoreAction).to(equal(DataStoreAction.reset))
- let arg = self.dispatcher.dispatchActionArgument.popLast() as! LoginRouteAction
- expect(arg).to(equal(LoginRouteAction.welcome))
+
+ let loginRouteAction = self.dispatcher.dispatchActionArgument.popLast() as! LoginRouteAction
+ expect(loginRouteAction).to(equal(LoginRouteAction.welcome))
}
}