Skip to content
This repository has been archived by the owner on Oct 19, 2024. It is now read-only.

Commit

Permalink
more tests, full reorganization
Browse files Browse the repository at this point in the history
  • Loading branch information
thesuzerain committed Oct 5, 2023
1 parent e9e7fc1 commit 3cead46
Show file tree
Hide file tree
Showing 17 changed files with 1,692 additions and 1,355 deletions.
8 changes: 4 additions & 4 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion src/auth/validate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -166,11 +166,12 @@ pub async fn check_is_moderator_from_headers<'a, 'b, E>(
executor: E,
redis: &RedisPool,
session_queue: &AuthQueue,
required_scopes: Option<&[Scopes]>,
) -> Result<User, AuthenticationError>
where
E: sqlx::Executor<'a, Database = sqlx::Postgres> + Copy,
{
let user = get_user_from_headers(req, executor, redis, session_queue, None)
let user = get_user_from_headers(req, executor, redis, session_queue, required_scopes)
.await?
.1;

Expand Down
28 changes: 26 additions & 2 deletions src/models/pats.rs
Original file line number Diff line number Diff line change
Expand Up @@ -104,15 +104,15 @@ bitflags::bitflags! {
const ORGANIZATION_DELETE = 1 << 38;

const ALL = 0b111111111111111111111111111111111111111;
const NOT_RESTRICTED = 0b1111111100000011111111111111100111;
const NOT_RESTRICTED = 0b1111111110000000111111111111111111100111;
const NONE = 0b0;
}
}

impl Scopes {
// these scopes cannot be specified in a personal access token
pub fn restricted(&self) -> bool {
self.contains(
self.intersects(
Scopes::PAT_CREATE
| Scopes::PAT_READ
| Scopes::PAT_WRITE
Expand Down Expand Up @@ -159,3 +159,27 @@ impl PersonalAccessToken {
}
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
pub fn pat_sanity() {
assert_eq!(Scopes::NONE, Scopes::empty());

// Ensure PATs add up and match
// (Such as NOT_RESTRICTED lining up with is_restricted())
let mut calculated_not_restricted = Scopes::NONE;
let mut calculated_all = Scopes::NONE;
for i in 0..64 {
let scope = Scopes::from_bits_truncate(1 << i);
if !scope.restricted() {
calculated_not_restricted |= scope;
}
calculated_all |= scope;
}
assert_eq!(Scopes::ALL | Scopes::NOT_RESTRICTED, calculated_all);
assert_eq!(Scopes::NOT_RESTRICTED, calculated_not_restricted);
}
}
75 changes: 35 additions & 40 deletions src/routes/v2/collections.rs
Original file line number Diff line number Diff line change
Expand Up @@ -212,16 +212,15 @@ pub async fn collection_edit(
redis: web::Data<RedisPool>,
session_queue: web::Data<AuthQueue>,
) -> Result<HttpResponse, ApiError> {
let user_option = get_user_from_headers(
let user = get_user_from_headers(
&req,
&**pool,
&redis,
&session_queue,
Some(&[Scopes::COLLECTION_WRITE]),
)
.await
.map(|x| x.1)
.ok();
.await?
.1;

new_collection
.validate()
Expand All @@ -232,7 +231,7 @@ pub async fn collection_edit(
let result = database::models::Collection::get(id, &**pool, &redis).await?;

if let Some(collection_item) = result {
if !is_authorized_collection(&collection_item, &user_option).await? {
if collection_item.user_id != user.id.into() && !user.role.is_mod() {
return Ok(HttpResponse::Unauthorized().body(""));
}

Expand Down Expand Up @@ -269,27 +268,25 @@ pub async fn collection_edit(
}

if let Some(status) = &new_collection.status {
if let Some(user) = user_option {
if !(user.role.is_mod()
|| collection_item.status.is_approved() && status.can_be_requested())
{
return Err(ApiError::CustomAuthentication(
"You don't have permission to set this status!".to_string(),
));
}

sqlx::query!(
"
UPDATE collections
SET status = $1
WHERE (id = $2)
",
status.to_string(),
id as database::models::ids::CollectionId,
)
.execute(&mut *transaction)
.await?;
if !(user.role.is_mod()
|| collection_item.status.is_approved() && status.can_be_requested())
{
return Err(ApiError::CustomAuthentication(
"You don't have permission to set this status!".to_string(),
));
}

sqlx::query!(
"
UPDATE collections
SET status = $1
WHERE (id = $2)
",
status.to_string(),
id as database::models::ids::CollectionId,
)
.execute(&mut *transaction)
.await?;
}

if let Some(new_project_ids) = &new_collection.new_projects {
Expand Down Expand Up @@ -356,16 +353,15 @@ pub async fn collection_icon_edit(
) -> Result<HttpResponse, ApiError> {
if let Some(content_type) = crate::util::ext::get_image_content_type(&ext.ext) {
let cdn_url = dotenvy::var("CDN_URL")?;
let user_option = get_user_from_headers(
let user = get_user_from_headers(
&req,
&**pool,
&redis,
&session_queue,
Some(&[Scopes::COLLECTION_WRITE]),
)
.await
.map(|x| x.1)
.ok();
.await?
.1;

let string = info.into_inner().0;
let id = database::models::CollectionId(parse_base62(&string)? as i64);
Expand All @@ -375,7 +371,7 @@ pub async fn collection_icon_edit(
ApiError::InvalidInput("The specified collection does not exist!".to_string())
})?;

if !is_authorized_collection(&collection_item, &user_option).await? {
if collection_item.user_id != user.id.into() && !user.role.is_mod() {
return Ok(HttpResponse::Unauthorized().body(""));
}

Expand Down Expand Up @@ -439,24 +435,24 @@ pub async fn delete_collection_icon(
file_host: web::Data<Arc<dyn FileHost + Send + Sync>>,
session_queue: web::Data<AuthQueue>,
) -> Result<HttpResponse, ApiError> {
let user_option = get_user_from_headers(
let user = get_user_from_headers(
&req,
&**pool,
&redis,
&session_queue,
Some(&[Scopes::COLLECTION_WRITE]),
)
.await
.map(|x| x.1)
.ok();
.await?
.1;

let string = info.into_inner().0;
let id = database::models::CollectionId(parse_base62(&string)? as i64);
let collection_item = database::models::Collection::get(id, &**pool, &redis)
.await?
.ok_or_else(|| {
ApiError::InvalidInput("The specified collection does not exist!".to_string())
})?;
if !is_authorized_collection(&collection_item, &user_option).await? {
if collection_item.user_id != user.id.into() && !user.role.is_mod() {
return Ok(HttpResponse::Unauthorized().body(""));
}

Expand Down Expand Up @@ -497,16 +493,15 @@ pub async fn collection_delete(
redis: web::Data<RedisPool>,
session_queue: web::Data<AuthQueue>,
) -> Result<HttpResponse, ApiError> {
let user_option = get_user_from_headers(
let user = get_user_from_headers(
&req,
&**pool,
&redis,
&session_queue,
Some(&[Scopes::COLLECTION_DELETE]),
)
.await
.map(|x| x.1)
.ok();
.await?
.1;

let string = info.into_inner().0;
let id = database::models::CollectionId(parse_base62(&string)? as i64);
Expand All @@ -515,7 +510,7 @@ pub async fn collection_delete(
.ok_or_else(|| {
ApiError::InvalidInput("The specified collection does not exist!".to_string())
})?;
if !is_authorized_collection(&collection, &user_option).await? {
if collection.user_id != user.id.into() && !user.role.is_mod() {
return Ok(HttpResponse::Unauthorized().body(""));
}
let mut transaction = pool.begin().await?;
Expand Down
11 changes: 9 additions & 2 deletions src/routes/v2/moderation.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
use super::ApiError;
use crate::auth::check_is_moderator_from_headers;
use crate::database;
use crate::database::redis::RedisPool;
use crate::models::projects::ProjectStatus;
use crate::queue::session::AuthQueue;
use crate::{auth::check_is_moderator_from_headers, models::pats::Scopes};
use actix_web::{get, web, HttpRequest, HttpResponse};
use serde::Deserialize;
use sqlx::PgPool;
Expand All @@ -30,7 +30,14 @@ pub async fn get_projects(
count: web::Query<ResultCount>,
session_queue: web::Data<AuthQueue>,
) -> Result<HttpResponse, ApiError> {
check_is_moderator_from_headers(&req, &**pool, &redis, &session_queue).await?;
check_is_moderator_from_headers(
&req,
&**pool,
&redis,
&session_queue,
Some(&[Scopes::PROJECT_READ]),
)
.await?;

use futures::stream::TryStreamExt;

Expand Down
11 changes: 4 additions & 7 deletions src/routes/v2/organizations.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,16 +40,14 @@ pub fn config(cfg: &mut web::ServiceConfig) {

#[derive(Deserialize, Validate)]
pub struct NewOrganization {
#[validate(length(min = 3, max = 256))]
pub description: String,
#[validate(
length(min = 3, max = 64),
regex = "crate::util::validate::RE_URL_SAFE"
)]
// Title of the organization, also used as slug
pub title: String,
#[serde(default = "crate::models::teams::ProjectPermissions::default")]
pub default_project_permissions: ProjectPermissions,
#[validate(length(min = 3, max = 256))]
pub description: String,
}

#[post("organization")]
Expand Down Expand Up @@ -290,7 +288,6 @@ pub struct OrganizationEdit {
)]
// Title of the organization, also used as slug
pub title: Option<String>,
pub default_project_permissions: Option<ProjectPermissions>,
}

#[patch("{id}")]
Expand Down Expand Up @@ -508,7 +505,7 @@ pub async fn organization_projects_get(
&**pool,
&redis,
&session_queue,
Some(&[Scopes::ORGANIZATION_READ]),
Some(&[Scopes::ORGANIZATION_READ, Scopes::PROJECT_READ]),
)
.await
.map(|x| x.1)
Expand All @@ -520,7 +517,7 @@ pub async fn organization_projects_get(
let project_ids = sqlx::query!(

Check failure on line 517 in src/routes/v2/organizations.rs

View workflow job for this annotation

GitHub Actions / clippy

the size for values of type `[database::models::ids::ProjectId]` cannot be known at compilation time

error[E0277]: the size for values of type `[database::models::ids::ProjectId]` cannot be known at compilation time --> src/routes/v2/organizations.rs:517:9 | 517 | let project_ids = sqlx::query!( | ^^^^^^^^^^^ doesn't have a size known at compile-time | = help: the trait `std::marker::Sized` is not implemented for `[database::models::ids::ProjectId]` = note: all local variables must have a statically known size = help: unsized locals are gated as an unstable feature
"
SELECT m.id FROM organizations o
LEFT JOIN mods m ON m.id = o.id
INNER JOIN mods m ON m.organization_id = o.id
WHERE (o.id = $1 AND $1 IS NOT NULL) OR (o.title = $2 AND $2 IS NOT NULL)
",
possible_organization_id.map(|x| x as i64),
Expand Down
11 changes: 9 additions & 2 deletions src/routes/v2/reports.rs
Original file line number Diff line number Diff line change
Expand Up @@ -405,7 +405,7 @@ pub async fn report_edit(
let report = crate::database::models::report_item::Report::get(id, &**pool).await?;

if let Some(report) = report {
if !user.role.is_mod() && report.user_id != Some(user.id.into()) {
if !user.role.is_mod() && report.reporter != user.id.into() {
return Ok(HttpResponse::NotFound().body(""));
}

Expand Down Expand Up @@ -496,7 +496,14 @@ pub async fn report_delete(
redis: web::Data<RedisPool>,
session_queue: web::Data<AuthQueue>,
) -> Result<HttpResponse, ApiError> {
check_is_moderator_from_headers(&req, &**pool, &redis, &session_queue).await?;
check_is_moderator_from_headers(
&req,
&**pool,
&redis,
&session_queue,
Some(&[Scopes::REPORT_DELETE]),
)
.await?;

let mut transaction = pool.begin().await?;

Expand Down
1 change: 0 additions & 1 deletion src/routes/v2/teams.rs
Original file line number Diff line number Diff line change
Expand Up @@ -453,7 +453,6 @@ pub async fn add_team_member(
let organization_permissions =
OrganizationPermissions::get_permissions_by_role(&current_user.role, &member)
.unwrap_or_default();
println!("{:?}", organization_permissions);
if !organization_permissions.contains(OrganizationPermissions::MANAGE_INVITES) {
return Err(ApiError::CustomAuthentication(
"You don't have permission to invite users to this organization".to_string(),
Expand Down
18 changes: 16 additions & 2 deletions src/routes/v2/threads.rs
Original file line number Diff line number Diff line change
Expand Up @@ -512,7 +512,14 @@ pub async fn moderation_inbox(
redis: web::Data<RedisPool>,
session_queue: web::Data<AuthQueue>,
) -> Result<HttpResponse, ApiError> {
let user = check_is_moderator_from_headers(&req, &**pool, &redis, &session_queue).await?;
let user = check_is_moderator_from_headers(
&req,
&**pool,
&redis,
&session_queue,
Some(&[Scopes::THREAD_READ]),
)
.await?;

let ids = sqlx::query!(
"
Expand Down Expand Up @@ -540,7 +547,14 @@ pub async fn thread_read(
redis: web::Data<RedisPool>,
session_queue: web::Data<AuthQueue>,
) -> Result<HttpResponse, ApiError> {
check_is_moderator_from_headers(&req, &**pool, &redis, &session_queue).await?;
check_is_moderator_from_headers(
&req,
&**pool,
&redis,
&session_queue,
Some(&[Scopes::THREAD_READ]),
)
.await?;

let id = info.into_inner().0;
let mut transaction = pool.begin().await?;
Expand Down
Loading

0 comments on commit 3cead46

Please sign in to comment.