Skip to content

Commit fbee4dd

Browse files
committed
Log user id and token id when applicable.
In rust-lang/rfcs#2947 (comment), @pietroalbini says, with regards to restricting the default authorization of API tokens: > this would break everyone using the token to authenticate to endpoints > not used by Cargo. Even though we're not providing stability guarantees > for them I'd be wary of blindly breaking them. At least I'd like some > stats on which endpoints are accessed with the cookie and which are used > with tokens. This change attempts to provide those stats via the logs. All authenticated requests receive a `uid` field in the log output. If the request was authenticated via an API token, the log output additionally contains a `tokenid` field. Because the method I used, log_request::add_custom_metadata, requires a mutable request, I had to make req.authenticate() take a mutable ref. Since that conflicted with many call sites that were already holding an immutable ref to the request's DB connection, I moved the taking of the DB connection reference after the authenticate call. In crate_owner_invitations.rs and follow.rs, this also meant removing duplicate authenticate calls and passing through the already-authenticated user ID from a calling function instead. In a few places, `req.authenticate(&conn)?.find_user(&conn)?` has been replaced with two lines, one to do the authentication, and one to do the database lookup for the user object, after `let conn = req.db_conn()?`
1 parent fb0c5f2 commit fbee4dd

File tree

10 files changed

+83
-48
lines changed

10 files changed

+83
-48
lines changed

src/controllers.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ mod prelude {
2626
use serde::Serialize;
2727

2828
pub trait UserAuthenticationExt {
29-
fn authenticate(&self, conn: &PgConnection) -> AppResult<super::util::AuthenticatedUser>;
29+
fn authenticate(&mut self) -> AppResult<super::util::AuthenticatedUser>;
3030
}
3131

3232
pub trait RequestUtils {

src/controllers/crate_owner_invitation.rs

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ use crate::views::{EncodableCrateOwnerInvitation, InvitationResponse};
66

77
/// Handles the `GET /me/crate_owner_invitations` route.
88
pub fn list(req: &mut dyn RequestExt) -> EndpointResult {
9+
let user_id = { req.authenticate()? }.user_id();
910
let conn = &*req.db_conn()?;
10-
let user_id = req.authenticate(conn)?.user_id();
1111

1212
let crate_owner_invitations = crate_owner_invitations::table
1313
.filter(crate_owner_invitations::invited_user_id.eq(user_id))
@@ -35,18 +35,17 @@ pub fn handle_invite(req: &mut dyn RequestExt) -> EndpointResult {
3535
let mut body = String::new();
3636
req.body().read_to_string(&mut body)?;
3737

38-
let conn = &*req.db_conn()?;
39-
4038
let crate_invite: OwnerInvitation =
4139
serde_json::from_str(&body).map_err(|_| bad_request("invalid json request"))?;
4240

4341
let crate_invite = crate_invite.crate_owner_invite;
44-
let user_id = req.authenticate(conn)?.user_id();
42+
let user_id = req.authenticate()?.user_id();
43+
let conn = &*req.db_conn()?;
4544

4645
if crate_invite.accepted {
4746
accept_invite(req, conn, crate_invite, user_id)
4847
} else {
49-
decline_invite(req, conn, crate_invite)
48+
decline_invite(req, conn, crate_invite, user_id)
5049
}
5150
}
5251

@@ -113,10 +112,10 @@ fn decline_invite(
113112
req: &dyn RequestExt,
114113
conn: &PgConnection,
115114
crate_invite: InvitationResponse,
115+
user_id: i32,
116116
) -> EndpointResult {
117117
use diesel::delete;
118118

119-
let user_id = req.authenticate(conn)?.user_id();
120119
delete(crate_owner_invitations::table.find((user_id, crate_invite.crate_id))).execute(conn)?;
121120

122121
#[derive(Serialize)]

src/controllers/krate/follow.rs

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,11 @@ use crate::db::DieselPooledConn;
77
use crate::models::{Crate, Follow};
88
use crate::schema::*;
99

10-
fn follow_target(req: &dyn RequestExt, conn: &DieselPooledConn<'_>) -> AppResult<Follow> {
11-
let user_id = req.authenticate(conn)?.user_id();
10+
fn follow_target(
11+
req: &dyn RequestExt,
12+
conn: &DieselPooledConn<'_>,
13+
user_id: i32,
14+
) -> AppResult<Follow> {
1215
let crate_name = &req.params()["crate_id"];
1316
let crate_id = Crate::by_name(crate_name)
1417
.select(crates::id)
@@ -18,8 +21,9 @@ fn follow_target(req: &dyn RequestExt, conn: &DieselPooledConn<'_>) -> AppResult
1821

1922
/// Handles the `PUT /crates/:crate_id/follow` route.
2023
pub fn follow(req: &mut dyn RequestExt) -> EndpointResult {
24+
let user_id = req.authenticate()?.user_id();
2125
let conn = req.db_conn()?;
22-
let follow = follow_target(req, &conn)?;
26+
let follow = follow_target(req, &conn, user_id)?;
2327
diesel::insert_into(follows::table)
2428
.values(&follow)
2529
.on_conflict_do_nothing()
@@ -30,8 +34,9 @@ pub fn follow(req: &mut dyn RequestExt) -> EndpointResult {
3034

3135
/// Handles the `DELETE /crates/:crate_id/follow` route.
3236
pub fn unfollow(req: &mut dyn RequestExt) -> EndpointResult {
37+
let user_id = req.authenticate()?.user_id();
3338
let conn = req.db_conn()?;
34-
let follow = follow_target(req, &conn)?;
39+
let follow = follow_target(req, &conn, user_id)?;
3540
diesel::delete(&follow).execute(&*conn)?;
3641

3742
ok_true()
@@ -41,8 +46,9 @@ pub fn unfollow(req: &mut dyn RequestExt) -> EndpointResult {
4146
pub fn following(req: &mut dyn RequestExt) -> EndpointResult {
4247
use diesel::dsl::exists;
4348

49+
let user_id = req.authenticate()?.user_id();
4450
let conn = req.db_conn()?;
45-
let follow = follow_target(req, &conn)?;
51+
let follow = follow_target(req, &conn, user_id)?;
4652
let following = diesel::select(exists(follows::table.find(follow.id()))).get_result(&*conn)?;
4753

4854
#[derive(Serialize)]

src/controllers/krate/owners.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,12 +88,13 @@ fn parse_owners_request(req: &mut dyn RequestExt) -> AppResult<Vec<String>> {
8888
}
8989

9090
fn modify_owners(req: &mut dyn RequestExt, add: bool) -> EndpointResult {
91+
let authenticated_user = req.authenticate()?;
9192
let logins = parse_owners_request(req)?;
9293
let app = req.app();
9394
let crate_name = &req.params()["crate_id"];
9495

9596
let conn = req.db_conn()?;
96-
let user = req.authenticate(&conn)?.find_user(&conn)?;
97+
let user = authenticated_user.find_user(&conn)?;
9798

9899
conn.transaction(|| {
99100
let krate = Crate::by_name(crate_name).first::<Crate>(&*conn)?;

src/controllers/krate/publish.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ pub fn publish(req: &mut dyn RequestExt) -> EndpointResult {
4545
req.log_metadata("crate_version", new_crate.vers.to_string());
4646

4747
let conn = app.primary_database.get()?;
48-
let ids = req.authenticate(&conn)?;
48+
let ids = req.authenticate()?;
4949
let user = ids.find_user(&conn)?;
5050

5151
let verified_email_address = user.verified_email(&conn)?;

src/controllers/krate/search.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ use diesel_full_text_search::*;
55

66
use crate::controllers::cargo_prelude::*;
77
use crate::controllers::helpers::Paginate;
8+
use crate::controllers::util::AuthenticatedUser;
89
use crate::models::{Crate, CrateBadge, CrateOwner, CrateVersions, OwnerKind, Version};
910
use crate::schema::*;
1011
use crate::util::errors::{bad_request, ChainError};
@@ -36,6 +37,9 @@ use crate::models::krate::{canon_crate_name, ALL_COLUMNS};
3637
pub fn search(req: &mut dyn RequestExt) -> EndpointResult {
3738
use diesel::sql_types::{Bool, Text};
3839

40+
// Don't require that authentication succeed, because it's only necessary
41+
// if the "following" param is set.
42+
let authenticated_user: AppResult<AuthenticatedUser> = req.authenticate();
3943
let conn = req.db_read_only()?;
4044
let params = req.query();
4145
let sort = params.get("sort").map(|s| &**s);
@@ -154,7 +158,7 @@ pub fn search(req: &mut dyn RequestExt) -> EndpointResult {
154158
),
155159
);
156160
} else if params.get("following").is_some() {
157-
let user_id = req.authenticate(&conn)?.user_id();
161+
let user_id = authenticated_user?.user_id();
158162
query = query.filter(
159163
crates::id.eq_any(
160164
follows::table

src/controllers/token.rs

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,9 @@ use serde_json as json;
99

1010
/// Handles the `GET /me/tokens` route.
1111
pub fn list(req: &mut dyn RequestExt) -> EndpointResult {
12+
let authenticated_user = req.authenticate()?;
1213
let conn = req.db_conn()?;
13-
let user = req.authenticate(&conn)?.find_user(&conn)?;
14+
let user = authenticated_user.find_user(&conn)?;
1415

1516
let tokens = ApiToken::belonging_to(&user)
1617
.filter(api_tokens::revoked.eq(false))
@@ -60,16 +61,16 @@ pub fn new(req: &mut dyn RequestExt) -> EndpointResult {
6061
return Err(bad_request("name must have a value"));
6162
}
6263

63-
let conn = req.db_conn()?;
64-
let ids = req.authenticate(&conn)?;
65-
let user = ids.find_user(&conn)?;
66-
67-
if ids.api_token_id().is_some() {
64+
let authenticated_user = req.authenticate()?;
65+
if authenticated_user.api_token_id().is_some() {
6866
return Err(bad_request(
6967
"cannot use an API token to create a new API token",
7068
));
7169
}
7270

71+
let conn = req.db_conn()?;
72+
let user = authenticated_user.find_user(&conn)?;
73+
7374
let max_token_per_user = 500;
7475
let count = ApiToken::belonging_to(&user)
7576
.count()
@@ -98,8 +99,9 @@ pub fn revoke(req: &mut dyn RequestExt) -> EndpointResult {
9899
.parse::<i32>()
99100
.map_err(|e| bad_request(&format!("invalid token id: {:?}", e)))?;
100101

102+
let authenticated_user = req.authenticate()?;
101103
let conn = req.db_conn()?;
102-
let user = req.authenticate(&conn)?.find_user(&conn)?;
104+
let user = authenticated_user.find_user(&conn)?;
103105
diesel::update(ApiToken::belonging_to(&user).find(id))
104106
.set(api_tokens::revoked.eq(true))
105107
.execute(&*conn)?;

src/controllers/user/me.rs

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@ use crate::views::{EncodableMe, EncodableVersion, OwnedCrate};
1313

1414
/// Handles the `GET /me` route.
1515
pub fn me(req: &mut dyn RequestExt) -> EndpointResult {
16+
let user_id = req.authenticate()?.user_id();
1617
let conn = req.db_conn()?;
17-
let user_id = req.authenticate(&conn)?.user_id();
1818

1919
let (user, verified, email, verification_sent) = users::table
2020
.find(user_id)
@@ -53,8 +53,9 @@ pub fn me(req: &mut dyn RequestExt) -> EndpointResult {
5353
pub fn updates(req: &mut dyn RequestExt) -> EndpointResult {
5454
use diesel::dsl::any;
5555

56+
let authenticated_user = req.authenticate()?;
5657
let conn = req.db_conn()?;
57-
let user = req.authenticate(&conn)?.find_user(&conn)?;
58+
let user = authenticated_user.find_user(&conn)?;
5859

5960
let followed_crates = Follow::belonging_to(&user).select(follows::crate_id);
6061
let data = versions::table
@@ -104,12 +105,14 @@ pub fn update_user(req: &mut dyn RequestExt) -> EndpointResult {
104105
use self::emails::user_id;
105106
use diesel::insert_into;
106107

107-
let mut body = String::new();
108+
let authenticated_user = req.authenticate()?;
108109

110+
let mut body = String::new();
109111
req.body().read_to_string(&mut body)?;
112+
110113
let param_user_id = &req.params()["user_id"];
111114
let conn = req.db_conn()?;
112-
let user = req.authenticate(&conn)?.find_user(&conn)?;
115+
let user = authenticated_user.find_user(&conn)?;
113116

114117
// need to check if current user matches user to be updated
115118
if &user.id.to_string() != param_user_id {
@@ -187,8 +190,9 @@ pub fn regenerate_token_and_send(req: &mut dyn RequestExt) -> EndpointResult {
187190
let param_user_id = req.params()["user_id"]
188191
.parse::<i32>()
189192
.chain_error(|| bad_request("invalid user_id"))?;
193+
let authenticated_user = req.authenticate()?;
190194
let conn = req.db_conn()?;
191-
let user = req.authenticate(&conn)?.find_user(&conn)?;
195+
let user = authenticated_user.find_user(&conn)?;
192196

193197
// need to check if current user matches user to be updated
194198
if user.id != param_user_id {
@@ -227,8 +231,8 @@ pub fn update_email_notifications(req: &mut dyn RequestExt) -> EndpointResult {
227231
.map(|c| (c.id, c.email_notifications))
228232
.collect();
229233

234+
let user_id = req.authenticate()?.user_id();
230235
let conn = req.db_conn()?;
231-
let user_id = req.authenticate(&conn)?.user_id();
232236

233237
// Build inserts from existing crates belonging to the current user
234238
let to_insert = CrateOwner::by_owner_kind(OwnerKind::User)

src/controllers/util.rs

Lines changed: 30 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use super::prelude::*;
22

33
use crate::middleware::current_user::TrustedUserId;
4+
use crate::middleware::log_request;
45
use crate::models::{ApiToken, User};
56
use crate::util::errors::{
67
forbidden, internal, AppError, AppResult, ChainError, InsecurelyGeneratedTokenRevoked,
@@ -29,28 +30,40 @@ impl AuthenticatedUser {
2930

3031
impl<'a> UserAuthenticationExt for dyn RequestExt + 'a {
3132
/// Obtain `AuthenticatedUser` for the request or return an `Forbidden` error
32-
fn authenticate(&self, conn: &PgConnection) -> AppResult<AuthenticatedUser> {
33-
if let Some(id) = self.extensions().find::<TrustedUserId>() {
34-
// A trusted user_id was provided by a signed cookie (or a test `MockCookieUser`)
33+
fn authenticate(&mut self) -> AppResult<AuthenticatedUser> {
34+
if let Some(id) = self.extensions().find::<TrustedUserId>().map(|x| x.0) {
35+
log_request::add_custom_metadata(self, "uid", id);
3536
Ok(AuthenticatedUser {
36-
user_id: id.0,
37+
user_id: id,
3738
token_id: None,
3839
})
3940
} else {
4041
// Otherwise, look for an `Authorization` header on the request
41-
if let Some(headers) = self.headers().get(header::AUTHORIZATION) {
42-
ApiToken::find_by_api_token(conn, headers.to_str().unwrap_or_default())
43-
.map(|token| AuthenticatedUser {
44-
user_id: token.user_id,
45-
token_id: Some(token.id),
46-
})
47-
.map_err(|e| {
48-
if e.is::<InsecurelyGeneratedTokenRevoked>() {
49-
e
50-
} else {
51-
e.chain(internal("invalid token")).chain(forbidden())
52-
}
53-
})
42+
let maybe_authorization: Option<String> = {
43+
self.headers()
44+
.get(header::AUTHORIZATION)
45+
.and_then(|h| h.to_str().ok())
46+
.map(|h| h.to_string())
47+
};
48+
if let Some(header_value) = maybe_authorization {
49+
let user = {
50+
let conn = self.db_conn()?;
51+
ApiToken::find_by_api_token(&conn, &header_value)
52+
.map(|token| AuthenticatedUser {
53+
user_id: token.user_id,
54+
token_id: Some(token.id),
55+
})
56+
.map_err(|e| {
57+
if e.is::<InsecurelyGeneratedTokenRevoked>() {
58+
e
59+
} else {
60+
e.chain(internal("invalid token")).chain(forbidden())
61+
}
62+
})?
63+
};
64+
log_request::add_custom_metadata(self, "uid", user.user_id);
65+
log_request::add_custom_metadata(self, "tokenid", user.token_id.unwrap_or(0));
66+
Ok(user)
5467
} else {
5568
// Unable to authenticate the user
5669
Err(internal("no cookie session or auth header found")).chain_error(forbidden)

src/controllers/version/yank.rs

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,9 @@ pub fn unyank(req: &mut dyn RequestExt) -> EndpointResult {
2828

2929
/// Changes `yanked` flag on a crate version record
3030
fn modify_yank(req: &mut dyn RequestExt, yanked: bool) -> EndpointResult {
31+
let authenticated_user = req.authenticate()?;
3132
let (conn, version, krate) = version_and_crate(req)?;
32-
let ids = req.authenticate(&conn)?;
33-
let user = ids.find_user(&conn)?;
33+
let user = authenticated_user.find_user(&conn)?;
3434
let owners = krate.owners(&conn)?;
3535

3636
if user.rights(req.app(), &owners)? < Rights::Publish {
@@ -42,7 +42,13 @@ fn modify_yank(req: &mut dyn RequestExt, yanked: bool) -> EndpointResult {
4242
VersionAction::Unyank
4343
};
4444

45-
insert_version_owner_action(&conn, version.id, user.id, ids.api_token_id(), action)?;
45+
insert_version_owner_action(
46+
&conn,
47+
version.id,
48+
user.id,
49+
authenticated_user.api_token_id(),
50+
action,
51+
)?;
4652

4753
git::yank(krate.name, version, yanked).enqueue(&conn)?;
4854

0 commit comments

Comments
 (0)