Fix conflict resolution logic for `read_only` and `hide_passwords` flags
For one of these flags to be in effect for a cipher, upstream requires all of (rather than any of) the collections the cipher is in to have that flag set. Also, some of the logic for loading access restrictions was wrong. I think that only malicious clients that also had knowledge of the UUIDs of ciphers they didn't have access to would have been able to take advantage of that.
This commit is contained in:
parent
a2316ca091
commit
6cbb724069
|
@ -343,36 +343,39 @@ impl Cipher {
|
||||||
db_run! {conn: {
|
db_run! {conn: {
|
||||||
// Check whether this cipher is in any collections accessible to the
|
// Check whether this cipher is in any collections accessible to the
|
||||||
// user. If so, retrieve the access flags for each collection.
|
// user. If so, retrieve the access flags for each collection.
|
||||||
let query = ciphers::table
|
let rows = ciphers::table
|
||||||
.filter(ciphers::uuid.eq(&self.uuid))
|
.filter(ciphers::uuid.eq(&self.uuid))
|
||||||
.inner_join(ciphers_collections::table.on(
|
.inner_join(ciphers_collections::table.on(
|
||||||
ciphers::uuid.eq(ciphers_collections::cipher_uuid)))
|
ciphers::uuid.eq(ciphers_collections::cipher_uuid)))
|
||||||
.inner_join(users_collections::table.on(
|
.inner_join(users_collections::table.on(
|
||||||
ciphers_collections::collection_uuid.eq(users_collections::collection_uuid)
|
ciphers_collections::collection_uuid.eq(users_collections::collection_uuid)
|
||||||
.and(users_collections::user_uuid.eq(user_uuid))))
|
.and(users_collections::user_uuid.eq(user_uuid))))
|
||||||
.select((users_collections::read_only, users_collections::hide_passwords));
|
.select((users_collections::read_only, users_collections::hide_passwords))
|
||||||
|
.load::<(bool, bool)>(conn)
|
||||||
|
.expect("Error getting access restrictions");
|
||||||
|
|
||||||
// There's an edge case where a cipher can be in multiple collections
|
if rows.is_empty() {
|
||||||
// with inconsistent access flags. For example, a cipher could be in
|
// This cipher isn't in any collections accessible to the user.
|
||||||
// one collection where the user has read-only access, but also in
|
return None;
|
||||||
// another collection where the user has read/write access. To handle
|
}
|
||||||
// this, we do a boolean OR of all values in each of the `read_only`
|
|
||||||
// and `hide_passwords` columns. This could ideally be done as part
|
// A cipher can be in multiple collections with inconsistent access flags.
|
||||||
// of the query, but Diesel doesn't support a max() or bool_or()
|
// For example, a cipher could be in one collection where the user has
|
||||||
// function on booleans and this behavior isn't portable anyway.
|
// read-only access, but also in another collection where the user has
|
||||||
if let Ok(vec) = query.load::<(bool, bool)>(conn) {
|
// read/write access. For a flag to be in effect for a cipher, upstream
|
||||||
let mut read_only = false;
|
// requires all collections the cipher is in to have that flag set.
|
||||||
let mut hide_passwords = false;
|
// Therefore, we do a boolean AND of all values in each of the `read_only`
|
||||||
for (ro, hp) in vec.iter() {
|
// and `hide_passwords` columns. This could ideally be done as part of the
|
||||||
read_only |= ro;
|
// query, but Diesel doesn't support a min() or bool_and() function on
|
||||||
hide_passwords |= hp;
|
// booleans and this behavior isn't portable anyway.
|
||||||
|
let mut read_only = true;
|
||||||
|
let mut hide_passwords = true;
|
||||||
|
for (ro, hp) in rows.iter() {
|
||||||
|
read_only &= ro;
|
||||||
|
hide_passwords &= hp;
|
||||||
}
|
}
|
||||||
|
|
||||||
Some((read_only, hide_passwords))
|
Some((read_only, hide_passwords))
|
||||||
} else {
|
|
||||||
// This cipher isn't in any collections accessible to the user.
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue