You've already forked authentication-service
mirror of
https://github.com/matrix-org/matrix-authentication-service.git
synced 2025-11-20 12:02:22 +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:
@@ -49,10 +49,10 @@ pub enum Requester {
|
||||
Anonymous,
|
||||
|
||||
/// The requester is a browser session, stored in a cookie.
|
||||
BrowserSession(BrowserSession),
|
||||
BrowserSession(Box<BrowserSession>),
|
||||
|
||||
/// The requester is a OAuth2 session, with an access token.
|
||||
OAuth2Session(Session, Option<User>),
|
||||
OAuth2Session(Box<(Session, Option<User>)>),
|
||||
}
|
||||
|
||||
trait OwnerId {
|
||||
@@ -108,21 +108,21 @@ impl Requester {
|
||||
fn browser_session(&self) -> Option<&BrowserSession> {
|
||||
match self {
|
||||
Self::BrowserSession(session) => Some(session),
|
||||
Self::OAuth2Session(_, _) | Self::Anonymous => None,
|
||||
Self::OAuth2Session(_) | Self::Anonymous => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn user(&self) -> Option<&User> {
|
||||
match self {
|
||||
Self::BrowserSession(session) => Some(&session.user),
|
||||
Self::OAuth2Session(_session, user) => user.as_ref(),
|
||||
Self::OAuth2Session(tuple) => tuple.1.as_ref(),
|
||||
Self::Anonymous => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn oauth2_session(&self) -> Option<&Session> {
|
||||
match self {
|
||||
Self::OAuth2Session(session, _) => Some(session),
|
||||
Self::OAuth2Session(tuple) => Some(&tuple.0),
|
||||
Self::BrowserSession(_) | Self::Anonymous => None,
|
||||
}
|
||||
}
|
||||
@@ -148,10 +148,10 @@ impl Requester {
|
||||
|
||||
fn is_admin(&self) -> bool {
|
||||
match self {
|
||||
Self::OAuth2Session(session, _user) => {
|
||||
Self::OAuth2Session(tuple) => {
|
||||
// TODO: is this the right scope?
|
||||
// This has to be in sync with the policy
|
||||
session.scope.contains("urn:mas:admin")
|
||||
tuple.0.scope.contains("urn:mas:admin")
|
||||
}
|
||||
Self::BrowserSession(_) | Self::Anonymous => false,
|
||||
}
|
||||
@@ -160,7 +160,7 @@ impl Requester {
|
||||
|
||||
impl From<BrowserSession> for Requester {
|
||||
fn from(session: BrowserSession) -> Self {
|
||||
Self::BrowserSession(session)
|
||||
Self::BrowserSession(Box::new(session))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ use mas_storage::{
|
||||
|
||||
use super::{
|
||||
AppSession, CompatSession, Cursor, NodeCursor, NodeType, OAuth2Session, PreloadedTotalCount,
|
||||
SessionState, User,
|
||||
SessionState, User, UserAgent,
|
||||
};
|
||||
use crate::state::ContextExt;
|
||||
|
||||
@@ -87,9 +87,9 @@ impl BrowserSession {
|
||||
}
|
||||
}
|
||||
|
||||
/// The user-agent string with which the session was created.
|
||||
pub async fn user_agent(&self) -> Option<&str> {
|
||||
self.0.user_agent.as_deref()
|
||||
/// The user-agent with which the session was created.
|
||||
pub async fn user_agent(&self) -> Option<UserAgent> {
|
||||
self.0.user_agent.clone().map(UserAgent::from)
|
||||
}
|
||||
|
||||
/// The last IP address used by the session.
|
||||
|
||||
@@ -18,7 +18,7 @@ use chrono::{DateTime, Utc};
|
||||
use mas_storage::{compat::CompatSessionRepository, user::UserRepository};
|
||||
use url::Url;
|
||||
|
||||
use super::{BrowserSession, NodeType, SessionState, User};
|
||||
use super::{BrowserSession, NodeType, SessionState, User, UserAgent};
|
||||
use crate::state::ContextExt;
|
||||
|
||||
/// Lazy-loaded reverse reference.
|
||||
@@ -103,6 +103,11 @@ impl CompatSession {
|
||||
self.session.finished_at()
|
||||
}
|
||||
|
||||
/// The user-agent with which the session was created.
|
||||
pub async fn user_agent(&self) -> Option<UserAgent> {
|
||||
self.session.user_agent.clone().map(UserAgent::from)
|
||||
}
|
||||
|
||||
/// The associated SSO login, if any.
|
||||
pub async fn sso_login(
|
||||
&self,
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use async_graphql::{Enum, Interface, Object};
|
||||
use async_graphql::{Enum, Interface, Object, SimpleObject};
|
||||
use chrono::{DateTime, Utc};
|
||||
|
||||
mod browser_sessions;
|
||||
@@ -73,3 +73,69 @@ pub enum SessionState {
|
||||
/// The session is no longer active.
|
||||
Finished,
|
||||
}
|
||||
|
||||
/// The type of a user agent
|
||||
#[derive(Enum, Copy, Clone, Eq, PartialEq)]
|
||||
pub enum DeviceType {
|
||||
/// A personal computer, laptop or desktop
|
||||
Pc,
|
||||
|
||||
/// A mobile phone. Can also sometimes be a tablet.
|
||||
Mobile,
|
||||
|
||||
/// A tablet
|
||||
Tablet,
|
||||
|
||||
/// Unknown device type
|
||||
Unknown,
|
||||
}
|
||||
|
||||
impl From<mas_data_model::DeviceType> for DeviceType {
|
||||
fn from(device_type: mas_data_model::DeviceType) -> Self {
|
||||
match device_type {
|
||||
mas_data_model::DeviceType::Pc => Self::Pc,
|
||||
mas_data_model::DeviceType::Mobile => Self::Mobile,
|
||||
mas_data_model::DeviceType::Tablet => Self::Tablet,
|
||||
mas_data_model::DeviceType::Unknown => Self::Unknown,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A parsed user agent string
|
||||
#[derive(SimpleObject)]
|
||||
pub struct UserAgent {
|
||||
/// The user agent string
|
||||
pub raw: String,
|
||||
|
||||
/// The name of the browser
|
||||
pub name: Option<String>,
|
||||
|
||||
/// The version of the browser
|
||||
pub version: Option<String>,
|
||||
|
||||
/// The operating system name
|
||||
pub os: Option<String>,
|
||||
|
||||
/// The operating system version
|
||||
pub os_version: Option<String>,
|
||||
|
||||
/// The device model
|
||||
pub model: Option<String>,
|
||||
|
||||
/// The device type
|
||||
pub device_type: DeviceType,
|
||||
}
|
||||
|
||||
impl From<mas_data_model::UserAgent> for UserAgent {
|
||||
fn from(ua: mas_data_model::UserAgent) -> Self {
|
||||
Self {
|
||||
raw: ua.raw,
|
||||
name: ua.name,
|
||||
version: ua.version,
|
||||
os: ua.os,
|
||||
os_version: ua.os_version,
|
||||
model: ua.model,
|
||||
device_type: ua.device_type.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ use oauth2_types::{oidc::ApplicationType, scope::Scope};
|
||||
use ulid::Ulid;
|
||||
use url::Url;
|
||||
|
||||
use super::{BrowserSession, NodeType, SessionState, User};
|
||||
use super::{BrowserSession, NodeType, SessionState, User, UserAgent};
|
||||
use crate::{state::ContextExt, UserId};
|
||||
|
||||
/// An OAuth 2.0 session represents a client session which used the OAuth APIs
|
||||
@@ -67,6 +67,11 @@ impl OAuth2Session {
|
||||
}
|
||||
}
|
||||
|
||||
/// The user-agent with which the session was created.
|
||||
pub async fn user_agent(&self) -> Option<UserAgent> {
|
||||
self.0.user_agent.clone().map(UserAgent::from)
|
||||
}
|
||||
|
||||
/// The state of the session.
|
||||
pub async fn state(&self) -> SessionState {
|
||||
match &self.0.state {
|
||||
|
||||
@@ -40,7 +40,7 @@ pub struct EndCompatSessionInput {
|
||||
/// The payload of the `endCompatSession` mutation.
|
||||
pub enum EndCompatSessionPayload {
|
||||
NotFound,
|
||||
Ended(mas_data_model::CompatSession),
|
||||
Ended(Box<mas_data_model::CompatSession>),
|
||||
}
|
||||
|
||||
/// The status of the `endCompatSession` mutation.
|
||||
@@ -66,7 +66,7 @@ impl EndCompatSessionPayload {
|
||||
/// Returns the ended session.
|
||||
async fn compat_session(&self) -> Option<CompatSession> {
|
||||
match self {
|
||||
Self::Ended(session) => Some(CompatSession::new(session.clone())),
|
||||
Self::Ended(session) => Some(CompatSession::new(*session.clone())),
|
||||
Self::NotFound => None,
|
||||
}
|
||||
}
|
||||
@@ -110,6 +110,6 @@ impl CompatSessionMutations {
|
||||
|
||||
repo.save().await?;
|
||||
|
||||
Ok(EndCompatSessionPayload::Ended(session))
|
||||
Ok(EndCompatSessionPayload::Ended(Box::new(session)))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,8 +31,11 @@ impl ViewerQuery {
|
||||
|
||||
match requester {
|
||||
Requester::BrowserSession(session) => Viewer::user(session.user.clone()),
|
||||
Requester::OAuth2Session(_session, Some(user)) => Viewer::user(user.clone()),
|
||||
Requester::OAuth2Session(_, None) | Requester::Anonymous => Viewer::anonymous(),
|
||||
Requester::OAuth2Session(tuple) => match &tuple.1 {
|
||||
Some(user) => Viewer::user(user.clone()),
|
||||
None => Viewer::anonymous(),
|
||||
},
|
||||
Requester::Anonymous => Viewer::anonymous(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,10 +44,8 @@ impl ViewerQuery {
|
||||
let requester = ctx.requester();
|
||||
|
||||
match requester {
|
||||
Requester::BrowserSession(session) => ViewerSession::browser_session(session.clone()),
|
||||
Requester::OAuth2Session(session, _user) => {
|
||||
ViewerSession::oauth2_session(session.clone())
|
||||
}
|
||||
Requester::BrowserSession(session) => ViewerSession::browser_session(*session.clone()),
|
||||
Requester::OAuth2Session(tuple) => ViewerSession::oauth2_session(tuple.0.clone()),
|
||||
Requester::Anonymous => ViewerSession::anonymous(),
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user