1
1
//! Functionality related to publishing a new crate or version of a crate.
2
2
3
3
use crate :: app:: AppState ;
4
- use crate :: auth:: AuthCheck ;
4
+ use crate :: auth:: { AuthCheck , Authentication } ;
5
5
use crate :: worker:: jobs:: {
6
6
self , CheckTyposquat , SendPublishNotificationsJob , UpdateDefaultVersion ,
7
7
} ;
@@ -11,16 +11,16 @@ use cargo_manifest::{Dependency, DepsSet, TargetDepsSet};
11
11
use chrono:: { DateTime , SecondsFormat , Utc } ;
12
12
use crates_io_tarball:: { TarballError , process_tarball} ;
13
13
use crates_io_worker:: { BackgroundJob , EnqueueError } ;
14
- use diesel:: dsl:: { exists, select} ;
14
+ use diesel:: dsl:: { exists, now , select} ;
15
15
use diesel:: prelude:: * ;
16
16
use diesel:: sql_types:: Timestamptz ;
17
17
use diesel_async:: scoped_futures:: ScopedFutureExt ;
18
18
use diesel_async:: { AsyncConnection , AsyncPgConnection , RunQueryDsl } ;
19
19
use futures_util:: TryFutureExt ;
20
20
use futures_util:: TryStreamExt ;
21
21
use hex:: ToHex ;
22
- use http:: StatusCode ;
23
22
use http:: request:: Parts ;
23
+ use http:: { StatusCode , header} ;
24
24
use sha2:: { Digest , Sha256 } ;
25
25
use std:: collections:: HashMap ;
26
26
use tokio:: io:: { AsyncRead , AsyncReadExt } ;
@@ -43,6 +43,7 @@ use crate::views::{
43
43
EncodableCrate , EncodableCrateDependency , GoodCrate , PublishMetadata , PublishWarnings ,
44
44
} ;
45
45
use crates_io_diesel_helpers:: canon_crate_name;
46
+ use crates_io_trustpub:: access_token:: AccessToken ;
46
47
47
48
const MISSING_RIGHTS_ERROR_MESSAGE : & str = "this crate exists but you don't seem to be an owner. \
48
49
If you believe this is a mistake, perhaps you need \
@@ -51,6 +52,11 @@ const MISSING_RIGHTS_ERROR_MESSAGE: &str = "this crate exists but you don't seem
51
52
52
53
const MAX_DESCRIPTION_LENGTH : usize = 1000 ;
53
54
55
+ enum AuthType {
56
+ Regular ( Box < Authentication > ) ,
57
+ Oidc ( ) ,
58
+ }
59
+
54
60
/// Publish a new crate/version.
55
61
///
56
62
/// Used by `cargo publish` to publish a new crate or to publish a new version of an
@@ -130,30 +136,63 @@ pub async fn publish(app: AppState, req: Parts, body: Body) -> AppResult<Json<Go
130
136
None => EndpointScope :: PublishNew ,
131
137
} ;
132
138
133
- let auth = AuthCheck :: default ( )
134
- . with_endpoint_scope ( endpoint_scope)
135
- . for_crate ( & metadata. name )
136
- . check ( & req, & mut conn)
137
- . await ?;
139
+ let access_token = req
140
+ . headers
141
+ . get ( header:: AUTHORIZATION )
142
+ . and_then ( |h| h. as_bytes ( ) . strip_prefix ( b"Bearer " ) )
143
+ . and_then ( AccessToken :: from_bytes) ;
144
+
145
+ let auth = match ( access_token, & existing_crate) {
146
+ ( Some ( access_token) , Some ( existing_crate) ) => {
147
+ let hashed_token = access_token. sha256 ( ) ;
148
+
149
+ trustpub_tokens:: table
150
+ . filter ( trustpub_tokens:: crate_ids. contains ( vec ! [ existing_crate. id] ) )
151
+ . filter ( trustpub_tokens:: hashed_token. eq ( hashed_token. as_slice ( ) ) )
152
+ . filter ( trustpub_tokens:: expires_at. gt ( now) )
153
+ . select ( trustpub_tokens:: id)
154
+ . get_result :: < i64 > ( & mut conn)
155
+ . await ?;
156
+
157
+ AuthType :: Oidc ( )
158
+ }
159
+ _ => {
160
+ let auth = AuthCheck :: default ( )
161
+ . with_endpoint_scope ( endpoint_scope)
162
+ . for_crate ( & metadata. name )
163
+ . check ( & req, & mut conn)
164
+ . await ?;
165
+
166
+ AuthType :: Regular ( Box :: new ( auth) )
167
+ }
168
+ } ;
138
169
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. \
170
+ let verified_email_address = match & auth {
171
+ AuthType :: Regular ( auth) => {
172
+ let verified_email_address = auth. user ( ) . verified_email ( & mut conn) . await ?;
173
+ let verified_email_address = verified_email_address. ok_or_else ( || {
174
+ bad_request ( format ! (
175
+ "A verified email address is required to publish crates to crates.io. \
143
176
Visit https://{}/settings/profile to set and verify your email address.",
144
- app. config. domain_name,
145
- ) )
146
- } ) ?;
147
-
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 ,
177
+ app. config. domain_name,
178
+ ) )
179
+ } ) ?;
180
+ Some ( verified_email_address)
181
+ }
182
+ AuthType :: Oidc ( ) => None ,
152
183
} ;
153
184
154
- app. rate_limiter
155
- . check_rate_limit ( auth. user ( ) . id , rate_limit_action, & mut conn)
156
- . await ?;
185
+ if let AuthType :: Regular ( auth) = & auth {
186
+ // Use a different rate limit whether this is a new or an existing crate.
187
+ let rate_limit_action = match existing_crate {
188
+ Some ( _) => LimitedAction :: PublishUpdate ,
189
+ None => LimitedAction :: PublishNew ,
190
+ } ;
191
+
192
+ app. rate_limiter
193
+ . check_rate_limit ( auth. user ( ) . id , rate_limit_action, & mut conn)
194
+ . await ?;
195
+ }
157
196
158
197
let max_upload_size = existing_crate
159
198
. as_ref ( )
@@ -342,9 +381,6 @@ pub async fn publish(app: AppState, req: Parts, body: Body) -> AppResult<Json<Go
342
381
validate_dependency ( dep) ?;
343
382
}
344
383
345
- let api_token_id = auth. api_token_id ( ) ;
346
- let user = auth. user ( ) ;
347
-
348
384
// Create a transaction on the database, if there are no errors,
349
385
// commit the transactions to record a new or updated crate.
350
386
conn. transaction ( |conn| async move {
@@ -368,17 +404,29 @@ pub async fn publish(app: AppState, req: Parts, body: Body) -> AppResult<Json<Go
368
404
return Err ( bad_request ( "cannot upload a crate with a reserved name" ) ) ;
369
405
}
370
406
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
- } ;
407
+ let krate = match & auth {
408
+ AuthType :: Regular ( auth) => {
409
+ let user = auth. user ( ) ;
377
410
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
- }
411
+ // To avoid race conditions, we try to insert
412
+ // first so we know whether to add an owner
413
+ let krate = match persist. create ( conn, user. id ) . await . optional ( ) ? {
414
+ Some ( krate) => krate,
415
+ None => persist. update ( conn) . await ?,
416
+ } ;
417
+
418
+ let owners = krate. owners ( conn) . await ?;
419
+ if Rights :: get ( user, & * app. github , & owners) . await ? < Rights :: Publish {
420
+ return Err ( custom ( StatusCode :: FORBIDDEN , MISSING_RIGHTS_ERROR_MESSAGE ) ) ;
421
+ }
422
+
423
+ krate
424
+ }
425
+ AuthType :: Oidc ( ) => {
426
+ // OIDC does not support creating new crates
427
+ persist. update ( conn) . await ?
428
+ }
429
+ } ;
382
430
383
431
if krate. name != * name {
384
432
return Err ( bad_request ( format_args ! (
@@ -407,6 +455,11 @@ pub async fn publish(app: AppState, req: Parts, body: Body) -> AppResult<Json<Go
407
455
408
456
let edition = edition. map ( |edition| edition. as_str ( ) ) ;
409
457
458
+ let published_by = match & auth {
459
+ AuthType :: Regular ( auth) => Some ( auth. user ( ) . id ) ,
460
+ AuthType :: Oidc ( ) => None ,
461
+ } ;
462
+
410
463
// Read tarball from request
411
464
let hex_cksum: String = Sha256 :: digest ( & tarball_bytes) . encode_hex ( ) ;
412
465
@@ -417,7 +470,7 @@ pub async fn publish(app: AppState, req: Parts, body: Body) -> AppResult<Json<Go
417
470
// Downcast is okay because the file length must be less than the max upload size
418
471
// to get here, and max upload sizes are way less than i32 max
419
472
. size ( content_length as i32 )
420
- . published_by ( user . id )
473
+ . maybe_published_by ( published_by )
421
474
. checksum ( & hex_cksum)
422
475
. maybe_links ( package. links . as_deref ( ) )
423
476
. maybe_rust_version ( rust_version. as_deref ( ) )
@@ -432,7 +485,7 @@ pub async fn publish(app: AppState, req: Parts, body: Body) -> AppResult<Json<Go
432
485
. keywords ( & keywords)
433
486
. build ( ) ;
434
487
435
- let version = new_version. save ( conn, & verified_email_address) . await . map_err ( |error| {
488
+ let version = new_version. save ( conn, verified_email_address. as_deref ( ) ) . await . map_err ( |error| {
436
489
use diesel:: result:: { Error , DatabaseErrorKind } ;
437
490
match error {
438
491
Error :: DatabaseError ( DatabaseErrorKind :: UniqueViolation , _) =>
@@ -441,14 +494,16 @@ pub async fn publish(app: AppState, req: Parts, body: Body) -> AppResult<Json<Go
441
494
}
442
495
} ) ?;
443
496
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 ?;
497
+ if let AuthType :: Regular ( auth) = & auth {
498
+ NewVersionOwnerAction :: builder ( )
499
+ . version_id ( version. id )
500
+ . user_id ( auth. user ( ) . id )
501
+ . maybe_api_token_id ( auth. api_token_id ( ) )
502
+ . action ( VersionAction :: Publish )
503
+ . build ( )
504
+ . insert ( conn)
505
+ . await ?;
506
+ }
452
507
453
508
// Link this new version to all dependencies
454
509
add_dependencies ( conn, & deps, version. id ) . await ?;
0 commit comments