You've already forked authentication-service
mirror of
https://github.com/matrix-org/matrix-authentication-service.git
synced 2025-08-09 04:22:45 +03:00
Merge the mas_graphql
crate into the mas_handlers
crate (#2783)
This commit is contained in:
26
Cargo.lock
generated
26
Cargo.lock
generated
@@ -3018,7 +3018,6 @@ dependencies = [
|
|||||||
"mas-config",
|
"mas-config",
|
||||||
"mas-data-model",
|
"mas-data-model",
|
||||||
"mas-email",
|
"mas-email",
|
||||||
"mas-graphql",
|
|
||||||
"mas-handlers",
|
"mas-handlers",
|
||||||
"mas-http",
|
"mas-http",
|
||||||
"mas-i18n",
|
"mas-i18n",
|
||||||
@@ -3125,29 +3124,6 @@ dependencies = [
|
|||||||
"tracing",
|
"tracing",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "mas-graphql"
|
|
||||||
version = "0.9.0"
|
|
||||||
dependencies = [
|
|
||||||
"anyhow",
|
|
||||||
"async-graphql",
|
|
||||||
"async-trait",
|
|
||||||
"chrono",
|
|
||||||
"lettre",
|
|
||||||
"mas-data-model",
|
|
||||||
"mas-matrix",
|
|
||||||
"mas-policy",
|
|
||||||
"mas-storage",
|
|
||||||
"oauth2-types",
|
|
||||||
"serde",
|
|
||||||
"thiserror",
|
|
||||||
"tokio",
|
|
||||||
"tower",
|
|
||||||
"tracing",
|
|
||||||
"ulid",
|
|
||||||
"url",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mas-handlers"
|
name = "mas-handlers"
|
||||||
version = "0.9.0"
|
version = "0.9.0"
|
||||||
@@ -3155,6 +3131,7 @@ dependencies = [
|
|||||||
"anyhow",
|
"anyhow",
|
||||||
"argon2",
|
"argon2",
|
||||||
"async-graphql",
|
"async-graphql",
|
||||||
|
"async-trait",
|
||||||
"axum",
|
"axum",
|
||||||
"axum-extra",
|
"axum-extra",
|
||||||
"axum-macros",
|
"axum-macros",
|
||||||
@@ -3170,7 +3147,6 @@ dependencies = [
|
|||||||
"lettre",
|
"lettre",
|
||||||
"mas-axum-utils",
|
"mas-axum-utils",
|
||||||
"mas-data-model",
|
"mas-data-model",
|
||||||
"mas-graphql",
|
|
||||||
"mas-http",
|
"mas-http",
|
||||||
"mas-i18n",
|
"mas-i18n",
|
||||||
"mas-iana",
|
"mas-iana",
|
||||||
|
@@ -57,7 +57,6 @@ sentry-tower = { version = "0.31.8", features = ["http"] }
|
|||||||
mas-config.workspace = true
|
mas-config.workspace = true
|
||||||
mas-data-model.workspace = true
|
mas-data-model.workspace = true
|
||||||
mas-email.workspace = true
|
mas-email.workspace = true
|
||||||
mas-graphql.workspace = true
|
|
||||||
mas-handlers = { workspace = true }
|
mas-handlers = { workspace = true }
|
||||||
mas-http = { workspace = true, features = ["client"] }
|
mas-http = { workspace = true, features = ["client"] }
|
||||||
mas-i18n.workspace = true
|
mas-i18n.workspace = true
|
||||||
|
@@ -22,7 +22,7 @@ use ipnetwork::IpNetwork;
|
|||||||
use mas_data_model::SiteConfig;
|
use mas_data_model::SiteConfig;
|
||||||
use mas_handlers::{
|
use mas_handlers::{
|
||||||
passwords::PasswordManager, ActivityTracker, BoundActivityTracker, CookieManager, ErrorWrapper,
|
passwords::PasswordManager, ActivityTracker, BoundActivityTracker, CookieManager, ErrorWrapper,
|
||||||
HttpClientFactory, MetadataCache,
|
GraphQLSchema, HttpClientFactory, MetadataCache,
|
||||||
};
|
};
|
||||||
use mas_i18n::Translator;
|
use mas_i18n::Translator;
|
||||||
use mas_keystore::{Encrypter, Keystore};
|
use mas_keystore::{Encrypter, Keystore};
|
||||||
@@ -50,7 +50,7 @@ pub struct AppState {
|
|||||||
pub url_builder: UrlBuilder,
|
pub url_builder: UrlBuilder,
|
||||||
pub homeserver_connection: SynapseConnection,
|
pub homeserver_connection: SynapseConnection,
|
||||||
pub policy_factory: Arc<PolicyFactory>,
|
pub policy_factory: Arc<PolicyFactory>,
|
||||||
pub graphql_schema: mas_graphql::Schema,
|
pub graphql_schema: GraphQLSchema,
|
||||||
pub http_client_factory: HttpClientFactory,
|
pub http_client_factory: HttpClientFactory,
|
||||||
pub password_manager: PasswordManager,
|
pub password_manager: PasswordManager,
|
||||||
pub metadata_cache: MetadataCache,
|
pub metadata_cache: MetadataCache,
|
||||||
@@ -144,7 +144,7 @@ impl FromRef<AppState> for PgPool {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FromRef<AppState> for mas_graphql::Schema {
|
impl FromRef<AppState> for GraphQLSchema {
|
||||||
fn from_ref(input: &AppState) -> Self {
|
fn from_ref(input: &AppState) -> Self {
|
||||||
input.graphql_schema.clone()
|
input.graphql_schema.clone()
|
||||||
}
|
}
|
||||||
|
@@ -1,36 +0,0 @@
|
|||||||
[package]
|
|
||||||
name = "mas-graphql"
|
|
||||||
version.workspace = true
|
|
||||||
authors.workspace = true
|
|
||||||
edition.workspace = true
|
|
||||||
license.workspace = true
|
|
||||||
homepage.workspace = true
|
|
||||||
repository.workspace = true
|
|
||||||
publish = false
|
|
||||||
|
|
||||||
[lints]
|
|
||||||
workspace = true
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
anyhow.workspace = true
|
|
||||||
async-graphql.workspace = true
|
|
||||||
async-trait.workspace = true
|
|
||||||
chrono.workspace = true
|
|
||||||
lettre.workspace = true
|
|
||||||
serde.workspace = true
|
|
||||||
thiserror.workspace = true
|
|
||||||
tokio.workspace = true
|
|
||||||
tracing.workspace = true
|
|
||||||
tower.workspace = true
|
|
||||||
ulid.workspace = true
|
|
||||||
url.workspace = true
|
|
||||||
|
|
||||||
oauth2-types.workspace = true
|
|
||||||
mas-data-model.workspace = true
|
|
||||||
mas-matrix.workspace = true
|
|
||||||
mas-policy.workspace = true
|
|
||||||
mas-storage.workspace = true
|
|
||||||
|
|
||||||
[[bin]]
|
|
||||||
name = "schema"
|
|
||||||
doc = false
|
|
@@ -1,174 +0,0 @@
|
|||||||
// Copyright 2022-2023 The Matrix.org Foundation C.I.C.
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
|
|
||||||
#![deny(clippy::future_not_send)]
|
|
||||||
#![allow(clippy::module_name_repetitions, clippy::unused_async)]
|
|
||||||
|
|
||||||
use async_graphql::EmptySubscription;
|
|
||||||
use mas_data_model::{BrowserSession, Session, User};
|
|
||||||
use ulid::Ulid;
|
|
||||||
|
|
||||||
mod model;
|
|
||||||
mod mutations;
|
|
||||||
mod query;
|
|
||||||
mod state;
|
|
||||||
|
|
||||||
pub use self::{
|
|
||||||
model::{CreationEvent, Node},
|
|
||||||
mutations::Mutation,
|
|
||||||
query::Query,
|
|
||||||
state::{BoxState, State},
|
|
||||||
};
|
|
||||||
|
|
||||||
pub type Schema = async_graphql::Schema<Query, Mutation, EmptySubscription>;
|
|
||||||
pub type SchemaBuilder = async_graphql::SchemaBuilder<Query, Mutation, EmptySubscription>;
|
|
||||||
|
|
||||||
#[must_use]
|
|
||||||
pub fn schema_builder() -> SchemaBuilder {
|
|
||||||
async_graphql::Schema::build(Query::new(), Mutation::new(), EmptySubscription)
|
|
||||||
.register_output_type::<Node>()
|
|
||||||
.register_output_type::<CreationEvent>()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The identity of the requester.
|
|
||||||
#[derive(Debug, Clone, Default, PartialEq, Eq)]
|
|
||||||
pub enum Requester {
|
|
||||||
/// The requester presented no authentication information.
|
|
||||||
#[default]
|
|
||||||
Anonymous,
|
|
||||||
|
|
||||||
/// The requester is a browser session, stored in a cookie.
|
|
||||||
BrowserSession(Box<BrowserSession>),
|
|
||||||
|
|
||||||
/// The requester is a `OAuth2` session, with an access token.
|
|
||||||
OAuth2Session(Box<(Session, Option<User>)>),
|
|
||||||
}
|
|
||||||
|
|
||||||
trait OwnerId {
|
|
||||||
fn owner_id(&self) -> Option<Ulid>;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl OwnerId for User {
|
|
||||||
fn owner_id(&self) -> Option<Ulid> {
|
|
||||||
Some(self.id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl OwnerId for BrowserSession {
|
|
||||||
fn owner_id(&self) -> Option<Ulid> {
|
|
||||||
Some(self.user.id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl OwnerId for mas_data_model::UserEmail {
|
|
||||||
fn owner_id(&self) -> Option<Ulid> {
|
|
||||||
Some(self.user_id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl OwnerId for Session {
|
|
||||||
fn owner_id(&self) -> Option<Ulid> {
|
|
||||||
self.user_id
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl OwnerId for mas_data_model::CompatSession {
|
|
||||||
fn owner_id(&self) -> Option<Ulid> {
|
|
||||||
Some(self.user_id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl OwnerId for mas_data_model::UpstreamOAuthLink {
|
|
||||||
fn owner_id(&self) -> Option<Ulid> {
|
|
||||||
self.user_id
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A dumb wrapper around a `Ulid` to implement `OwnerId` for it.
|
|
||||||
pub struct UserId(Ulid);
|
|
||||||
|
|
||||||
impl OwnerId for UserId {
|
|
||||||
fn owner_id(&self) -> Option<Ulid> {
|
|
||||||
Some(self.0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Requester {
|
|
||||||
fn browser_session(&self) -> Option<&BrowserSession> {
|
|
||||||
match self {
|
|
||||||
Self::BrowserSession(session) => Some(session),
|
|
||||||
Self::OAuth2Session(_) | Self::Anonymous => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn user(&self) -> Option<&User> {
|
|
||||||
match self {
|
|
||||||
Self::BrowserSession(session) => Some(&session.user),
|
|
||||||
Self::OAuth2Session(tuple) => tuple.1.as_ref(),
|
|
||||||
Self::Anonymous => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn oauth2_session(&self) -> Option<&Session> {
|
|
||||||
match self {
|
|
||||||
Self::OAuth2Session(tuple) => Some(&tuple.0),
|
|
||||||
Self::BrowserSession(_) | Self::Anonymous => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns true if the requester can access the resource.
|
|
||||||
fn is_owner_or_admin(&self, resource: &impl OwnerId) -> bool {
|
|
||||||
// If the requester is an admin, they can do anything.
|
|
||||||
if self.is_admin() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Otherwise, they must be the owner of the resource.
|
|
||||||
let Some(owner_id) = resource.owner_id() else {
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
|
|
||||||
let Some(user) = self.user() else {
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
|
|
||||||
user.id == owner_id
|
|
||||||
}
|
|
||||||
|
|
||||||
fn is_admin(&self) -> bool {
|
|
||||||
match self {
|
|
||||||
Self::OAuth2Session(tuple) => {
|
|
||||||
// TODO: is this the right scope?
|
|
||||||
// This has to be in sync with the policy
|
|
||||||
tuple.0.scope.contains("urn:mas:admin")
|
|
||||||
}
|
|
||||||
Self::BrowserSession(_) | Self::Anonymous => false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<BrowserSession> for Requester {
|
|
||||||
fn from(session: BrowserSession) -> Self {
|
|
||||||
Self::BrowserSession(Box::new(session))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> From<Option<T>> for Requester
|
|
||||||
where
|
|
||||||
T: Into<Requester>,
|
|
||||||
{
|
|
||||||
fn from(session: Option<T>) -> Self {
|
|
||||||
session.map(Into::into).unwrap_or_default()
|
|
||||||
}
|
|
||||||
}
|
|
@@ -15,6 +15,7 @@ workspace = true
|
|||||||
# Async runtime
|
# Async runtime
|
||||||
tokio.workspace = true
|
tokio.workspace = true
|
||||||
futures-util = "0.3.30"
|
futures-util = "0.3.30"
|
||||||
|
async-trait.workspace = true
|
||||||
|
|
||||||
# Logging and tracing
|
# Logging and tracing
|
||||||
tracing.workspace = true
|
tracing.workspace = true
|
||||||
@@ -70,7 +71,6 @@ ulid.workspace = true
|
|||||||
|
|
||||||
mas-axum-utils.workspace = true
|
mas-axum-utils.workspace = true
|
||||||
mas-data-model.workspace = true
|
mas-data-model.workspace = true
|
||||||
mas-graphql.workspace = true
|
|
||||||
mas-http.workspace = true
|
mas-http.workspace = true
|
||||||
mas-i18n.workspace = true
|
mas-i18n.workspace = true
|
||||||
mas-iana.workspace = true
|
mas-iana.workspace = true
|
||||||
|
@@ -22,6 +22,6 @@
|
|||||||
#![warn(clippy::pedantic)]
|
#![warn(clippy::pedantic)]
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let schema = mas_graphql::schema_builder().finish();
|
let schema = mas_handlers::graphql_schema_builder().finish();
|
||||||
println!("{}", schema.sdl());
|
println!("{}", schema.sdl());
|
||||||
}
|
}
|
@@ -12,15 +12,18 @@
|
|||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
|
#![allow(clippy::module_name_repetitions)]
|
||||||
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use async_graphql::{
|
use async_graphql::{
|
||||||
extensions::Tracing,
|
extensions::Tracing,
|
||||||
http::{playground_source, GraphQLPlaygroundConfig, MultipartOptions},
|
http::{playground_source, GraphQLPlaygroundConfig, MultipartOptions},
|
||||||
|
EmptySubscription,
|
||||||
};
|
};
|
||||||
use axum::{
|
use axum::{
|
||||||
async_trait,
|
async_trait,
|
||||||
extract::{BodyStream, RawQuery, State},
|
extract::{BodyStream, RawQuery, State as AxumState},
|
||||||
http::StatusCode,
|
http::StatusCode,
|
||||||
response::{Html, IntoResponse, Response},
|
response::{Html, IntoResponse, Response},
|
||||||
Json, TypedHeader,
|
Json, TypedHeader,
|
||||||
@@ -31,8 +34,7 @@ use hyper::header::CACHE_CONTROL;
|
|||||||
use mas_axum_utils::{
|
use mas_axum_utils::{
|
||||||
cookies::CookieJar, sentry::SentryEventID, FancyError, SessionInfo, SessionInfoExt,
|
cookies::CookieJar, sentry::SentryEventID, FancyError, SessionInfo, SessionInfoExt,
|
||||||
};
|
};
|
||||||
use mas_data_model::{SiteConfig, User};
|
use mas_data_model::{BrowserSession, Session, SiteConfig, User};
|
||||||
use mas_graphql::{Requester, Schema};
|
|
||||||
use mas_matrix::HomeserverConnection;
|
use mas_matrix::HomeserverConnection;
|
||||||
use mas_policy::{InstantiateError, Policy, PolicyFactory};
|
use mas_policy::{InstantiateError, Policy, PolicyFactory};
|
||||||
use mas_storage::{
|
use mas_storage::{
|
||||||
@@ -44,7 +46,19 @@ use rand::{thread_rng, SeedableRng};
|
|||||||
use rand_chacha::ChaChaRng;
|
use rand_chacha::ChaChaRng;
|
||||||
use sqlx::PgPool;
|
use sqlx::PgPool;
|
||||||
use tracing::{info_span, Instrument};
|
use tracing::{info_span, Instrument};
|
||||||
|
use ulid::Ulid;
|
||||||
|
|
||||||
|
mod model;
|
||||||
|
mod mutations;
|
||||||
|
mod query;
|
||||||
|
mod state;
|
||||||
|
|
||||||
|
pub use self::state::{BoxState, State};
|
||||||
|
use self::{
|
||||||
|
model::{CreationEvent, Node},
|
||||||
|
mutations::Mutation,
|
||||||
|
query::Query,
|
||||||
|
};
|
||||||
use crate::{impl_from_error_for_route, BoundActivityTracker};
|
use crate::{impl_from_error_for_route, BoundActivityTracker};
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
@@ -58,7 +72,7 @@ struct GraphQLState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl mas_graphql::State for GraphQLState {
|
impl state::State for GraphQLState {
|
||||||
async fn repository(&self) -> Result<BoxRepository, RepositoryError> {
|
async fn repository(&self) -> Result<BoxRepository, RepositoryError> {
|
||||||
let repo = PgRepository::from_pool(&self.pool)
|
let repo = PgRepository::from_pool(&self.pool)
|
||||||
.await
|
.await
|
||||||
@@ -106,12 +120,9 @@ pub fn schema(
|
|||||||
homeserver_connection: Arc::new(homeserver_connection),
|
homeserver_connection: Arc::new(homeserver_connection),
|
||||||
site_config,
|
site_config,
|
||||||
};
|
};
|
||||||
let state: mas_graphql::BoxState = Box::new(state);
|
let state: BoxState = Box::new(state);
|
||||||
|
|
||||||
mas_graphql::schema_builder()
|
schema_builder().extension(Tracing).data(state).finish()
|
||||||
.extension(Tracing)
|
|
||||||
.data(state)
|
|
||||||
.finish()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn span_for_graphql_request(request: &async_graphql::Request) -> tracing::Span {
|
fn span_for_graphql_request(request: &async_graphql::Request) -> tracing::Span {
|
||||||
@@ -261,7 +272,7 @@ async fn get_requester(
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async fn post(
|
pub async fn post(
|
||||||
State(schema): State<Schema>,
|
AxumState(schema): AxumState<Schema>,
|
||||||
clock: BoxClock,
|
clock: BoxClock,
|
||||||
repo: BoxRepository,
|
repo: BoxRepository,
|
||||||
activity_tracker: BoundActivityTracker,
|
activity_tracker: BoundActivityTracker,
|
||||||
@@ -302,7 +313,7 @@ pub async fn post(
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get(
|
pub async fn get(
|
||||||
State(schema): State<Schema>,
|
AxumState(schema): AxumState<Schema>,
|
||||||
clock: BoxClock,
|
clock: BoxClock,
|
||||||
repo: BoxRepository,
|
repo: BoxRepository,
|
||||||
activity_tracker: BoundActivityTracker,
|
activity_tracker: BoundActivityTracker,
|
||||||
@@ -338,3 +349,145 @@ pub async fn playground() -> impl IntoResponse {
|
|||||||
GraphQLPlaygroundConfig::new("/graphql").with_setting("request.credentials", "include"),
|
GraphQLPlaygroundConfig::new("/graphql").with_setting("request.credentials", "include"),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub type Schema = async_graphql::Schema<Query, Mutation, EmptySubscription>;
|
||||||
|
pub type SchemaBuilder = async_graphql::SchemaBuilder<Query, Mutation, EmptySubscription>;
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn schema_builder() -> SchemaBuilder {
|
||||||
|
async_graphql::Schema::build(Query::new(), Mutation::new(), EmptySubscription)
|
||||||
|
.register_output_type::<Node>()
|
||||||
|
.register_output_type::<CreationEvent>()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The identity of the requester.
|
||||||
|
#[derive(Debug, Clone, Default, PartialEq, Eq)]
|
||||||
|
pub enum Requester {
|
||||||
|
/// The requester presented no authentication information.
|
||||||
|
#[default]
|
||||||
|
Anonymous,
|
||||||
|
|
||||||
|
/// The requester is a browser session, stored in a cookie.
|
||||||
|
BrowserSession(Box<BrowserSession>),
|
||||||
|
|
||||||
|
/// The requester is a `OAuth2` session, with an access token.
|
||||||
|
OAuth2Session(Box<(Session, Option<User>)>),
|
||||||
|
}
|
||||||
|
|
||||||
|
trait OwnerId {
|
||||||
|
fn owner_id(&self) -> Option<Ulid>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl OwnerId for User {
|
||||||
|
fn owner_id(&self) -> Option<Ulid> {
|
||||||
|
Some(self.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl OwnerId for BrowserSession {
|
||||||
|
fn owner_id(&self) -> Option<Ulid> {
|
||||||
|
Some(self.user.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl OwnerId for mas_data_model::UserEmail {
|
||||||
|
fn owner_id(&self) -> Option<Ulid> {
|
||||||
|
Some(self.user_id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl OwnerId for Session {
|
||||||
|
fn owner_id(&self) -> Option<Ulid> {
|
||||||
|
self.user_id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl OwnerId for mas_data_model::CompatSession {
|
||||||
|
fn owner_id(&self) -> Option<Ulid> {
|
||||||
|
Some(self.user_id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl OwnerId for mas_data_model::UpstreamOAuthLink {
|
||||||
|
fn owner_id(&self) -> Option<Ulid> {
|
||||||
|
self.user_id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A dumb wrapper around a `Ulid` to implement `OwnerId` for it.
|
||||||
|
pub struct UserId(Ulid);
|
||||||
|
|
||||||
|
impl OwnerId for UserId {
|
||||||
|
fn owner_id(&self) -> Option<Ulid> {
|
||||||
|
Some(self.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Requester {
|
||||||
|
fn browser_session(&self) -> Option<&BrowserSession> {
|
||||||
|
match self {
|
||||||
|
Self::BrowserSession(session) => Some(session),
|
||||||
|
Self::OAuth2Session(_) | Self::Anonymous => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn user(&self) -> Option<&User> {
|
||||||
|
match self {
|
||||||
|
Self::BrowserSession(session) => Some(&session.user),
|
||||||
|
Self::OAuth2Session(tuple) => tuple.1.as_ref(),
|
||||||
|
Self::Anonymous => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn oauth2_session(&self) -> Option<&Session> {
|
||||||
|
match self {
|
||||||
|
Self::OAuth2Session(tuple) => Some(&tuple.0),
|
||||||
|
Self::BrowserSession(_) | Self::Anonymous => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns true if the requester can access the resource.
|
||||||
|
fn is_owner_or_admin(&self, resource: &impl OwnerId) -> bool {
|
||||||
|
// If the requester is an admin, they can do anything.
|
||||||
|
if self.is_admin() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise, they must be the owner of the resource.
|
||||||
|
let Some(owner_id) = resource.owner_id() else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
let Some(user) = self.user() else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
user.id == owner_id
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_admin(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
Self::OAuth2Session(tuple) => {
|
||||||
|
// TODO: is this the right scope?
|
||||||
|
// This has to be in sync with the policy
|
||||||
|
tuple.0.scope.contains("urn:mas:admin")
|
||||||
|
}
|
||||||
|
Self::BrowserSession(_) | Self::Anonymous => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<BrowserSession> for Requester {
|
||||||
|
fn from(session: BrowserSession) -> Self {
|
||||||
|
Self::BrowserSession(Box::new(session))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> From<Option<T>> for Requester
|
||||||
|
where
|
||||||
|
T: Into<Requester>,
|
||||||
|
{
|
||||||
|
fn from(session: Option<T>) -> Self {
|
||||||
|
session.map(Into::into).unwrap_or_default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@@ -26,7 +26,7 @@ use super::{
|
|||||||
AppSession, CompatSession, Cursor, NodeCursor, NodeType, OAuth2Session, PreloadedTotalCount,
|
AppSession, CompatSession, Cursor, NodeCursor, NodeType, OAuth2Session, PreloadedTotalCount,
|
||||||
SessionState, User, UserAgent,
|
SessionState, User, UserAgent,
|
||||||
};
|
};
|
||||||
use crate::state::ContextExt;
|
use crate::graphql::state::ContextExt;
|
||||||
|
|
||||||
/// A browser session represents a logged in user in a browser.
|
/// A browser session represents a logged in user in a browser.
|
||||||
#[derive(Description)]
|
#[derive(Description)]
|
@@ -19,7 +19,7 @@ use mas_storage::{compat::CompatSessionRepository, user::UserRepository};
|
|||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
use super::{BrowserSession, NodeType, SessionState, User, UserAgent};
|
use super::{BrowserSession, NodeType, SessionState, User, UserAgent};
|
||||||
use crate::state::ContextExt;
|
use crate::graphql::state::ContextExt;
|
||||||
|
|
||||||
/// Lazy-loaded reverse reference.
|
/// Lazy-loaded reverse reference.
|
||||||
///
|
///
|
@@ -21,7 +21,7 @@ use ulid::Ulid;
|
|||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
use super::{BrowserSession, NodeType, SessionState, User, UserAgent};
|
use super::{BrowserSession, NodeType, SessionState, User, UserAgent};
|
||||||
use crate::{state::ContextExt, UserId};
|
use crate::graphql::{state::ContextExt, UserId};
|
||||||
|
|
||||||
/// An OAuth 2.0 session represents a client session which used the OAuth APIs
|
/// An OAuth 2.0 session represents a client session which used the OAuth APIs
|
||||||
/// to login.
|
/// to login.
|
@@ -18,7 +18,7 @@ use chrono::{DateTime, Utc};
|
|||||||
use mas_storage::{upstream_oauth2::UpstreamOAuthProviderRepository, user::UserRepository};
|
use mas_storage::{upstream_oauth2::UpstreamOAuthProviderRepository, user::UserRepository};
|
||||||
|
|
||||||
use super::{NodeType, User};
|
use super::{NodeType, User};
|
||||||
use crate::state::ContextExt;
|
use crate::graphql::state::ContextExt;
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct UpstreamOAuth2Provider {
|
pub struct UpstreamOAuth2Provider {
|
@@ -34,7 +34,7 @@ use super::{
|
|||||||
BrowserSession, CompatSession, Cursor, NodeCursor, NodeType, OAuth2Session,
|
BrowserSession, CompatSession, Cursor, NodeCursor, NodeType, OAuth2Session,
|
||||||
PreloadedTotalCount, SessionState, UpstreamOAuth2Link,
|
PreloadedTotalCount, SessionState, UpstreamOAuth2Link,
|
||||||
};
|
};
|
||||||
use crate::state::ContextExt;
|
use crate::graphql::state::ContextExt;
|
||||||
|
|
||||||
#[derive(Description)]
|
#[derive(Description)]
|
||||||
/// A user is an individual's account.
|
/// A user is an individual's account.
|
@@ -14,7 +14,7 @@
|
|||||||
|
|
||||||
use async_graphql::Union;
|
use async_graphql::Union;
|
||||||
|
|
||||||
use crate::model::{BrowserSession, OAuth2Session, User};
|
use crate::graphql::model::{BrowserSession, OAuth2Session, User};
|
||||||
|
|
||||||
mod anonymous;
|
mod anonymous;
|
||||||
pub use self::anonymous::Anonymous;
|
pub use self::anonymous::Anonymous;
|
@@ -15,7 +15,7 @@
|
|||||||
use async_graphql::{Context, Enum, InputObject, Object, ID};
|
use async_graphql::{Context, Enum, InputObject, Object, ID};
|
||||||
use mas_storage::RepositoryAccess;
|
use mas_storage::RepositoryAccess;
|
||||||
|
|
||||||
use crate::{
|
use crate::graphql::{
|
||||||
model::{BrowserSession, NodeType},
|
model::{BrowserSession, NodeType},
|
||||||
state::ContextExt,
|
state::ContextExt,
|
||||||
};
|
};
|
@@ -20,7 +20,7 @@ use mas_storage::{
|
|||||||
RepositoryAccess,
|
RepositoryAccess,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::graphql::{
|
||||||
model::{CompatSession, NodeType},
|
model::{CompatSession, NodeType},
|
||||||
state::ContextExt,
|
state::ContextExt,
|
||||||
};
|
};
|
@@ -15,7 +15,7 @@
|
|||||||
use anyhow::Context as _;
|
use anyhow::Context as _;
|
||||||
use async_graphql::{Context, Description, Enum, InputObject, Object, ID};
|
use async_graphql::{Context, Description, Enum, InputObject, Object, ID};
|
||||||
|
|
||||||
use crate::{
|
use crate::graphql::{
|
||||||
model::{NodeType, User},
|
model::{NodeType, User},
|
||||||
state::ContextExt,
|
state::ContextExt,
|
||||||
UserId,
|
UserId,
|
@@ -27,7 +27,7 @@ use mas_storage::{
|
|||||||
};
|
};
|
||||||
use oauth2_types::scope::Scope;
|
use oauth2_types::scope::Scope;
|
||||||
|
|
||||||
use crate::{
|
use crate::graphql::{
|
||||||
model::{NodeType, OAuth2Session},
|
model::{NodeType, OAuth2Session},
|
||||||
state::ContextExt,
|
state::ContextExt,
|
||||||
};
|
};
|
@@ -20,7 +20,7 @@ use mas_storage::{
|
|||||||
};
|
};
|
||||||
use tracing::{info, warn};
|
use tracing::{info, warn};
|
||||||
|
|
||||||
use crate::{
|
use crate::graphql::{
|
||||||
model::{NodeType, User},
|
model::{NodeType, User},
|
||||||
state::ContextExt,
|
state::ContextExt,
|
||||||
UserId,
|
UserId,
|
@@ -20,7 +20,7 @@ use mas_storage::{
|
|||||||
RepositoryAccess,
|
RepositoryAccess,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::graphql::{
|
||||||
model::{NodeType, User, UserEmail},
|
model::{NodeType, User, UserEmail},
|
||||||
state::ContextExt,
|
state::ContextExt,
|
||||||
UserId,
|
UserId,
|
@@ -15,7 +15,7 @@
|
|||||||
use async_graphql::{Context, MergedObject, Object, ID};
|
use async_graphql::{Context, MergedObject, Object, ID};
|
||||||
use mas_storage::user::UserRepository;
|
use mas_storage::user::UserRepository;
|
||||||
|
|
||||||
use crate::{
|
use crate::graphql::{
|
||||||
model::{
|
model::{
|
||||||
Anonymous, BrowserSession, CompatSession, Node, NodeType, OAuth2Client, OAuth2Session,
|
Anonymous, BrowserSession, CompatSession, Node, NodeType, OAuth2Client, OAuth2Session,
|
||||||
SiteConfig, User, UserEmail,
|
SiteConfig, User, UserEmail,
|
||||||
@@ -234,7 +234,7 @@ impl BaseQuery {
|
|||||||
return Ok(Some(Node::Anonymous(Box::new(Anonymous))));
|
return Ok(Some(Node::Anonymous(Box::new(Anonymous))));
|
||||||
}
|
}
|
||||||
|
|
||||||
if id.as_str() == crate::model::SITE_CONFIG_ID {
|
if id.as_str() == crate::graphql::model::SITE_CONFIG_ID {
|
||||||
return Ok(Some(Node::SiteConfig(Box::new(SiteConfig::new(
|
return Ok(Some(Node::SiteConfig(Box::new(SiteConfig::new(
|
||||||
ctx.state().site_config(),
|
ctx.state().site_config(),
|
||||||
)))));
|
)))));
|
@@ -21,7 +21,7 @@ use mas_storage::{
|
|||||||
};
|
};
|
||||||
use oauth2_types::scope::Scope;
|
use oauth2_types::scope::Scope;
|
||||||
|
|
||||||
use crate::{
|
use crate::graphql::{
|
||||||
model::{CompatSession, NodeType, OAuth2Session},
|
model::{CompatSession, NodeType, OAuth2Session},
|
||||||
state::ContextExt,
|
state::ContextExt,
|
||||||
UserId,
|
UserId,
|
@@ -18,7 +18,7 @@ use async_graphql::{
|
|||||||
};
|
};
|
||||||
use mas_storage::{upstream_oauth2::UpstreamOAuthProviderFilter, Pagination, RepositoryAccess};
|
use mas_storage::{upstream_oauth2::UpstreamOAuthProviderFilter, Pagination, RepositoryAccess};
|
||||||
|
|
||||||
use crate::{
|
use crate::graphql::{
|
||||||
model::{
|
model::{
|
||||||
Cursor, NodeCursor, NodeType, PreloadedTotalCount, UpstreamOAuth2Link,
|
Cursor, NodeCursor, NodeType, PreloadedTotalCount, UpstreamOAuth2Link,
|
||||||
UpstreamOAuth2Provider,
|
UpstreamOAuth2Provider,
|
@@ -14,7 +14,7 @@
|
|||||||
|
|
||||||
use async_graphql::{Context, Object};
|
use async_graphql::{Context, Object};
|
||||||
|
|
||||||
use crate::{
|
use crate::graphql::{
|
||||||
model::{Viewer, ViewerSession},
|
model::{Viewer, ViewerSession},
|
||||||
state::ContextExt,
|
state::ContextExt,
|
||||||
Requester,
|
Requester,
|
@@ -17,7 +17,7 @@ use mas_matrix::HomeserverConnection;
|
|||||||
use mas_policy::Policy;
|
use mas_policy::Policy;
|
||||||
use mas_storage::{BoxClock, BoxRepository, BoxRng, RepositoryError};
|
use mas_storage::{BoxClock, BoxRepository, BoxRng, RepositoryError};
|
||||||
|
|
||||||
use crate::Requester;
|
use crate::graphql::Requester;
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
pub trait State {
|
pub trait State {
|
@@ -90,7 +90,9 @@ pub use mas_axum_utils::{
|
|||||||
|
|
||||||
pub use self::{
|
pub use self::{
|
||||||
activity_tracker::{ActivityTracker, Bound as BoundActivityTracker},
|
activity_tracker::{ActivityTracker, Bound as BoundActivityTracker},
|
||||||
graphql::schema as graphql_schema,
|
graphql::{
|
||||||
|
schema as graphql_schema, schema_builder as graphql_schema_builder, Schema as GraphQLSchema,
|
||||||
|
},
|
||||||
preferred_language::PreferredLanguage,
|
preferred_language::PreferredLanguage,
|
||||||
upstream_oauth2::cache::MetadataCache,
|
upstream_oauth2::cache::MetadataCache,
|
||||||
};
|
};
|
||||||
@@ -110,7 +112,7 @@ where
|
|||||||
<B as HttpBody>::Data: Into<Bytes>,
|
<B as HttpBody>::Data: Into<Bytes>,
|
||||||
<B as HttpBody>::Error: std::error::Error + Send + Sync,
|
<B as HttpBody>::Error: std::error::Error + Send + Sync,
|
||||||
S: Clone + Send + Sync + 'static,
|
S: Clone + Send + Sync + 'static,
|
||||||
mas_graphql::Schema: FromRef<S>,
|
graphql::Schema: FromRef<S>,
|
||||||
BoundActivityTracker: FromRequestParts<S>,
|
BoundActivityTracker: FromRequestParts<S>,
|
||||||
BoxRepository: FromRequestParts<S>,
|
BoxRepository: FromRequestParts<S>,
|
||||||
BoxClock: FromRequestParts<S>,
|
BoxClock: FromRequestParts<S>,
|
||||||
|
@@ -54,6 +54,7 @@ use tower::{Layer, Service, ServiceExt};
|
|||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
graphql,
|
||||||
passwords::{Hasher, PasswordManager},
|
passwords::{Hasher, PasswordManager},
|
||||||
upstream_oauth2::cache::MetadataCache,
|
upstream_oauth2::cache::MetadataCache,
|
||||||
ActivityTracker, BoundActivityTracker,
|
ActivityTracker, BoundActivityTracker,
|
||||||
@@ -102,7 +103,7 @@ pub(crate) struct TestState {
|
|||||||
pub url_builder: UrlBuilder,
|
pub url_builder: UrlBuilder,
|
||||||
pub homeserver_connection: Arc<MockHomeserverConnection>,
|
pub homeserver_connection: Arc<MockHomeserverConnection>,
|
||||||
pub policy_factory: Arc<PolicyFactory>,
|
pub policy_factory: Arc<PolicyFactory>,
|
||||||
pub graphql_schema: mas_graphql::Schema,
|
pub graphql_schema: graphql::Schema,
|
||||||
pub http_client_factory: HttpClientFactory,
|
pub http_client_factory: HttpClientFactory,
|
||||||
pub password_manager: PasswordManager,
|
pub password_manager: PasswordManager,
|
||||||
pub site_config: SiteConfig,
|
pub site_config: SiteConfig,
|
||||||
@@ -198,9 +199,9 @@ impl TestState {
|
|||||||
rng: Arc::clone(&rng),
|
rng: Arc::clone(&rng),
|
||||||
clock: Arc::clone(&clock),
|
clock: Arc::clone(&clock),
|
||||||
};
|
};
|
||||||
let state: mas_graphql::BoxState = Box::new(graphql_state);
|
let state: crate::graphql::BoxState = Box::new(graphql_state);
|
||||||
|
|
||||||
let graphql_schema = mas_graphql::schema_builder().data(state).finish();
|
let graphql_schema = graphql::schema_builder().data(state).finish();
|
||||||
|
|
||||||
let activity_tracker =
|
let activity_tracker =
|
||||||
ActivityTracker::new(pool.clone(), std::time::Duration::from_secs(1));
|
ActivityTracker::new(pool.clone(), std::time::Duration::from_secs(1));
|
||||||
@@ -316,7 +317,7 @@ struct TestGraphQLState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl mas_graphql::State for TestGraphQLState {
|
impl graphql::State for TestGraphQLState {
|
||||||
async fn repository(&self) -> Result<BoxRepository, mas_storage::RepositoryError> {
|
async fn repository(&self) -> Result<BoxRepository, mas_storage::RepositoryError> {
|
||||||
let repo = PgRepository::from_pool(&self.pool)
|
let repo = PgRepository::from_pool(&self.pool)
|
||||||
.await
|
.await
|
||||||
@@ -356,7 +357,7 @@ impl FromRef<TestState> for PgPool {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FromRef<TestState> for mas_graphql::Schema {
|
impl FromRef<TestState> for graphql::Schema {
|
||||||
fn from_ref(input: &TestState) -> Self {
|
fn from_ref(input: &TestState) -> Self {
|
||||||
input.graphql_schema.clone()
|
input.graphql_schema.clone()
|
||||||
}
|
}
|
||||||
|
@@ -10,7 +10,7 @@ POLICIES_SCHEMA="${BASE_DIR}/policies/schema/"
|
|||||||
|
|
||||||
set -x
|
set -x
|
||||||
cargo run -p mas-config > "${CONFIG_SCHEMA}"
|
cargo run -p mas-config > "${CONFIG_SCHEMA}"
|
||||||
cargo run -p mas-graphql > "${GRAPHQL_SCHEMA}"
|
cargo run -p mas-handlers --bin graphql-schema > "${GRAPHQL_SCHEMA}"
|
||||||
cargo run -p mas-i18n-scan -- --update "${BASE_DIR}/templates/" "${BASE_DIR}/translations/en.json"
|
cargo run -p mas-i18n-scan -- --update "${BASE_DIR}/templates/" "${BASE_DIR}/translations/en.json"
|
||||||
OUT_DIR="${POLICIES_SCHEMA}" cargo run -p mas-policy --features jsonschema
|
OUT_DIR="${POLICIES_SCHEMA}" cargo run -p mas-policy --features jsonschema
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user