Skip to content

Commit 92aa46b

Browse files
committed
Stop reading from users.gh_login
1 parent c1726c8 commit 92aa46b

11 files changed

Lines changed: 72 additions & 54 deletions

File tree

crates/crates_io_api_types/src/lib.rs

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -527,6 +527,14 @@ pub struct EncodableCrateLinks {
527527
pub reverse_dependencies: String,
528528
}
529529

530+
fn url_or_deprecation_message(gh_login: Option<&str>) -> String {
531+
gh_login
532+
.map(|gh_login| format!("https://github.com/{gh_login}"))
533+
.unwrap_or(String::from(
534+
"This field is deprecated. Use LinkedAccount instead.",
535+
))
536+
}
537+
530538
/// A user or team that owns a crate.
531539
#[derive(Serialize, Deserialize, Debug, utoipa::ToSchema)]
532540
#[schema(as = Owner)]
@@ -592,7 +600,7 @@ impl EncodableOwner {
592600
pub fn from_user(user: PublicUser) -> Self {
593601
Self::User {
594602
github_username_matches: user.github_username_matches,
595-
url: format!("https://github.com/{}", user.gh_login),
603+
url: url_or_deprecation_message(user.gh_login.as_deref()),
596604
id: user.id,
597605
login: user.username,
598606
avatar: user.gh_avatar,
@@ -771,7 +779,8 @@ impl EncodablePrivateUser {
771779
created_at,
772780
..
773781
} = user;
774-
let url = format!("https://github.com/{gh_login}");
782+
783+
let url = url_or_deprecation_message(gh_login.as_deref());
775784

776785
EncodablePrivateUser {
777786
id,
@@ -840,7 +849,9 @@ impl From<PublicUser> for EncodablePublicUser {
840849
created_at,
841850
..
842851
} = user;
843-
let url = format!("https://github.com/{gh_login}");
852+
853+
let url = url_or_deprecation_message(gh_login.as_deref());
854+
844855
EncodablePublicUser {
845856
id,
846857
avatar: gh_avatar,

crates/crates_io_database/src/models/owner.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ impl Owner {
104104

105105
pub fn login(&self) -> &str {
106106
match self {
107-
Owner::User(user) => &user.gh_login,
107+
Owner::User(user) => &user.username,
108108
Owner::Team(team) => &team.login,
109109
}
110110
}

crates/crates_io_database/src/models/user.rs

Lines changed: 5 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use diesel::upsert::excluded;
77
use diesel_async::{AsyncPgConnection, RunQueryDsl};
88
use serde::Serialize;
99

10-
use crate::fns::{canon_username, lower};
10+
use crate::fns::canon_username;
1111
use crate::models::{Crate, CrateOwner, Email, OwnerKind};
1212
use crate::schema::{crate_owners, emails, oauth_github, users};
1313

@@ -46,7 +46,8 @@ pub fn users_by_username<'a>(username: &'a str) -> _ {
4646
pub struct PublicUser {
4747
pub id: i32,
4848
pub name: Option<String>,
49-
pub gh_login: String,
49+
#[diesel(select_expression = oauth_github::login.nullable())]
50+
pub gh_login: Option<String>,
5051
#[diesel(select_expression = oauth_github::avatar.nullable())]
5152
pub gh_avatar: Option<String>,
5253
#[diesel(select_expression = github_username_matches())]
@@ -76,7 +77,8 @@ pub struct User {
7677
pub id: i32,
7778
pub name: Option<String>,
7879
pub gh_id: i32,
79-
pub gh_login: String,
80+
#[diesel(select_expression = oauth_github::login.nullable())]
81+
pub gh_login: Option<String>,
8082
#[diesel(select_expression = oauth_github::avatar.nullable())]
8183
pub gh_avatar: Option<String>,
8284
#[diesel(select_expression = oauth_github::encrypted_token.nullable())]
@@ -98,15 +100,6 @@ impl User {
98100
.await
99101
}
100102

101-
pub async fn find_by_login(mut conn: &AsyncPgConnection, login: &str) -> QueryResult<User> {
102-
User::query()
103-
.filter(lower(users::gh_login).eq(login.to_lowercase()))
104-
.filter(users::gh_id.ne(-1))
105-
.order(users::gh_id.desc())
106-
.first(&mut conn)
107-
.await
108-
}
109-
110103
pub async fn owning(krate: &Crate, mut conn: &AsyncPgConnection) -> QueryResult<Vec<Self>> {
111104
CrateOwner::by_owner_kind(OwnerKind::User)
112105
.inner_join(users::table.left_join(oauth_github::table))

crates/crates_io_test_utils/src/builders/user.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ impl<'a> UserBuilder<'a> {
7070
pub fn build(self) -> User {
7171
User {
7272
id: 1,
73-
gh_login: self.gh_login.into(),
73+
gh_login: Some(self.gh_login.into()),
7474
name: self.display_name.map(ToString::to_string),
7575
gh_id: self.gh_id.unwrap_or(123),
7676
gh_avatar: None,
@@ -115,7 +115,7 @@ impl<'a> OauthGithubBuilder<'a> {
115115
user_id: user.id,
116116
account_id: user.gh_id as i64,
117117
encrypted_token: &ENCRYPTED_TOKEN,
118-
login: &user.gh_login,
118+
login: user.gh_login.as_ref().unwrap_or(&user.username),
119119
avatar: None,
120120
}
121121
}

src/controllers/helpers/authorization.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,13 +48,13 @@ impl Rights {
4848
}
4949
}
5050
Owner::Team(ref team) => {
51-
if let Some(ref auth) = auth {
51+
if let (Some(auth), Some(gh_login)) = (&auth, &user.gh_login) {
5252
// Phones home to GitHub to ask if this User is a member of the given team.
5353
// Note that we're assuming that the given user is the one interested in
5454
// the answer. If this is not the case, then we could accidentally leak
5555
// private membership information here.
5656
let is_team_member = match gh_client
57-
.team_membership(team.org_id, team.github_id, &user.gh_login, auth)
57+
.team_membership(team.org_id, team.github_id, gh_login, auth)
5858
.await
5959
{
6060
Ok(membership) => membership.is_some_and(|m| m.is_active()),

src/controllers/krate/owners.rs

Lines changed: 24 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,24 @@
11
//! All routes related to managing owners of a crate
22
3-
use crate::controllers::helpers::authorization::Rights;
4-
use crate::controllers::krate::CratePath;
5-
use crate::models::krate::OwnerRemoveError;
6-
use crate::models::{Crate, Owner, PublicUser, Team, User};
3+
use crate::controllers::{helpers::authorization::Rights, krate::CratePath};
74
use crate::models::{
8-
CrateOwner, NewCrateOwnerInvitation, NewCrateOwnerInvitationOutcome, NewTeam,
9-
krate::NewOwnerInvite, token::EndpointScope,
5+
Crate, CrateOwner, NewCrateOwnerInvitation, NewCrateOwnerInvitationOutcome, NewTeam, Owner,
6+
PublicUser, Team, User,
7+
krate::{NewOwnerInvite, OwnerRemoveError},
8+
token::EndpointScope,
9+
users_by_username,
1010
};
11+
use crate::schema::oauth_github;
1112
use crate::util::errors::{AppResult, BoxedAppError, bad_request, custom, forbidden};
1213
use crate::views::EncodableOwner;
13-
use crate::worker::jobs::SendEmail;
14-
use crate::{App, app::AppState};
15-
use crate::{auth::AuthCheck, email::EmailMessage};
14+
use crate::{App, app::AppState, auth::AuthCheck, email::EmailMessage, worker::jobs::SendEmail};
1615
use axum::Json;
1716
use chrono::Utc;
1817
use crates_io_encryption::TokenEncryption;
1918
use crates_io_github::{GitHubAuth, GitHubClient, GitHubError};
2019
use crates_io_worker::BackgroundJob;
2120
use diesel::prelude::*;
22-
use diesel_async::{AsyncConnection, AsyncPgConnection};
21+
use diesel_async::{AsyncConnection, AsyncPgConnection, RunQueryDsl};
2322
use http::StatusCode;
2423
use http::request::Parts;
2524
use minijinja::context;
@@ -368,7 +367,12 @@ async fn add_owner(
368367
add_github_team_owner(github, conn, req_user, krate, team, encryption).await
369368
}
370369
Login::Unprefixed(login) => {
371-
let user = User::find_by_login(conn, login).await.optional()?;
370+
let user = users_by_username(login)
371+
.left_join(oauth_github::table)
372+
.select(User::as_select())
373+
.first(conn)
374+
.await
375+
.optional()?;
372376
let user = user.ok_or_else(|| {
373377
bad_request(format_args!("could not find user with login `{login}`"))
374378
})?;
@@ -543,13 +547,16 @@ pub async fn create_or_update_github_team(
543547
let org_id = team.organization.id;
544548
let gh_login = &req_user.gh_login;
545549

546-
let is_team_member = gh_client
547-
.team_membership(org_id, team.id, gh_login, &auth)
548-
.await?
549-
.is_some_and(|m| m.is_active());
550+
let can_add_team = if let Some(gh_login) = gh_login {
551+
let is_team_member = gh_client
552+
.team_membership(org_id, team.id, gh_login, &auth)
553+
.await?
554+
.is_some_and(|m| m.is_active());
550555

551-
let can_add_team =
552-
is_team_member || is_gh_org_owner(gh_client, org_id, gh_login, &auth).await?;
556+
is_team_member || is_gh_org_owner(gh_client, org_id, gh_login, &auth).await?
557+
} else {
558+
false
559+
};
553560

554561
if !can_add_team {
555562
return Err(custom(

src/controllers/trustpub/github_configs/create.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -94,13 +94,16 @@ pub async fn create_trustpub_github_config(
9494
let owner = &json_config.repository_owner;
9595

9696
let encryption = &state.config.token_encryption;
97-
let Some(gh_auth) = auth_user.gh_encrypted_token.as_ref() else {
97+
let (Some(gh_auth), Some(login)) = (
98+
auth_user.gh_encrypted_token.as_ref(),
99+
auth_user.gh_login.as_ref(),
100+
) else {
98101
return Err(bad_request(
99102
"Must have a linked GitHub account to create a Trusted Publishing config",
100103
));
101104
};
105+
102106
let gh_auth = encryption.decrypt(gh_auth).map_err(|err| {
103-
let login = &auth_user.gh_login;
104107
warn!("Failed to decrypt GitHub token for user {login}: {err}");
105108
server_error("Internal server error")
106109
})?;

src/tests/routes/crates/owners/add.rs

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -211,36 +211,36 @@ async fn invite_distinct_login_user(login: &str) -> Response<OwnerResp> {
211211
#[tokio::test(flavor = "multi_thread")]
212212
async fn unprefixed_crates_io_username_verbatim() {
213213
let response = invite_distinct_login_user("crates-user").await;
214-
assert_snapshot!(response.status(), @"400 Bad Request");
215-
assert_snapshot!(response.text(), @r#"{"errors":[{"detail":"could not find user with login `crates-user`"}]}"#);
214+
assert_snapshot!(response.status(), @"200 OK");
215+
assert_snapshot!(response.text(), @r#"{"msg":"Crates.io user crates-user has been invited to be an owner of crate foo","ok":true}"#);
216216
}
217217

218218
#[tokio::test(flavor = "multi_thread")]
219219
async fn unprefixed_crates_io_username_case_insensitive() {
220220
let response = invite_distinct_login_user("CRATES-USER").await;
221-
assert_snapshot!(response.status(), @"400 Bad Request");
222-
assert_snapshot!(response.text(), @r#"{"errors":[{"detail":"could not find user with login `CRATES-USER`"}]}"#);
221+
assert_snapshot!(response.status(), @"200 OK");
222+
assert_snapshot!(response.text(), @r#"{"msg":"Crates.io user crates-user has been invited to be an owner of crate foo","ok":true}"#);
223223
}
224224

225225
#[tokio::test(flavor = "multi_thread")]
226226
async fn unprefixed_crates_io_username_separator_variant() {
227227
let response = invite_distinct_login_user("crates_user").await;
228-
assert_snapshot!(response.status(), @"400 Bad Request");
229-
assert_snapshot!(response.text(), @r#"{"errors":[{"detail":"could not find user with login `crates_user`"}]}"#);
228+
assert_snapshot!(response.status(), @"200 OK");
229+
assert_snapshot!(response.text(), @r#"{"msg":"Crates.io user crates-user has been invited to be an owner of crate foo","ok":true}"#);
230230
}
231231

232232
#[tokio::test(flavor = "multi_thread")]
233233
async fn unprefixed_github_login_verbatim() {
234234
let response = invite_distinct_login_user("github-user").await;
235-
assert_snapshot!(response.status(), @"200 OK");
236-
assert_snapshot!(response.text(), @r#"{"msg":"Crates.io user crates-user has been invited to be an owner of crate foo","ok":true}"#);
235+
assert_snapshot!(response.status(), @"400 Bad Request");
236+
assert_snapshot!(response.text(), @r#"{"errors":[{"detail":"could not find user with login `github-user`"}]}"#);
237237
}
238238

239239
#[tokio::test(flavor = "multi_thread")]
240240
async fn unprefixed_github_login_case_insensitive() {
241241
let response = invite_distinct_login_user("GITHUB-USER").await;
242-
assert_snapshot!(response.status(), @"200 OK");
243-
assert_snapshot!(response.text(), @r#"{"msg":"Crates.io user crates-user has been invited to be an owner of crate foo","ok":true}"#);
242+
assert_snapshot!(response.status(), @"400 Bad Request");
243+
assert_snapshot!(response.text(), @r#"{"errors":[{"detail":"could not find user with login `GITHUB-USER`"}]}"#);
244244
}
245245

246246
#[tokio::test(flavor = "multi_thread")]

src/tests/user.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -377,7 +377,7 @@ async fn write_to_users_and_oauth_github() -> anyhow::Result<()> {
377377
assert_eq!(u.username, gh_login);
378378
assert_eq!(u.name.unwrap(), gh_display_name);
379379
assert_eq!(u.gh_id, gh_id);
380-
assert_eq!(u.gh_login, gh_login);
380+
assert_eq!(u.gh_login.unwrap(), gh_login);
381381
assert_eq!(u.gh_avatar.unwrap(), gh_avatar);
382382
let decrypted_token = encryption.decrypt(&u.gh_encrypted_token.unwrap())?;
383383
assert_eq!(decrypted_token.expose_secret(), gh_token);
@@ -413,7 +413,7 @@ async fn write_to_users_and_oauth_github() -> anyhow::Result<()> {
413413
assert_eq!(u.username, different_gh_login);
414414
assert_eq!(u.name.unwrap(), different_gh_display_name);
415415
assert_eq!(u.gh_id, gh_id);
416-
assert_eq!(u.gh_login, different_gh_login);
416+
assert_eq!(u.gh_login.unwrap(), different_gh_login);
417417
assert_eq!(u.gh_avatar.unwrap(), different_gh_avatar);
418418
let decrypted_token = encryption.decrypt(&u.gh_encrypted_token.unwrap())?;
419419
assert_eq!(decrypted_token.expose_secret(), different_gh_token);
@@ -445,7 +445,7 @@ async fn write_to_users_and_oauth_github() -> anyhow::Result<()> {
445445
.unwrap();
446446
let u = User::find(&conn, uid).await?;
447447

448-
assert_eq!(u.gh_login, gh_login);
448+
assert_eq!(u.gh_login.unwrap(), gh_login);
449449
assert_eq!(u.gh_id, new_gh_id);
450450

451451
let oauth_github_records: Vec<OauthGithub> = oauth_github::table.load(&mut conn).await.unwrap();

src/tests/util/test_app.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -161,11 +161,15 @@ impl TestApp {
161161
let conn = self.db_conn().await;
162162

163163
let new_user = builder.new_user();
164+
let gh_login = new_user.gh_login;
164165
let email = format!("{}@example.com", new_user.username);
165166
let id = new_user.insert(&conn).await.unwrap();
166167
let user = User::find(&conn, id).await.unwrap();
167168

168-
OauthGithubBuilder::for_user(&user).insert(&conn).await;
169+
OauthGithubBuilder::for_user(&user)
170+
.with_login(gh_login)
171+
.insert(&conn)
172+
.await;
169173

170174
let new_email = NewEmail::builder()
171175
.user_id(id)

0 commit comments

Comments
 (0)