2
2
3
3
use crate :: controllers:: krate:: CratePath ;
4
4
use crate :: models:: krate:: OwnerRemoveError ;
5
+ use crate :: models:: team:: can_add_team;
5
6
use crate :: models:: {
6
7
krate:: NewOwnerInvite , token:: EndpointScope , CrateOwner , NewCrateOwnerInvitation ,
7
- NewCrateOwnerInvitationOutcome ,
8
+ NewCrateOwnerInvitationOutcome , NewTeam ,
8
9
} ;
9
10
use crate :: models:: { Crate , Owner , Rights , Team , User } ;
10
11
use crate :: util:: errors:: { bad_request, crate_not_found, custom, AppResult , BoxedAppError } ;
@@ -21,6 +22,7 @@ use diesel_async::scoped_futures::ScopedFutureExt;
21
22
use diesel_async:: { AsyncConnection , AsyncPgConnection , RunQueryDsl } ;
22
23
use http:: request:: Parts ;
23
24
use http:: StatusCode ;
25
+ use oauth2:: AccessToken ;
24
26
use secrecy:: { ExposeSecret , SecretString } ;
25
27
use thiserror:: Error ;
26
28
@@ -353,15 +355,9 @@ async fn add_team_owner(
353
355
} ) ?;
354
356
355
357
// Always recreate teams to get the most up-to-date GitHub ID
356
- let team = Team :: create_or_update_github_team (
357
- gh_client,
358
- conn,
359
- & login. to_lowercase ( ) ,
360
- org,
361
- team,
362
- req_user,
363
- )
364
- . await ?;
358
+ let team =
359
+ create_or_update_github_team ( gh_client, conn, & login. to_lowercase ( ) , org, team, req_user)
360
+ . await ?;
365
361
366
362
// Teams are added as owners immediately, since the above call ensures
367
363
// the user is a team member.
@@ -376,6 +372,66 @@ async fn add_team_owner(
376
372
Ok ( NewOwnerInvite :: Team ( team) )
377
373
}
378
374
375
+ /// Tries to create or update a Github Team. Assumes `org` and `team` are
376
+ /// correctly parsed out of the full `name`. `name` is passed as a
377
+ /// convenience to avoid rebuilding it.
378
+ pub async fn create_or_update_github_team (
379
+ gh_client : & dyn GitHubClient ,
380
+ conn : & mut AsyncPgConnection ,
381
+ login : & str ,
382
+ org_name : & str ,
383
+ team_name : & str ,
384
+ req_user : & User ,
385
+ ) -> AppResult < Team > {
386
+ // GET orgs/:org/teams
387
+ // check that `team` is the `slug` in results, and grab its data
388
+
389
+ // "sanitization"
390
+ fn is_allowed_char ( c : char ) -> bool {
391
+ matches ! ( c, 'a' ..='z' | 'A' ..='Z' | '0' ..='9' | '-' | '_' )
392
+ }
393
+
394
+ if let Some ( c) = org_name. chars ( ) . find ( |c| !is_allowed_char ( * c) ) {
395
+ return Err ( bad_request ( format_args ! (
396
+ "organization cannot contain special \
397
+ characters like {c}"
398
+ ) ) ) ;
399
+ }
400
+
401
+ let token = AccessToken :: new ( req_user. gh_access_token . expose_secret ( ) . to_string ( ) ) ;
402
+ let team = gh_client. team_by_name ( org_name, team_name, & token) . await
403
+ . map_err ( |_| {
404
+ bad_request ( format_args ! (
405
+ "could not find the github team {org_name}/{team_name}. \
406
+ Make sure that you have the right permissions in GitHub. \
407
+ See https://doc.rust-lang.org/cargo/reference/publishing.html#github-permissions"
408
+ ) )
409
+ } ) ?;
410
+
411
+ let org_id = team. organization . id ;
412
+ let gh_login = & req_user. gh_login ;
413
+
414
+ if !can_add_team ( gh_client, org_id, team. id , gh_login, & token) . await ? {
415
+ return Err ( custom (
416
+ StatusCode :: FORBIDDEN ,
417
+ "only members of a team or organization owners can add it as an owner" ,
418
+ ) ) ;
419
+ }
420
+
421
+ let org = gh_client. org_by_name ( org_name, & token) . await ?;
422
+
423
+ NewTeam :: builder ( )
424
+ . login ( & login. to_lowercase ( ) )
425
+ . org_id ( org_id)
426
+ . github_id ( team. id )
427
+ . maybe_name ( team. name . as_deref ( ) )
428
+ . maybe_avatar ( org. avatar_url . as_deref ( ) )
429
+ . build ( )
430
+ . create_or_update ( conn)
431
+ . await
432
+ . map_err ( Into :: into)
433
+ }
434
+
379
435
/// Error results from a [`add_owner()`] model call.
380
436
#[ derive( Debug , Error ) ]
381
437
enum OwnerAddError {
0 commit comments