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
"Can request admin" flag on user
This commit is contained in:
@@ -27,6 +27,7 @@ pub struct User {
|
|||||||
pub primary_user_email_id: Option<Ulid>,
|
pub primary_user_email_id: Option<Ulid>,
|
||||||
pub created_at: DateTime<Utc>,
|
pub created_at: DateTime<Utc>,
|
||||||
pub locked_at: Option<DateTime<Utc>>,
|
pub locked_at: Option<DateTime<Utc>>,
|
||||||
|
pub can_request_admin: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl User {
|
impl User {
|
||||||
@@ -47,6 +48,7 @@ impl User {
|
|||||||
primary_user_email_id: None,
|
primary_user_email_id: None,
|
||||||
created_at: now,
|
created_at: now,
|
||||||
locked_at: None,
|
locked_at: None,
|
||||||
|
can_request_admin: false,
|
||||||
}]
|
}]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -73,6 +73,11 @@ impl User {
|
|||||||
self.0.locked_at
|
self.0.locked_at
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Whether the user can request admin privileges.
|
||||||
|
pub async fn can_request_admin(&self) -> bool {
|
||||||
|
self.0.can_request_admin
|
||||||
|
}
|
||||||
|
|
||||||
/// Access to the user's Matrix account information.
|
/// Access to the user's Matrix account information.
|
||||||
async fn matrix(&self, ctx: &Context<'_>) -> Result<MatrixUser, async_graphql::Error> {
|
async fn matrix(&self, ctx: &Context<'_>) -> Result<MatrixUser, async_graphql::Error> {
|
||||||
let state = ctx.state();
|
let state = ctx.state();
|
||||||
|
@@ -126,6 +126,37 @@ impl LockUserPayload {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The input for the `setCanRequestAdmin` mutation.
|
||||||
|
#[derive(InputObject)]
|
||||||
|
struct SetCanRequestAdminInput {
|
||||||
|
/// The ID of the user to update.
|
||||||
|
user_id: ID,
|
||||||
|
|
||||||
|
/// Whether the user can request admin.
|
||||||
|
can_request_admin: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The payload for the `setCanRequestAdmin` mutation.
|
||||||
|
#[derive(Description)]
|
||||||
|
enum SetCanRequestAdminPayload {
|
||||||
|
/// The user was updated.
|
||||||
|
Updated(mas_data_model::User),
|
||||||
|
|
||||||
|
/// The user was not found.
|
||||||
|
NotFound,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Object(use_type_description)]
|
||||||
|
impl SetCanRequestAdminPayload {
|
||||||
|
/// The user that was updated.
|
||||||
|
async fn user(&self) -> Option<User> {
|
||||||
|
match self {
|
||||||
|
Self::Updated(user) => Some(User(user.clone())),
|
||||||
|
Self::NotFound => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn valid_username_character(c: char) -> bool {
|
fn valid_username_character(c: char) -> bool {
|
||||||
c.is_ascii_lowercase()
|
c.is_ascii_lowercase()
|
||||||
|| c.is_ascii_digit()
|
|| c.is_ascii_digit()
|
||||||
@@ -232,4 +263,37 @@ impl UserMutations {
|
|||||||
|
|
||||||
Ok(LockUserPayload::Locked(user))
|
Ok(LockUserPayload::Locked(user))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Set whether a user can request admin. This is only available to
|
||||||
|
/// administrators.
|
||||||
|
async fn set_can_request_admin(
|
||||||
|
&self,
|
||||||
|
ctx: &Context<'_>,
|
||||||
|
input: SetCanRequestAdminInput,
|
||||||
|
) -> Result<SetCanRequestAdminPayload, async_graphql::Error> {
|
||||||
|
let state = ctx.state();
|
||||||
|
let requester = ctx.requester();
|
||||||
|
|
||||||
|
if !requester.is_admin() {
|
||||||
|
return Err(async_graphql::Error::new("Unauthorized"));
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut repo = state.repository().await?;
|
||||||
|
|
||||||
|
let user_id = NodeType::User.extract_ulid(&input.user_id)?;
|
||||||
|
let user = repo.user().lookup(user_id).await?;
|
||||||
|
|
||||||
|
let Some(user) = user else {
|
||||||
|
return Ok(SetCanRequestAdminPayload::NotFound);
|
||||||
|
};
|
||||||
|
|
||||||
|
let user = repo
|
||||||
|
.user()
|
||||||
|
.set_can_request_admin(user, input.can_request_admin)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
repo.save().await?;
|
||||||
|
|
||||||
|
Ok(SetCanRequestAdminPayload::Updated(user))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"db_name": "PostgreSQL",
|
"db_name": "PostgreSQL",
|
||||||
"query": "\n SELECT user_id\n , username\n , primary_user_email_id\n , created_at\n , locked_at\n FROM users\n WHERE user_id = $1\n ",
|
"query": "\n SELECT user_id\n , username\n , primary_user_email_id\n , created_at\n , locked_at\n , can_request_admin\n FROM users\n WHERE user_id = $1\n ",
|
||||||
"describe": {
|
"describe": {
|
||||||
"columns": [
|
"columns": [
|
||||||
{
|
{
|
||||||
@@ -27,6 +27,11 @@
|
|||||||
"ordinal": 4,
|
"ordinal": 4,
|
||||||
"name": "locked_at",
|
"name": "locked_at",
|
||||||
"type_info": "Timestamptz"
|
"type_info": "Timestamptz"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ordinal": 5,
|
||||||
|
"name": "can_request_admin",
|
||||||
|
"type_info": "Bool"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"parameters": {
|
"parameters": {
|
||||||
@@ -39,8 +44,9 @@
|
|||||||
false,
|
false,
|
||||||
true,
|
true,
|
||||||
false,
|
false,
|
||||||
true
|
true,
|
||||||
|
false
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"hash": "e0ea7d93ab3f565828b2faab4cc5e1a6ac868c95bfaee3a6960df1cf484d53da"
|
"hash": "0d892dc8589ba54bb886972b6db00eaf7e41ff0db98fabdff5dcba0a7aa4e77d"
|
||||||
}
|
}
|
15
crates/storage-pg/.sqlx/query-1dbc50cdab36da307c569891ab7b1ab4aaf128fed6be67ca0f139d697614c63b.json
generated
Normal file
15
crates/storage-pg/.sqlx/query-1dbc50cdab36da307c569891ab7b1ab4aaf128fed6be67ca0f139d697614c63b.json
generated
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"db_name": "PostgreSQL",
|
||||||
|
"query": "\n UPDATE users\n SET can_request_admin = $2\n WHERE user_id = $1\n ",
|
||||||
|
"describe": {
|
||||||
|
"columns": [],
|
||||||
|
"parameters": {
|
||||||
|
"Left": [
|
||||||
|
"Uuid",
|
||||||
|
"Bool"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"nullable": []
|
||||||
|
},
|
||||||
|
"hash": "1dbc50cdab36da307c569891ab7b1ab4aaf128fed6be67ca0f139d697614c63b"
|
||||||
|
}
|
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"db_name": "PostgreSQL",
|
"db_name": "PostgreSQL",
|
||||||
"query": "\n SELECT user_id\n , username\n , primary_user_email_id\n , created_at\n , locked_at\n FROM users\n WHERE username = $1\n ",
|
"query": "\n SELECT user_id\n , username\n , primary_user_email_id\n , created_at\n , locked_at\n , can_request_admin\n FROM users\n WHERE username = $1\n ",
|
||||||
"describe": {
|
"describe": {
|
||||||
"columns": [
|
"columns": [
|
||||||
{
|
{
|
||||||
@@ -27,6 +27,11 @@
|
|||||||
"ordinal": 4,
|
"ordinal": 4,
|
||||||
"name": "locked_at",
|
"name": "locked_at",
|
||||||
"type_info": "Timestamptz"
|
"type_info": "Timestamptz"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ordinal": 5,
|
||||||
|
"name": "can_request_admin",
|
||||||
|
"type_info": "Bool"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"parameters": {
|
"parameters": {
|
||||||
@@ -39,8 +44,9 @@
|
|||||||
false,
|
false,
|
||||||
true,
|
true,
|
||||||
false,
|
false,
|
||||||
true
|
true,
|
||||||
|
false
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"hash": "bfa5eaeaa5b4574bb083c86711eb4599f6374c96bb4a6827d400acb22fb0fd39"
|
"hash": "423e6aa88e0b8a01a90e108107a3d3998418fa43638b6510f28b56a2d6952222"
|
||||||
}
|
}
|
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"db_name": "PostgreSQL",
|
"db_name": "PostgreSQL",
|
||||||
"query": "\n SELECT s.user_session_id\n , s.created_at AS \"user_session_created_at\"\n , s.finished_at AS \"user_session_finished_at\"\n , s.user_agent AS \"user_session_user_agent\"\n , s.last_active_at AS \"user_session_last_active_at\"\n , s.last_active_ip AS \"user_session_last_active_ip: IpAddr\"\n , u.user_id\n , u.username AS \"user_username\"\n , u.primary_user_email_id AS \"user_primary_user_email_id\"\n , u.created_at AS \"user_created_at\"\n , u.locked_at AS \"user_locked_at\"\n FROM user_sessions s\n INNER JOIN users u\n USING (user_id)\n WHERE s.user_session_id = $1\n ",
|
"query": "\n SELECT s.user_session_id\n , s.created_at AS \"user_session_created_at\"\n , s.finished_at AS \"user_session_finished_at\"\n , s.user_agent AS \"user_session_user_agent\"\n , s.last_active_at AS \"user_session_last_active_at\"\n , s.last_active_ip AS \"user_session_last_active_ip: IpAddr\"\n , u.user_id\n , u.username AS \"user_username\"\n , u.primary_user_email_id AS \"user_primary_user_email_id\"\n , u.created_at AS \"user_created_at\"\n , u.locked_at AS \"user_locked_at\"\n , u.can_request_admin AS \"user_can_request_admin\"\n FROM user_sessions s\n INNER JOIN users u\n USING (user_id)\n WHERE s.user_session_id = $1\n ",
|
||||||
"describe": {
|
"describe": {
|
||||||
"columns": [
|
"columns": [
|
||||||
{
|
{
|
||||||
@@ -57,6 +57,11 @@
|
|||||||
"ordinal": 10,
|
"ordinal": 10,
|
||||||
"name": "user_locked_at",
|
"name": "user_locked_at",
|
||||||
"type_info": "Timestamptz"
|
"type_info": "Timestamptz"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ordinal": 11,
|
||||||
|
"name": "user_can_request_admin",
|
||||||
|
"type_info": "Bool"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"parameters": {
|
"parameters": {
|
||||||
@@ -75,8 +80,9 @@
|
|||||||
false,
|
false,
|
||||||
true,
|
true,
|
||||||
false,
|
false,
|
||||||
true
|
true,
|
||||||
|
false
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"hash": "2b0d54c284dc4d946faae4190568bf597c04b40f010132dd7bf68462c47f9eac"
|
"hash": "e602a7c76386f732de686694257e03f35c18643c91a06f9c4a3fa0a5f103df58"
|
||||||
}
|
}
|
@@ -0,0 +1,17 @@
|
|||||||
|
-- Copyright 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.
|
||||||
|
|
||||||
|
-- Adds a `can_request_admin` column to the `users` table
|
||||||
|
ALTER TABLE users
|
||||||
|
ADD COLUMN can_request_admin BOOLEAN NOT NULL DEFAULT FALSE;
|
@@ -34,6 +34,7 @@ pub enum Users {
|
|||||||
PrimaryUserEmailId,
|
PrimaryUserEmailId,
|
||||||
CreatedAt,
|
CreatedAt,
|
||||||
LockedAt,
|
LockedAt,
|
||||||
|
CanRequestAdmin,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(sea_query::Iden)]
|
#[derive(sea_query::Iden)]
|
||||||
|
@@ -57,6 +57,7 @@ struct UserLookup {
|
|||||||
primary_user_email_id: Option<Uuid>,
|
primary_user_email_id: Option<Uuid>,
|
||||||
created_at: DateTime<Utc>,
|
created_at: DateTime<Utc>,
|
||||||
locked_at: Option<DateTime<Utc>>,
|
locked_at: Option<DateTime<Utc>>,
|
||||||
|
can_request_admin: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<UserLookup> for User {
|
impl From<UserLookup> for User {
|
||||||
@@ -69,6 +70,7 @@ impl From<UserLookup> for User {
|
|||||||
primary_user_email_id: value.primary_user_email_id.map(Into::into),
|
primary_user_email_id: value.primary_user_email_id.map(Into::into),
|
||||||
created_at: value.created_at,
|
created_at: value.created_at,
|
||||||
locked_at: value.locked_at,
|
locked_at: value.locked_at,
|
||||||
|
can_request_admin: value.can_request_admin,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -95,6 +97,7 @@ impl<'c> UserRepository for PgUserRepository<'c> {
|
|||||||
, primary_user_email_id
|
, primary_user_email_id
|
||||||
, created_at
|
, created_at
|
||||||
, locked_at
|
, locked_at
|
||||||
|
, can_request_admin
|
||||||
FROM users
|
FROM users
|
||||||
WHERE user_id = $1
|
WHERE user_id = $1
|
||||||
"#,
|
"#,
|
||||||
@@ -127,6 +130,7 @@ impl<'c> UserRepository for PgUserRepository<'c> {
|
|||||||
, primary_user_email_id
|
, primary_user_email_id
|
||||||
, created_at
|
, created_at
|
||||||
, locked_at
|
, locked_at
|
||||||
|
, can_request_admin
|
||||||
FROM users
|
FROM users
|
||||||
WHERE username = $1
|
WHERE username = $1
|
||||||
"#,
|
"#,
|
||||||
@@ -186,6 +190,7 @@ impl<'c> UserRepository for PgUserRepository<'c> {
|
|||||||
primary_user_email_id: None,
|
primary_user_email_id: None,
|
||||||
created_at,
|
created_at,
|
||||||
locked_at: None,
|
locked_at: None,
|
||||||
|
can_request_admin: false,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -281,4 +286,39 @@ impl<'c> UserRepository for PgUserRepository<'c> {
|
|||||||
|
|
||||||
Ok(user)
|
Ok(user)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(
|
||||||
|
name = "db.user.set_can_request_admin",
|
||||||
|
skip_all,
|
||||||
|
fields(
|
||||||
|
db.statement,
|
||||||
|
%user.id,
|
||||||
|
user.can_request_admin = can_request_admin,
|
||||||
|
),
|
||||||
|
err,
|
||||||
|
)]
|
||||||
|
async fn set_can_request_admin(
|
||||||
|
&mut self,
|
||||||
|
mut user: User,
|
||||||
|
can_request_admin: bool,
|
||||||
|
) -> Result<User, Self::Error> {
|
||||||
|
let res = sqlx::query!(
|
||||||
|
r#"
|
||||||
|
UPDATE users
|
||||||
|
SET can_request_admin = $2
|
||||||
|
WHERE user_id = $1
|
||||||
|
"#,
|
||||||
|
Uuid::from(user.id),
|
||||||
|
can_request_admin,
|
||||||
|
)
|
||||||
|
.traced()
|
||||||
|
.execute(&mut *self.conn)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
DatabaseError::ensure_affected_rows(&res, 1)?;
|
||||||
|
|
||||||
|
user.can_request_admin = can_request_admin;
|
||||||
|
|
||||||
|
Ok(user)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -63,6 +63,7 @@ struct SessionLookup {
|
|||||||
user_primary_user_email_id: Option<Uuid>,
|
user_primary_user_email_id: Option<Uuid>,
|
||||||
user_created_at: DateTime<Utc>,
|
user_created_at: DateTime<Utc>,
|
||||||
user_locked_at: Option<DateTime<Utc>>,
|
user_locked_at: Option<DateTime<Utc>>,
|
||||||
|
user_can_request_admin: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TryFrom<SessionLookup> for BrowserSession {
|
impl TryFrom<SessionLookup> for BrowserSession {
|
||||||
@@ -77,6 +78,7 @@ impl TryFrom<SessionLookup> for BrowserSession {
|
|||||||
primary_user_email_id: value.user_primary_user_email_id.map(Into::into),
|
primary_user_email_id: value.user_primary_user_email_id.map(Into::into),
|
||||||
created_at: value.user_created_at,
|
created_at: value.user_created_at,
|
||||||
locked_at: value.user_locked_at,
|
locked_at: value.user_locked_at,
|
||||||
|
can_request_admin: value.user_can_request_admin,
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(BrowserSession {
|
Ok(BrowserSession {
|
||||||
@@ -155,6 +157,7 @@ impl<'c> BrowserSessionRepository for PgBrowserSessionRepository<'c> {
|
|||||||
, u.primary_user_email_id AS "user_primary_user_email_id"
|
, u.primary_user_email_id AS "user_primary_user_email_id"
|
||||||
, u.created_at AS "user_created_at"
|
, u.created_at AS "user_created_at"
|
||||||
, u.locked_at AS "user_locked_at"
|
, u.locked_at AS "user_locked_at"
|
||||||
|
, u.can_request_admin AS "user_can_request_admin"
|
||||||
FROM user_sessions s
|
FROM user_sessions s
|
||||||
INNER JOIN users u
|
INNER JOIN users u
|
||||||
USING (user_id)
|
USING (user_id)
|
||||||
@@ -313,6 +316,10 @@ impl<'c> BrowserSessionRepository for PgBrowserSessionRepository<'c> {
|
|||||||
Expr::col((Users::Table, Users::LockedAt)),
|
Expr::col((Users::Table, Users::LockedAt)),
|
||||||
SessionLookupIden::UserLockedAt,
|
SessionLookupIden::UserLockedAt,
|
||||||
)
|
)
|
||||||
|
.expr_as(
|
||||||
|
Expr::col((Users::Table, Users::CanRequestAdmin)),
|
||||||
|
SessionLookupIden::UserCanRequestAdmin,
|
||||||
|
)
|
||||||
.from(UserSessions::Table)
|
.from(UserSessions::Table)
|
||||||
.inner_join(
|
.inner_join(
|
||||||
Users::Table,
|
Users::Table,
|
||||||
|
@@ -95,6 +95,26 @@ async fn test_user_repo(pool: PgPool) {
|
|||||||
let user = repo.user().unlock(user).await.unwrap();
|
let user = repo.user().unlock(user).await.unwrap();
|
||||||
assert!(user.is_valid());
|
assert!(user.is_valid());
|
||||||
|
|
||||||
|
// Set the can_request_admin flag
|
||||||
|
let user = repo.user().set_can_request_admin(user, true).await.unwrap();
|
||||||
|
assert!(user.can_request_admin);
|
||||||
|
|
||||||
|
// Check that the property is retrieved on lookup
|
||||||
|
let user = repo.user().lookup(user.id).await.unwrap().unwrap();
|
||||||
|
assert!(user.can_request_admin);
|
||||||
|
|
||||||
|
// Unset the can_request_admin flag
|
||||||
|
let user = repo
|
||||||
|
.user()
|
||||||
|
.set_can_request_admin(user, false)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
assert!(!user.can_request_admin);
|
||||||
|
|
||||||
|
// Check that the property is retrieved on lookup
|
||||||
|
let user = repo.user().lookup(user.id).await.unwrap().unwrap();
|
||||||
|
assert!(!user.can_request_admin);
|
||||||
|
|
||||||
repo.save().await.unwrap();
|
repo.save().await.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -123,6 +123,23 @@ pub trait UserRepository: Send + Sync {
|
|||||||
///
|
///
|
||||||
/// Returns [`Self::Error`] if the underlying repository fails
|
/// Returns [`Self::Error`] if the underlying repository fails
|
||||||
async fn unlock(&mut self, user: User) -> Result<User, Self::Error>;
|
async fn unlock(&mut self, user: User) -> Result<User, Self::Error>;
|
||||||
|
|
||||||
|
/// Set whether a [`User`] can request admin
|
||||||
|
///
|
||||||
|
/// Returns the [`User`] with the new `can_request_admin` value
|
||||||
|
///
|
||||||
|
/// # Parameters
|
||||||
|
///
|
||||||
|
/// * `user`: The [`User`] to update
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// Returns [`Self::Error`] if the underlying repository fails
|
||||||
|
async fn set_can_request_admin(
|
||||||
|
&mut self,
|
||||||
|
user: User,
|
||||||
|
can_request_admin: bool,
|
||||||
|
) -> Result<User, Self::Error>;
|
||||||
}
|
}
|
||||||
|
|
||||||
repository_impl!(UserRepository:
|
repository_impl!(UserRepository:
|
||||||
@@ -137,4 +154,9 @@ repository_impl!(UserRepository:
|
|||||||
async fn exists(&mut self, username: &str) -> Result<bool, Self::Error>;
|
async fn exists(&mut self, username: &str) -> Result<bool, Self::Error>;
|
||||||
async fn lock(&mut self, clock: &dyn Clock, user: User) -> Result<User, Self::Error>;
|
async fn lock(&mut self, clock: &dyn Clock, user: User) -> Result<User, Self::Error>;
|
||||||
async fn unlock(&mut self, user: User) -> Result<User, Self::Error>;
|
async fn unlock(&mut self, user: User) -> Result<User, Self::Error>;
|
||||||
|
async fn set_can_request_admin(
|
||||||
|
&mut self,
|
||||||
|
user: User,
|
||||||
|
can_request_admin: bool,
|
||||||
|
) -> Result<User, Self::Error>;
|
||||||
);
|
);
|
||||||
|
@@ -643,6 +643,13 @@ type Mutation {
|
|||||||
"""
|
"""
|
||||||
lockUser(input: LockUserInput!): LockUserPayload!
|
lockUser(input: LockUserInput!): LockUserPayload!
|
||||||
"""
|
"""
|
||||||
|
Set whether a user can request admin. This is only available to
|
||||||
|
administrators.
|
||||||
|
"""
|
||||||
|
setCanRequestAdmin(
|
||||||
|
input: SetCanRequestAdminInput!
|
||||||
|
): SetCanRequestAdminPayload!
|
||||||
|
"""
|
||||||
Create a new arbitrary OAuth 2.0 Session.
|
Create a new arbitrary OAuth 2.0 Session.
|
||||||
|
|
||||||
Only available for administrators.
|
Only available for administrators.
|
||||||
@@ -1018,6 +1025,30 @@ enum SessionState {
|
|||||||
FINISHED
|
FINISHED
|
||||||
}
|
}
|
||||||
|
|
||||||
|
"""
|
||||||
|
The input for the `setCanRequestAdmin` mutation.
|
||||||
|
"""
|
||||||
|
input SetCanRequestAdminInput {
|
||||||
|
"""
|
||||||
|
The ID of the user to update.
|
||||||
|
"""
|
||||||
|
userId: ID!
|
||||||
|
"""
|
||||||
|
Whether the user can request admin.
|
||||||
|
"""
|
||||||
|
canRequestAdmin: Boolean!
|
||||||
|
}
|
||||||
|
|
||||||
|
"""
|
||||||
|
The payload for the `setCanRequestAdmin` mutation.
|
||||||
|
"""
|
||||||
|
type SetCanRequestAdminPayload {
|
||||||
|
"""
|
||||||
|
The user that was updated.
|
||||||
|
"""
|
||||||
|
user: User
|
||||||
|
}
|
||||||
|
|
||||||
"""
|
"""
|
||||||
The input for the `addEmail` mutation
|
The input for the `addEmail` mutation
|
||||||
"""
|
"""
|
||||||
@@ -1233,6 +1264,10 @@ type User implements Node {
|
|||||||
"""
|
"""
|
||||||
lockedAt: DateTime
|
lockedAt: DateTime
|
||||||
"""
|
"""
|
||||||
|
Whether the user can request admin privileges.
|
||||||
|
"""
|
||||||
|
canRequestAdmin: Boolean!
|
||||||
|
"""
|
||||||
Access to the user's Matrix account information.
|
Access to the user's Matrix account information.
|
||||||
"""
|
"""
|
||||||
matrix: MatrixUser!
|
matrix: MatrixUser!
|
||||||
|
@@ -436,6 +436,11 @@ export type Mutation = {
|
|||||||
removeEmail: RemoveEmailPayload;
|
removeEmail: RemoveEmailPayload;
|
||||||
/** Send a verification code for an email address */
|
/** Send a verification code for an email address */
|
||||||
sendVerificationEmail: SendVerificationEmailPayload;
|
sendVerificationEmail: SendVerificationEmailPayload;
|
||||||
|
/**
|
||||||
|
* Set whether a user can request admin. This is only available to
|
||||||
|
* administrators.
|
||||||
|
*/
|
||||||
|
setCanRequestAdmin: SetCanRequestAdminPayload;
|
||||||
/** Set the display name of a user */
|
/** Set the display name of a user */
|
||||||
setDisplayName: SetDisplayNamePayload;
|
setDisplayName: SetDisplayNamePayload;
|
||||||
/** Set an email address as primary */
|
/** Set an email address as primary */
|
||||||
@@ -489,6 +494,11 @@ export type MutationSendVerificationEmailArgs = {
|
|||||||
input: SendVerificationEmailInput;
|
input: SendVerificationEmailInput;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/** The mutations root of the GraphQL interface. */
|
||||||
|
export type MutationSetCanRequestAdminArgs = {
|
||||||
|
input: SetCanRequestAdminInput;
|
||||||
|
};
|
||||||
|
|
||||||
/** The mutations root of the GraphQL interface. */
|
/** The mutations root of the GraphQL interface. */
|
||||||
export type MutationSetDisplayNameArgs = {
|
export type MutationSetDisplayNameArgs = {
|
||||||
input: SetDisplayNameInput;
|
input: SetDisplayNameInput;
|
||||||
@@ -762,6 +772,21 @@ export enum SessionState {
|
|||||||
Finished = "FINISHED",
|
Finished = "FINISHED",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** The input for the `setCanRequestAdmin` mutation. */
|
||||||
|
export type SetCanRequestAdminInput = {
|
||||||
|
/** Whether the user can request admin. */
|
||||||
|
canRequestAdmin: Scalars["Boolean"]["input"];
|
||||||
|
/** The ID of the user to update. */
|
||||||
|
userId: Scalars["ID"]["input"];
|
||||||
|
};
|
||||||
|
|
||||||
|
/** The payload for the `setCanRequestAdmin` mutation. */
|
||||||
|
export type SetCanRequestAdminPayload = {
|
||||||
|
__typename?: "SetCanRequestAdminPayload";
|
||||||
|
/** The user that was updated. */
|
||||||
|
user?: Maybe<User>;
|
||||||
|
};
|
||||||
|
|
||||||
/** The input for the `addEmail` mutation */
|
/** The input for the `addEmail` mutation */
|
||||||
export type SetDisplayNameInput = {
|
export type SetDisplayNameInput = {
|
||||||
/** The display name to set. If `None`, the display name will be removed. */
|
/** The display name to set. If `None`, the display name will be removed. */
|
||||||
@@ -891,6 +916,8 @@ export type User = Node & {
|
|||||||
appSessions: AppSessionConnection;
|
appSessions: AppSessionConnection;
|
||||||
/** Get the list of active browser sessions, chronologically sorted */
|
/** Get the list of active browser sessions, chronologically sorted */
|
||||||
browserSessions: BrowserSessionConnection;
|
browserSessions: BrowserSessionConnection;
|
||||||
|
/** Whether the user can request admin privileges. */
|
||||||
|
canRequestAdmin: Scalars["Boolean"]["output"];
|
||||||
/** Get the list of compatibility sessions, chronologically sorted */
|
/** Get the list of compatibility sessions, chronologically sorted */
|
||||||
compatSessions: CompatSessionConnection;
|
compatSessions: CompatSessionConnection;
|
||||||
/** Get the list of compatibility SSO logins, chronologically sorted */
|
/** Get the list of compatibility SSO logins, chronologically sorted */
|
||||||
|
@@ -1261,6 +1261,29 @@ export default {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "setCanRequestAdmin",
|
||||||
|
type: {
|
||||||
|
kind: "NON_NULL",
|
||||||
|
ofType: {
|
||||||
|
kind: "OBJECT",
|
||||||
|
name: "SetCanRequestAdminPayload",
|
||||||
|
ofType: null,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
args: [
|
||||||
|
{
|
||||||
|
name: "input",
|
||||||
|
type: {
|
||||||
|
kind: "NON_NULL",
|
||||||
|
ofType: {
|
||||||
|
kind: "SCALAR",
|
||||||
|
name: "Any",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "setDisplayName",
|
name: "setDisplayName",
|
||||||
type: {
|
type: {
|
||||||
@@ -2140,6 +2163,22 @@ export default {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
kind: "OBJECT",
|
||||||
|
name: "SetCanRequestAdminPayload",
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
name: "user",
|
||||||
|
type: {
|
||||||
|
kind: "OBJECT",
|
||||||
|
name: "User",
|
||||||
|
ofType: null,
|
||||||
|
},
|
||||||
|
args: [],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
interfaces: [],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
kind: "OBJECT",
|
kind: "OBJECT",
|
||||||
name: "SetDisplayNamePayload",
|
name: "SetDisplayNamePayload",
|
||||||
@@ -2623,6 +2662,17 @@ export default {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "canRequestAdmin",
|
||||||
|
type: {
|
||||||
|
kind: "NON_NULL",
|
||||||
|
ofType: {
|
||||||
|
kind: "SCALAR",
|
||||||
|
name: "Any",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
args: [],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "compatSessions",
|
name: "compatSessions",
|
||||||
type: {
|
type: {
|
||||||
|
@@ -11,6 +11,18 @@ allow {
|
|||||||
count(violation) == 0
|
count(violation) == 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Users can request admin scopes if either:
|
||||||
|
# 1. They are in the admin_users list
|
||||||
|
can_request_admin(user) {
|
||||||
|
some admin_user in data.admin_users
|
||||||
|
user.username == admin_user
|
||||||
|
}
|
||||||
|
|
||||||
|
# 2. They have the can_request_admin flag set to true
|
||||||
|
can_request_admin(user) {
|
||||||
|
user.can_request_admin
|
||||||
|
}
|
||||||
|
|
||||||
# Special case to make empty scope work
|
# Special case to make empty scope work
|
||||||
allowed_scope("") = true
|
allowed_scope("") = true
|
||||||
|
|
||||||
@@ -22,8 +34,7 @@ allowed_scope("email") = true
|
|||||||
allowed_scope("urn:synapse:admin:*") {
|
allowed_scope("urn:synapse:admin:*") {
|
||||||
# Synapse doesn't support user-less tokens yet, so access to the admin API can only be used with an authorization_code grant as the user is present
|
# Synapse doesn't support user-less tokens yet, so access to the admin API can only be used with an authorization_code grant as the user is present
|
||||||
input.grant_type == "authorization_code"
|
input.grant_type == "authorization_code"
|
||||||
some user in data.admin_users
|
can_request_admin(input.user)
|
||||||
input.user.username == user
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# This grants access to the /graphql API endpoint
|
# This grants access to the /graphql API endpoint
|
||||||
@@ -32,8 +43,7 @@ allowed_scope("urn:mas:graphql:*") = true
|
|||||||
# This makes it possible to query and do anything in the GraphQL API as an admin
|
# This makes it possible to query and do anything in the GraphQL API as an admin
|
||||||
allowed_scope("urn:mas:admin") {
|
allowed_scope("urn:mas:admin") {
|
||||||
input.grant_type == "authorization_code"
|
input.grant_type == "authorization_code"
|
||||||
some user in data.admin_users
|
can_request_admin(input.user)
|
||||||
input.user.username == user
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# This makes it possible to get the admin scope for clients that are allowed
|
# This makes it possible to get the admin scope for clients that are allowed
|
||||||
|
@@ -96,6 +96,20 @@ test_synapse_admin_scopes {
|
|||||||
with data.admin_users as []
|
with data.admin_users as []
|
||||||
with input.grant_type as "authorization_code"
|
with input.grant_type as "authorization_code"
|
||||||
with input.scope as "urn:synapse:admin:*"
|
with input.scope as "urn:synapse:admin:*"
|
||||||
|
|
||||||
|
allow with input.user as user
|
||||||
|
with input.user.can_request_admin as true
|
||||||
|
with input.client as client
|
||||||
|
with data.admin_users as []
|
||||||
|
with input.grant_type as "authorization_code"
|
||||||
|
with input.scope as "urn:synapse:admin:*"
|
||||||
|
|
||||||
|
not allow with input.user as user
|
||||||
|
with input.user.can_request_admin as false
|
||||||
|
with input.client as client
|
||||||
|
with data.admin_users as []
|
||||||
|
with input.grant_type as "authorization_code"
|
||||||
|
with input.scope as "urn:synapse:admin:*"
|
||||||
}
|
}
|
||||||
|
|
||||||
test_mas_scopes {
|
test_mas_scopes {
|
||||||
|
Reference in New Issue
Block a user