Add partial role support for manager only

- Add the custom role which replaces the manager role
- Added mini-details endpoint used by v2024.11.1

These changes try to add the custom role in such a way that it stays compatible with the older manager role.
It will convert a manager role into a custom role, and if a manager has `access-all` rights, it will enable the correct custom roles.
Upon saving it will convert these back to the old format.

What this does is making sure you are able to revert back to an older version of Vaultwarden without issues.
This way we can support newer web-vault's and still be compatible with a previous Vaultwarden version if needed.

In the future this needs to be changed to full role support though.

Signed-off-by: BlackDex <black.dex@gmail.com>
pull/5219/head
BlackDex 2 days ago
parent 96813b1317
commit 6e337c69ef
No known key found for this signature in database
GPG Key ID: 58C80A2AA6C765E1

136
Cargo.lock generated

@ -55,9 +55,9 @@ dependencies = [
[[package]]
name = "allocator-api2"
version = "0.2.19"
version = "0.2.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "611cc2ae7d2e242c457e4be7f97036b8ad9ca152b499f53faf99b1ed8fc2553f"
checksum = "45862d1c77f2228b9e10bc609d5bc203d86ebc9b87ad8d5d5167a6c9abf739d9"
[[package]]
name = "android-tzdata"
@ -441,9 +441,9 @@ checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c"
[[package]]
name = "bytemuck"
version = "1.19.0"
version = "1.20.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8334215b81e418a0a7bdb8ef0849474f40bb10c8b71f1c4ed315cff49f32494d"
checksum = "8b37c88a63ffd85d15b406896cc343916d7cf57838a847b3a6f2ca5d39a5695a"
[[package]]
name = "byteorder"
@ -495,9 +495,9 @@ checksum = "ade8366b8bd5ba243f0a58f036cc0ca8a2f069cff1a2351ef1cac6b083e16fc0"
[[package]]
name = "cc"
version = "1.1.37"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "40545c26d092346d8a8dab71ee48e7685a7a9cba76e634790c215b41a4a7b4cf"
checksum = "fd9de9f2205d5ef3fd67e685b0df337994ddd4495e2a28d185500d0e1edfea47"
dependencies = [
"shlex",
]
@ -614,9 +614,9 @@ checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
[[package]]
name = "cpufeatures"
version = "0.2.14"
version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "608697df725056feaccfa42cffdaeeec3fccc4ffc38358ecd19b243e716a78e0"
checksum = "16b80225097f2e5ae4e7179dd2266824648f3e2f49d9134d584b76389d31c4c3"
dependencies = [
"libc",
]
@ -762,9 +762,9 @@ dependencies = [
[[package]]
name = "diesel"
version = "2.2.4"
version = "2.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "158fe8e2e68695bd615d7e4f3227c0727b151330d3e253b525086c348d055d5e"
checksum = "cbf9649c05e0a9dbd6d0b0b8301db5182b972d0fd02f0a7c6736cf632d7c0fd5"
dependencies = [
"bigdecimal",
"bitflags 2.6.0",
@ -799,9 +799,9 @@ dependencies = [
[[package]]
name = "diesel_logger"
version = "0.3.0"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23010b507517129dc9b11fb35f36d76fd2d3dd4c85232733697622e345375f2f"
checksum = "8074833fffb675cf22a6ee669124f65f02971e48dd520bb80c7473ff70aeaf95"
dependencies = [
"diesel",
"log",
@ -1010,9 +1010,9 @@ dependencies = [
[[package]]
name = "flate2"
version = "1.0.34"
version = "1.0.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1b589b4dc103969ad3cf85c950899926ec64300a1a46d76c03a6072957036f0"
checksum = "c936bfdafb507ebbf50b8074c54fa31c5be9a1e7e5f467dd659697041407d07c"
dependencies = [
"crc32fast",
"miniz_oxide",
@ -1271,9 +1271,9 @@ dependencies = [
[[package]]
name = "h2"
version = "0.4.6"
version = "0.4.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "524e8ac6999421f49a846c2d4411f337e53497d8ec55d67753beffa43c5d9205"
checksum = "ccae279728d634d083c00f6099cb58f01cc99c145b84b8be2f6c74618d79922e"
dependencies = [
"atomic-waker",
"bytes",
@ -1532,14 +1532,14 @@ dependencies = [
[[package]]
name = "hyper"
version = "1.5.0"
version = "1.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbbff0a806a4728c99295b254c8838933b5b082d75e3cb70c8dab21fdfbcfa9a"
checksum = "97818827ef4f364230e16705d4706e2897df2bb60617d6ca15d598025a3c481f"
dependencies = [
"bytes",
"futures-channel",
"futures-util",
"h2 0.4.6",
"h2 0.4.7",
"http 1.1.0",
"http-body 1.0.1",
"httparse",
@ -1558,9 +1558,9 @@ checksum = "08afdbb5c31130e3034af566421053ab03787c640246a446327f550d11bcb333"
dependencies = [
"futures-util",
"http 1.1.0",
"hyper 1.5.0",
"hyper 1.5.1",
"hyper-util",
"rustls 0.23.16",
"rustls 0.23.18",
"rustls-pki-types",
"tokio",
"tokio-rustls 0.26.0",
@ -1588,7 +1588,7 @@ checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0"
dependencies = [
"bytes",
"http-body-util",
"hyper 1.5.0",
"hyper 1.5.1",
"hyper-util",
"native-tls",
"tokio",
@ -1607,7 +1607,7 @@ dependencies = [
"futures-util",
"http 1.1.0",
"http-body 1.0.1",
"hyper 1.5.0",
"hyper 1.5.1",
"pin-project-lite",
"socket2",
"tokio",
@ -1762,16 +1762,6 @@ version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
[[package]]
name = "idna"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6"
dependencies = [
"unicode-bidi",
"unicode-normalization",
]
[[package]]
name = "idna"
version = "0.4.0"
@ -1851,9 +1841,9 @@ dependencies = [
[[package]]
name = "itoa"
version = "1.0.11"
version = "1.0.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b"
checksum = "540654e97a3f4470a492cd30ff187bc95d89557a903a2bbf112e2fae98104ef2"
[[package]]
name = "jetscii"
@ -1953,9 +1943,9 @@ dependencies = [
[[package]]
name = "libc"
version = "0.2.162"
version = "0.2.164"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "18d287de67fe55fd7e1581fe933d965a5a9477b38e949cfa9f8574ef01506398"
checksum = "433bfe06b8c75da9b2e3fbea6e5329ff87748f0b144ef75306e674c3f6f7c13f"
[[package]]
name = "libm"
@ -2156,9 +2146,9 @@ dependencies = [
[[package]]
name = "mysqlclient-sys"
version = "0.4.1"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "478e2040dbc35c73927b77a2be91a496de19deab376a6982ed61e89592434619"
checksum = "6bbb9b017b98c4cde5802998113e182eecc1ebce8d47e9ea1697b9a623d53870"
dependencies = [
"pkg-config",
"vcpkg",
@ -2341,9 +2331,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf"
[[package]]
name = "openssl-src"
version = "300.4.0+3.4.0"
version = "300.4.1+3.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a709e02f2b4aca747929cca5ed248880847c650233cf8b8cdc48f40aaf4898a6"
checksum = "faa4eac4138c62414b5622d1b31c5c304f34b406b013c079c2bbc652fdd6678c"
dependencies = [
"cc",
]
@ -2640,9 +2630,9 @@ dependencies = [
[[package]]
name = "proc-macro2"
version = "1.0.89"
version = "1.0.92"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e"
checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0"
dependencies = [
"unicode-ident",
]
@ -2668,20 +2658,20 @@ checksum = "33cb294fe86a74cbcf50d4445b37da762029549ebeea341421c7c70370f86cac"
[[package]]
name = "psm"
version = "0.1.23"
version = "0.1.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aa37f80ca58604976033fae9515a8a2989fc13797d953f7c04fb8fa36a11f205"
checksum = "200b9ff220857e53e184257720a14553b2f4aa02577d2ed9842d45d4b9654810"
dependencies = [
"cc",
]
[[package]]
name = "publicsuffix"
version = "2.2.3"
version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96a8c1bda5ae1af7f99a2962e49df150414a43d62404644d98dd5c3a93d07457"
checksum = "6f42ea446cab60335f76979ec15e12619a2165b5ae2c12166bef27d283a9fadf"
dependencies = [
"idna 0.3.0",
"idna 1.0.3",
"psl-types",
]
@ -2808,7 +2798,7 @@ checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191"
dependencies = [
"aho-corasick",
"memchr",
"regex-automata 0.4.8",
"regex-automata 0.4.9",
"regex-syntax 0.8.5",
]
@ -2823,9 +2813,9 @@ dependencies = [
[[package]]
name = "regex-automata"
version = "0.4.8"
version = "0.4.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "368758f23274712b504848e9d5a6f010445cc8b87a7cdb4d7cbee666c1288da3"
checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908"
dependencies = [
"aho-corasick",
"memchr",
@ -2909,11 +2899,11 @@ dependencies = [
"encoding_rs",
"futures-core",
"futures-util",
"h2 0.4.6",
"h2 0.4.7",
"http 1.1.0",
"http-body 1.0.1",
"http-body-util",
"hyper 1.5.0",
"hyper 1.5.1",
"hyper-rustls",
"hyper-tls 0.6.0",
"hyper-util",
@ -2929,7 +2919,7 @@ dependencies = [
"serde",
"serde_json",
"serde_urlencoded",
"sync_wrapper 1.0.1",
"sync_wrapper 1.0.2",
"system-configuration 0.6.1",
"tokio",
"tokio-native-tls",
@ -3114,9 +3104,9 @@ checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f"
[[package]]
name = "rustix"
version = "0.38.40"
version = "0.38.41"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "99e4ea3e1cdc4b559b8e5650f9c8e5998e3e5c1343b4eaf034565f32318d63c0"
checksum = "d7f649912bc1495e167a6edee79151c84b1bad49748cb4f1f1167f459f6224f6"
dependencies = [
"bitflags 2.6.0",
"errno",
@ -3139,9 +3129,9 @@ dependencies = [
[[package]]
name = "rustls"
version = "0.23.16"
version = "0.23.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eee87ff5d9b36712a58574e12e9f0ea80f915a5b0ac518d322b24a465617925e"
checksum = "9c9cc1d47e243d655ace55ed38201c19ae02c148ae56412ab8750e8f0166ab7f"
dependencies = [
"once_cell",
"rustls-pki-types",
@ -3218,9 +3208,9 @@ dependencies = [
[[package]]
name = "schannel"
version = "0.1.26"
version = "0.1.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "01227be5826fa0690321a2ba6c5cd57a19cf3f6a09e76973b58e61de6ab9d1c1"
checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d"
dependencies = [
"windows-sys 0.59.0",
]
@ -3287,9 +3277,9 @@ checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b"
[[package]]
name = "serde"
version = "1.0.214"
version = "1.0.215"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f55c3193aca71c12ad7890f1785d2b73e1b9f63a0bbc353c08ef26fe03fc56b5"
checksum = "6513c1ad0b11a9376da888e3e0baa0077f1aed55c17f50e7b2397136129fb88f"
dependencies = [
"serde_derive",
]
@ -3306,9 +3296,9 @@ dependencies = [
[[package]]
name = "serde_derive"
version = "1.0.214"
version = "1.0.215"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "de523f781f095e28fa605cdce0f8307e451cc0fd14e2eb4cd2e98a355b147766"
checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0"
dependencies = [
"proc-macro2",
"quote",
@ -3317,9 +3307,9 @@ dependencies = [
[[package]]
name = "serde_json"
version = "1.0.132"
version = "1.0.133"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d726bfaff4b320266d395898905d0eba0345aae23b54aee3a737e260fd46db03"
checksum = "c7fceb2473b9166b2294ef05efcb65a3db80803f0b03ef86a5fc88a2b85ee377"
dependencies = [
"itoa",
"memchr",
@ -3513,9 +3503,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
[[package]]
name = "syn"
version = "2.0.87"
version = "2.0.89"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "25aa4ce346d03a6dcd68dd8b4010bcb74e54e62c90c573f394c46eae99aba32d"
checksum = "44d46482f1c1c87acd84dea20c1bf5ebff4c757009ed6bf19cfd36fb10e92c4e"
dependencies = [
"proc-macro2",
"quote",
@ -3530,9 +3520,9 @@ checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160"
[[package]]
name = "sync_wrapper"
version = "1.0.1"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394"
checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263"
dependencies = [
"futures-core",
]
@ -3768,7 +3758,7 @@ version = "0.26.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4"
dependencies = [
"rustls 0.23.16",
"rustls 0.23.18",
"rustls-pki-types",
"tokio",
]
@ -3999,9 +3989,9 @@ checksum = "5ab17db44d7388991a428b2ee655ce0c212e862eff1768a455c58f9aad6e7893"
[[package]]
name = "unicode-ident"
version = "1.0.13"
version = "1.0.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe"
checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83"
[[package]]
name = "unicode-normalization"

@ -70,13 +70,13 @@ futures = "0.3.31"
tokio = { version = "1.41.1", features = ["rt-multi-thread", "fs", "io-util", "parking_lot", "time", "signal", "net"] }
# A generic serialization/deserialization framework
serde = { version = "1.0.214", features = ["derive"] }
serde_json = "1.0.132"
serde = { version = "1.0.215", features = ["derive"] }
serde_json = "1.0.133"
# A safe, extensible ORM and Query builder
diesel = { version = "2.2.4", features = ["chrono", "r2d2", "numeric"] }
diesel = { version = "2.2.5", features = ["chrono", "r2d2", "numeric"] }
diesel_migrations = "2.2.0"
diesel_logger = { version = "0.3.0", optional = true }
diesel_logger = { version = "0.4.0", optional = true }
# Bundled/Static SQLite
libsqlite3-sys = { version = "0.30.1", features = ["bundled"], optional = true }

@ -48,6 +48,7 @@ pub fn routes() -> Vec<Route> {
confirm_invite,
bulk_confirm_invite,
accept_invite,
get_org_user_mini_details,
get_user,
edit_user,
put_organization_user,
@ -857,13 +858,18 @@ struct InviteData {
collections: Option<Vec<CollectionData>>,
#[serde(default)]
access_all: bool,
#[serde(default)]
permissions: HashMap<String, Value>,
}
#[post("/organizations/<org_id>/users/invite", data = "<data>")]
async fn send_invite(org_id: &str, data: Json<InviteData>, headers: AdminHeaders, mut conn: DbConn) -> EmptyResult {
let data: InviteData = data.into_inner();
let mut data: InviteData = data.into_inner();
let new_type = match UserOrgType::from_str(&data.r#type.into_string()) {
// HACK: We need the raw user-type be be sure custom role is selected to determine the access_all permission
// The from_str() will convert the custom role type into a manager role type
let raw_type = &data.r#type.into_string();
let new_type = match UserOrgType::from_str(raw_type) {
Some(new_type) => new_type as i32,
None => err!("Invalid type"),
};
@ -872,6 +878,17 @@ async fn send_invite(org_id: &str, data: Json<InviteData>, headers: AdminHeaders
err!("Only Owners can invite Managers, Admins or Owners")
}
// HACK: This converts the Custom role which has the `Manage all collections` box checked into an access_all flag
// Since the parent checkbox is not send to the server we need to check and verify the child checkboxes
// If the box is not checked, the user will still be a manager, but not with the access_all permission
if raw_type.eq("4")
&& data.permissions.get("editAnyCollection") == Some(&json!(true))
&& data.permissions.get("deleteAnyCollection") == Some(&json!(true))
&& data.permissions.get("createNewCollections") == Some(&json!(true))
{
data.access_all = true;
}
for email in data.emails.iter() {
let mut user_org_status = UserOrgStatus::Invited as i32;
let user = match User::find_by_mail(email, &mut conn).await {
@ -1279,7 +1296,21 @@ async fn _confirm_invite(
save_result
}
#[get("/organizations/<org_id>/users/<org_user_id>?<data..>")]
#[get("/organizations/<org_id>/users/mini-details", rank = 1)]
async fn get_org_user_mini_details(org_id: &str, _headers: AdminHeaders, mut conn: DbConn) -> Json<Value> {
let mut users_json = Vec::new();
for u in UserOrganization::find_by_org(org_id, &mut conn).await {
users_json.push(u.to_json_mini_details(&mut conn).await);
}
Json(json!({
"data": users_json,
"object": "list",
"continuationToken": null,
}))
}
#[get("/organizations/<org_id>/users/<org_user_id>?<data..>", rank = 2)]
async fn get_user(
org_id: &str,
org_user_id: &str,
@ -1308,6 +1339,8 @@ struct EditUserData {
groups: Option<Vec<String>>,
#[serde(default)]
access_all: bool,
#[serde(default)]
permissions: HashMap<String, Value>,
}
#[put("/organizations/<org_id>/users/<org_user_id>", data = "<data>", rank = 1)]
@ -1329,13 +1362,27 @@ async fn edit_user(
headers: AdminHeaders,
mut conn: DbConn,
) -> EmptyResult {
let data: EditUserData = data.into_inner();
let mut data: EditUserData = data.into_inner();
let new_type = match UserOrgType::from_str(&data.r#type.into_string()) {
// HACK: We need the raw user-type be be sure custom role is selected to determine the access_all permission
// The from_str() will convert the custom role type into a manager role type
let raw_type = &data.r#type.into_string();
let new_type = match UserOrgType::from_str(raw_type) {
Some(new_type) => new_type,
None => err!("Invalid type"),
};
// HACK: This converts the Custom role which has the `Manage all collections` box checked into an access_all flag
// Since the parent checkbox is not send to the server we need to check and verify the child checkboxes
// If the box is not checked, the user will still be a manager, but not with the access_all permission
if raw_type.eq("4")
&& data.permissions.get("editAnyCollection") == Some(&json!(true))
&& data.permissions.get("deleteAnyCollection") == Some(&json!(true))
&& data.permissions.get("createNewCollections") == Some(&json!(true))
{
data.access_all = true;
}
let mut user_to_edit = match UserOrganization::find_by_uuid_and_org(org_user_id, org_id, &mut conn).await {
Some(user) => user,
None => err!("The specified user isn't member of the organization"),
@ -2308,14 +2355,14 @@ async fn _restore_organization_user(
}
#[get("/organizations/<org_id>/groups")]
async fn get_groups(org_id: &str, _headers: ManagerHeadersLoose, mut conn: DbConn) -> JsonResult {
async fn get_groups(org_id: &str, headers: ManagerHeadersLoose, mut conn: DbConn) -> JsonResult {
let groups: Vec<Value> = if CONFIG.org_groups_enabled() {
// Group::find_by_organization(&org_id, &mut conn).await.iter().map(Group::to_json).collect::<Value>()
let groups = Group::find_by_organization(org_id, &mut conn).await;
let mut groups_json = Vec::with_capacity(groups.len());
for g in groups {
groups_json.push(g.to_json_details(&mut conn).await)
groups_json.push(g.to_json_details(&headers.org_user.atype, &mut conn).await)
}
groups_json
} else {
@ -2503,7 +2550,7 @@ async fn add_update_group(
}
#[get("/organizations/<_org_id>/groups/<group_id>/details")]
async fn get_group_details(_org_id: &str, group_id: &str, _headers: AdminHeaders, mut conn: DbConn) -> JsonResult {
async fn get_group_details(_org_id: &str, group_id: &str, headers: AdminHeaders, mut conn: DbConn) -> JsonResult {
if !CONFIG.org_groups_enabled() {
err!("Group support is disabled");
}
@ -2513,7 +2560,7 @@ async fn get_group_details(_org_id: &str, group_id: &str, _headers: AdminHeaders
_ => err!("Group could not be found!"),
};
Ok(Json(group.to_json_details(&mut conn).await))
Ok(Json(group.to_json_details(&(headers.org_user_type as i32), &mut conn).await))
}
#[post("/organizations/<org_id>/groups/<group_id>/delete")]

@ -1,4 +1,4 @@
use super::{User, UserOrganization};
use super::{User, UserOrgType, UserOrganization};
use crate::api::EmptyResult;
use crate::db::DbConn;
use crate::error::MapResult;
@ -73,7 +73,7 @@ impl Group {
})
}
pub async fn to_json_details(&self, conn: &mut DbConn) -> Value {
pub async fn to_json_details(&self, user_org_type: &i32, conn: &mut DbConn) -> Value {
let collections_groups: Vec<Value> = CollectionGroup::find_by_group(&self.uuid, conn)
.await
.iter()
@ -82,7 +82,7 @@ impl Group {
"id": entry.collections_uuid,
"readOnly": entry.read_only,
"hidePasswords": entry.hide_passwords,
"manage": false
"manage": *user_org_type >= UserOrgType::Admin || (*user_org_type == UserOrgType::Manager && !entry.read_only && !entry.hide_passwords)
})
})
.collect();

@ -73,6 +73,8 @@ impl UserOrgType {
"1" | "Admin" => Some(UserOrgType::Admin),
"2" | "User" => Some(UserOrgType::User),
"3" | "Manager" => Some(UserOrgType::Manager),
// HACK: We convert the custom role to a manager role
"4" | "Custom" => Some(UserOrgType::Manager),
_ => None,
}
}
@ -85,7 +87,7 @@ impl Ord for UserOrgType {
3, // Owner
2, // Admin
0, // User
1, // Manager
1, // Manager && Custom
];
ACCESS_LEVEL[*self as usize].cmp(&ACCESS_LEVEL[*other as usize])
}
@ -158,33 +160,46 @@ impl Organization {
pub fn to_json(&self) -> Value {
json!({
"id": self.uuid,
"identifier": null, // not supported by us
"name": self.name,
"seats": null,
"maxCollections": null,
"maxStorageGb": i16::MAX, // The value doesn't matter, we don't check server-side
"use2fa": true,
"useCustomPermissions": false,
"useCustomPermissions": true,
"useDirectory": false, // Is supported, but this value isn't checked anywhere (yet)
"useEvents": CONFIG.org_events_enabled(),
"useGroups": CONFIG.org_groups_enabled(),
"useTotp": true,
"usePolicies": true,
// "useScim": false, // Not supported (Not AGPLv3 Licensed)
"useScim": false, // Not supported (Not AGPLv3 Licensed)
"useSso": false, // Not supported
// "useKeyConnector": false, // Not supported
"useKeyConnector": false, // Not supported
"usePasswordManager": true,
"useSecretsManager": false, // Not supported (Not AGPLv3 Licensed)
"selfHost": true,
"useApi": true,
"hasPublicAndPrivateKeys": self.private_key.is_some() && self.public_key.is_some(),
"useResetPassword": CONFIG.mail_enabled(),
"allowAdminAccessToAllCollectionItems": true,
"limitCollectionCreation": true,
"limitCollectionCreationDeletion": true,
"limitCollectionDeletion": true,
"businessName": null,
"businessName": self.name,
"businessAddress1": null,
"businessAddress2": null,
"businessAddress3": null,
"businessCountry": null,
"businessTaxNumber": null,
"maxAutoscaleSeats": null,
"maxAutoscaleSmSeats": null,
"maxAutoscaleSmServiceAccounts": null,
"secretsManagerPlan": null,
"smSeats": null,
"smServiceAccounts": null,
"billingEmail": self.billing_email,
"planType": 6, // Custom plan
"usersGetPremium": true,
@ -356,17 +371,24 @@ impl UserOrganization {
pub async fn to_json(&self, conn: &mut DbConn) -> Value {
let org = Organization::find_by_uuid(&self.org_uuid, conn).await.unwrap();
// HACK: Convert the manager type to a custom type
// It will be converted back on other locations
let user_org_type = match self.atype {
3 => 4,
_ => self.atype,
};
let permissions = json!({
// TODO: Add support for Custom User Roles
// TODO: Add full support for Custom User Roles
// See: https://bitwarden.com/help/article/user-types-access-control/#custom-role
// Currently we use the custom role as a manager role and link the 3 Collection roles to mimic the access_all permission
"accessEventLogs": false,
"accessImportExport": false,
"accessReports": false,
"createNewCollections": false,
"editAnyCollection": false,
"deleteAnyCollection": false,
"editAssignedCollections": false,
"deleteAssignedCollections": false,
// If the following 3 Collection roles are set to true a custom user has access all permission
"createNewCollections": user_org_type == 4 && self.access_all,
"editAnyCollection": user_org_type == 4 && self.access_all,
"deleteAnyCollection": user_org_type == 4 && self.access_all,
"manageGroups": false,
"managePolicies": false,
"manageSso": false, // Not supported
@ -398,9 +420,9 @@ impl UserOrganization {
"ssoBound": false, // Not supported
"useSso": false, // Not supported
"useKeyConnector": false,
"useSecretsManager": false,
"useSecretsManager": false, // Not supported (Not AGPLv3 Licensed)
"usePasswordManager": true,
"useCustomPermissions": false,
"useCustomPermissions": true,
"useActivateAutofillPolicy": false,
"organizationUserId": self.uuid,
@ -417,9 +439,11 @@ impl UserOrganization {
"familySponsorshipValidUntil": null,
"familySponsorshipToDelete": null,
"accessSecretsManager": false,
"limitCollectionCreationDeletion": false, // This should be set to true only when we can handle roles like createNewCollections
"limitCollectionCreation": true,
"limitCollectionCreationDeletion": true,
"limitCollectionDeletion": true,
"allowAdminAccessToAllCollectionItems": true,
"flexibleCollections": false,
"userIsManagedByOrganization": false, // Means not managed via the Members UI, like SSO
"permissions": permissions,
@ -429,7 +453,7 @@ impl UserOrganization {
"userId": self.user_uuid,
"key": self.akey,
"status": self.status,
"type": self.atype,
"type": user_org_type,
"enabled": true,
"object": "profileOrganization",
@ -516,24 +540,37 @@ impl UserOrganization {
Vec::with_capacity(0)
};
let permissions = json!({
// TODO: Add support for Custom User Roles
// See: https://bitwarden.com/help/article/user-types-access-control/#custom-role
"accessEventLogs": false,
"accessImportExport": false,
"accessReports": false,
"createNewCollections": false,
"editAnyCollection": false,
"deleteAnyCollection": false,
"editAssignedCollections": false,
"deleteAssignedCollections": false,
"manageGroups": false,
"managePolicies": false,
"manageSso": false, // Not supported
"manageUsers": false,
"manageResetPassword": false,
"manageScim": false // Not supported (Not AGPLv3 Licensed)
});
// HACK: Convert the manager type to a custom type
// It will be converted back on other locations
let user_org_type = match self.atype {
3 => 4,
_ => self.atype,
};
// HACK: Only return permissions if the user is of type custom and has access_all
// Else Bitwarden will assume the defaults of all false
let permissions = if user_org_type == 4 && self.access_all {
json!({
// TODO: Add full support for Custom User Roles
// See: https://bitwarden.com/help/article/user-types-access-control/#custom-role
// Currently we use the custom role as a manager role and link the 3 Collection roles to mimic the access_all permission
"accessEventLogs": false,
"accessImportExport": false,
"accessReports": false,
// If the following 3 Collection roles are set to true a custom user has access all permission
"createNewCollections": true,
"editAnyCollection": true,
"deleteAnyCollection": true,
"manageGroups": false,
"managePolicies": false,
"manageSso": false, // Not supported
"manageUsers": false,
"manageResetPassword": false,
"manageScim": false // Not supported (Not AGPLv3 Licensed)
})
} else {
json!(null)
};
json!({
"id": self.uuid,
@ -546,7 +583,7 @@ impl UserOrganization {
"collections": collections,
"status": status,
"type": self.atype,
"type": user_org_type,
"accessAll": self.access_all,
"twoFactorEnabled": twofactor_enabled,
"resetPasswordEnrolled": self.reset_password_key.is_some(),
@ -608,6 +645,29 @@ impl UserOrganization {
"object": "organizationUserDetails",
})
}
pub async fn to_json_mini_details(&self, conn: &mut DbConn) -> Value {
let user = User::find_by_uuid(&self.user_uuid, conn).await.unwrap();
// Because BitWarden want the status to be -1 for revoked users we need to catch that here.
// We subtract/add a number so we can restore/activate the user to it's previous state again.
let status = if self.status < UserOrgStatus::Revoked as i32 {
UserOrgStatus::Revoked as i32
} else {
self.status
};
json!({
"id": self.uuid,
"userId": self.user_uuid,
"type": self.atype,
"status": status,
"name": user.name,
"email": user.email,
"object": "organizationUserUserMiniDetails",
})
}
pub async fn save(&self, conn: &mut DbConn) -> EmptyResult {
User::update_uuid_revision(&self.user_uuid, conn).await;

@ -42,12 +42,6 @@ label[for^="ownedBusiness"] {
@extend %vw-hide;
}
/* Hide the radio button and label for the `Custom` org user type */
#userTypeCustom,
label[for^="userTypeCustom"] {
@extend %vw-hide;
}
/* Hide Business Name */
app-org-account form div bit-form-field.tw-block:nth-child(3) {
@extend %vw-hide;
@ -58,10 +52,33 @@ app-organization-plans > form > bit-section:nth-child(2) {
@extend %vw-hide;
}
/* Hide Collection Management Form */
app-org-account form.ng-untouched:nth-child(6) {
display:none !important
}
/* Hide Device Verification form at the Two Step Login screen */
app-security > app-two-factor-setup > form {
@extend %vw-hide;
}
/* Hide unsupported Custom Role options */
bit-dialog div.tw-ml-4:has(bit-form-control input),
bit-dialog div.tw-col-span-4:has(input[formcontrolname*="access"], input[formcontrolname*="manage"]) {
@extend %vw-hide;
}
/* Change collapsed menu icon to Vaultwarden */
bit-nav-logo bit-nav-item a:before {
content:"";
background:url("../images/icon-white.png") no-repeat;
background-position: center center;
height:32px;
display: block;
}
bit-nav-logo bit-nav-item .bwi-shield {
@extend %vw-hide;
}
/**** END Static Vaultwarden Changes ****/
/**** START Dynamic Vaultwarden Changes ****/
{{#if signup_disabled}}

Loading…
Cancel
Save