diff --git a/crates/data-model/src/users.rs b/crates/data-model/src/users.rs index 40d507dc..84b466ce 100644 --- a/crates/data-model/src/users.rs +++ b/crates/data-model/src/users.rs @@ -27,6 +27,7 @@ pub struct User { pub primary_user_email_id: Option, pub created_at: DateTime, pub locked_at: Option>, + pub can_request_admin: bool, } impl User { @@ -47,6 +48,7 @@ impl User { primary_user_email_id: None, created_at: now, locked_at: None, + can_request_admin: false, }] } } diff --git a/crates/graphql/src/model/users.rs b/crates/graphql/src/model/users.rs index d2b1b497..b83e13ce 100644 --- a/crates/graphql/src/model/users.rs +++ b/crates/graphql/src/model/users.rs @@ -73,6 +73,11 @@ impl User { 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. async fn matrix(&self, ctx: &Context<'_>) -> Result { let state = ctx.state(); diff --git a/crates/graphql/src/mutations/user.rs b/crates/graphql/src/mutations/user.rs index 1238c96f..ecd61e80 100644 --- a/crates/graphql/src/mutations/user.rs +++ b/crates/graphql/src/mutations/user.rs @@ -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 { + match self { + Self::Updated(user) => Some(User(user.clone())), + Self::NotFound => None, + } + } +} + fn valid_username_character(c: char) -> bool { c.is_ascii_lowercase() || c.is_ascii_digit() @@ -232,4 +263,37 @@ impl UserMutations { 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 { + 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)) + } } diff --git a/crates/storage-pg/.sqlx/query-e0ea7d93ab3f565828b2faab4cc5e1a6ac868c95bfaee3a6960df1cf484d53da.json b/crates/storage-pg/.sqlx/query-0d892dc8589ba54bb886972b6db00eaf7e41ff0db98fabdff5dcba0a7aa4e77d.json similarity index 73% rename from crates/storage-pg/.sqlx/query-e0ea7d93ab3f565828b2faab4cc5e1a6ac868c95bfaee3a6960df1cf484d53da.json rename to crates/storage-pg/.sqlx/query-0d892dc8589ba54bb886972b6db00eaf7e41ff0db98fabdff5dcba0a7aa4e77d.json index c38eb7db..5e70ba8d 100644 --- a/crates/storage-pg/.sqlx/query-e0ea7d93ab3f565828b2faab4cc5e1a6ac868c95bfaee3a6960df1cf484d53da.json +++ b/crates/storage-pg/.sqlx/query-0d892dc8589ba54bb886972b6db00eaf7e41ff0db98fabdff5dcba0a7aa4e77d.json @@ -1,6 +1,6 @@ { "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": { "columns": [ { @@ -27,6 +27,11 @@ "ordinal": 4, "name": "locked_at", "type_info": "Timestamptz" + }, + { + "ordinal": 5, + "name": "can_request_admin", + "type_info": "Bool" } ], "parameters": { @@ -39,8 +44,9 @@ false, true, false, - true + true, + false ] }, - "hash": "e0ea7d93ab3f565828b2faab4cc5e1a6ac868c95bfaee3a6960df1cf484d53da" + "hash": "0d892dc8589ba54bb886972b6db00eaf7e41ff0db98fabdff5dcba0a7aa4e77d" } diff --git a/crates/storage-pg/.sqlx/query-1dbc50cdab36da307c569891ab7b1ab4aaf128fed6be67ca0f139d697614c63b.json b/crates/storage-pg/.sqlx/query-1dbc50cdab36da307c569891ab7b1ab4aaf128fed6be67ca0f139d697614c63b.json new file mode 100644 index 00000000..f4e08b5c --- /dev/null +++ b/crates/storage-pg/.sqlx/query-1dbc50cdab36da307c569891ab7b1ab4aaf128fed6be67ca0f139d697614c63b.json @@ -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" +} diff --git a/crates/storage-pg/.sqlx/query-bfa5eaeaa5b4574bb083c86711eb4599f6374c96bb4a6827d400acb22fb0fd39.json b/crates/storage-pg/.sqlx/query-423e6aa88e0b8a01a90e108107a3d3998418fa43638b6510f28b56a2d6952222.json similarity index 73% rename from crates/storage-pg/.sqlx/query-bfa5eaeaa5b4574bb083c86711eb4599f6374c96bb4a6827d400acb22fb0fd39.json rename to crates/storage-pg/.sqlx/query-423e6aa88e0b8a01a90e108107a3d3998418fa43638b6510f28b56a2d6952222.json index f70906f2..e95ba53a 100644 --- a/crates/storage-pg/.sqlx/query-bfa5eaeaa5b4574bb083c86711eb4599f6374c96bb4a6827d400acb22fb0fd39.json +++ b/crates/storage-pg/.sqlx/query-423e6aa88e0b8a01a90e108107a3d3998418fa43638b6510f28b56a2d6952222.json @@ -1,6 +1,6 @@ { "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": { "columns": [ { @@ -27,6 +27,11 @@ "ordinal": 4, "name": "locked_at", "type_info": "Timestamptz" + }, + { + "ordinal": 5, + "name": "can_request_admin", + "type_info": "Bool" } ], "parameters": { @@ -39,8 +44,9 @@ false, true, false, - true + true, + false ] }, - "hash": "bfa5eaeaa5b4574bb083c86711eb4599f6374c96bb4a6827d400acb22fb0fd39" + "hash": "423e6aa88e0b8a01a90e108107a3d3998418fa43638b6510f28b56a2d6952222" } diff --git a/crates/storage-pg/.sqlx/query-2b0d54c284dc4d946faae4190568bf597c04b40f010132dd7bf68462c47f9eac.json b/crates/storage-pg/.sqlx/query-e602a7c76386f732de686694257e03f35c18643c91a06f9c4a3fa0a5f103df58.json similarity index 82% rename from crates/storage-pg/.sqlx/query-2b0d54c284dc4d946faae4190568bf597c04b40f010132dd7bf68462c47f9eac.json rename to crates/storage-pg/.sqlx/query-e602a7c76386f732de686694257e03f35c18643c91a06f9c4a3fa0a5f103df58.json index 9e251fa0..848130a9 100644 --- a/crates/storage-pg/.sqlx/query-2b0d54c284dc4d946faae4190568bf597c04b40f010132dd7bf68462c47f9eac.json +++ b/crates/storage-pg/.sqlx/query-e602a7c76386f732de686694257e03f35c18643c91a06f9c4a3fa0a5f103df58.json @@ -1,6 +1,6 @@ { "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": { "columns": [ { @@ -57,6 +57,11 @@ "ordinal": 10, "name": "user_locked_at", "type_info": "Timestamptz" + }, + { + "ordinal": 11, + "name": "user_can_request_admin", + "type_info": "Bool" } ], "parameters": { @@ -75,8 +80,9 @@ false, true, false, - true + true, + false ] }, - "hash": "2b0d54c284dc4d946faae4190568bf597c04b40f010132dd7bf68462c47f9eac" + "hash": "e602a7c76386f732de686694257e03f35c18643c91a06f9c4a3fa0a5f103df58" } diff --git a/crates/storage-pg/migrations/20231009142904_user_can_request_admin.sql b/crates/storage-pg/migrations/20231009142904_user_can_request_admin.sql new file mode 100644 index 00000000..a545f477 --- /dev/null +++ b/crates/storage-pg/migrations/20231009142904_user_can_request_admin.sql @@ -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; \ No newline at end of file diff --git a/crates/storage-pg/src/iden.rs b/crates/storage-pg/src/iden.rs index 7fee50e2..009c0037 100644 --- a/crates/storage-pg/src/iden.rs +++ b/crates/storage-pg/src/iden.rs @@ -34,6 +34,7 @@ pub enum Users { PrimaryUserEmailId, CreatedAt, LockedAt, + CanRequestAdmin, } #[derive(sea_query::Iden)] diff --git a/crates/storage-pg/src/user/mod.rs b/crates/storage-pg/src/user/mod.rs index 7dbf666d..34fc37ce 100644 --- a/crates/storage-pg/src/user/mod.rs +++ b/crates/storage-pg/src/user/mod.rs @@ -57,6 +57,7 @@ struct UserLookup { primary_user_email_id: Option, created_at: DateTime, locked_at: Option>, + can_request_admin: bool, } impl From for User { @@ -69,6 +70,7 @@ impl From for User { primary_user_email_id: value.primary_user_email_id.map(Into::into), created_at: value.created_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 , created_at , locked_at + , can_request_admin FROM users WHERE user_id = $1 "#, @@ -127,6 +130,7 @@ impl<'c> UserRepository for PgUserRepository<'c> { , primary_user_email_id , created_at , locked_at + , can_request_admin FROM users WHERE username = $1 "#, @@ -186,6 +190,7 @@ impl<'c> UserRepository for PgUserRepository<'c> { primary_user_email_id: None, created_at, locked_at: None, + can_request_admin: false, }) } @@ -281,4 +286,39 @@ impl<'c> UserRepository for PgUserRepository<'c> { 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 { + 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) + } } diff --git a/crates/storage-pg/src/user/session.rs b/crates/storage-pg/src/user/session.rs index 631785d2..f19905a1 100644 --- a/crates/storage-pg/src/user/session.rs +++ b/crates/storage-pg/src/user/session.rs @@ -63,6 +63,7 @@ struct SessionLookup { user_primary_user_email_id: Option, user_created_at: DateTime, user_locked_at: Option>, + user_can_request_admin: bool, } impl TryFrom for BrowserSession { @@ -77,6 +78,7 @@ impl TryFrom for BrowserSession { primary_user_email_id: value.user_primary_user_email_id.map(Into::into), created_at: value.user_created_at, locked_at: value.user_locked_at, + can_request_admin: value.user_can_request_admin, }; Ok(BrowserSession { @@ -155,6 +157,7 @@ impl<'c> BrowserSessionRepository for PgBrowserSessionRepository<'c> { , u.primary_user_email_id AS "user_primary_user_email_id" , u.created_at AS "user_created_at" , u.locked_at AS "user_locked_at" + , u.can_request_admin AS "user_can_request_admin" FROM user_sessions s INNER JOIN users u USING (user_id) @@ -313,6 +316,10 @@ impl<'c> BrowserSessionRepository for PgBrowserSessionRepository<'c> { Expr::col((Users::Table, Users::LockedAt)), SessionLookupIden::UserLockedAt, ) + .expr_as( + Expr::col((Users::Table, Users::CanRequestAdmin)), + SessionLookupIden::UserCanRequestAdmin, + ) .from(UserSessions::Table) .inner_join( Users::Table, diff --git a/crates/storage-pg/src/user/tests.rs b/crates/storage-pg/src/user/tests.rs index 5ea30c74..53ab9b9e 100644 --- a/crates/storage-pg/src/user/tests.rs +++ b/crates/storage-pg/src/user/tests.rs @@ -95,6 +95,26 @@ async fn test_user_repo(pool: PgPool) { let user = repo.user().unlock(user).await.unwrap(); 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(); } diff --git a/crates/storage/src/user/mod.rs b/crates/storage/src/user/mod.rs index 82ad656e..86c9adcb 100644 --- a/crates/storage/src/user/mod.rs +++ b/crates/storage/src/user/mod.rs @@ -123,6 +123,23 @@ pub trait UserRepository: Send + Sync { /// /// Returns [`Self::Error`] if the underlying repository fails async fn unlock(&mut self, user: User) -> Result; + + /// 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; } repository_impl!(UserRepository: @@ -137,4 +154,9 @@ repository_impl!(UserRepository: async fn exists(&mut self, username: &str) -> Result; async fn lock(&mut self, clock: &dyn Clock, user: User) -> Result; async fn unlock(&mut self, user: User) -> Result; + async fn set_can_request_admin( + &mut self, + user: User, + can_request_admin: bool, + ) -> Result; ); diff --git a/frontend/schema.graphql b/frontend/schema.graphql index d1149b82..7c3af547 100644 --- a/frontend/schema.graphql +++ b/frontend/schema.graphql @@ -643,6 +643,13 @@ type Mutation { """ 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. Only available for administrators. @@ -1018,6 +1025,30 @@ enum SessionState { 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 """ @@ -1233,6 +1264,10 @@ type User implements Node { """ lockedAt: DateTime """ + Whether the user can request admin privileges. + """ + canRequestAdmin: Boolean! + """ Access to the user's Matrix account information. """ matrix: MatrixUser! diff --git a/frontend/src/gql/graphql.ts b/frontend/src/gql/graphql.ts index 719ba341..54b38004 100644 --- a/frontend/src/gql/graphql.ts +++ b/frontend/src/gql/graphql.ts @@ -436,6 +436,11 @@ export type Mutation = { removeEmail: RemoveEmailPayload; /** Send a verification code for an email address */ sendVerificationEmail: SendVerificationEmailPayload; + /** + * Set whether a user can request admin. This is only available to + * administrators. + */ + setCanRequestAdmin: SetCanRequestAdminPayload; /** Set the display name of a user */ setDisplayName: SetDisplayNamePayload; /** Set an email address as primary */ @@ -489,6 +494,11 @@ export type MutationSendVerificationEmailArgs = { input: SendVerificationEmailInput; }; +/** The mutations root of the GraphQL interface. */ +export type MutationSetCanRequestAdminArgs = { + input: SetCanRequestAdminInput; +}; + /** The mutations root of the GraphQL interface. */ export type MutationSetDisplayNameArgs = { input: SetDisplayNameInput; @@ -762,6 +772,21 @@ export enum SessionState { 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; +}; + /** The input for the `addEmail` mutation */ export type SetDisplayNameInput = { /** The display name to set. If `None`, the display name will be removed. */ @@ -891,6 +916,8 @@ export type User = Node & { appSessions: AppSessionConnection; /** Get the list of active browser sessions, chronologically sorted */ browserSessions: BrowserSessionConnection; + /** Whether the user can request admin privileges. */ + canRequestAdmin: Scalars["Boolean"]["output"]; /** Get the list of compatibility sessions, chronologically sorted */ compatSessions: CompatSessionConnection; /** Get the list of compatibility SSO logins, chronologically sorted */ diff --git a/frontend/src/gql/schema.ts b/frontend/src/gql/schema.ts index 01fc1ed4..69780105 100644 --- a/frontend/src/gql/schema.ts +++ b/frontend/src/gql/schema.ts @@ -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", 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", name: "SetDisplayNamePayload", @@ -2623,6 +2662,17 @@ export default { }, ], }, + { + name: "canRequestAdmin", + type: { + kind: "NON_NULL", + ofType: { + kind: "SCALAR", + name: "Any", + }, + }, + args: [], + }, { name: "compatSessions", type: { diff --git a/policies/authorization_grant.rego b/policies/authorization_grant.rego index aaf3595e..8a44fffc 100644 --- a/policies/authorization_grant.rego +++ b/policies/authorization_grant.rego @@ -11,6 +11,18 @@ allow { 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 allowed_scope("") = true @@ -22,8 +34,7 @@ allowed_scope("email") = true 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 input.grant_type == "authorization_code" - some user in data.admin_users - input.user.username == user + can_request_admin(input.user) } # 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 allowed_scope("urn:mas:admin") { input.grant_type == "authorization_code" - some user in data.admin_users - input.user.username == user + can_request_admin(input.user) } # This makes it possible to get the admin scope for clients that are allowed diff --git a/policies/authorization_grant_test.rego b/policies/authorization_grant_test.rego index 68c2e215..33d8f71b 100644 --- a/policies/authorization_grant_test.rego +++ b/policies/authorization_grant_test.rego @@ -96,6 +96,20 @@ test_synapse_admin_scopes { with data.admin_users as [] with input.grant_type as "authorization_code" 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 {