-
Notifications
You must be signed in to change notification settings - Fork 227
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add reference docs about the expected behaviour of login record fields. #2158
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for writing this up!
The Logins component ***does not*** offer, and we have no plans to offer: | ||
|
||
1. Any form-autofill of other UI-level functionality. | ||
1. Storage of other secret data, such as credit card numbers. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: I think this may change in the future if we want to store other secure data for Lockwise like secure notes, account recovery questions/answers, etc. I don't think a separate store for that would be great; many password managers including Keychain, show the records in one view and we would probably do the same. I agree that credit card records are a bit different as they are more structured.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's reasonable. I'll add a note that this might be considered in the future, and direct to an appropriate place for discussion.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
https://bugzilla.mozilla.org/show_bug.cgi?id=1119558 is a related bug though it currently says "with a saved password".
components/logins/README.md
Outdated
You can read about the fields on a login record in the code [here](./src/login.rs). | ||
* A **logins store** is a syncable encrypted database containing login records. In order to use the logins store, | ||
the application must first *unlock* it by providing a secret key (preferably obtained from an OS-level keystore | ||
mechanism). It can then create, read, update and delete login records from th database. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
typo: s/ th / the /
components/logins/README.md
Outdated
When records are added, the logins component performs a three-way merge between the local record, the remote record and the shared parent (last update on the server). Details on the merging algorithm are contained in the [generic sync rfc](https://github.com/mozilla/application-services/blob/1e2ba102ee1709f51d200a2dd5e96155581a81b2/docs/design/remerge/rfc.md#three-way-merge-algorithm). | ||
|
||
### Record de-duplication | ||
#### Record de-duplication | ||
|
||
De-duplication compares the records for same the username and same url, but with different passwords. De-duplication compares the records for same the username and same url, but with different passwords. Deduplication logic is based on age, the username and hostname. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
While you're here would you mind fixing these sentences?
components/logins/src/login.rs
Outdated
//! matches this origin. This field must be a valid origin. | ||
//! | ||
//! YES, THIS FIELD IS CONFUSINGLY NAMED. IT SHOULD BE AN ORIGIN, NOT A FULL URL. | ||
//! WE INTEND TO RENAME THIS TO `formSubmitOrigin` IN A FUTURE RELEASE. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
s/formSubmitOrigin/formActionOrigin/ ?
@@ -36,7 +36,7 @@ data class ServerPassword( | |||
val password: String, | |||
|
|||
/** | |||
* The HTTP realm, which is the challenge string for HTTP Basic Auth). May be null in the case | |||
* The HTTP realm, which is the challenge string for HTTP Basic Auth. May be null in the case | |||
* that this login has a formSubmitURL instead. | |||
*/ | |||
val httpRealm: String? = null, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should formSubmitURL
docs also say that it can be ""
to mean that it's a wildcard match. It can also be javascript:
(we strip the body and leave just the 'javascript:' portion) if you have a form like <form action="javascript:validateForm()">
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great, yes, these are exactly the sort of details I want to iron out!
components/logins/src/login.rs
Outdated
//! This field is managed internally by the logins store and any attempt by the | ||
//! application to specify a value will be ignored. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Again this is problematic for importing from other desktop browsers or from Fennec so I hope we can change this.
components/logins/src/login.rs
Outdated
//! This field is managed internally by the logins store and any attempt by the | ||
//! application to specify a value will be ignored. | ||
//! | ||
//! XXX TODO: are these local machine timestamps, and thus possibly wildly inaccurate? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes and yes
components/logins/src/login.rs
Outdated
//! application to specify a value will be ignored. | ||
//! | ||
//! XXX TODO: are these local machine timestamps, and thus possibly wildly inaccurate? | ||
//! XXX TODO: should we talk about merge strategies here? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe? Ideally we should take the min
of the non-zero values to merge.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What does a zero in this field indicate?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unfortunately GtiHub is dumb and I no longer can tell which property this was commenting on… I believe this was timeCreated
… There should never be a 0 here (I guess that should be documented).
components/logins/src/login.rs
Outdated
//! application to specify a value will be ignored. | ||
//! | ||
//! XXX TODO: are these local machine timestamps, and thus possibly wildly inaccurate? | ||
//! XXX TODO: should we talk about merge strategies here? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it should be the max
of non-zero here
components/logins/src/login.rs
Outdated
//! XXX TODO: are these local machine timestamps, and thus possibly wildly inaccurate? | ||
//! XXX TODO: should we talk about merge strategies here? | ||
//! | ||
//! - `timePasswordChanged`: A lower bound on the time that the `password` field was last changed, in integer milliseconds from the unix epoch. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe we should be explicit here and say that username (or other field) changes shouldn't be recorded here as that's what we want and it's been discussed a few times.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For more context, this property is useful for breach alerts of passwords since changing the username wouldn't have addressed the breach.
e3f6073
to
06a3621
Compare
Marking this ready for review. When viewing the changes, please be sure to expand the large diff that is not shown by default for We have a lot of open bugs about validating logins data, normalizing it in various ways, and fixing up invalid data if found in existing records. In this essay I will seek to reach agreement on the rules that we expect logins data to follow, and propose a code structure in which we can conveniently start enforcing them one rule at a time. The key ideas here are:
I'm not sure it's the right approach, but I think it gives a nice framework to pursue other pending cleanups, such as the punycode normalization. @thomcc, I'd really appreciate a gut-check from you on the approach here :-) @lougeniaC64, how would this fit with the additional validation work you've been taking on? @eliserichards, could you please take a look at the description of the login record fields in |
06a3621
to
ad91498
Compare
components/logins/src/ffi.rs
Outdated
@@ -45,6 +45,7 @@ pub mod error_codes { | |||
pub const INVALID_LOGIN_DUPLICATE_LOGIN: i32 = 64 + 2; | |||
pub const INVALID_LOGIN_BOTH_TARGETS: i32 = 64 + 3; | |||
pub const INVALID_LOGIN_NO_TARGET: i32 = 64 + 4; | |||
pub const INVALID_LOGIN_UNSPECIFIED: i32 = 64 + 5; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I fear we may be about to proliferate error codes here, are we sure we want to individually expose each of these over the FFI?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This was done intentionally, as the lockwise team indicated that they'd like to know a precise reason the records were invalid.
// * The android-components docs at | ||
// https://github.com/mozilla-mobile/android-components/tree/master/components/service/sync-logins | ||
// | ||
// We'll figure out a more scalable approach to maintaining all those docs at some point... |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I realize I have not followed my own advice here, but I can once we're happy with the descriptions, if it seems valuable. Or, we could consider moving this out into a markdown document and just putting pointers at it in the various API docs.
}; | ||
// For now, we want to apply fixups but still return the record if | ||
// there is unfixably invalid data in the db. | ||
Ok(login.maybe_fixup().unwrap_or(None).unwrap_or(login)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we'd eventually like to be confident our db only holds validated data, and not have to do this on every read. But that seems like a much later step in the process.
Some(record) | ||
// If we can fixup incoming records from sync, do so. | ||
// But if we can't then keep the invalid data. | ||
record.maybe_fixup().unwrap_or(None).or(Some(record)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure if this is a good idea, just putting it out here for consideration...
* The way we [generate ffi bindings](../../docs/howtos/building-a-rust-component.md) and expose them to | ||
[Kotlin](../../docs/howtos/exposing-rust-components-to-kotlin.md) and | ||
[Swift](../../docs/howtos/exposing-rust-components-to-swift.md). | ||
* The key ideas behind [how Firefox Sync works](../../docs/synconomicon/) and the [sync15 crate](../sync15/README.md). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: The sync15 README link was broken for me as there doesn't appear to be a README.md
file in the sync15
directory.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, good catch. I've added a very very small stub README, but we should flesh it out further in followup work.
components/logins/README.md
Outdated
editing logins using the code in `src`. | ||
- `ffi`: The Rust public FFI bindings. This is a (memory-unsafe, by necessity) | ||
API that is exposed to Kotlin and Swift. It leverages the `ffi_support` crate | ||
- [`example`](./example): This contains example rust code that implements a command-line app |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: This link is also broken. I think
this should be [examples](./examples)
.
I think it makes a lot of sense to have functionality that fixes up logins before the validity check occurs in order to reduce the number of errors thrown for issues we could resolve internally. Particularly because the additional checks I've added are all for invalid characters, which could easily be removed before the check takes place. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this looks great. Fantastic job with the documentation and examples. It was extremely easy to absorb all of the context despite being such a complicated and dense subject. Also A+ for having tests and leaving todos to cover later 💯 👍
components/logins/src/error.rs
Outdated
#[fail(display = "Some unspecified problem; no error details sorry :-(")] | ||
Unspecified, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Guessing we'll be able to add more errors here as we iron out more of the details about what failures we need, correct?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right. @lougeniaC64 is adding a better error type over in #2262 and we can expand it as necessary from there.
components/logins/src/ffi.rs
Outdated
@@ -45,6 +45,7 @@ pub mod error_codes { | |||
pub const INVALID_LOGIN_DUPLICATE_LOGIN: i32 = 64 + 2; | |||
pub const INVALID_LOGIN_BOTH_TARGETS: i32 = 64 + 3; | |||
pub const INVALID_LOGIN_NO_TARGET: i32 = 64 + 4; | |||
pub const INVALID_LOGIN_UNSPECIFIED: i32 = 64 + 5; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm personally fine with a generic "this is breaking because of something" error for now.
components/logins/src/login.rs
Outdated
//! **YES, THIS FIELD IS CONFUSINGLY NAMED. IT SHOULD BE A FULL ORIGIN, NOT A HOSTNAME. | ||
//! WE INTEND TO RENAME THIS TO `origin` IN A FUTURE RELEASE.** |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👏
components/logins/src/login.rs
Outdated
// Like `fixup()` above, but takes `self` by reference and returns | ||
// an Option for the fixed-up version, allowing the caller to make | ||
// more choices about what to do next. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
allowing the caller to make more choices about what to do next
Love it. I think a certain amount of autonomy will be useful (for now).
ad91498
to
ddc09dc
Compare
//! - "ftp://ftp.site.com" | ||
//! - "moz-proxy://127.0.0.1:8888" | ||
//! - "chrome://MyLegacyExtension" | ||
//! - "file:///" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure if github ate thom's comment or what, but he had a good point here that we need to clarify.
@mnoorenberghe what should the behaviour be for values that have an opaque origin per the WhatWG URL Spec?
Concrete example: if one record has origin="file:///"
and another has origin="file:///hello/world"
, what happens?
My take is that we should treat them as valid and leave them alone, and that calling code should only consider them a match based on exact string matching.
I had a rummage around in the Desktop passwordmgr
directory but couldn't find any testcases covering such cases.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good to me. Some concerns I had with earlier vesions that seem to be addressed now is that the distinction between empty strings, null, and missing seems very important (in terms of how other clients behave) and subtle. It's worth being certain that we get it right everywhere (I think an earlier version didn't, but I didn't notice anywhere that this had it wrong).
It's worth checking that the behavior documented around insertion/updates matches what we'll actually do. For example, we manage the metadata fields entirely, and don't allow the application to manage them. I suspect the main reason that logins does on desktop is for sync's benefit, (and perhaps for testing use cases) otherwise it's hard to see why the application would need to.
components/logins/src/login.rs
Outdated
Ok(()) | ||
} | ||
|
||
// Return either the existing login, a fixed-up verion, or an error. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: ///
is for item-level doc comments in rust. Here and elsewhere (except for the module-level comment, which is correctly //!
).
components/logins/src/login.rs
Outdated
// A little helper to magic a Some(self.clone()) into existence when needed. | ||
macro_rules! get_fixed_or_throw { | ||
($err:expr) => { | ||
if ! fixup { throw!($err) } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: space between ! and fixup. And general formatting. Rustfmt doesn't really touch macro bodies,unfortunately.
components/logins/src/login.rs
Outdated
($err:expr) => { | ||
if ! fixup { throw!($err) } | ||
else { Ok(fixed.get_or_insert_with(|| self.clone())) } | ||
as Result<&mut Login> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think this as
does anything (note that it's not type ascription -- it doesn't act as a type constraint, it's a type cast)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Heh, I added it to stop rustc
yelling at me about:
error[E0282]: type annotations needed
--> components/logins/src/login.rs:392:17
|
392 | get_fixed_or_throw!(InvalidLogin::Unspecified)?
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ cannot infer type
error: aborting due to previous error
But I'll see if I can replace it with e.g. a local variable.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Huh, surprised that worked. It's fine then, usually that doesn't work (and my understanding of rust-lang/rfcs#803 is that the syntax for ascribing a type in this way is different and unstable)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using a local variable with an explicit type seems to have worked, so I'll switch to that to avoid confusing future readers
components/logins/src/login.rs
Outdated
//! may be wildly inaccurate if the client does not have an accurate clock. | ||
//! | ||
//! This field is managed internally by the logins store by default and does not need to | ||
//! be set explicitly, although any application-provided value will be stored. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think it's true that we allow application provided values, unless that changed recently. We could in the future, but the only use case I can think of is test code.
components/logins/src/ffi.rs
Outdated
@@ -45,6 +45,7 @@ pub mod error_codes { | |||
pub const INVALID_LOGIN_DUPLICATE_LOGIN: i32 = 64 + 2; | |||
pub const INVALID_LOGIN_BOTH_TARGETS: i32 = 64 + 3; | |||
pub const INVALID_LOGIN_NO_TARGET: i32 = 64 + 4; | |||
pub const INVALID_LOGIN_UNSPECIFIED: i32 = 64 + 5; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This was done intentionally, as the lockwise team indicated that they'd like to know a precise reason the records were invalid.
components/logins/src/login.rs
Outdated
//! When merging duplicate records, the largest non-zero value is taken. | ||
//! | ||
//! This field is managed internally by the logins store by default and does not need to | ||
//! be set explicitly, although any application-provided value will be stored. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think we will do this either.
IIRC Matt mentioned one other user-case, which was importing from other browsers. If we aspire to land this component in Desktop at some point we should probably support this behavior (although we don't need to add it in this PR). |
IMO we might want a dedicated API for this case, but sure, barring that it's a valid case for the metadata. It is worth noting that invalid metadata imported from other browsers has caused substantial problems with sync, causing us to have to even patch the firefox-for-android JSON parser implementation to handle it in the past. So I'd prefer we discouraged handling it externally, although I guess it's plausible we could have the same bug. |
91806dc
to
aad4350
Compare
I'm pretty happy with where this is at, but I'm going to wait for #2262 to land and then rebase on top of it. |
b6c3c5a
to
98a1f45
Compare
To my great relief, this looks to have been a very very trivial rebase :-) |
98a1f45
to
eaf154e
Compare
…t enforcing it. Along with a whole lotta docs, this commit adds a `fixup` method to the Login struct, which can be used to repair records that fail validation in ways that it's obvious how to fix.
eaf154e
to
db432a5
Compare
Fixes #2155.
In trying to get my head around the "valid login records" bug (#695, #2156, #2157 and friends), my first instinct was to go to the logins component README in order to find docs on the details of what a "login record" should be. It didn't point me in an obvious direction, but I eventually found three sources:
But none of them capture the validity questions we're discussing in those bugs. So, in this PR I have:
I'm not really sure how to feel about the fourth doc, and the maintenance overhead of trying to keep all the different docs in sync. But I do think we need to capture this all somewhere. Suggestions welcome!
@mnoorenberghe @thomcc could you please take a look at my understanding of all these fields and see if it's accurate, and whether you can answer any of the "TODO" questions?
@lougeniaC64, could you please take a look at the general changes to the logins README (rendered view here based on our earlier discussion about adding more contextual pointers to help with onboarding?