Skip to content

Commit 7c3c409

Browse files
committed
Add policy checks to prevent manual update of any hosted file URL
1 parent 1afc722 commit 7c3c409

5 files changed

Lines changed: 78 additions & 4 deletions

File tree

thoth-api/src/model/additional_resource/policy.rs

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,18 @@
11
use crate::model::additional_resource::{
22
AdditionalResource, NewAdditionalResource, PatchAdditionalResource,
33
};
4+
use crate::model::file::File;
45
use crate::model::work::{Work, WorkType};
56
use crate::model::Crud;
67
use crate::policy::{CreatePolicy, DeletePolicy, MovePolicy, PolicyContext, UpdatePolicy};
78
use thoth_errors::{ThothError, ThothResult};
89

910
/// Write policies for `AdditionalResource`.
1011
///
11-
/// These policies enforce publisher scoping and prevent attachment to chapter records.
12+
/// These policies are responsible for:
13+
/// - enforcing publisher scoping
14+
/// - preventing attachment to chapter records
15+
/// - preventing manual update of auto-generated Thoth Hosting URLs
1216
pub struct AdditionalResourcePolicy;
1317

1418
fn ensure_work_is_book(db: &crate::db::PgPool, work_id: uuid::Uuid) -> ThothResult<()> {
@@ -20,6 +24,18 @@ fn ensure_work_is_book(db: &crate::db::PgPool, work_id: uuid::Uuid) -> ThothResu
2024
}
2125
}
2226

27+
fn ensure_no_hosted_file(
28+
db: &crate::db::PgPool,
29+
additional_resource_id: uuid::Uuid,
30+
) -> ThothResult<()> {
31+
let file = File::from_additional_resource_id(db, &additional_resource_id)?;
32+
if file.is_some() {
33+
Err(ThothError::HostedFileUrlEditError)
34+
} else {
35+
Ok(())
36+
}
37+
}
38+
2339
impl CreatePolicy<NewAdditionalResource> for AdditionalResourcePolicy {
2440
fn can_create<C: PolicyContext>(
2541
ctx: &C,
@@ -41,7 +57,12 @@ impl UpdatePolicy<AdditionalResource, PatchAdditionalResource> for AdditionalRes
4157
ctx.require_publisher_for(current)?;
4258
ctx.require_publisher_for(patch)?;
4359
ensure_work_is_book(ctx.db(), current.work_id)?;
44-
ensure_work_is_book(ctx.db(), patch.work_id)
60+
ensure_work_is_book(ctx.db(), patch.work_id)?;
61+
62+
if patch.url != current.url {
63+
ensure_no_hosted_file(ctx.db(), current.additional_resource_id)?;
64+
}
65+
Ok(())
4566
}
4667
}
4768

thoth-api/src/model/publication/policy.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use crate::model::{
2+
file::{File, FileType},
23
publication::{NewPublication, PatchPublication, Publication, PublicationProperties},
34
work::{Work, WorkProperties},
45
Crud,
@@ -11,8 +12,18 @@ use thoth_errors::{ThothError, ThothResult};
1112
/// These policies are responsible for:
1213
/// - requiring authentication
1314
/// - requiring publisher membership (tenant boundary)
15+
/// - preventing manual update of auto-generated Thoth Hosting URLs
1416
pub struct PublicationPolicy;
1517

18+
fn ensure_no_hosted_file(db: &crate::db::PgPool, publication_id: uuid::Uuid) -> ThothResult<()> {
19+
let file = File::from_publication_id(db, &publication_id, FileType::A11yReport)?;
20+
if file.is_some() {
21+
Err(ThothError::HostedFileUrlEditError)
22+
} else {
23+
Ok(())
24+
}
25+
}
26+
1627
impl CreatePolicy<NewPublication> for PublicationPolicy {
1728
fn can_create<C: PolicyContext>(
1829
ctx: &C,
@@ -34,6 +45,10 @@ impl UpdatePolicy<Publication, PatchPublication> for PublicationPolicy {
3445
ctx.require_publisher_for(current)?;
3546
ctx.require_publisher_for(patch)?;
3647

48+
if patch.accessibility_report_url != current.accessibility_report_url {
49+
ensure_no_hosted_file(ctx.db(), current.publication_id)?;
50+
}
51+
3752
patch.validate(ctx.db())
3853
}
3954
}

thoth-api/src/model/work/policy.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use crate::model::file::File;
12
use crate::model::work::{NewWork, PatchWork, Work, WorkProperties, WorkType};
23
use crate::policy::{CreatePolicy, DeletePolicy, PolicyContext, UpdatePolicy, UserAccess};
34
use thoth_errors::{ThothError, ThothResult};
@@ -7,8 +8,18 @@ use thoth_errors::{ThothError, ThothResult};
78
/// This policy layer enforces:
89
/// - authentication
910
/// - publisher membership derived from the entity / input via `PublisherId`
11+
/// - preventing manual update of auto-generated Thoth Hosting URLs
1012
pub struct WorkPolicy;
1113

14+
fn ensure_no_hosted_file(db: &crate::db::PgPool, work_id: uuid::Uuid) -> ThothResult<()> {
15+
let file = File::from_work_id(db, &work_id)?;
16+
if file.is_some() {
17+
Err(ThothError::HostedFileUrlEditError)
18+
} else {
19+
Ok(())
20+
}
21+
}
22+
1223
impl CreatePolicy<NewWork> for WorkPolicy {
1324
fn can_create<C: PolicyContext>(ctx: &C, data: &NewWork, _params: ()) -> ThothResult<()> {
1425
ctx.require_publisher_for(data)?;
@@ -41,6 +52,10 @@ impl UpdatePolicy<Work, PatchWork> for WorkPolicy {
4152
ctx.require_work_lifecycle_for(patch)?;
4253
}
4354

55+
if patch.cover_url != current.cover_url {
56+
ensure_no_hosted_file(ctx.db(), current.work_id)?;
57+
}
58+
4459
patch.validate()?;
4560

4661
if current.is_published() && !patch.is_published() && !user.is_superuser() {

thoth-api/src/model/work_featured_video/policy.rs

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use crate::model::file::File;
12
use crate::model::work::{Work, WorkType};
23
use crate::model::work_featured_video::{
34
NewWorkFeaturedVideo, PatchWorkFeaturedVideo, WorkFeaturedVideo,
@@ -8,7 +9,10 @@ use thoth_errors::{ThothError, ThothResult};
89

910
/// Write policies for `WorkFeaturedVideo`.
1011
///
11-
/// These policies enforce publisher scoping and prevent attachment to chapter records.
12+
/// These policies are responsible for:
13+
/// - enforcing publisher scoping
14+
/// - preventing attachment to chapter records
15+
/// - preventing manual update of auto-generated Thoth Hosting URLs
1216
pub struct WorkFeaturedVideoPolicy;
1317

1418
fn ensure_work_is_book(db: &crate::db::PgPool, work_id: uuid::Uuid) -> ThothResult<()> {
@@ -20,6 +24,18 @@ fn ensure_work_is_book(db: &crate::db::PgPool, work_id: uuid::Uuid) -> ThothResu
2024
}
2125
}
2226

27+
fn ensure_no_hosted_file(
28+
db: &crate::db::PgPool,
29+
work_featured_video_id: uuid::Uuid,
30+
) -> ThothResult<()> {
31+
let file = File::from_work_featured_video_id(db, &work_featured_video_id)?;
32+
if file.is_some() {
33+
Err(ThothError::HostedFileUrlEditError)
34+
} else {
35+
Ok(())
36+
}
37+
}
38+
2339
impl CreatePolicy<NewWorkFeaturedVideo> for WorkFeaturedVideoPolicy {
2440
fn can_create<C: PolicyContext>(
2541
ctx: &C,
@@ -41,7 +57,12 @@ impl UpdatePolicy<WorkFeaturedVideo, PatchWorkFeaturedVideo> for WorkFeaturedVid
4157
ctx.require_publisher_for(current)?;
4258
ctx.require_publisher_for(patch)?;
4359
ensure_work_is_book(ctx.db(), current.work_id)?;
44-
ensure_work_is_book(ctx.db(), patch.work_id)
60+
ensure_work_is_book(ctx.db(), patch.work_id)?;
61+
62+
if patch.url != current.url {
63+
ensure_no_hosted_file(ctx.db(), current.work_featured_video_id)?;
64+
}
65+
Ok(())
4566
}
4667
}
4768

thoth-errors/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,8 @@ pub enum ThothError {
164164
WorkFeaturedVideoFileUploadMissingWorkFeaturedVideoId,
165165
#[error("Accessibility report file upload missing publication_id")]
166166
AccessibilityReportFileUploadMissingPublicationId,
167+
#[error("URLs of uploaded files cannot be overwritten.")]
168+
HostedFileUrlEditError,
167169
}
168170

169171
impl ThothError {

0 commit comments

Comments
 (0)