Skip to content

Commit c7ac5cb

Browse files
committed
Merge remote-tracking branch 'upstream/master' into drop_email_column
2 parents 2410f44 + f3e0511 commit c7ac5cb

File tree

15 files changed

+503
-48
lines changed

15 files changed

+503
-48
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
ALTER TABLE version_owner_actions
2+
ALTER COLUMN user_id
3+
DROP NOT NULL
4+
;
5+
6+
ALTER TABLE version_owner_actions
7+
ALTER COLUMN version_id
8+
DROP NOT NULL
9+
;
10+
11+
ALTER TABLE version_owner_actions
12+
RENAME COLUMN user_id TO owner_id
13+
;
14+
15+
ALTER TABLE version_owner_actions
16+
RENAME COLUMN api_token_id TO owner_token_id
17+
;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
-- I expect that it will be save to apply this migration in production as the code that adds records
2+
-- to this table is not used until this changeset.
3+
4+
ALTER TABLE version_owner_actions
5+
RENAME COLUMN owner_id TO user_id
6+
;
7+
8+
ALTER TABLE version_owner_actions
9+
RENAME COLUMN owner_token_id TO api_token_id
10+
;
11+
12+
ALTER TABLE version_owner_actions
13+
ALTER COLUMN user_id
14+
SET NOT NULL
15+
;
16+
17+
ALTER TABLE version_owner_actions
18+
ALTER COLUMN version_id
19+
SET NOT NULL
20+
;

src/controllers/krate/publish.rs

+13-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,11 @@ use swirl::Job;
77
use crate::controllers::prelude::*;
88
use crate::git;
99
use crate::models::dependency;
10-
use crate::models::{Badge, Category, Keyword, NewCrate, NewVersion, Rights, User};
10+
use crate::models::{
11+
insert_version_owner_action, Badge, Category, Keyword, NewCrate, NewVersion, Rights, User,
12+
VersionAction,
13+
};
14+
1115
use crate::render;
1216
use crate::util::{read_fill, read_le_u32};
1317
use crate::util::{AppError, ChainError, Maximums};
@@ -143,6 +147,14 @@ pub fn publish(req: &mut dyn Request) -> AppResult<Response> {
143147
)?
144148
.save(&conn, &new_crate.authors, &verified_email_address)?;
145149

150+
insert_version_owner_action(
151+
&conn,
152+
version.id,
153+
user.id,
154+
req.authentication_source()?.api_token_id(),
155+
VersionAction::Publish,
156+
)?;
157+
146158
// Link this new version to all dependencies
147159
let git_deps = dependency::add_dependencies(&conn, &new_crate.deps, version.id)?;
148160

src/controllers/version/yank.rs

+9-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use swirl::Job;
55
use super::version_and_crate;
66
use crate::controllers::prelude::*;
77
use crate::git;
8-
use crate::models::Rights;
8+
use crate::models::{insert_version_owner_action, Rights, VersionAction};
99
use crate::util::AppError;
1010

1111
/// Handles the `DELETE /crates/:crate_id/:version/yank` route.
@@ -35,6 +35,14 @@ fn modify_yank(req: &mut dyn Request, yanked: bool) -> AppResult<Response> {
3535
if user.rights(req.app(), &owners)? < Rights::Publish {
3636
return Err(cargo_err("must already be an owner to yank or unyank"));
3737
}
38+
let action = if yanked {
39+
VersionAction::Yank
40+
} else {
41+
VersionAction::Unyank
42+
};
43+
let api_token_id = req.authentication_source()?.api_token_id();
44+
45+
insert_version_owner_action(&conn, version.id, user.id, api_token_id, action)?;
3846

3947
git::yank(krate.name, version, yanked)
4048
.enqueue(&conn)

src/middleware/current_user.rs

+23-6
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,17 @@ use diesel::prelude::*;
66
use crate::db::RequestTransaction;
77
use crate::util::errors::{std_error, AppResult, ChainError, Unauthorized};
88

9+
use crate::models::ApiToken;
910
use crate::models::User;
1011
use crate::schema::users;
1112

1213
#[derive(Debug, Clone, Copy)]
1314
pub struct CurrentUser;
1415

15-
#[derive(Debug, Clone, Eq, PartialEq)]
16+
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
1617
pub enum AuthenticationSource {
1718
SessionCookie,
18-
ApiToken { auth_header: String },
19+
ApiToken { api_token_id: i32 },
1920
}
2021

2122
impl Middleware for CurrentUser {
@@ -43,10 +44,17 @@ impl Middleware for CurrentUser {
4344
// Otherwise, look for an `Authorization` header on the request
4445
// and try to find a user in the database with a matching API token
4546
let user_auth = if let Some(headers) = req.headers().find("Authorization") {
46-
let auth_header = headers[0].to_string();
47-
48-
User::find_by_api_token(&conn, &auth_header)
49-
.map(|user| (AuthenticationSource::ApiToken { auth_header }, user))
47+
ApiToken::find_by_api_token(&conn, headers[0])
48+
.and_then(|api_token| {
49+
User::find(&conn, api_token.user_id).map(|user| {
50+
(
51+
AuthenticationSource::ApiToken {
52+
api_token_id: api_token.id,
53+
},
54+
user,
55+
)
56+
})
57+
})
5058
.optional()
5159
.map_err(|e| Box::new(e) as Box<dyn Error + Send>)?
5260
} else {
@@ -85,3 +93,12 @@ impl<'a> RequestUser for dyn Request + 'a {
8593
.chain_error(|| Unauthorized)
8694
}
8795
}
96+
97+
impl AuthenticationSource {
98+
pub fn api_token_id(self) -> Option<i32> {
99+
match self {
100+
AuthenticationSource::SessionCookie => None,
101+
AuthenticationSource::ApiToken { api_token_id } => Some(api_token_id),
102+
}
103+
}
104+
}

src/models.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
pub use self::action::{VersionAction, VersionOwnerAction};
1+
pub use self::action::{insert_version_owner_action, VersionAction, VersionOwnerAction};
22
pub use self::badge::{Badge, CrateBadge, MaintenanceStatus};
33
pub use self::category::{Category, CrateCategory, NewCategory};
44
pub use self::crate_owner_invitation::{CrateOwnerInvitation, NewCrateOwnerInvitation};

src/models/action.rs

+70-6
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,90 @@
11
use chrono::NaiveDateTime;
2+
use diesel::prelude::*;
3+
use diesel::{
4+
deserialize::{self, FromSql},
5+
pg::Pg,
6+
serialize::{self, Output, ToSql},
7+
sql_types::Integer,
8+
};
9+
use std::io::Write;
210

311
use crate::models::{ApiToken, User, Version};
412
use crate::schema::*;
513

6-
#[derive(Debug, Clone, Copy)]
7-
#[repr(u32)]
14+
#[derive(Debug, Clone, Copy, PartialEq, FromSqlRow, AsExpression)]
15+
#[repr(i32)]
16+
#[sql_type = "Integer"]
817
pub enum VersionAction {
918
Publish = 0,
1019
Yank = 1,
1120
Unyank = 2,
1221
}
1322

23+
impl FromSql<Integer, Pg> for VersionAction {
24+
fn from_sql(bytes: Option<&[u8]>) -> deserialize::Result<Self> {
25+
match <i32 as FromSql<Integer, Pg>>::from_sql(bytes)? {
26+
0 => Ok(VersionAction::Publish),
27+
1 => Ok(VersionAction::Yank),
28+
2 => Ok(VersionAction::Unyank),
29+
n => Err(format!("unknown version action: {}", n).into()),
30+
}
31+
}
32+
}
33+
34+
impl ToSql<Integer, Pg> for VersionAction {
35+
fn to_sql<W: Write>(&self, out: &mut Output<'_, W, Pg>) -> serialize::Result {
36+
ToSql::<Integer, Pg>::to_sql(&(*self as i32), out)
37+
}
38+
}
39+
1440
#[derive(Debug, Clone, Copy, Queryable, Identifiable, Associations)]
1541
#[belongs_to(Version)]
16-
#[belongs_to(User, foreign_key = "owner_id")]
17-
#[belongs_to(ApiToken, foreign_key = "owner_token_id")]
42+
#[belongs_to(User, foreign_key = "user_id")]
43+
#[belongs_to(ApiToken, foreign_key = "api_token_id")]
1844
#[table_name = "version_owner_actions"]
1945
pub struct VersionOwnerAction {
2046
pub id: i32,
2147
pub version_id: i32,
22-
pub owner_id: i32,
23-
pub owner_token_id: i32,
48+
pub user_id: i32,
49+
pub api_token_id: Option<i32>,
2450
pub action: VersionAction,
2551
pub time: NaiveDateTime,
2652
}
53+
54+
impl VersionOwnerAction {
55+
pub fn all(conn: &PgConnection) -> QueryResult<Vec<VersionOwnerAction>> {
56+
version_owner_actions::table.load(conn)
57+
}
58+
59+
pub fn by_version_id_and_action(
60+
conn: &PgConnection,
61+
version_id_: i32,
62+
action_: VersionAction,
63+
) -> QueryResult<Vec<VersionOwnerAction>> {
64+
use version_owner_actions::dsl::{action, version_id};
65+
66+
version_owner_actions::table
67+
.filter(version_id.eq(version_id_))
68+
.filter(action.eq(action_))
69+
.load(conn)
70+
}
71+
}
72+
73+
pub fn insert_version_owner_action(
74+
conn: &PgConnection,
75+
version_id_: i32,
76+
user_id_: i32,
77+
api_token_id_: Option<i32>,
78+
action_: VersionAction,
79+
) -> QueryResult<VersionOwnerAction> {
80+
use version_owner_actions::dsl::{action, api_token_id, user_id, version_id};
81+
82+
diesel::insert_into(version_owner_actions::table)
83+
.values((
84+
version_id.eq(version_id_),
85+
user_id.eq(user_id_),
86+
api_token_id.eq(api_token_id_),
87+
action.eq(action_),
88+
))
89+
.get_result(conn)
90+
}

src/models/token.rs

+18
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,24 @@ impl ApiToken {
4646
last_used_at: self.last_used_at,
4747
}
4848
}
49+
50+
pub fn find_by_api_token(conn: &PgConnection, token_: &str) -> QueryResult<ApiToken> {
51+
use crate::schema::api_tokens::dsl::{api_tokens, last_used_at, revoked, token};
52+
use diesel::{dsl::now, update};
53+
54+
let tokens = api_tokens
55+
.filter(token.eq(token_))
56+
.filter(revoked.eq(false));
57+
58+
// If the database is in read only mode, we can't update last_used_at.
59+
// Try updating in a new transaction, if that fails, fall back to reading
60+
conn.transaction(|| {
61+
update(tokens)
62+
.set(last_used_at.eq(now.nullable()))
63+
.get_result::<ApiToken>(conn)
64+
})
65+
.or_else(|_| tokens.first(conn))
66+
}
4967
}
5068

5169
#[cfg(test)]

src/models/user.rs

+9-22
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
1-
use diesel::dsl::now;
21
use diesel::prelude::*;
32
use std::borrow::Cow;
43

54
use crate::app::App;
65
use crate::util::AppResult;
76

8-
use crate::models::{Crate, CrateOwner, Email, NewEmail, Owner, OwnerKind, Rights};
7+
use crate::models::{ApiToken, Crate, CrateOwner, Email, NewEmail, Owner, OwnerKind, Rights};
98
use crate::schema::{crate_owners, emails, users};
109
use crate::views::{EncodablePrivateUser, EncodablePublicUser};
1110

@@ -106,27 +105,15 @@ impl<'a> NewUser<'a> {
106105
}
107106

108107
impl User {
108+
pub fn find(conn: &PgConnection, id: i32) -> QueryResult<User> {
109+
users::table.find(id).first(conn)
110+
}
111+
109112
/// Queries the database for a user with a certain `api_token` value.
110-
pub fn find_by_api_token(conn: &PgConnection, token_: &str) -> QueryResult<User> {
111-
use crate::schema::api_tokens::dsl::{api_tokens, last_used_at, revoked, token, user_id};
112-
use diesel::update;
113-
114-
let tokens = api_tokens
115-
.filter(token.eq(token_))
116-
.filter(revoked.eq(false));
117-
118-
// If the database is in read only mode, we can't update last_used_at.
119-
// Try updating in a new transaction, if that fails, fall back to reading
120-
let user_id_ = conn
121-
.transaction(|| {
122-
update(tokens)
123-
.set(last_used_at.eq(now.nullable()))
124-
.returning(user_id)
125-
.get_result::<i32>(conn)
126-
})
127-
.or_else(|_| tokens.select(user_id).first(conn))?;
128-
129-
users::table.find(user_id_).first(conn)
113+
pub fn find_by_api_token(conn: &PgConnection, token: &str) -> QueryResult<User> {
114+
let api_token = ApiToken::find_by_api_token(conn, token)?;
115+
116+
Self::find(conn, api_token.user_id)
130117
}
131118

132119
pub fn owning(krate: &Crate, conn: &PgConnection) -> AppResult<Vec<Owner>> {

src/schema.rs

+9-9
Original file line numberDiff line numberDiff line change
@@ -876,22 +876,22 @@ table! {
876876
id -> Int4,
877877
/// The `version_id` column of the `version_owner_actions` table.
878878
///
879-
/// Its SQL type is `Nullable<Int4>`.
879+
/// Its SQL type is `Int4`.
880880
///
881881
/// (Automatically generated by Diesel.)
882-
version_id -> Nullable<Int4>,
883-
/// The `owner_id` column of the `version_owner_actions` table.
882+
version_id -> Int4,
883+
/// The `user_id` column of the `version_owner_actions` table.
884884
///
885-
/// Its SQL type is `Nullable<Int4>`.
885+
/// Its SQL type is `Int4`.
886886
///
887887
/// (Automatically generated by Diesel.)
888-
owner_id -> Nullable<Int4>,
889-
/// The `owner_token_id` column of the `version_owner_actions` table.
888+
user_id -> Int4,
889+
/// The `api_token_id` column of the `version_owner_actions` table.
890890
///
891891
/// Its SQL type is `Nullable<Int4>`.
892892
///
893893
/// (Automatically generated by Diesel.)
894-
owner_token_id -> Nullable<Int4>,
894+
api_token_id -> Nullable<Int4>,
895895
/// The `action` column of the `version_owner_actions` table.
896896
///
897897
/// Its SQL type is `Int4`.
@@ -1029,8 +1029,8 @@ joinable!(recent_crate_downloads -> crates (crate_id));
10291029
joinable!(version_authors -> users (user_id));
10301030
joinable!(version_authors -> versions (version_id));
10311031
joinable!(version_downloads -> versions (version_id));
1032-
joinable!(version_owner_actions -> api_tokens (owner_token_id));
1033-
joinable!(version_owner_actions -> users (owner_id));
1032+
joinable!(version_owner_actions -> api_tokens (api_token_id));
1033+
joinable!(version_owner_actions -> users (user_id));
10341034
joinable!(version_owner_actions -> versions (version_id));
10351035
joinable!(versions -> crates (crate_id));
10361036
joinable!(versions -> users (published_by));

src/tasks/dump_db/dump-db.toml

+2-2
Original file line numberDiff line numberDiff line change
@@ -194,8 +194,8 @@ processed = "private"
194194
[version_owner_actions.columns]
195195
id = "private"
196196
version_id = "private"
197-
owner_id = "private"
198-
owner_token_id = "private"
197+
user_id = "private"
198+
api_token_id = "private"
199199
action = "private"
200200
time = "private"
201201

0 commit comments

Comments
 (0)