@@ -6,13 +6,13 @@ use crate::controllers::helpers::pagination::{
66use crate :: controllers:: krate:: load_crate;
77use crate :: controllers:: trustpub:: github_configs:: json:: { self , ListResponse , ListResponseMeta } ;
88use crate :: util:: RequestUtils ;
9- use crate :: util:: errors:: { AppResult , bad_request} ;
9+ use crate :: util:: errors:: { AppResult , bad_request, forbidden } ;
1010use axum:: Json ;
1111use axum:: extract:: { FromRequestParts , Query } ;
1212use crates_io_database:: models:: OwnerKind ;
1313use crates_io_database:: models:: token:: EndpointScope ;
1414use crates_io_database:: models:: trustpub:: GitHubConfig ;
15- use crates_io_database:: schema:: { crate_owners, trustpub_configs_github} ;
15+ use crates_io_database:: schema:: { crate_owners, crates , trustpub_configs_github} ;
1616use diesel:: dsl:: { exists, select} ;
1717use diesel:: prelude:: * ;
1818use diesel_async:: RunQueryDsl ;
@@ -26,7 +26,10 @@ use serde::Deserialize;
2626pub struct ListQueryParams {
2727 /// Name of the crate to list Trusted Publishing configurations for.
2828 #[ serde( rename = "crate" ) ]
29- pub krate : String ,
29+ pub krate : Option < String > ,
30+
31+ /// User ID to list Trusted Publishing configurations for all crates owned by the user.
32+ pub user_id : Option < i32 > ,
3033}
3134
3235/// List Trusted Publishing configurations for GitHub Actions.
@@ -42,17 +45,34 @@ pub async fn list_trustpub_github_configs(
4245 state : AppState ,
4346 params : ListQueryParams ,
4447 parts : Parts ,
48+ ) -> AppResult < Json < ListResponse > > {
49+ match ( & params. krate , params. user_id ) {
50+ ( Some ( krate) , None ) => list_by_crate ( state, krate, parts) . await ,
51+ ( None , Some ( user_id) ) => list_by_user ( state, user_id, parts) . await ,
52+ ( Some ( _) , Some ( _) ) => Err ( bad_request (
53+ "Cannot specify both `crate` and `user_id` query parameters" ,
54+ ) ) ,
55+ ( None , None ) => Err ( bad_request (
56+ "Must specify either `crate` or `user_id` query parameter" ,
57+ ) ) ,
58+ }
59+ }
60+
61+ async fn list_by_crate (
62+ state : AppState ,
63+ krate_name : & str ,
64+ parts : Parts ,
4565) -> AppResult < Json < ListResponse > > {
4666 let mut conn = state. db_read ( ) . await ?;
4767
4868 let auth = AuthCheck :: default ( )
4969 . with_endpoint_scope ( EndpointScope :: TrustedPublishing )
50- . for_crate ( & params . krate )
70+ . for_crate ( krate_name )
5171 . check ( & parts, & mut conn)
5272 . await ?;
5373 let auth_user = auth. user ( ) ;
5474
55- let krate = load_crate ( & mut conn, & params . krate ) . await ?;
75+ let krate = load_crate ( & mut conn, krate_name ) . await ?;
5676
5777 // Check if the authenticated user is an owner of the crate
5878 let is_owner = select ( exists (
@@ -69,40 +89,111 @@ pub async fn list_trustpub_github_configs(
6989 return Err ( bad_request ( "You are not an owner of this crate" ) ) ;
7090 }
7191
92+ paginated_response ( & mut conn, & [ krate. id ] , & parts) . await
93+ }
94+
95+ async fn list_by_user (
96+ state : AppState ,
97+ user_id : i32 ,
98+ parts : Parts ,
99+ ) -> AppResult < Json < ListResponse > > {
100+ let mut conn = state. db_read ( ) . await ?;
101+
102+ let auth = AuthCheck :: default ( )
103+ . with_endpoint_scope ( EndpointScope :: TrustedPublishing )
104+ . allow_any_crate_scope ( )
105+ . check ( & parts, & mut conn)
106+ . await ?;
107+
108+ // Reject legacy tokens for this endpoint
109+ auth. reject_legacy_tokens ( ) ?;
110+
111+ let auth_user = auth. user ( ) ;
112+
113+ // Verify the authenticated user matches the requested user_id
114+ if auth_user. id != user_id {
115+ return Err ( forbidden (
116+ "this action requires authentication as the specified user" ,
117+ ) ) ;
118+ }
119+
120+ // Get crate scopes from the token (if any)
121+ let crate_scopes = auth. api_token ( ) . and_then ( |t| t. crate_scopes . as_ref ( ) ) ;
122+
123+ // Get all crate IDs owned by the user
124+ let mut owned_crates: Vec < ( i32 , String ) > = crate_owners:: table
125+ . inner_join ( crates:: table)
126+ . filter ( crate_owners:: owner_id. eq ( user_id) )
127+ . filter ( crate_owners:: owner_kind. eq ( OwnerKind :: User ) )
128+ . filter ( crate_owners:: deleted. eq ( false ) )
129+ . select ( ( crates:: id, crates:: name) )
130+ . load ( & mut conn)
131+ . await ?;
132+
133+ // Filter by crate scopes if the token has any
134+ if let Some ( scopes) = crate_scopes
135+ && !scopes. is_empty ( )
136+ {
137+ owned_crates. retain ( |( _, name) | scopes. iter ( ) . any ( |scope| scope. matches ( name) ) ) ;
138+ }
139+
140+ let crate_ids: Vec < i32 > = owned_crates. iter ( ) . map ( |( id, _) | * id) . collect ( ) ;
141+
142+ paginated_response ( & mut conn, & crate_ids, & parts) . await
143+ }
144+
145+ async fn paginated_response (
146+ conn : & mut diesel_async:: AsyncPgConnection ,
147+ crate_ids : & [ i32 ] ,
148+ parts : & Parts ,
149+ ) -> AppResult < Json < ListResponse > > {
72150 let pagination = PaginationOptions :: builder ( )
73151 . enable_seek ( true )
74152 . enable_pages ( false )
75- . gather ( & parts) ?;
76-
77- let ( configs, total, next_page) =
78- list_configs ( & mut conn, krate. id , & pagination, & parts) . await ?;
79-
80- let github_configs = configs
81- . into_iter ( )
82- . map ( |config| json:: GitHubConfig {
83- id : config. id ,
84- krate : krate. name . clone ( ) ,
85- repository_owner : config. repository_owner ,
86- repository_owner_id : config. repository_owner_id ,
87- repository_name : config. repository_name ,
88- workflow_filename : config. workflow_filename ,
89- environment : config. environment ,
90- created_at : config. created_at ,
91- } )
92- . collect ( ) ;
153+ . gather ( parts) ?;
154+
155+ let ( configs, total, next_page) = list_configs ( conn, crate_ids, & pagination, parts) . await ?;
156+
157+ let github_configs = configs. into_iter ( ) . map ( to_json_config) . collect ( ) ;
93158
94159 Ok ( Json ( ListResponse {
95160 github_configs,
96161 meta : ListResponseMeta { total, next_page } ,
97162 } ) )
98163}
99164
165+ fn to_json_config ( config : ConfigWithCrateName ) -> json:: GitHubConfig {
166+ let crate_name = config. crate_name ;
167+ let config = config. config ;
168+
169+ json:: GitHubConfig {
170+ id : config. id ,
171+ krate : crate_name,
172+ repository_owner : config. repository_owner ,
173+ repository_owner_id : config. repository_owner_id ,
174+ repository_name : config. repository_name ,
175+ workflow_filename : config. workflow_filename ,
176+ environment : config. environment ,
177+ created_at : config. created_at ,
178+ }
179+ }
180+
181+ #[ derive( Debug , HasQuery ) ]
182+ #[ diesel( base_query = trustpub_configs_github:: table. inner_join( crates:: table) ) ]
183+ #[ diesel( check_for_backend( diesel:: pg:: Pg ) ) ]
184+ struct ConfigWithCrateName {
185+ #[ diesel( select_expression = crates:: name) ]
186+ crate_name : String ,
187+ #[ diesel( embed) ]
188+ config : GitHubConfig ,
189+ }
190+
100191async fn list_configs (
101192 conn : & mut diesel_async:: AsyncPgConnection ,
102- crate_id : i32 ,
193+ crate_ids : & [ i32 ] ,
103194 options : & PaginationOptions ,
104195 req : & Parts ,
105- ) -> AppResult < ( Vec < GitHubConfig > , i64 , Option < String > ) > {
196+ ) -> AppResult < ( Vec < ConfigWithCrateName > , i64 , Option < String > ) > {
106197 use seek:: * ;
107198
108199 let seek = Seek :: Id ;
@@ -113,8 +204,8 @@ async fn list_configs(
113204 ) ;
114205
115206 let make_base_query = || {
116- GitHubConfig :: query ( )
117- . filter ( trustpub_configs_github:: crate_id. eq ( crate_id ) )
207+ ConfigWithCrateName :: query ( )
208+ . filter ( trustpub_configs_github:: crate_id. eq_any ( crate_ids ) )
118209 . into_boxed ( )
119210 } ;
120211
@@ -126,7 +217,7 @@ async fn list_configs(
126217 query = query. filter ( trustpub_configs_github:: id. gt ( id) ) ;
127218 }
128219
129- let data: Vec < GitHubConfig > = query. load ( conn) . await ?;
220+ let data = query. load ( conn) . await ?;
130221
131222 let next_page = next_seek_params ( & data, options, |last| seek. to_payload ( last) ) ?
132223 . map ( |p| req. query_with_params ( p) ) ;
@@ -162,8 +253,8 @@ where
162253}
163254
164255mod seek {
256+ use super :: ConfigWithCrateName ;
165257 use crate :: controllers:: helpers:: pagination:: seek;
166- use crates_io_database:: models:: trustpub:: GitHubConfig ;
167258
168259 seek ! (
169260 pub enum Seek {
@@ -172,9 +263,11 @@ mod seek {
172263 ) ;
173264
174265 impl Seek {
175- pub ( crate ) fn to_payload ( & self , record : & GitHubConfig ) -> SeekPayload {
266+ pub ( crate ) fn to_payload ( & self , record : & ConfigWithCrateName ) -> SeekPayload {
176267 match * self {
177- Seek :: Id => SeekPayload :: Id ( Id { id : record. id } ) ,
268+ Seek :: Id => SeekPayload :: Id ( Id {
269+ id : record. config . id ,
270+ } ) ,
178271 }
179272 }
180273 }
0 commit comments