11//! Functionality related to publishing a new crate or version of a crate.
22
33use crate :: app:: AppState ;
4- use crate :: auth:: AuthCheck ;
4+ use crate :: auth:: { AuthCheck , Authentication } ;
5+ use crate :: models:: {
6+ Category , Crate , DependencyKind , Keyword , NewCrate , NewVersion , NewVersionOwnerAction ,
7+ VersionAction , default_versions:: Version as DefaultVersion ,
8+ } ;
59use crate :: worker:: jobs:: {
610 self , CheckTyposquat , SendPublishNotificationsJob , UpdateDefaultVersion ,
711} ;
@@ -11,27 +15,22 @@ use cargo_manifest::{Dependency, DepsSet, TargetDepsSet};
1115use chrono:: { DateTime , SecondsFormat , Utc } ;
1216use crates_io_tarball:: { TarballError , process_tarball} ;
1317use crates_io_worker:: { BackgroundJob , EnqueueError } ;
14- use diesel:: dsl:: { exists, select} ;
18+ use diesel:: dsl:: { exists, now , select} ;
1519use diesel:: prelude:: * ;
1620use diesel:: sql_types:: Timestamptz ;
1721use diesel_async:: scoped_futures:: ScopedFutureExt ;
1822use diesel_async:: { AsyncConnection , AsyncPgConnection , RunQueryDsl } ;
1923use futures_util:: TryFutureExt ;
2024use futures_util:: TryStreamExt ;
2125use hex:: ToHex ;
22- use http:: StatusCode ;
2326use http:: request:: Parts ;
27+ use http:: { StatusCode , header} ;
2428use sha2:: { Digest , Sha256 } ;
2529use std:: collections:: HashMap ;
2630use tokio:: io:: { AsyncRead , AsyncReadExt } ;
2731use tokio_util:: io:: StreamReader ;
2832use url:: Url ;
2933
30- use crate :: models:: {
31- Category , Crate , DependencyKind , Keyword , NewCrate , NewVersion , NewVersionOwnerAction ,
32- VersionAction , default_versions:: Version as DefaultVersion ,
33- } ;
34-
3534use crate :: controllers:: helpers:: authorization:: Rights ;
3635use crate :: licenses:: parse_license_expr;
3736use crate :: middleware:: log_request:: RequestLogExt ;
@@ -51,6 +50,11 @@ const MISSING_RIGHTS_ERROR_MESSAGE: &str = "this crate exists but you don't seem
5150
5251const MAX_DESCRIPTION_LENGTH : usize = 1000 ;
5352
53+ enum AuthType {
54+ Regular ( Box < Authentication > ) ,
55+ Oidc ( ) ,
56+ }
57+
5458/// Publish a new crate/version.
5559///
5660/// Used by `cargo publish` to publish a new crate or to publish a new version of an
@@ -130,30 +134,62 @@ pub async fn publish(app: AppState, req: Parts, body: Body) -> AppResult<Json<Go
130134 None => EndpointScope :: PublishNew ,
131135 } ;
132136
133- let auth = AuthCheck :: default ( )
134- . with_endpoint_scope ( endpoint_scope)
135- . for_crate ( & metadata. name )
136- . check ( & req, & mut conn)
137- . await ?;
137+ let bearer = req
138+ . headers
139+ . get ( header:: AUTHORIZATION )
140+ . and_then ( |h| h. as_bytes ( ) . strip_prefix ( b"Bearer " ) ) ;
138141
139- let verified_email_address = auth. user ( ) . verified_email ( & mut conn) . await ?;
140- let verified_email_address = verified_email_address. ok_or_else ( || {
141- bad_request ( format ! (
142- "A verified email address is required to publish crates to crates.io. \
143- Visit https://{}/settings/profile to set and verify your email address.",
144- app. config. domain_name,
145- ) )
146- } ) ?;
142+ let auth = match ( bearer, & existing_crate) {
143+ ( Some ( bearer) , Some ( existing_crate) ) if bearer. starts_with ( b"crates.io/oidc/" ) => {
144+ let hashed_token = Sha256 :: digest ( bearer) ;
145+
146+ trustpub_tokens:: table
147+ . filter ( trustpub_tokens:: crate_ids. contains ( vec ! [ existing_crate. id] ) )
148+ . filter ( trustpub_tokens:: hashed_token. eq ( hashed_token. as_slice ( ) ) )
149+ . filter ( trustpub_tokens:: expires_at. gt ( now) )
150+ . select ( trustpub_tokens:: id)
151+ . get_result :: < i64 > ( & mut conn)
152+ . await ?;
147153
148- // Use a different rate limit whether this is a new or an existing crate.
149- let rate_limit_action = match existing_crate {
150- Some ( _) => LimitedAction :: PublishUpdate ,
151- None => LimitedAction :: PublishNew ,
154+ AuthType :: Oidc ( )
155+ }
156+ _ => {
157+ let auth = AuthCheck :: default ( )
158+ . with_endpoint_scope ( endpoint_scope)
159+ . for_crate ( & metadata. name )
160+ . check ( & req, & mut conn)
161+ . await ?;
162+
163+ AuthType :: Regular ( Box :: new ( auth) )
164+ }
152165 } ;
153166
154- app. rate_limiter
155- . check_rate_limit ( auth. user ( ) . id , rate_limit_action, & mut conn)
156- . await ?;
167+ let verified_email_address = match & auth {
168+ AuthType :: Regular ( auth) => {
169+ let verified_email_address = auth. user ( ) . verified_email ( & mut conn) . await ?;
170+ let verified_email_address = verified_email_address. ok_or_else ( || {
171+ bad_request ( format ! (
172+ "A verified email address is required to publish crates to crates.io. \
173+ Visit https://{}/settings/profile to set and verify your email address.",
174+ app. config. domain_name,
175+ ) )
176+ } ) ?;
177+ Some ( verified_email_address)
178+ }
179+ AuthType :: Oidc ( ) => None ,
180+ } ;
181+
182+ if let AuthType :: Regular ( auth) = & auth {
183+ // Use a different rate limit whether this is a new or an existing crate.
184+ let rate_limit_action = match existing_crate {
185+ Some ( _) => LimitedAction :: PublishUpdate ,
186+ None => LimitedAction :: PublishNew ,
187+ } ;
188+
189+ app. rate_limiter
190+ . check_rate_limit ( auth. user ( ) . id , rate_limit_action, & mut conn)
191+ . await ?;
192+ }
157193
158194 let max_upload_size = existing_crate
159195 . as_ref ( )
@@ -342,9 +378,6 @@ pub async fn publish(app: AppState, req: Parts, body: Body) -> AppResult<Json<Go
342378 validate_dependency ( dep) ?;
343379 }
344380
345- let api_token_id = auth. api_token_id ( ) ;
346- let user = auth. user ( ) ;
347-
348381 // Create a transaction on the database, if there are no errors,
349382 // commit the transactions to record a new or updated crate.
350383 conn. transaction ( |conn| async move {
@@ -368,17 +401,29 @@ pub async fn publish(app: AppState, req: Parts, body: Body) -> AppResult<Json<Go
368401 return Err ( bad_request ( "cannot upload a crate with a reserved name" ) ) ;
369402 }
370403
371- // To avoid race conditions, we try to insert
372- // first so we know whether to add an owner
373- let krate = match persist. create ( conn, user. id ) . await . optional ( ) ? {
374- Some ( krate) => krate,
375- None => persist. update ( conn) . await ?,
376- } ;
404+ let krate = match & auth {
405+ AuthType :: Regular ( auth) => {
406+ let user = auth. user ( ) ;
377407
378- let owners = krate. owners ( conn) . await ?;
379- if Rights :: get ( user, & * app. github , & owners) . await ? < Rights :: Publish {
380- return Err ( custom ( StatusCode :: FORBIDDEN , MISSING_RIGHTS_ERROR_MESSAGE ) ) ;
381- }
408+ // To avoid race conditions, we try to insert
409+ // first so we know whether to add an owner
410+ let krate = match persist. create ( conn, user. id ) . await . optional ( ) ? {
411+ Some ( krate) => krate,
412+ None => persist. update ( conn) . await ?,
413+ } ;
414+
415+ let owners = krate. owners ( conn) . await ?;
416+ if Rights :: get ( user, & * app. github , & owners) . await ? < Rights :: Publish {
417+ return Err ( custom ( StatusCode :: FORBIDDEN , MISSING_RIGHTS_ERROR_MESSAGE ) ) ;
418+ }
419+
420+ krate
421+ }
422+ AuthType :: Oidc ( ) => {
423+ // OIDC does not support creating new crates
424+ persist. update ( conn) . await ?
425+ }
426+ } ;
382427
383428 if krate. name != * name {
384429 return Err ( bad_request ( format_args ! (
@@ -407,6 +452,11 @@ pub async fn publish(app: AppState, req: Parts, body: Body) -> AppResult<Json<Go
407452
408453 let edition = edition. map ( |edition| edition. as_str ( ) ) ;
409454
455+ let published_by = match & auth {
456+ AuthType :: Regular ( auth) => Some ( auth. user ( ) . id ) ,
457+ AuthType :: Oidc ( ) => None ,
458+ } ;
459+
410460 // Read tarball from request
411461 let hex_cksum: String = Sha256 :: digest ( & tarball_bytes) . encode_hex ( ) ;
412462
@@ -417,7 +467,7 @@ pub async fn publish(app: AppState, req: Parts, body: Body) -> AppResult<Json<Go
417467 // Downcast is okay because the file length must be less than the max upload size
418468 // to get here, and max upload sizes are way less than i32 max
419469 . size ( content_length as i32 )
420- . published_by ( user . id )
470+ . maybe_published_by ( published_by )
421471 . checksum ( & hex_cksum)
422472 . maybe_links ( package. links . as_deref ( ) )
423473 . maybe_rust_version ( rust_version. as_deref ( ) )
@@ -432,7 +482,7 @@ pub async fn publish(app: AppState, req: Parts, body: Body) -> AppResult<Json<Go
432482 . keywords ( & keywords)
433483 . build ( ) ;
434484
435- let version = new_version. save ( conn, & verified_email_address) . await . map_err ( |error| {
485+ let version = new_version. save ( conn, verified_email_address. as_deref ( ) ) . await . map_err ( |error| {
436486 use diesel:: result:: { Error , DatabaseErrorKind } ;
437487 match error {
438488 Error :: DatabaseError ( DatabaseErrorKind :: UniqueViolation , _) =>
@@ -441,14 +491,16 @@ pub async fn publish(app: AppState, req: Parts, body: Body) -> AppResult<Json<Go
441491 }
442492 } ) ?;
443493
444- NewVersionOwnerAction :: builder ( )
445- . version_id ( version. id )
446- . user_id ( user. id )
447- . maybe_api_token_id ( api_token_id)
448- . action ( VersionAction :: Publish )
449- . build ( )
450- . insert ( conn)
451- . await ?;
494+ if let AuthType :: Regular ( auth) = & auth {
495+ NewVersionOwnerAction :: builder ( )
496+ . version_id ( version. id )
497+ . user_id ( auth. user ( ) . id )
498+ . maybe_api_token_id ( auth. api_token_id ( ) )
499+ . action ( VersionAction :: Publish )
500+ . build ( )
501+ . insert ( conn)
502+ . await ?;
503+ }
452504
453505 // Link this new version to all dependencies
454506 add_dependencies ( conn, & deps, version. id ) . await ?;
0 commit comments