Skip to content

Commit 1a33394

Browse files
authored
models/team: Replace App arguments with GitHubClient (#10571)
We don't need the full-blown `App` struct. All we need is a way to talk to the GitHub API.
1 parent 5b5b963 commit 1a33394

File tree

7 files changed

+47
-30
lines changed

7 files changed

+47
-30
lines changed

src/controllers/crate_owner_invitation.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,7 @@ async fn prepare_list(
157157
// Only allow crate owners to query pending invitations for their crate.
158158
let krate: Crate = Crate::by_name(&crate_name).first(conn).await?;
159159
let owners = krate.owners(conn).await?;
160-
if user.rights(state, &owners).await? != Rights::Full {
160+
if user.rights(&*state.github, &owners).await? != Rights::Full {
161161
let detail = "only crate owners can query pending invitations for their crate";
162162
return Err(forbidden(detail));
163163
}

src/controllers/krate/delete.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ pub async fn delete_crate(
6868
// Check that the user is an owner of the crate (team owners are not allowed to delete crates)
6969
let user = auth.user();
7070
let owners = krate.owners(&mut conn).await?;
71-
match user.rights(&app, &owners).await? {
71+
match user.rights(&*app.github, &owners).await? {
7272
Rights::Full => {}
7373
Rights::Publish => {
7474
let msg = "team members don't have permission to delete crates";

src/controllers/krate/owners.rs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ use axum_extra::json;
1616
use axum_extra::response::ErasedJson;
1717
use chrono::Utc;
1818
use crates_io_database::schema::crate_owners;
19+
use crates_io_github::GitHubClient;
1920
use diesel::prelude::*;
2021
use diesel_async::scoped_futures::ScopedFutureExt;
2122
use diesel_async::{AsyncConnection, AsyncPgConnection, RunQueryDsl};
@@ -176,7 +177,7 @@ async fn modify_owners(
176177

177178
let owners = krate.owners(conn).await?;
178179

179-
match user.rights(&app, &owners).await? {
180+
match user.rights(&*app.github, &owners).await? {
180181
Rights::Full => {}
181182
// Yes!
182183
Rights::Publish => {
@@ -292,7 +293,7 @@ async fn add_owner(
292293
login: &str,
293294
) -> Result<NewOwnerInvite, OwnerAddError> {
294295
if login.contains(':') {
295-
add_team_owner(app, conn, req_user, krate, login).await
296+
add_team_owner(&*app.github, conn, req_user, krate, login).await
296297
} else {
297298
invite_user_owner(app, conn, req_user, krate, login).await
298299
}
@@ -330,14 +331,14 @@ async fn invite_user_owner(
330331
}
331332

332333
async fn add_team_owner(
333-
app: &App,
334+
gh_client: &dyn GitHubClient,
334335
conn: &mut AsyncPgConnection,
335336
req_user: &User,
336337
krate: &Crate,
337338
login: &str,
338339
) -> Result<NewOwnerInvite, OwnerAddError> {
339340
// Always recreate teams to get the most up-to-date GitHub ID
340-
let team = Team::create_or_update(app, conn, login, req_user).await?;
341+
let team = Team::create_or_update(gh_client, conn, login, req_user).await?;
341342

342343
// Teams are added as owners immediately, since the above call ensures
343344
// the user is a team member.

src/controllers/krate/publish.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -356,7 +356,7 @@ pub async fn publish(app: AppState, req: Parts, body: Body) -> AppResult<Json<Go
356356
};
357357

358358
let owners = krate.owners(conn).await?;
359-
if user.rights(&app, &owners).await? < Rights::Publish {
359+
if user.rights(&*app.github, &owners).await? < Rights::Publish {
360360
return Err(custom(StatusCode::FORBIDDEN, MISSING_RIGHTS_ERROR_MESSAGE));
361361
}
362362

src/controllers/version/update.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ pub async fn perform_version_yank_update(
122122

123123
let yanked = yanked.unwrap_or(version.yanked);
124124

125-
if user.rights(state, &owners).await? < Rights::Publish {
125+
if user.rights(&*state.github, &owners).await? < Rights::Publish {
126126
if user.is_admin {
127127
let action = if yanked { "yanking" } else { "unyanking" };
128128
warn!(

src/models/team.rs

Lines changed: 31 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,9 @@ use diesel::prelude::*;
33
use diesel_async::{AsyncPgConnection, RunQueryDsl};
44
use http::StatusCode;
55

6-
use crate::app::App;
76
use crate::util::errors::{bad_request, custom, AppResult};
87

9-
use crates_io_github::GitHubError;
8+
use crates_io_github::{GitHubClient, GitHubError};
109
use oauth2::AccessToken;
1110

1211
use crate::models::{Crate, CrateOwner, Owner, OwnerKind, User};
@@ -64,7 +63,7 @@ impl Team {
6463
///
6564
/// This function will panic if login contains less than 2 `:` characters.
6665
pub async fn create_or_update(
67-
app: &App,
66+
gh_client: &dyn GitHubClient,
6867
conn: &mut AsyncPgConnection,
6968
login: &str,
7069
req_user: &User,
@@ -84,7 +83,7 @@ impl Team {
8483
)
8584
})?;
8685
Team::create_or_update_github_team(
87-
app,
86+
gh_client,
8887
conn,
8988
&login.to_lowercase(),
9089
org,
@@ -104,7 +103,7 @@ impl Team {
104103
/// correctly parsed out of the full `name`. `name` is passed as a
105104
/// convenience to avoid rebuilding it.
106105
async fn create_or_update_github_team(
107-
app: &App,
106+
gh_client: &dyn GitHubClient,
108107
conn: &mut AsyncPgConnection,
109108
login: &str,
110109
org_name: &str,
@@ -127,7 +126,7 @@ impl Team {
127126
}
128127

129128
let token = AccessToken::new(req_user.gh_access_token.clone());
130-
let team = app.github.team_by_name(org_name, team_name, &token).await
129+
let team = gh_client.team_by_name(org_name, team_name, &token).await
131130
.map_err(|_| {
132131
bad_request(format_args!(
133132
"could not find the github team {org_name}/{team_name}. \
@@ -138,14 +137,14 @@ impl Team {
138137

139138
let org_id = team.organization.id;
140139

141-
if !can_add_team(app, org_id, team.id, req_user).await? {
140+
if !can_add_team(gh_client, org_id, team.id, req_user).await? {
142141
return Err(custom(
143142
StatusCode::FORBIDDEN,
144143
"only members of a team or organization owners can add it as an owner",
145144
));
146145
}
147146

148-
let org = app.github.org_by_name(org_name, &token).await?;
147+
let org = gh_client.org_by_name(org_name, &token).await?;
149148

150149
NewTeam::builder()
151150
.login(&login.to_lowercase())
@@ -163,9 +162,15 @@ impl Team {
163162
/// Note that we're assuming that the given user is the one interested in
164163
/// the answer. If this is not the case, then we could accidentally leak
165164
/// private membership information here.
166-
pub async fn contains_user(&self, app: &App, user: &User) -> AppResult<bool> {
165+
pub async fn contains_user(
166+
&self,
167+
gh_client: &dyn GitHubClient,
168+
user: &User,
169+
) -> AppResult<bool> {
167170
match self.org_id {
168-
Some(org_id) => team_with_gh_id_contains_user(app, org_id, self.github_id, user).await,
171+
Some(org_id) => {
172+
team_with_gh_id_contains_user(gh_client, org_id, self.github_id, user).await
173+
}
169174
// This means we don't have an org_id on file for the `self` team. It much
170175
// probably was deleted from github by the time we backfilled the database.
171176
// Short-circuiting to false since a non-existent team cannot contain any
@@ -189,17 +194,25 @@ impl Team {
189194
}
190195
}
191196

192-
async fn can_add_team(app: &App, org_id: i32, team_id: i32, user: &User) -> AppResult<bool> {
197+
async fn can_add_team(
198+
gh_client: &dyn GitHubClient,
199+
org_id: i32,
200+
team_id: i32,
201+
user: &User,
202+
) -> AppResult<bool> {
193203
Ok(
194-
team_with_gh_id_contains_user(app, org_id, team_id, user).await?
195-
|| is_gh_org_owner(app, org_id, user).await?,
204+
team_with_gh_id_contains_user(gh_client, org_id, team_id, user).await?
205+
|| is_gh_org_owner(gh_client, org_id, user).await?,
196206
)
197207
}
198208

199-
async fn is_gh_org_owner(app: &App, org_id: i32, user: &User) -> AppResult<bool> {
209+
async fn is_gh_org_owner(
210+
gh_client: &dyn GitHubClient,
211+
org_id: i32,
212+
user: &User,
213+
) -> AppResult<bool> {
200214
let token = AccessToken::new(user.gh_access_token.clone());
201-
match app
202-
.github
215+
match gh_client
203216
.org_membership(org_id, &user.gh_login, &token)
204217
.await
205218
{
@@ -210,7 +223,7 @@ async fn is_gh_org_owner(app: &App, org_id: i32, user: &User) -> AppResult<bool>
210223
}
211224

212225
async fn team_with_gh_id_contains_user(
213-
app: &App,
226+
gh_client: &dyn GitHubClient,
214227
github_org_id: i32,
215228
github_team_id: i32,
216229
user: &User,
@@ -219,8 +232,7 @@ async fn team_with_gh_id_contains_user(
219232
// check that "state": "active"
220233

221234
let token = AccessToken::new(user.gh_access_token.clone());
222-
let membership = match app
223-
.github
235+
let membership = match gh_client
224236
.team_membership(github_org_id, github_team_id, &user.gh_login, &token)
225237
.await
226238
{

src/models/user.rs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,12 @@ use diesel::sql_types::Integer;
66
use diesel::upsert::excluded;
77
use diesel_async::{AsyncPgConnection, RunQueryDsl};
88

9-
use crate::app::App;
109
use crate::util::errors::AppResult;
1110

1211
use crate::models::{Crate, CrateOwner, Email, Owner, OwnerKind, Rights};
1312
use crate::schema::{crate_owners, emails, users};
1413
use crates_io_diesel_helpers::lower;
14+
use crates_io_github::GitHubClient;
1515

1616
/// The model representing a row in the `users` database table.
1717
#[derive(Clone, Debug, PartialEq, Eq, Queryable, Identifiable, AsChangeset, Selectable)]
@@ -63,7 +63,11 @@ impl User {
6363
/// `Publish` as well, but this is a non-obvious invariant so we don't bother.
6464
/// Sweet free optimization if teams are proving burdensome to check.
6565
/// More than one team isn't really expected, though.
66-
pub async fn rights(&self, app: &App, owners: &[Owner]) -> AppResult<Rights> {
66+
pub async fn rights(
67+
&self,
68+
gh_client: &dyn GitHubClient,
69+
owners: &[Owner],
70+
) -> AppResult<Rights> {
6771
let mut best = Rights::None;
6872
for owner in owners {
6973
match *owner {
@@ -73,7 +77,7 @@ impl User {
7377
}
7478
}
7579
Owner::Team(ref team) => {
76-
if team.contains_user(app, self).await? {
80+
if team.contains_user(gh_client, self).await? {
7781
best = Rights::Publish;
7882
}
7983
}

0 commit comments

Comments
 (0)