Move permissions to its own module
Diff
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(-)
@@ -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<Option<UserCratePermission>> {
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<Self>,
conn: ConnectionPool,
) -> Result<Vec<(crate::users::User, crate::users::UserCratePermissionValue)>> {
if !self.permissions.contains(Permissions::MANAGE_USERS) {
return Err(Error::MissingCratePermission(Permissions::MANAGE_USERS));
) -> Result<Vec<(crate::users::User, UserPermission)>> {
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<Self>,
conn: ConnectionPool,
given_user_id: i32,
given_permissions: crate::users::UserCratePermissionValue,
given_permissions: UserPermission,
) -> Result<usize> {
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<Self>,
conn: ConnectionPool,
given_user_id: i32,
given_permissions: crate::users::UserCratePermissionValue,
given_permissions: UserPermission,
) -> Result<usize> {
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 || {
@@ -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 @@
KeyParse(#[from] thrussh_keys::Error),
MissingCratePermission(crate::users::UserCratePermissionValue),
MissingCratePermission(crate::permissions::UserPermission),
MissingOrganisationPermission(crate::users::UserCratePermissionValue),
MissingOrganisationPermission(crate::permissions::UserPermission),
MissingCrate,
@@ -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
}
@@ -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<Permission>, _)>(&conn)
.get_result::<(Option<UserPermission>, _)>(&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<Self>, conn: ConnectionPool) -> Result<Vec<Crate>> {
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<Self>, conn: ConnectionPool) -> Result<Vec<(User, Permission)>> {
if !self.permissions.contains(Permission::VISIBLE) {
return Err(Error::MissingOrganisationPermission(Permission::VISIBLE));
pub async fn members(
self: Arc<Self>,
conn: ConnectionPool,
) -> Result<Vec<(User, UserPermission)>> {
if !self.permissions.contains(UserPermission::VISIBLE) {
return Err(Error::MissingOrganisationPermission(
UserPermission::VISIBLE,
));
}
tokio::task::spawn_blocking(move || {
@@ -113,10 +117,10 @@
self: Arc<Self>,
conn: ConnectionPool,
given_user_id: i32,
given_permissions: crate::users::UserCratePermissionValue,
given_permissions: UserPermission,
) -> Result<usize> {
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<Self>,
conn: ConnectionPool,
given_user_id: i32,
given_permissions: crate::users::UserCratePermissionValue,
given_permissions: UserPermission,
) -> Result<usize> {
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 || {
@@ -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<B: diesel::backend::Backend> diesel::deserialize::FromSql<diesel::sql_types::Integer, B>
for UserPermission
where
i32: diesel::deserialize::FromSql<diesel::sql_types::Integer, B>,
{
fn from_sql(
bytes: Option<&B::RawValue>,
) -> std::result::Result<UserPermission, Box<dyn std::error::Error + Send + Sync>> {
let val = i32::from_sql(bytes)?;
Ok(UserPermission::from_bits_truncate(val))
}
}
@@ -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<Self>,
conn: ConnectionPool,
) -> Result<Vec<(UserCratePermissionValue, crate::crates::Crate)>> {
) -> Result<Vec<(UserPermission, crate::crates::Crate)>> {
use crate::schema::crates;
tokio::task::spawn_blocking(move || {
@@ -210,7 +210,7 @@
self: Arc<Self>,
conn: ConnectionPool,
crate_id: i32,
) -> Result<UserCratePermissionValue> {
) -> Result<UserPermission> {
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<B: diesel::backend::Backend> diesel::deserialize::FromSql<diesel::sql_types::Integer, B>
for UserCratePermissionValue
where
i32: diesel::deserialize::FromSql<diesel::sql_types::Integer, B>,
{
fn from_sql(
bytes: Option<&B::RawValue>,
) -> std::result::Result<UserCratePermissionValue, Box<dyn std::error::Error + Send + Sync>>
{
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<Option<UserCratePermission>> {
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?
}
@@ -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(
@@ -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<Permission>,
possible_permissions: Option<UserPermission>,
crates: Vec<ResponseCrate>,
members: Vec<ResponseUser>,
}
@@ -78,5 +76,5 @@
pub struct ResponseUser {
uuid: String,
username: String,
permissions: Option<Permission>,
permissions: Option<UserPermission>,
}
@@ -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(