1
0
mirror of https://github.com/matrix-org/matrix-authentication-service.git synced 2025-08-09 04:22:45 +03:00

Parse User Agents on the backend side (#2388)

* Parse user agents on the server side

* Parse and expose user agents on the backend

* Use the parsed user agent in the device consent page

* Fix the device icon tests

* Fix clippy warnings

* Box stuff to avoid large enum variants

* Ignore a clippy warning

* Fix the requester boxing
This commit is contained in:
Quentin Gliech
2024-02-23 16:47:48 +01:00
committed by GitHub
parent f171d76dc5
commit f3cbd3b315
58 changed files with 1019 additions and 855 deletions

View File

@@ -16,7 +16,7 @@ use axum::{extract::State, response::IntoResponse, Json, TypedHeader};
use chrono::Duration;
use hyper::StatusCode;
use mas_axum_utils::sentry::SentryEventID;
use mas_data_model::{CompatSession, CompatSsoLoginState, Device, TokenType, User};
use mas_data_model::{CompatSession, CompatSsoLoginState, Device, TokenType, User, UserAgent};
use mas_storage::{
compat::{
CompatAccessTokenRepository, CompatRefreshTokenRepository, CompatSessionRepository,
@@ -220,7 +220,7 @@ pub(crate) async fn post(
user_agent: Option<TypedHeader<headers::UserAgent>>,
Json(input): Json<RequestBody>,
) -> Result<impl IntoResponse, RouteError> {
let user_agent = user_agent.map(|ua| ua.to_string());
let user_agent = user_agent.map(|ua| UserAgent::parse(ua.as_str().to_owned()));
let (mut session, user) = match (password_manager.is_enabled(), input.credentials) {
(
true,

View File

@@ -237,7 +237,7 @@ async fn get_requester(
return Err(RouteError::MissingScope);
}
Requester::OAuth2Session(session, user)
Requester::OAuth2Session(Box::new((session, user)))
} else {
let maybe_session = session_info.load_session(&mut repo).await?;

View File

@@ -14,13 +14,14 @@
use axum::{extract::State, response::IntoResponse, Json, TypedHeader};
use chrono::Duration;
use headers::{CacheControl, Pragma, UserAgent};
use headers::{CacheControl, Pragma};
use hyper::StatusCode;
use mas_axum_utils::{
client_authorization::{ClientAuthorization, CredentialsVerificationError},
http_client_factory::HttpClientFactory,
sentry::SentryEventID,
};
use mas_data_model::UserAgent;
use mas_keystore::Encrypter;
use mas_router::UrlBuilder;
use mas_storage::{oauth2::OAuth2DeviceCodeGrantParams, BoxClock, BoxRepository, BoxRng};
@@ -84,7 +85,7 @@ pub(crate) async fn post(
mut rng: BoxRng,
clock: BoxClock,
mut repo: BoxRepository,
user_agent: Option<TypedHeader<UserAgent>>,
user_agent: Option<TypedHeader<headers::UserAgent>>,
activity_tracker: BoundActivityTracker,
State(url_builder): State<UrlBuilder>,
State(http_client_factory): State<HttpClientFactory>,
@@ -125,7 +126,7 @@ pub(crate) async fn post(
let expires_in = Duration::minutes(20);
let user_agent = user_agent.map(|ua| ua.0.to_string());
let user_agent = user_agent.map(|ua| UserAgent::parse(ua.as_str().to_owned()));
let ip_address = activity_tracker.ip();
let device_code = Alphanumeric.sample_string(&mut rng, 32);

View File

@@ -21,7 +21,9 @@ use mas_axum_utils::{
http_client_factory::HttpClientFactory,
sentry::SentryEventID,
};
use mas_data_model::{AuthorizationGrantStage, Client, Device, DeviceCodeGrantState, TokenType};
use mas_data_model::{
AuthorizationGrantStage, Client, Device, DeviceCodeGrantState, TokenType, UserAgent,
};
use mas_keystore::{Encrypter, Keystore};
use mas_oidc_client::types::scope::ScopeToken;
use mas_policy::Policy;
@@ -233,7 +235,7 @@ pub(crate) async fn post(
user_agent: Option<TypedHeader<headers::UserAgent>>,
client_authorization: ClientAuthorization<AccessTokenRequest>,
) -> Result<impl IntoResponse, RouteError> {
let user_agent = user_agent.map(|ua| ua.to_string());
let user_agent = user_agent.map(|ua| UserAgent::parse(ua.as_str().to_owned()));
let client = client_authorization
.credentials
.fetch(&mut repo)
@@ -335,7 +337,7 @@ async fn authorization_code_grant(
url_builder: &UrlBuilder,
site_config: &SiteConfig,
mut repo: BoxRepository,
user_agent: Option<String>,
user_agent: Option<UserAgent>,
) -> Result<(AccessTokenResponse, BoxRepository), RouteError> {
// Check that the client is allowed to use this grant type
if !client.grant_types.contains(&GrantType::AuthorizationCode) {
@@ -504,7 +506,7 @@ async fn refresh_token_grant(
client: &Client,
site_config: &SiteConfig,
mut repo: BoxRepository,
user_agent: Option<String>,
user_agent: Option<UserAgent>,
) -> Result<(AccessTokenResponse, BoxRepository), RouteError> {
// Check that the client is allowed to use this grant type
if !client.grant_types.contains(&GrantType::RefreshToken) {
@@ -587,7 +589,7 @@ async fn client_credentials_grant(
site_config: &SiteConfig,
mut repo: BoxRepository,
mut policy: Policy,
user_agent: Option<String>,
user_agent: Option<UserAgent>,
) -> Result<(AccessTokenResponse, BoxRepository), RouteError> {
// Check that the client is allowed to use this grant type
if !client.grant_types.contains(&GrantType::ClientCredentials) {
@@ -656,7 +658,7 @@ async fn device_code_grant(
url_builder: &UrlBuilder,
site_config: &SiteConfig,
mut repo: BoxRepository,
user_agent: Option<String>,
user_agent: Option<UserAgent>,
) -> Result<(AccessTokenResponse, BoxRepository), RouteError> {
// Check that the client is allowed to use this grant type
if !client.grant_types.contains(&GrantType::DeviceCode) {

View File

@@ -24,7 +24,7 @@ use mas_axum_utils::{
sentry::SentryEventID,
FancyError, SessionInfoExt,
};
use mas_data_model::User;
use mas_data_model::{User, UserAgent};
use mas_jose::jwt::Jwt;
use mas_policy::Policy;
use mas_router::UrlBuilder;
@@ -200,7 +200,7 @@ pub(crate) async fn get(
user_agent: Option<TypedHeader<headers::UserAgent>>,
Path(link_id): Path<Ulid>,
) -> Result<impl IntoResponse, RouteError> {
let user_agent = user_agent.map(|ua| ua.as_str().to_owned());
let user_agent = user_agent.map(|ua| UserAgent::parse(ua.as_str().to_owned()));
let sessions_cookie = UpstreamSessionsCookie::load(&cookie_jar);
let (session_id, post_auth_action) = sessions_cookie
.lookup_link(link_id)
@@ -481,7 +481,7 @@ pub(crate) async fn post(
Path(link_id): Path<Ulid>,
Form(form): Form<ProtectedForm<FormData>>,
) -> Result<Response, RouteError> {
let user_agent = user_agent.map(|ua| ua.as_str().to_owned());
let user_agent = user_agent.map(|ua| UserAgent::parse(ua.as_str().to_owned()));
let form = cookie_jar.verify_form(&clock, form)?;
let sessions_cookie = UpstreamSessionsCookie::load(&cookie_jar);

View File

@@ -17,14 +17,13 @@ use axum::{
response::{Html, IntoResponse, Response},
TypedHeader,
};
use headers::UserAgent;
use hyper::StatusCode;
use mas_axum_utils::{
cookies::CookieJar,
csrf::{CsrfExt, CsrfToken, ProtectedForm},
FancyError, SessionInfoExt,
};
use mas_data_model::BrowserSession;
use mas_data_model::{BrowserSession, UserAgent};
use mas_i18n::DataLocale;
use mas_router::{UpstreamOAuth2Authorize, UrlBuilder};
use mas_storage::{
@@ -123,10 +122,10 @@ pub(crate) async fn post(
activity_tracker: BoundActivityTracker,
Query(query): Query<OptionalPostAuthAction>,
cookie_jar: CookieJar,
user_agent: Option<TypedHeader<UserAgent>>,
user_agent: Option<TypedHeader<headers::UserAgent>>,
Form(form): Form<ProtectedForm<LoginForm>>,
) -> Result<Response, FancyError> {
let user_agent = user_agent.map(|ua| ua.as_str().to_owned());
let user_agent = user_agent.map(|ua| UserAgent::parse(ua.as_str().to_owned()));
if !password_manager.is_enabled() {
// XXX: is it necessary to have better errors here?
return Ok(StatusCode::METHOD_NOT_ALLOWED.into_response());
@@ -216,7 +215,7 @@ async fn login(
clock: &impl Clock,
username: &str,
password: &str,
user_agent: Option<String>,
user_agent: Option<UserAgent>,
) -> Result<BrowserSession, FormError> {
// XXX: we're loosing the error context here
// First, lookup the user

View File

@@ -19,7 +19,6 @@ use axum::{
response::{Html, IntoResponse, Response},
TypedHeader,
};
use headers::UserAgent;
use hyper::StatusCode;
use lettre::Address;
use mas_axum_utils::{
@@ -27,6 +26,7 @@ use mas_axum_utils::{
csrf::{CsrfExt, CsrfToken, ProtectedForm},
FancyError, SessionInfoExt,
};
use mas_data_model::UserAgent;
use mas_i18n::DataLocale;
use mas_policy::Policy;
use mas_router::UrlBuilder;
@@ -116,10 +116,10 @@ pub(crate) async fn post(
activity_tracker: BoundActivityTracker,
Query(query): Query<OptionalPostAuthAction>,
cookie_jar: CookieJar,
user_agent: Option<TypedHeader<UserAgent>>,
user_agent: Option<TypedHeader<headers::UserAgent>>,
Form(form): Form<ProtectedForm<RegisterForm>>,
) -> Result<Response, FancyError> {
let user_agent = user_agent.map(|ua| ua.as_str().to_owned());
let user_agent = user_agent.map(|ua| UserAgent::parse(ua.as_str().to_owned()));
if !password_manager.is_enabled() {
return Ok(StatusCode::METHOD_NOT_ALLOWED.into_response());
}