1
0
mirror of https://github.com/matrix-org/matrix-authentication-service.git synced 2025-07-31 09:24:31 +03:00

GraphQL API: query oauth2 sessions and clients

This commit is contained in:
Quentin Gliech
2022-11-09 10:32:03 +01:00
parent bb8160c541
commit 08421b6fbe
8 changed files with 322 additions and 5 deletions

View File

@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
use std::string::ToString;
use std::{collections::HashMap, string::ToString};
use mas_data_model::{Client, JwksOrJwksUri};
use mas_iana::{
@ -250,6 +250,54 @@ impl TryInto<Client<PostgresqlBackend>> for OAuth2ClientLookup {
}
}
#[tracing::instrument(skip_all, err)]
pub async fn lookup_clients(
executor: impl PgExecutor<'_>,
ids: impl IntoIterator<Item = Ulid> + Send,
) -> Result<HashMap<Ulid, Client<PostgresqlBackend>>, ClientFetchError> {
let ids: Vec<Uuid> = ids.into_iter().map(Uuid::from).collect();
let res = sqlx::query_as!(
OAuth2ClientLookup,
r#"
SELECT
c.oauth2_client_id,
c.encrypted_client_secret,
ARRAY(
SELECT redirect_uri
FROM oauth2_client_redirect_uris r
WHERE r.oauth2_client_id = c.oauth2_client_id
) AS "redirect_uris!",
c.grant_type_authorization_code,
c.grant_type_refresh_token,
c.client_name,
c.logo_uri,
c.client_uri,
c.policy_uri,
c.tos_uri,
c.jwks_uri,
c.jwks,
c.id_token_signed_response_alg,
c.userinfo_signed_response_alg,
c.token_endpoint_auth_method,
c.token_endpoint_auth_signing_alg,
c.initiate_login_uri
FROM oauth2_clients c
WHERE c.oauth2_client_id = ANY($1::uuid[])
"#,
&ids,
)
.fetch_all(executor)
.await?;
let clients: Result<HashMap<Ulid, Client<PostgresqlBackend>>, _> = res
.into_iter()
.map(|r| r.try_into().map(|c: Client<PostgresqlBackend>| (c.data, c)))
.collect();
clients
}
#[tracing::instrument(
skip_all,
fields(client.id = %id),

View File

@ -12,11 +12,21 @@
// See the License for the specific language governing permissions and
// limitations under the License.
use mas_data_model::Session;
use sqlx::PgExecutor;
use std::collections::{BTreeSet, HashMap};
use anyhow::Context;
use mas_data_model::{BrowserSession, Session, User};
use sqlx::{postgres::PgArguments, Arguments, PgConnection, PgExecutor};
use tracing::{info_span, Instrument};
use ulid::Ulid;
use uuid::Uuid;
use crate::{Clock, PostgresqlBackend};
use self::client::lookup_clients;
use crate::{
pagination::{generate_pagination, process_page},
user::lookup_active_session,
Clock, PostgresqlBackend,
};
pub mod access_token;
pub mod authorization_grant;
@ -56,3 +66,113 @@ pub async fn end_oauth_session(
Ok(())
}
#[derive(sqlx::FromRow)]
struct OAuthSessionLookup {
oauth2_session_id: Uuid,
user_session_id: Uuid,
oauth2_client_id: Uuid,
scope: String,
}
#[tracing::instrument(
skip_all,
fields(
user.id = %user.data,
user.username = user.username,
),
err(Display),
)]
pub async fn get_paginated_user_oauth_sessions(
conn: &mut PgConnection,
user: &User<PostgresqlBackend>,
before: Option<Ulid>,
after: Option<Ulid>,
first: Option<usize>,
last: Option<usize>,
) -> Result<(bool, bool, Vec<Session<PostgresqlBackend>>), anyhow::Error> {
let mut query = String::from(
r#"
SELECT
os.oauth2_session_id,
os.user_session_id,
os.oauth2_client_id,
os.scope,
os.created_at,
os.finished_at
FROM oauth2_sessions os
LEFT JOIN user_sessions us
USING (user_session_id)
"#,
);
let mut arguments = PgArguments::default();
query += " WHERE us.user_id = ";
arguments.add(Uuid::from(user.data));
arguments.format_placeholder(&mut query)?;
generate_pagination(
&mut query,
"oauth2_session_id",
&mut arguments,
before,
after,
first,
last,
)?;
let page: Vec<OAuthSessionLookup> = sqlx::query_as_with(&query, arguments)
.fetch_all(&mut *conn)
.instrument(info_span!(
"Fetch paginated user oauth sessions",
query = query
))
.await?;
let (has_previous_page, has_next_page, page) = process_page(page, first, last)?;
let client_ids: BTreeSet<Ulid> = page
.iter()
.map(|i| Ulid::from(i.oauth2_client_id))
.collect();
let browser_session_ids: BTreeSet<Ulid> =
page.iter().map(|i| Ulid::from(i.user_session_id)).collect();
let clients = lookup_clients(&mut *conn, client_ids).await?;
// TODO: this can generate N queries instead of batching. This is less than
// ideal
let mut browser_sessions: HashMap<Ulid, BrowserSession<PostgresqlBackend>> = HashMap::new();
for id in browser_session_ids {
let v = lookup_active_session(&mut *conn, id).await?;
browser_sessions.insert(id, v);
}
let page: Result<Vec<_>, _> = page
.into_iter()
.map(|item| {
let client = clients
.get(&Ulid::from(item.oauth2_client_id))
.context("client was not fetched")?
.clone();
let browser_session = browser_sessions
.get(&Ulid::from(item.user_session_id))
.context("browser session was not fetched")?
.clone();
let scope = item.scope.parse()?;
anyhow::Ok(Session {
data: Ulid::from(item.oauth2_session_id),
client,
browser_session,
scope,
})
})
.collect();
Ok((has_previous_page, has_next_page, page?))
}