Skip to content

Commit 0eb6638

Browse files
committed
Expose the user agent string to the policy execution context
1 parent 38fa52a commit 0eb6638

File tree

17 files changed

+89
-11
lines changed

17 files changed

+89
-11
lines changed

crates/handlers/src/graphql/mod.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,7 @@ async fn get_requester(
238238
activity_tracker: &BoundActivityTracker,
239239
mut repo: BoxRepository,
240240
session_info: SessionInfo,
241+
user_agent: Option<String>,
241242
token: Option<&str>,
242243
) -> Result<Requester, RouteError> {
243244
let entity = if let Some(token) = token {
@@ -301,6 +302,7 @@ async fn get_requester(
301302
let requester = Requester {
302303
entity,
303304
ip_address: activity_tracker.ip(),
305+
user_agent,
304306
};
305307

306308
repo.cancel().await?;
@@ -318,19 +320,22 @@ pub async fn post(
318320
cookie_jar: CookieJar,
319321
content_type: Option<TypedHeader<ContentType>>,
320322
authorization: Option<TypedHeader<Authorization<Bearer>>>,
323+
user_agent: Option<TypedHeader<headers::UserAgent>>,
321324
body: Body,
322325
) -> Result<impl IntoResponse, RouteError> {
323326
let body = body.into_data_stream();
324327
let token = authorization
325328
.as_ref()
326329
.map(|TypedHeader(Authorization(bearer))| bearer.token());
330+
let user_agent = user_agent.map(|TypedHeader(h)| h.to_string());
327331
let (session_info, _cookie_jar) = cookie_jar.session_info();
328332
let requester = get_requester(
329333
undocumented_oauth2_access,
330334
&clock,
331335
&activity_tracker,
332336
repo,
333337
session_info,
338+
user_agent,
334339
token,
335340
)
336341
.await?;
@@ -370,18 +375,21 @@ pub async fn get(
370375
activity_tracker: BoundActivityTracker,
371376
cookie_jar: CookieJar,
372377
authorization: Option<TypedHeader<Authorization<Bearer>>>,
378+
user_agent: Option<TypedHeader<headers::UserAgent>>,
373379
RawQuery(query): RawQuery,
374380
) -> Result<impl IntoResponse, FancyError> {
375381
let token = authorization
376382
.as_ref()
377383
.map(|TypedHeader(Authorization(bearer))| bearer.token());
384+
let user_agent = user_agent.map(|TypedHeader(h)| h.to_string());
378385
let (session_info, _cookie_jar) = cookie_jar.session_info();
379386
let requester = get_requester(
380387
undocumented_oauth2_access,
381388
&clock,
382389
&activity_tracker,
383390
repo,
384391
session_info,
392+
user_agent,
385393
token,
386394
)
387395
.await?;
@@ -422,6 +430,7 @@ pub fn schema_builder() -> SchemaBuilder {
422430
pub struct Requester {
423431
entity: RequestingEntity,
424432
ip_address: Option<IpAddr>,
433+
user_agent: Option<String>,
425434
}
426435

427436
impl Requester {
@@ -432,6 +441,13 @@ impl Requester {
432441
RequesterFingerprint::EMPTY
433442
}
434443
}
444+
445+
pub fn for_policy(&self) -> mas_policy::Requester {
446+
mas_policy::Requester {
447+
ip_address: self.ip_address,
448+
user_agent: self.user_agent.clone(),
449+
}
450+
}
435451
}
436452

437453
impl Deref for Requester {

crates/handlers/src/graphql/mutations/user_email.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -427,7 +427,7 @@ impl UserEmailMutations {
427427
let res = policy
428428
.evaluate_email(mas_policy::EmailInput {
429429
email: &input.email,
430-
requester: requester.fingerprint().into(),
430+
requester: requester.for_policy(),
431431
})
432432
.await?;
433433
if !res.valid() {
@@ -618,7 +618,7 @@ impl UserEmailMutations {
618618
let res = policy
619619
.evaluate_email(mas_policy::EmailInput {
620620
email: &input.email,
621-
requester: requester.fingerprint().into(),
621+
requester: requester.for_policy(),
622622
})
623623
.await?;
624624
if !res.valid() {

crates/handlers/src/oauth2/authorization/complete.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ use axum::{
88
extract::{Path, State},
99
response::{Html, IntoResponse, Response},
1010
};
11+
use axum_extra::TypedHeader;
1112
use hyper::StatusCode;
1213
use mas_axum_utils::{cookies::CookieJar, csrf::CsrfExt, sentry::SentryEventID, SessionInfoExt};
1314
use mas_data_model::{AuthorizationGrant, BrowserSession, Client, Device};
@@ -89,6 +90,7 @@ pub(crate) async fn get(
8990
State(key_store): State<Keystore>,
9091
policy: Policy,
9192
activity_tracker: BoundActivityTracker,
93+
user_agent: Option<TypedHeader<headers::UserAgent>>,
9294
mut repo: BoxRepository,
9395
cookie_jar: CookieJar,
9496
Path(grant_id): Path<Ulid>,
@@ -97,6 +99,8 @@ pub(crate) async fn get(
9799

98100
let maybe_session = session_info.load_session(&mut repo).await?;
99101

102+
let user_agent = user_agent.map(|TypedHeader(ua)| ua.to_string());
103+
100104
let grant = repo
101105
.oauth2_authorization_grant()
102106
.lookup(grant_id)
@@ -130,6 +134,7 @@ pub(crate) async fn get(
130134
&mut rng,
131135
&clock,
132136
&activity_tracker,
137+
user_agent,
133138
repo,
134139
key_store,
135140
policy,
@@ -199,6 +204,7 @@ pub(crate) async fn complete(
199204
rng: &mut (impl rand::RngCore + rand::CryptoRng + Send),
200205
clock: &impl Clock,
201206
activity_tracker: &BoundActivityTracker,
207+
user_agent: Option<String>,
202208
mut repo: BoxRepository,
203209
key_store: Keystore,
204210
mut policy: Policy,
@@ -233,6 +239,7 @@ pub(crate) async fn complete(
233239
grant_type: mas_policy::GrantType::AuthorizationCode,
234240
requester: mas_policy::Requester {
235241
ip_address: activity_tracker.ip(),
242+
user_agent,
236243
},
237244
})
238245
.await?;

crates/handlers/src/oauth2/authorization/mod.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ use axum::{
88
extract::{Form, State},
99
response::{Html, IntoResponse, Response},
1010
};
11+
use axum_extra::TypedHeader;
1112
use hyper::StatusCode;
1213
use mas_axum_utils::{cookies::CookieJar, csrf::CsrfExt, sentry::SentryEventID, SessionInfoExt};
1314
use mas_data_model::{AuthorizationCode, Pkce};
@@ -136,6 +137,7 @@ pub(crate) async fn get(
136137
State(key_store): State<Keystore>,
137138
State(url_builder): State<UrlBuilder>,
138139
policy: Policy,
140+
user_agent: Option<TypedHeader<headers::UserAgent>>,
139141
activity_tracker: BoundActivityTracker,
140142
mut repo: BoxRepository,
141143
cookie_jar: CookieJar,
@@ -166,6 +168,8 @@ pub(crate) async fn get(
166168
let (session_info, cookie_jar) = cookie_jar.session_info();
167169
let (csrf_token, cookie_jar) = cookie_jar.csrf_token(&clock, &mut rng);
168170

171+
let user_agent = user_agent.map(|TypedHeader(ua)| ua.to_string());
172+
169173
// One day, we will have try blocks
170174
let res: Result<Response, RouteError> = ({
171175
let templates = templates.clone();
@@ -349,6 +353,7 @@ pub(crate) async fn get(
349353
&mut rng,
350354
&clock,
351355
&activity_tracker,
356+
user_agent,
352357
repo,
353358
key_store,
354359
policy,
@@ -401,6 +406,7 @@ pub(crate) async fn get(
401406
&mut rng,
402407
&clock,
403408
&activity_tracker,
409+
user_agent,
404410
repo,
405411
key_store,
406412
policy,

crates/handlers/src/oauth2/consent.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ use axum::{
88
extract::{Form, Path, State},
99
response::{Html, IntoResponse, Response},
1010
};
11+
use axum_extra::TypedHeader;
1112
use hyper::StatusCode;
1213
use mas_axum_utils::{
1314
cookies::CookieJar,
@@ -80,13 +81,16 @@ pub(crate) async fn get(
8081
mut policy: Policy,
8182
mut repo: BoxRepository,
8283
activity_tracker: BoundActivityTracker,
84+
user_agent: Option<TypedHeader<headers::UserAgent>>,
8385
cookie_jar: CookieJar,
8486
Path(grant_id): Path<Ulid>,
8587
) -> Result<Response, RouteError> {
8688
let (session_info, cookie_jar) = cookie_jar.session_info();
8789

8890
let maybe_session = session_info.load_session(&mut repo).await?;
8991

92+
let user_agent = user_agent.map(|ua| ua.to_string());
93+
9094
let grant = repo
9195
.oauth2_authorization_grant()
9296
.lookup(grant_id)
@@ -118,6 +122,7 @@ pub(crate) async fn get(
118122
grant_type: mas_policy::GrantType::AuthorizationCode,
119123
requester: mas_policy::Requester {
120124
ip_address: activity_tracker.ip(),
125+
user_agent,
121126
},
122127
})
123128
.await?;
@@ -159,6 +164,7 @@ pub(crate) async fn post(
159164
mut policy: Policy,
160165
mut repo: BoxRepository,
161166
activity_tracker: BoundActivityTracker,
167+
user_agent: Option<TypedHeader<headers::UserAgent>>,
162168
cookie_jar: CookieJar,
163169
State(url_builder): State<UrlBuilder>,
164170
Path(grant_id): Path<Ulid>,
@@ -170,6 +176,8 @@ pub(crate) async fn post(
170176

171177
let maybe_session = session_info.load_session(&mut repo).await?;
172178

179+
let user_agent = user_agent.map(|ua| ua.to_string());
180+
173181
let grant = repo
174182
.oauth2_authorization_grant()
175183
.lookup(grant_id)
@@ -200,6 +208,7 @@ pub(crate) async fn post(
200208
grant_type: mas_policy::GrantType::AuthorizationCode,
201209
requester: mas_policy::Requester {
202210
ip_address: activity_tracker.ip(),
211+
user_agent,
203212
},
204213
})
205214
.await?;

crates/handlers/src/oauth2/device/consent.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ use axum::{
1010
response::{Html, IntoResponse, Response},
1111
Form,
1212
};
13+
use axum_extra::TypedHeader;
1314
use mas_axum_utils::{
1415
cookies::CookieJar,
1516
csrf::{CsrfExt, ProtectedForm},
@@ -46,6 +47,7 @@ pub(crate) async fn get(
4647
mut repo: BoxRepository,
4748
mut policy: Policy,
4849
activity_tracker: BoundActivityTracker,
50+
user_agent: Option<TypedHeader<headers::UserAgent>>,
4951
cookie_jar: CookieJar,
5052
Path(grant_id): Path<Ulid>,
5153
) -> Result<Response, FancyError> {
@@ -54,6 +56,8 @@ pub(crate) async fn get(
5456

5557
let maybe_session = session_info.load_session(&mut repo).await?;
5658

59+
let user_agent = user_agent.map(|ua| ua.to_string());
60+
5761
let Some(session) = maybe_session else {
5862
let login = mas_router::Login::and_continue_device_code_grant(grant_id);
5963
return Ok((cookie_jar, url_builder.redirect(&login)).into_response());
@@ -89,6 +93,7 @@ pub(crate) async fn get(
8993
user: Some(&session.user),
9094
requester: mas_policy::Requester {
9195
ip_address: activity_tracker.ip(),
96+
user_agent,
9297
},
9398
})
9499
.await?;
@@ -127,6 +132,7 @@ pub(crate) async fn post(
127132
mut repo: BoxRepository,
128133
mut policy: Policy,
129134
activity_tracker: BoundActivityTracker,
135+
user_agent: Option<TypedHeader<headers::UserAgent>>,
130136
cookie_jar: CookieJar,
131137
Path(grant_id): Path<Ulid>,
132138
Form(form): Form<ProtectedForm<ConsentForm>>,
@@ -137,6 +143,8 @@ pub(crate) async fn post(
137143

138144
let maybe_session = session_info.load_session(&mut repo).await?;
139145

146+
let user_agent = user_agent.map(|TypedHeader(ua)| ua.to_string());
147+
140148
let Some(session) = maybe_session else {
141149
let login = mas_router::Login::and_continue_device_code_grant(grant_id);
142150
return Ok((cookie_jar, url_builder.redirect(&login)).into_response());
@@ -172,6 +180,7 @@ pub(crate) async fn post(
172180
user: Some(&session.user),
173181
requester: mas_policy::Requester {
174182
ip_address: activity_tracker.ip(),
183+
user_agent,
175184
},
176185
})
177186
.await?;

crates/handlers/src/oauth2/registration.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
// Please see LICENSE in the repository root for full details.
66

77
use axum::{extract::State, response::IntoResponse, Json};
8+
use axum_extra::TypedHeader;
89
use hyper::StatusCode;
910
use mas_axum_utils::sentry::SentryEventID;
1011
use mas_iana::oauth::OAuthClientAuthenticationMethod;
@@ -196,6 +197,7 @@ pub(crate) async fn post(
196197
mut repo: BoxRepository,
197198
mut policy: Policy,
198199
activity_tracker: BoundActivityTracker,
200+
user_agent: Option<TypedHeader<headers::UserAgent>>,
199201
State(encrypter): State<Encrypter>,
200202
body: Result<Json<ClientMetadata>, axum::extract::rejection::JsonRejection>,
201203
) -> Result<impl IntoResponse, RouteError> {
@@ -204,6 +206,8 @@ pub(crate) async fn post(
204206

205207
info!(?body, "Client registration");
206208

209+
let user_agent = user_agent.map(|ua| ua.to_string());
210+
207211
// Validate the body
208212
let metadata = body.validate()?;
209213

@@ -250,6 +254,7 @@ pub(crate) async fn post(
250254
client_metadata: &metadata,
251255
requester: mas_policy::Requester {
252256
ip_address: activity_tracker.ip(),
257+
user_agent,
253258
},
254259
})
255260
.await?;

crates/handlers/src/oauth2/token.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -683,6 +683,7 @@ async fn client_credentials_grant(
683683
grant_type: mas_policy::GrantType::ClientCredentials,
684684
requester: mas_policy::Requester {
685685
ip_address: activity_tracker.ip(),
686+
user_agent: user_agent.clone().map(|ua| ua.raw),
686687
},
687688
})
688689
.await?;

crates/handlers/src/rate_limit.rs

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -53,12 +53,6 @@ pub struct RequesterFingerprint {
5353
ip: Option<IpAddr>,
5454
}
5555

56-
impl From<RequesterFingerprint> for mas_policy::Requester {
57-
fn from(val: RequesterFingerprint) -> Self {
58-
mas_policy::Requester { ip_address: val.ip }
59-
}
60-
}
61-
6256
impl std::fmt::Display for RequesterFingerprint {
6357
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
6458
if let Some(ip) = self.ip {

crates/handlers/src/upstream_oauth2/link.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -449,6 +449,7 @@ pub(crate) async fn get(
449449
email: None,
450450
requester: mas_policy::Requester {
451451
ip_address: activity_tracker.ip(),
452+
user_agent: user_agent.clone().map(|ua| ua.raw),
452453
},
453454
})
454455
.await?;
@@ -768,6 +769,7 @@ pub(crate) async fn post(
768769
email: email.as_deref(),
769770
requester: mas_policy::Requester {
770771
ip_address: activity_tracker.ip(),
772+
user_agent: user_agent.clone().map(|ua| ua.raw),
771773
},
772774
})
773775
.await?;

0 commit comments

Comments
 (0)