1
0
mirror of https://github.com/matrix-org/matrix-authentication-service.git synced 2025-07-29 22:01:14 +03:00

Make the GraphQL interface accessible for OAuth clients

This commit is contained in:
Quentin Gliech
2023-08-10 15:05:44 +02:00
parent 9c7f6c2d4e
commit 4ef3bcf336
8 changed files with 203 additions and 20 deletions

View File

@ -26,8 +26,10 @@
clippy::unused_async
)]
use anyhow::Context;
use async_graphql::EmptySubscription;
use mas_data_model::{BrowserSession, User};
use mas_data_model::{BrowserSession, Session, User};
use ulid::Ulid;
mod model;
mod mutations;
@ -60,18 +62,51 @@ pub enum Requester {
/// The requester is a browser session, stored in a cookie.
BrowserSession(BrowserSession),
/// The requester is a OAuth2 session, with an access token.
OAuth2Session(Session, User),
}
impl Requester {
fn browser_session(&self) -> Option<&BrowserSession> {
match self {
Self::BrowserSession(session) => Some(session),
Self::Anonymous => None,
Self::OAuth2Session(_, _) | Self::Anonymous => None,
}
}
fn user(&self) -> Option<&User> {
self.browser_session().map(|session| &session.user)
match self {
Self::BrowserSession(session) => Some(&session.user),
Self::OAuth2Session(_session, user) => Some(user),
Self::Anonymous => None,
}
}
fn ensure_owner_or_admin(&self, user_id: Ulid) -> Result<(), async_graphql::Error> {
// If the requester is an admin, they can do anything.
if self.is_admin() {
return Ok(());
}
// Else check that they are the owner.
let user = self.user().context("Unauthorized")?;
if user.id == user_id {
Ok(())
} else {
Err(async_graphql::Error::new("Unauthorized"))
}
}
fn is_admin(&self) -> bool {
match self {
Self::OAuth2Session(session, _user) => {
// TODO: is this the right scope?
// This has to be in sync with the policy
session.scope.contains("urn:mas:admin")
}
Self::BrowserSession(_) | Self::Anonymous => false,
}
}
}

View File

@ -14,7 +14,7 @@
use async_graphql::Union;
use crate::model::{BrowserSession, User};
use crate::model::{BrowserSession, OAuth2Session, User};
mod anonymous;
pub use self::anonymous::Anonymous;
@ -40,6 +40,7 @@ impl Viewer {
#[derive(Union)]
pub enum ViewerSession {
BrowserSession(BrowserSession),
OAuth2Session(OAuth2Session),
Anonymous(Anonymous),
}
@ -48,6 +49,10 @@ impl ViewerSession {
Self::BrowserSession(BrowserSession(session))
}
pub fn oauth2_session(session: mas_data_model::Session) -> Self {
Self::OAuth2Session(OAuth2Session(session))
}
pub fn anonymous() -> Self {
Self::Anonymous(Anonymous)
}

View File

@ -88,6 +88,13 @@ impl BaseQuery {
if current_user.id == id {
Ok(Some(User(current_user.clone())))
} else if requester.is_admin() {
// An admin can fetch any user, not just themselves
let state = ctx.state();
let mut repo = state.repository().await?;
let user = repo.user().lookup(id).await?;
repo.cancel().await?;
Ok(user.map(User))
} else {
Ok(None)
}
@ -113,7 +120,7 @@ impl BaseQuery {
repo.cancel().await?;
let ret = browser_session.and_then(|browser_session| {
if browser_session.user.id == current_user.id {
if browser_session.user.id == current_user.id || requester.is_admin() {
Some(BrowserSession(browser_session))
} else {
None
@ -142,7 +149,7 @@ impl BaseQuery {
.user_email()
.lookup(id)
.await?
.filter(|e| e.user_id == current_user.id);
.filter(|e| e.user_id == current_user.id || requester.is_admin());
repo.cancel().await?;

View File

@ -49,7 +49,8 @@ impl UpstreamOAuthQuery {
let link = repo.upstream_oauth_link().lookup(id).await?;
// Ensure that the link belongs to the current user
let link = link.filter(|link| link.user_id == Some(current_user.id));
let link =
link.filter(|link| link.user_id == Some(current_user.id) || requester.is_admin());
Ok(link.map(UpstreamOAuth2Link::new))
}

View File

@ -31,6 +31,7 @@ impl ViewerQuery {
match requester {
Requester::BrowserSession(session) => Viewer::user(session.user.clone()),
Requester::OAuth2Session(_session, user) => Viewer::user(user.clone()),
Requester::Anonymous => Viewer::anonymous(),
}
}
@ -41,6 +42,9 @@ impl ViewerQuery {
match requester {
Requester::BrowserSession(session) => ViewerSession::browser_session(session.clone()),
Requester::OAuth2Session(session, _user) => {
ViewerSession::oauth2_session(session.clone())
}
Requester::Anonymous => ViewerSession::anonymous(),
}
}