Skip to content

Commit 9a94f04

Browse files
committed
Fix owner removal for duplicate logins
When users or teams on GitHub are deleted and recreated they might exist multiple times within our own database with the same `login`. When owners are deleted, only the newest matching `login` is considered for deletion though, which makes it impossible to delete older users/teams. This commit fixes the problem by deleting **all** owners with a matching `login` instead. Unfortunately, `diesel` does not support `UPDATE` queries with `JOINs`, so this has to be a "raw" SQL query with bindings instead…
1 parent 7092ed8 commit 9a94f04

File tree

3 files changed

+44
-11
lines changed

3 files changed

+44
-11
lines changed

src/models/krate.rs

Lines changed: 38 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use chrono::NaiveDateTime;
22
use diesel::associations::Identifiable;
33
use diesel::dsl;
44
use diesel::pg::Pg;
5-
use diesel::sql_types::{Bool, Text};
5+
use diesel::sql_types::{Bool, Integer, Text};
66
use diesel_async::AsyncPgConnection;
77
use secrecy::SecretString;
88
use thiserror::Error;
@@ -18,7 +18,7 @@ use crate::schema::*;
1818
use crate::sql::canon_crate_name;
1919
use crate::util::diesel::prelude::*;
2020
use crate::util::diesel::Conn;
21-
use crate::util::errors::{version_not_found, AppResult};
21+
use crate::util::errors::{bad_request, version_not_found, AppResult};
2222
use crate::{app::App, util::errors::BoxedAppError};
2323

2424
use super::Team;
@@ -453,12 +453,44 @@ impl Crate {
453453
pub fn owner_remove(&self, conn: &mut impl Conn, login: &str) -> AppResult<()> {
454454
use diesel::RunQueryDsl;
455455

456-
let owner = Owner::find_by_login(conn, login)?;
456+
let query = diesel::sql_query(
457+
r#"WITH crate_owners_with_login AS (
458+
SELECT
459+
crate_owners.*,
460+
CASE WHEN crate_owners.owner_kind = 1 THEN
461+
teams.login
462+
ELSE
463+
users.gh_login
464+
END AS login
465+
FROM crate_owners
466+
LEFT JOIN teams
467+
ON crate_owners.owner_id = teams.id
468+
AND crate_owners.owner_kind = 1
469+
LEFT JOIN users
470+
ON crate_owners.owner_id = users.id
471+
AND crate_owners.owner_kind = 0
472+
WHERE crate_owners.crate_id = $1
473+
AND crate_owners.deleted = false
474+
)
475+
UPDATE crate_owners
476+
SET deleted = true
477+
FROM crate_owners_with_login
478+
WHERE crate_owners.crate_id = crate_owners_with_login.crate_id
479+
AND crate_owners.owner_id = crate_owners_with_login.owner_id
480+
AND crate_owners.owner_kind = crate_owners_with_login.owner_kind
481+
AND crate_owners_with_login.login = $2;"#,
482+
);
457483

458-
let target = crate_owners::table.find((self.id(), owner.id(), owner.kind()));
459-
diesel::update(target)
460-
.set(crate_owners::deleted.eq(true))
484+
let num_updated_rows = query
485+
.bind::<Integer, _>(self.id)
486+
.bind::<Text, _>(login)
461487
.execute(conn)?;
488+
489+
if num_updated_rows == 0 {
490+
let error = format!("could not find owner with login `{login}`");
491+
return Err(bad_request(error));
492+
}
493+
462494
Ok(())
463495
}
464496

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ async fn test_unknown_user() {
6666
.delete_with_body::<()>("/api/v1/crates/foo/owners", body)
6767
.await;
6868
assert_eq!(response.status(), StatusCode::BAD_REQUEST);
69-
assert_snapshot!(response.text(), @r#"{"errors":[{"detail":"could not find user with login `unknown`"}]}"#);
69+
assert_snapshot!(response.text(), @r#"{"errors":[{"detail":"could not find owner with login `unknown`"}]}"#);
7070
}
7171

7272
#[tokio::test(flavor = "multi_thread")]
@@ -81,7 +81,7 @@ async fn test_unknown_team() {
8181
.delete_with_body::<()>("/api/v1/crates/foo/owners", body)
8282
.await;
8383
assert_eq!(response.status(), StatusCode::BAD_REQUEST);
84-
assert_snapshot!(response.text(), @r#"{"errors":[{"detail":"could not find team with login `github:unknown:unknown`"}]}"#);
84+
assert_snapshot!(response.text(), @r#"{"errors":[{"detail":"could not find owner with login `github:unknown:unknown`"}]}"#);
8585
}
8686

8787
/// See <https://github.com/rust-lang/crates.io/issues/2736>.

src/tests/team.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -270,13 +270,14 @@ async fn remove_nonexistent_team() {
270270
.execute(&mut conn)
271271
.expect("couldn't insert nonexistent team");
272272

273-
token
273+
let response = token
274274
.remove_named_owner(
275275
"foo_remove_nonexistent",
276276
"github:test-org:this-does-not-exist",
277277
)
278-
.await
279-
.good();
278+
.await;
279+
assert_eq!(response.status(), StatusCode::BAD_REQUEST);
280+
assert_snapshot!(response.text(), @r#"{"errors":[{"detail":"could not find owner with login `github:test-org:this-does-not-exist`"}]}"#);
280281
}
281282

282283
/// Test trying to publish a crate we don't own

0 commit comments

Comments
 (0)