From a7544b43b22dbd2baff1c1a736b766acac5519aa Mon Sep 17 00:00:00 2001 From: Jordan Doyle Date: Sat, 25 Sep 2021 02:09:01 +0100 Subject: [PATCH] Move permissions to its own module --- chartered-db/src/crates.rs | 121 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------- chartered-db/src/lib.rs | 7 ++++--- chartered-db/src/organisations.rs | 50 +++++++++++++++++++++++++++++++------------------- chartered-db/src/permissions.rs | 33 +++++++++++++++++++++++++++++++++ chartered-db/src/users.rs | 71 ++++------------------------------------------------------------------- chartered-web/src/endpoints/web_api/crates/members.rs | 11 ++++------- chartered-web/src/endpoints/web_api/organisations/info.rs | 12 +++++------- chartered-web/src/endpoints/web_api/organisations/members.rs | 6 ++---- 8 files changed, 155 insertions(+), 156 deletions(-) diff --git a/chartered-db/src/crates.rs b/chartered-db/src/crates.rs index 0cad66b..5840386 100644 --- a/chartered-db/src/crates.rs +++ a/chartered-db/src/crates.rs @@ -1,17 +1,47 @@ -use crate::organisations::Organisation; -use crate::users::{User, UserCratePermission}; - use super::{ coalesce, - schema::{crate_versions, crates, organisations, users}, - users::UserCratePermissionValue as Permissions, + organisations::Organisation, + permissions::UserPermission, + schema::{crate_versions, crates, organisations, user_crate_permissions, users}, + users::User, BitwiseExpressionMethods, ConnectionPool, Error, Result, }; use diesel::{insert_into, prelude::*, Associations, Identifiable, Queryable}; use itertools::Itertools; use serde::{Deserialize, Serialize}; use std::{collections::HashMap, sync::Arc}; + +#[derive(Identifiable, Queryable, Associations, Default, PartialEq, Eq, Hash, Debug)] +#[belongs_to(User)] +#[belongs_to(Crate)] +pub struct UserCratePermission { + pub id: i32, + pub user_id: i32, + pub crate_id: i32, + pub permissions: UserPermission, +} + +impl UserCratePermission { + pub async fn find( + conn: ConnectionPool, + given_user_id: i32, + given_crate_id: i32, + ) -> Result> { + use crate::schema::user_crate_permissions::dsl::{crate_id, user_id}; + + tokio::task::spawn_blocking(move || { + let conn = conn.get()?; + Ok(crate::schema::user_crate_permissions::table + .filter(user_id.eq(given_user_id)) + .filter(crate_id.eq(given_crate_id)) + .get_result(&conn) + .optional()?) + }) + .await? + } +} + #[derive(Identifiable, Queryable, Associations, PartialEq, Eq, Hash, Debug)] #[belongs_to(Organisation)] pub struct Crate { @@ -77,8 +107,8 @@ .filter(org_name.eq(given_org_name)) .filter( select_permissions!() - .bitwise_and(Permissions::VISIBLE.bits()) - .eq(Permissions::VISIBLE.bits()), + .bitwise_and(UserPermission::VISIBLE.bits()) + .eq(UserPermission::VISIBLE.bits()), ) .inner_join(crate_versions::table) .select((crates::all_columns, crate_versions::all_columns)) @@ -99,8 +129,8 @@ let crates = crate_with_permissions!(requesting_user_id) .filter( select_permissions!() - .bitwise_and(Permissions::VISIBLE.bits()) - .eq(Permissions::VISIBLE.bits()), + .bitwise_and(UserPermission::VISIBLE.bits()) + .eq(UserPermission::VISIBLE.bits()), ) .inner_join(organisations::table) .inner_join(crate_versions::table) @@ -135,17 +165,17 @@ .filter(org_name.eq(given_org_name)) .filter(crate_name.eq(given_crate_name)) .select((crate::schema::crates::all_columns, select_permissions!())) - .first::<(Crate, Permissions)>(&conn) + .first::<(Crate, UserPermission)>(&conn) .optional()? .ok_or(Error::MissingCrate)?; - if permissions.contains(Permissions::VISIBLE) { + if permissions.contains(UserPermission::VISIBLE) { Ok(CrateWithPermissions { crate_, permissions, }) } else { - Err(Error::MissingCratePermission(Permissions::VISIBLE)) + Err(Error::MissingCratePermission(UserPermission::VISIBLE)) } }) .await? @@ -172,13 +202,13 @@ .on(organisation_id.eq(id).and(user_id.eq(requesting_user_id))), ) .select((id, permissions)) - .first::<(i32, Permissions)>(&conn)?; + .first::<(i32, UserPermission)>(&conn)?; #[allow(clippy::if_not_else)] - if !perms.contains(Permissions::VISIBLE) { - Err(Error::MissingCratePermission(Permissions::VISIBLE)) - } else if !perms.contains(Permissions::CREATE_CRATE) { - Err(Error::MissingCratePermission(Permissions::CREATE_CRATE)) + if !perms.contains(UserPermission::VISIBLE) { + Err(Error::MissingCratePermission(UserPermission::VISIBLE)) + } else if !perms.contains(UserPermission::CREATE_CRATE) { + Err(Error::MissingCratePermission(UserPermission::CREATE_CRATE)) } else { use crate::schema::crates::dsl::{crates, name, organisation_id}; @@ -204,7 +234,7 @@ #[derive(Debug)] pub struct CrateWithPermissions { pub crate_: Crate, - pub permissions: Permissions, + pub permissions: UserPermission, } impl CrateWithPermissions { @@ -249,7 +279,7 @@ Ok(UserCratePermission::belonging_to(&self.crate_) .filter( permissions - .bitwise_and(crate::users::UserCratePermissionValue::MANAGE_USERS.bits()) + .bitwise_and(UserPermission::MANAGE_USERS.bits()) .ne(0), ) .inner_join(crate::schema::users::dsl::users) @@ -262,20 +292,17 @@ pub async fn members( self: Arc, conn: ConnectionPool, - ) -> Result> { - if !self.permissions.contains(Permissions::MANAGE_USERS) { - return Err(Error::MissingCratePermission(Permissions::MANAGE_USERS)); + ) -> Result> { + if !self.permissions.contains(UserPermission::MANAGE_USERS) { + return Err(Error::MissingCratePermission(UserPermission::MANAGE_USERS)); } tokio::task::spawn_blocking(move || { let conn = conn.get()?; Ok(UserCratePermission::belonging_to(&self.crate_) - .inner_join(crate::schema::users::dsl::users) - .select(( - crate::schema::users::all_columns, - crate::schema::user_crate_permissions::permissions, - )) + .inner_join(users::dsl::users) + .select((users::all_columns, user_crate_permissions::permissions)) .load(&conn)?) }) .await? @@ -285,10 +312,10 @@ self: Arc, conn: ConnectionPool, given_user_id: i32, - given_permissions: crate::users::UserCratePermissionValue, + given_permissions: UserPermission, ) -> Result { - if !self.permissions.contains(Permissions::MANAGE_USERS) { - return Err(Error::MissingCratePermission(Permissions::MANAGE_USERS)); + if !self.permissions.contains(UserPermission::MANAGE_USERS) { + return Err(Error::MissingCratePermission(UserPermission::MANAGE_USERS)); } tokio::task::spawn_blocking(move || { @@ -313,10 +340,10 @@ self: Arc, conn: ConnectionPool, given_user_id: i32, - given_permissions: crate::users::UserCratePermissionValue, + given_permissions: UserPermission, ) -> Result { - if !self.permissions.contains(Permissions::MANAGE_USERS) { - return Err(Error::MissingCratePermission(Permissions::MANAGE_USERS)); + if !self.permissions.contains(UserPermission::MANAGE_USERS) { + return Err(Error::MissingCratePermission(UserPermission::MANAGE_USERS)); } tokio::task::spawn_blocking(move || { @@ -342,8 +369,8 @@ conn: ConnectionPool, given_user_id: i32, ) -> Result<()> { - if !self.permissions.contains(Permissions::MANAGE_USERS) { - return Err(Error::MissingCratePermission(Permissions::MANAGE_USERS)); + if !self.permissions.contains(UserPermission::MANAGE_USERS) { + return Err(Error::MissingCratePermission(UserPermission::MANAGE_USERS)); } tokio::task::spawn_blocking(move || { @@ -376,16 +403,20 @@ given: chartered_types::cargo::CrateVersion<'static>, metadata: chartered_types::cargo::CrateVersionMetadata, ) -> Result<()> { - use crate::schema::crate_versions::dsl::{ - checksum, crate_id, crate_versions, dependencies, features, filesystem_object, links, - size, user_id, version, - }; - use crate::schema::crates::dsl::{ - crates, description, documentation, homepage, id, name, readme, repository, + use crate::schema::{ + crate_versions::dsl::{ + checksum, crate_id, crate_versions, dependencies, features, filesystem_object, + links, size, user_id, version, + }, + crates::dsl::{ + crates, description, documentation, homepage, id, name, readme, repository, + }, }; - if !self.permissions.contains(Permissions::PUBLISH_VERSION) { - return Err(Error::MissingCratePermission(Permissions::PUBLISH_VERSION)); + if !self.permissions.contains(UserPermission::PUBLISH_VERSION) { + return Err(Error::MissingCratePermission( + UserPermission::PUBLISH_VERSION, + )); } tokio::task::spawn_blocking(move || { @@ -441,8 +472,8 @@ ) -> Result<()> { use crate::schema::crate_versions::dsl::{crate_id, crate_versions, version, yanked}; - if !self.permissions.contains(Permissions::YANK_VERSION) { - return Err(Error::MissingCratePermission(Permissions::YANK_VERSION)); + if !self.permissions.contains(UserPermission::YANK_VERSION) { + return Err(Error::MissingCratePermission(UserPermission::YANK_VERSION)); } tokio::task::spawn_blocking(move || { diff --git a/chartered-db/src/lib.rs b/chartered-db/src/lib.rs index c66eb8a..bc4c9af 100644 --- a/chartered-db/src/lib.rs +++ a/chartered-db/src/lib.rs @@ -33,6 +33,7 @@ pub mod crates; pub mod organisations; +pub mod permissions; pub mod schema; pub mod users; pub mod uuid; @@ -68,9 +69,9 @@ /// Key parse failure: `{0}` KeyParse(#[from] thrussh_keys::Error), /// You don't have the {0:?} permission for this crate - MissingCratePermission(crate::users::UserCratePermissionValue), + MissingCratePermission(crate::permissions::UserPermission), /// You don't have the {0:?} permission for this organisation - MissingOrganisationPermission(crate::users::UserCratePermissionValue), + MissingOrganisationPermission(crate::permissions::UserPermission), /// The requested crate does not exist MissingCrate, /// The requested organisation does not exist @@ -85,7 +86,7 @@ match self { Self::MissingCrate => http::StatusCode::NOT_FOUND, Self::MissingCratePermission(v) | Self::MissingOrganisationPermission(v) - if v.contains(crate::users::UserCratePermissionValue::VISIBLE) => + if v.contains(crate::permissions::UserPermission::VISIBLE) => { http::StatusCode::NOT_FOUND } diff --git a/chartered-db/src/organisations.rs b/chartered-db/src/organisations.rs index 5a0e735..3ffad25 100644 --- a/chartered-db/src/organisations.rs +++ a/chartered-db/src/organisations.rs @@ -1,8 +1,4 @@ -use crate::{ - crates::Crate, - users::{User, UserCratePermissionValue as Permission}, - Error, -}; +use crate::{crates::Crate, permissions::UserPermission, users::User, Error}; use super::{ schema::{organisations, user_organisation_permissions, users}, @@ -46,12 +42,13 @@ user_organisation_permissions::dsl::permissions.nullable(), organisations::all_columns, )) - .get_result::<(Option, _)>(&conn) + .get_result::<(Option, _)>(&conn) .optional()? .ok_or(Error::MissingOrganisation)?; - let permissions = - permissions.ok_or(Error::MissingOrganisationPermission(Permission::VISIBLE))?; + let permissions = permissions.ok_or(Error::MissingOrganisationPermission( + UserPermission::VISIBLE, + ))?; Ok(OrganisationWithPermissions { organisation, @@ -64,18 +61,20 @@ pub struct OrganisationWithPermissions { organisation: Organisation, - permissions: Permission, + permissions: UserPermission, } impl OrganisationWithPermissions { #[must_use] - pub fn permissions(&self) -> Permission { + pub fn permissions(&self) -> UserPermission { self.permissions } pub async fn crates(self: Arc, conn: ConnectionPool) -> Result> { - if !self.permissions.contains(Permission::VISIBLE) { - return Err(Error::MissingOrganisationPermission(Permission::VISIBLE)); + if !self.permissions.contains(UserPermission::VISIBLE) { + return Err(Error::MissingOrganisationPermission( + UserPermission::VISIBLE, + )); } tokio::task::spawn_blocking(move || { @@ -87,9 +86,14 @@ .await? } - pub async fn members(self: Arc, conn: ConnectionPool) -> Result> { - if !self.permissions.contains(Permission::VISIBLE) { - return Err(Error::MissingOrganisationPermission(Permission::VISIBLE)); + pub async fn members( + self: Arc, + conn: ConnectionPool, + ) -> Result> { + if !self.permissions.contains(UserPermission::VISIBLE) { + return Err(Error::MissingOrganisationPermission( + UserPermission::VISIBLE, + )); } tokio::task::spawn_blocking(move || { @@ -113,10 +117,10 @@ self: Arc, conn: ConnectionPool, given_user_id: i32, - given_permissions: crate::users::UserCratePermissionValue, + given_permissions: UserPermission, ) -> Result { - if !self.permissions.contains(Permission::MANAGE_USERS) { - return Err(Error::MissingCratePermission(Permission::MANAGE_USERS)); + if !self.permissions.contains(UserPermission::MANAGE_USERS) { + return Err(Error::MissingCratePermission(UserPermission::MANAGE_USERS)); } tokio::task::spawn_blocking(move || { @@ -141,10 +145,10 @@ self: Arc, conn: ConnectionPool, given_user_id: i32, - given_permissions: crate::users::UserCratePermissionValue, + given_permissions: UserPermission, ) -> Result { - if !self.permissions.contains(Permission::MANAGE_USERS) { - return Err(Error::MissingCratePermission(Permission::MANAGE_USERS)); + if !self.permissions.contains(UserPermission::MANAGE_USERS) { + return Err(Error::MissingCratePermission(UserPermission::MANAGE_USERS)); } tokio::task::spawn_blocking(move || { @@ -170,8 +174,8 @@ conn: ConnectionPool, given_user_id: i32, ) -> Result<()> { - if !self.permissions.contains(Permission::MANAGE_USERS) { - return Err(Error::MissingCratePermission(Permission::MANAGE_USERS)); + if !self.permissions.contains(UserPermission::MANAGE_USERS) { + return Err(Error::MissingCratePermission(UserPermission::MANAGE_USERS)); } tokio::task::spawn_blocking(move || { diff --git a/chartered-db/src/permissions.rs b/chartered-db/src/permissions.rs new file mode 100644 index 0000000..0b3e282 100644 --- /dev/null +++ a/chartered-db/src/permissions.rs @@ -1,0 +1,33 @@ +use bitflags::bitflags; +use option_set::{option_set, OptionSet}; + +option_set! { + #[derive(FromSqlRow, AsExpression)] + pub struct UserPermission: Identity + i32 { + const VISIBLE = 0b0000_0000_0000_0000_0000_0000_0000_0001; + const PUBLISH_VERSION = 0b0000_0000_0000_0000_0000_0000_0000_0010; + const YANK_VERSION = 0b0000_0000_0000_0000_0000_0000_0000_0100; + const MANAGE_USERS = 0b0000_0000_0000_0000_0000_0000_0000_1000; + const CREATE_CRATE = 0b0000_0000_0000_0000_0000_0000_0001_0000; + } +} + +impl UserPermission { + #[must_use] + pub fn names() -> &'static [&'static str] { + Self::NAMES + } +} + +impl diesel::deserialize::FromSql + for UserPermission +where + i32: diesel::deserialize::FromSql, +{ + fn from_sql( + bytes: Option<&B::RawValue>, + ) -> std::result::Result> { + let val = i32::from_sql(bytes)?; + Ok(UserPermission::from_bits_truncate(val)) + } +} diff --git a/chartered-db/src/users.rs b/chartered-db/src/users.rs index 00d6011..69e16d3 100644 --- a/chartered-db/src/users.rs +++ a/chartered-db/src/users.rs @@ -1,11 +1,11 @@ use super::{ + crates::UserCratePermission, + permissions::UserPermission, schema::{user_crate_permissions, user_sessions, user_ssh_keys, users}, uuid::SqlUuid, ConnectionPool, Result, }; -use bitflags::bitflags; use diesel::{insert_into, prelude::*, Associations, Identifiable, Queryable}; -use option_set::{option_set, OptionSet}; use rand::{thread_rng, Rng}; use std::sync::Arc; use thrussh_keys::PublicKeyBase64; @@ -192,7 +192,7 @@ pub async fn accessible_crates( self: Arc, conn: ConnectionPool, - ) -> Result> { + ) -> Result> { use crate::schema::crates; tokio::task::spawn_blocking(move || { @@ -210,7 +210,7 @@ self: Arc, conn: ConnectionPool, crate_id: i32, - ) -> Result { + ) -> Result { Ok(UserCratePermission::find(conn, self.id, crate_id) .await? .unwrap_or_default() @@ -267,69 +267,6 @@ Ok(crate::schema::user_sessions::table .filter(session_key.eq(generated_session_key)) .get_result(&conn)?) - }) - .await? - } -} - -option_set! { - #[derive(FromSqlRow, AsExpression)] - pub struct UserCratePermissionValue: Identity + i32 { - const VISIBLE = 0b0000_0000_0000_0000_0000_0000_0000_0001; - const PUBLISH_VERSION = 0b0000_0000_0000_0000_0000_0000_0000_0010; - const YANK_VERSION = 0b0000_0000_0000_0000_0000_0000_0000_0100; - const MANAGE_USERS = 0b0000_0000_0000_0000_0000_0000_0000_1000; - const CREATE_CRATE = 0b0000_0000_0000_0000_0000_0000_0001_0000; - } -} - -impl UserCratePermissionValue { - #[must_use] - pub fn names() -> &'static [&'static str] { - Self::NAMES - } -} - -impl diesel::deserialize::FromSql - for UserCratePermissionValue -where - i32: diesel::deserialize::FromSql, -{ - fn from_sql( - bytes: Option<&B::RawValue>, - ) -> std::result::Result> - { - let val = i32::from_sql(bytes)?; - Ok(UserCratePermissionValue::from_bits_truncate(val)) - } -} - -#[derive(Identifiable, Queryable, Associations, Default, PartialEq, Eq, Hash, Debug)] -#[belongs_to(User)] -#[belongs_to(super::crates::Crate)] -pub struct UserCratePermission { - pub id: i32, - pub user_id: i32, - pub crate_id: i32, - pub permissions: UserCratePermissionValue, -} - -impl UserCratePermission { - pub async fn find( - conn: ConnectionPool, - given_user_id: i32, - given_crate_id: i32, - ) -> Result> { - use crate::schema::user_crate_permissions::dsl::{crate_id, user_id}; - - tokio::task::spawn_blocking(move || { - let conn = conn.get()?; - - Ok(crate::schema::user_crate_permissions::table - .filter(user_id.eq(given_user_id)) - .filter(crate_id.eq(given_crate_id)) - .get_result(&conn) - .optional()?) }) .await? } diff --git a/chartered-web/src/endpoints/web_api/crates/members.rs b/chartered-web/src/endpoints/web_api/crates/members.rs index 4059533..c9a186f 100644 --- a/chartered-web/src/endpoints/web_api/crates/members.rs +++ a/chartered-web/src/endpoints/web_api/crates/members.rs @@ -1,9 +1,6 @@ use axum::{extract, Json}; use chartered_db::{ - crates::Crate, - users::{User, UserCratePermissionValue as Permission}, - uuid::Uuid, - ConnectionPool, + crates::Crate, permissions::UserPermission, users::User, uuid::Uuid, ConnectionPool, }; use serde::{Deserialize, Serialize}; use std::sync::Arc; @@ -21,7 +18,7 @@ pub struct GetResponseMember { uuid: Uuid, username: String, - permissions: Permission, + permissions: UserPermission, } pub async fn handle_get( @@ -44,7 +41,7 @@ .collect(); Ok(Json(GetResponse { - allowed_permissions: Permission::names(), + allowed_permissions: UserPermission::names(), members, })) } @@ -52,7 +49,7 @@ #[derive(Deserialize)] pub struct PutOrPatchRequest { user_uuid: chartered_db::uuid::Uuid, - permissions: Permission, + permissions: UserPermission, } pub async fn handle_patch( diff --git a/chartered-web/src/endpoints/web_api/organisations/info.rs b/chartered-web/src/endpoints/web_api/organisations/info.rs index 052228f..26527a3 100644 --- a/chartered-web/src/endpoints/web_api/organisations/info.rs +++ a/chartered-web/src/endpoints/web_api/organisations/info.rs @@ -1,8 +1,6 @@ use axum::{extract, Json}; use chartered_db::{ - organisations::Organisation, - users::{User, UserCratePermissionValue as Permission}, - ConnectionPool, + organisations::Organisation, permissions::UserPermission, users::User, ConnectionPool, }; use serde::Serialize; use std::sync::Arc; @@ -34,7 +32,7 @@ let can_manage_users = organisation .permissions() - .contains(Permission::MANAGE_USERS); + .contains(UserPermission::MANAGE_USERS); let (crates, users) = tokio::try_join!( organisation.clone().crates(db.clone()), @@ -42,7 +40,7 @@ )?; Ok(Json(Response { - possible_permissions: can_manage_users.then(Permission::all), + possible_permissions: can_manage_users.then(UserPermission::all), crates: crates .into_iter() .map(|v| ResponseCrate { @@ -63,7 +61,7 @@ #[derive(Serialize)] pub struct Response { - possible_permissions: Option, + possible_permissions: Option, crates: Vec, members: Vec, } @@ -78,5 +76,5 @@ pub struct ResponseUser { uuid: String, username: String, - permissions: Option, + permissions: Option, } diff --git a/chartered-web/src/endpoints/web_api/organisations/members.rs b/chartered-web/src/endpoints/web_api/organisations/members.rs index f80f673..2262a17 100644 --- a/chartered-web/src/endpoints/web_api/organisations/members.rs +++ a/chartered-web/src/endpoints/web_api/organisations/members.rs @@ -1,8 +1,6 @@ use axum::{extract, Json}; use chartered_db::{ - organisations::Organisation, - users::{User, UserCratePermissionValue as Permission}, - ConnectionPool, + organisations::Organisation, permissions::UserPermission, users::User, ConnectionPool, }; use serde::Deserialize; use std::sync::Arc; @@ -13,7 +11,7 @@ #[derive(Deserialize)] pub struct PutOrPatchRequest { user_uuid: chartered_db::uuid::Uuid, - permissions: Permission, + permissions: UserPermission, } pub async fn handle_patch( -- rgit 0.1.3