33use super :: CrateVersionPath ;
44use crate :: app:: AppState ;
55use crate :: auth:: AuthCheck ;
6- use crate :: util:: errors:: { AppResult , server_error} ;
6+ use crate :: controllers:: helpers:: authorization:: Rights ;
7+ use crate :: util:: errors:: { AppResult , custom, server_error} ;
78use crate :: worker:: jobs;
89use axum:: response:: { IntoResponse as _, Response } ;
910use crates_io_worker:: BackgroundJob as _;
@@ -27,10 +28,20 @@ pub async fn rebuild_version_docs(
2728 req : Parts ,
2829) -> AppResult < Response > {
2930 let mut conn = app. db_write ( ) . await ?;
30- AuthCheck :: only_cookie ( ) . check ( & req, & mut conn) . await ?;
31+ let auth = AuthCheck :: only_cookie ( ) . check ( & req, & mut conn) . await ?;
3132
3233 // validate if version & crate exist
33- path. load_version_and_crate ( & mut conn) . await ?;
34+ let ( _, krate) = path. load_version_and_crate ( & mut conn) . await ?;
35+
36+ // Check that the user is an owner of the crate, or a team member (= publish rights)
37+ let user = auth. user ( ) ;
38+ let owners = krate. owners ( & mut conn) . await ?;
39+ if Rights :: get ( user, & * app. github , & owners) . await ? < Rights :: Publish {
40+ return Err ( custom (
41+ StatusCode :: FORBIDDEN ,
42+ "user doesn't have permission to trigger a docs rebuild" ,
43+ ) ) ;
44+ }
3445
3546 jobs:: DocsRsQueueRebuild :: new ( path. name , path. version )
3647 . enqueue ( & mut conn)
@@ -53,6 +64,7 @@ mod tests {
5364 builders:: { CrateBuilder , VersionBuilder } ,
5465 util:: { RequestHelper as _, TestApp } ,
5566 } ;
67+ use crates_io_database:: models:: NewUser ;
5668 use crates_io_docs_rs:: MockDocsRsClient ;
5769
5870 #[ tokio:: test( flavor = "multi_thread" ) ]
@@ -83,6 +95,42 @@ mod tests {
8395 Ok ( ( ) )
8496 }
8597
98+ #[ tokio:: test( flavor = "multi_thread" ) ]
99+ async fn test_trigger_rebuild_permission_failed ( ) -> anyhow:: Result < ( ) > {
100+ let mut docs_rs_mock = MockDocsRsClient :: new ( ) ;
101+ docs_rs_mock
102+ . expect_rebuild_docs ( )
103+ . returning ( |_, _| Ok ( ( ) ) )
104+ . never ( ) ;
105+
106+ let ( app, _client, cookie_client) =
107+ TestApp :: full ( ) . with_docs_rs ( docs_rs_mock) . with_user ( ) . await ;
108+
109+ let mut conn = app. db_conn ( ) . await ;
110+
111+ let other_user = NewUser :: builder ( )
112+ . gh_id ( 111 )
113+ . gh_login ( "other_user" )
114+ . gh_access_token ( "token" )
115+ . build ( )
116+ . insert ( & mut conn)
117+ . await ?;
118+
119+ CrateBuilder :: new ( "krate" , other_user. id )
120+ . version ( VersionBuilder :: new ( "0.1.0" ) )
121+ . build ( & mut conn)
122+ . await ?;
123+
124+ let response = cookie_client
125+ . post :: < ( ) > ( "/api/v1/crates/krate/0.1.0/rebuild_docs" , "" )
126+ . await ;
127+ assert_eq ! ( response. status( ) , StatusCode :: FORBIDDEN ) ;
128+
129+ app. run_pending_background_jobs ( ) . await ;
130+
131+ Ok ( ( ) )
132+ }
133+
86134 #[ tokio:: test( flavor = "multi_thread" ) ]
87135 async fn test_trigger_rebuild_unknown_crate_doesnt_queue_job ( ) -> anyhow:: Result < ( ) > {
88136 let mut docs_rs_mock = MockDocsRsClient :: new ( ) ;
0 commit comments