Add role management commands
This commit is contained in:
parent
301302f1d0
commit
63c2a7fb1d
@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT user_name, user_id, locked FROM users ORDER BY user_name",
|
||||
"query": "SELECT user_name, user_id, locked, COALESCE((\n SELECT array_agg(role_id ORDER BY role_id) FROM users_roles ur WHERE ur.user_id = u.user_id\n ), ARRAY[]::text[]) AS \"roles!\" FROM users u ORDER BY user_name",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@ -17,6 +17,11 @@
|
||||
"ordinal": 2,
|
||||
"name": "locked",
|
||||
"type_info": "Bool"
|
||||
},
|
||||
{
|
||||
"ordinal": 3,
|
||||
"name": "roles!",
|
||||
"type_info": "TextArray"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
@ -25,8 +30,9 @@
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
false
|
||||
false,
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "bbaa41c568bee22eae314186e0d7402f235f8eba9a4ed6adb7bfb6824ec86396"
|
||||
"hash": "21c71fb90265218b7422c756264618e9766dbed4aa10e1a57246fccd8bcedfe1"
|
||||
}
|
||||
32
.sqlx/query-5e059b78f4ebbbd340021e41f2ab64956fa02903597ac65c0ed6b0a527ca02aa.json
generated
Normal file
32
.sqlx/query-5e059b78f4ebbbd340021e41f2ab64956fa02903597ac65c0ed6b0a527ca02aa.json
generated
Normal file
@ -0,0 +1,32 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT role_id, role_name, (\n SELECT COUNT(1) FROM users_roles ur WHERE ur.role_id = r.role_id\n ) AS \"num_users!\" FROM roles r ORDER BY role_id",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "role_id",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "role_name",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "num_users!",
|
||||
"type_info": "Int8"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": []
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "5e059b78f4ebbbd340021e41f2ab64956fa02903597ac65c0ed6b0a527ca02aa"
|
||||
}
|
||||
15
.sqlx/query-81ca59dcf136d31e129e2d14fbbd6616f6642cdf93e55b85c6e0f7dae58cf229.json
generated
Normal file
15
.sqlx/query-81ca59dcf136d31e129e2d14fbbd6616f6642cdf93e55b85c6e0f7dae58cf229.json
generated
Normal file
@ -0,0 +1,15 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO roles (role_id, role_name) VALUES ($1, $2)",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "81ca59dcf136d31e129e2d14fbbd6616f6642cdf93e55b85c6e0f7dae58cf229"
|
||||
}
|
||||
14
.sqlx/query-8e4a982ac705a58413512a1064eb2d9d3e961fc1d7b707da92737380902fd2b8.json
generated
Normal file
14
.sqlx/query-8e4a982ac705a58413512a1064eb2d9d3e961fc1d7b707da92737380902fd2b8.json
generated
Normal file
@ -0,0 +1,14 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "DELETE FROM roles WHERE role_id = $1",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "8e4a982ac705a58413512a1064eb2d9d3e961fc1d7b707da92737380902fd2b8"
|
||||
}
|
||||
15
.sqlx/query-9650a20bd3bb3eee0453d4a8cb2283f41d9b9e84bbd895e1d53369524f611301.json
generated
Normal file
15
.sqlx/query-9650a20bd3bb3eee0453d4a8cb2283f41d9b9e84bbd895e1d53369524f611301.json
generated
Normal file
@ -0,0 +1,15 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO users_roles (user_id, role_id, granted_at_utc) VALUES ($1, $2, NOW())",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Uuid",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "9650a20bd3bb3eee0453d4a8cb2283f41d9b9e84bbd895e1d53369524f611301"
|
||||
}
|
||||
15
.sqlx/query-d822ddde0ad92c6480e6e10f7bbd7e0317d6f0a41368456032b5f0b5ecb58d7c.json
generated
Normal file
15
.sqlx/query-d822ddde0ad92c6480e6e10f7bbd7e0317d6f0a41368456032b5f0b5ecb58d7c.json
generated
Normal file
@ -0,0 +1,15 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "DELETE FROM users_roles WHERE user_id = $1 AND role_id = $2",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Uuid",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "d822ddde0ad92c6480e6e10f7bbd7e0317d6f0a41368456032b5f0b5ecb58d7c"
|
||||
}
|
||||
@ -6,6 +6,7 @@
|
||||
- [Command Line Tool](admin/cli/index.md)
|
||||
- [serve](admin/cli/serve.md)
|
||||
- [user](admin/cli/user.md)
|
||||
- [role](admin/cli/role.md)
|
||||
|
||||
|
||||
# Development
|
||||
|
||||
33
docs/admin/cli/role.md
Normal file
33
docs/admin/cli/role.md
Normal file
@ -0,0 +1,33 @@
|
||||
# `idcoop role` — role management commands
|
||||
|
||||
## `idcoop role add` — add a new role
|
||||
|
||||
```
|
||||
idcoop role add <ROLE> [--name ROLE_NAME]
|
||||
```
|
||||
aliases: `new`, `create`
|
||||
|
||||
Adds a role.
|
||||
The ROLE identifier must consist of only alphanumeric characters
|
||||
(this may be expanded in the future).
|
||||
|
||||
The optional `--name` flag can be used to give a human-readable name
|
||||
to the role.
|
||||
|
||||
|
||||
## `idcoop role rm` — remove a role
|
||||
|
||||
```
|
||||
idcoop role rm <ROLE>
|
||||
```
|
||||
aliases: `del`, `delete`, `remove`
|
||||
|
||||
Removes a role.
|
||||
|
||||
|
||||
## `idcoop role list` — list all roles
|
||||
|
||||
```
|
||||
idcoop role list
|
||||
```
|
||||
aliases: `ls`
|
||||
@ -53,15 +53,31 @@ idcoop user <lock|unlock> <USERNAME>
|
||||
- `<USERNAME>`: name of the user to be locked or unlocked
|
||||
|
||||
|
||||
## `idcoop user list-all` — list all users
|
||||
## `idcoop user list` — list all users
|
||||
|
||||
Displays a list of users in tabular form.
|
||||
|
||||
```
|
||||
idcoop user list-all [--usernames]
|
||||
idcoop user list [--usernames]
|
||||
```
|
||||
aliases: `idcoop user ls`
|
||||
|
||||
- `--usernames`: if specified, only the usernames of users will be shown, one per line.
|
||||
|
||||
The output of this command is not considered stable, and should not be used in scripts, unless the `--usernames` option is used.
|
||||
|
||||
## `idcoop user role-add` — add users to a role
|
||||
|
||||
The role must exist prior to adding any users to it.
|
||||
|
||||
```
|
||||
idcoop user role-add <ROLE> <USERNAME...>
|
||||
```
|
||||
aliases: `grant`
|
||||
|
||||
## `idcoop user role-rm` — remove users from a role
|
||||
|
||||
```
|
||||
idcoop user role-rm <ROLE> <USERNAME...>
|
||||
```
|
||||
aliases: `revoke`, `role-remove`
|
||||
|
||||
@ -4,7 +4,7 @@ use std::{net::SocketAddr, path::PathBuf};
|
||||
use clap::Parser;
|
||||
use confique::{Config, Partial};
|
||||
use eyre::{bail, Context};
|
||||
use idcoop::cli::{handle_user_command, UserCommand};
|
||||
use idcoop::cli::{handle_role_command, handle_user_command, RoleCommand, UserCommand};
|
||||
use idcoop::config::{SecretConfig, SeparateSecretConfiguration};
|
||||
use idcoop::store::IdCoopStore;
|
||||
use idcoop::{config::Configuration, web};
|
||||
@ -40,6 +40,12 @@ enum Subcommand {
|
||||
#[clap(subcommand)]
|
||||
cmd: UserCommand,
|
||||
},
|
||||
|
||||
/// Manage roles.
|
||||
Role {
|
||||
#[clap(subcommand)]
|
||||
cmd: RoleCommand,
|
||||
},
|
||||
}
|
||||
|
||||
fn load_config_files<C: Config>(files: &[PathBuf]) -> eyre::Result<C> {
|
||||
@ -111,6 +117,12 @@ async fn main() -> eyre::Result<()> {
|
||||
.context("Failed to connect to Postgres")?;
|
||||
handle_user_command(cmd, &config, &store).await?;
|
||||
}
|
||||
Subcommand::Role { cmd } => {
|
||||
let store = IdCoopStore::connect(&config.postgres.connect)
|
||||
.await
|
||||
.context("Failed to connect to Postgres")?;
|
||||
handle_role_command(cmd, &config, &store).await?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
||||
190
src/cli.rs
190
src/cli.rs
@ -1,14 +1,16 @@
|
||||
//! idCoop Command Line Interface
|
||||
|
||||
use std::collections::BTreeSet;
|
||||
use std::io::stdin;
|
||||
|
||||
use crate::config::Configuration;
|
||||
use crate::passwords::create_password_hash;
|
||||
use crate::store::{CreateUser, IdCoopStore};
|
||||
use crate::store::{CreateRole, CreateUser, IdCoopStore, IdCoopStoreTxn};
|
||||
use clap::Parser;
|
||||
use comfy_table::presets::UTF8_FULL;
|
||||
use comfy_table::{Attribute, Cell, Color, ContentArrangement, Row, Table};
|
||||
use eyre::{bail, Context, ContextCompat};
|
||||
use uuid::Uuid;
|
||||
|
||||
/// Commands for user management.
|
||||
#[derive(Clone, Parser)]
|
||||
@ -49,11 +51,27 @@ pub enum UserCommand {
|
||||
},
|
||||
/// Lists all users that are registered.
|
||||
#[clap(alias = "ls")]
|
||||
ListAll {
|
||||
List {
|
||||
/// Only show a list of usernames, without table formatting characters and one per line. May be useful in scripts.
|
||||
#[clap(long = "usernames")]
|
||||
usernames: bool,
|
||||
},
|
||||
/// Adds users to a role.
|
||||
#[clap(alias = "grant")]
|
||||
RoleAdd {
|
||||
/// The ID of the role to add users to.
|
||||
role: String,
|
||||
/// The names of users to add to the role.
|
||||
usernames: Vec<String>,
|
||||
},
|
||||
/// Adds users to a role.
|
||||
#[clap(alias = "role-rm", alias = "revoke")]
|
||||
RoleRemove {
|
||||
/// The ID of the role to remove users from.
|
||||
role: String,
|
||||
/// The names of users to remove from the role.
|
||||
usernames: Vec<String>,
|
||||
},
|
||||
}
|
||||
|
||||
/// Handles a user command from the command-line interface.
|
||||
@ -143,7 +161,7 @@ pub async fn handle_user_command(
|
||||
})
|
||||
.await?;
|
||||
}
|
||||
UserCommand::ListAll { usernames } => {
|
||||
UserCommand::List { usernames } => {
|
||||
let user_infos = store
|
||||
.txn(|mut txn| Box::pin(async move { txn.list_user_info().await }))
|
||||
.await?;
|
||||
@ -157,11 +175,12 @@ pub async fn handle_user_command(
|
||||
table
|
||||
.load_preset(UTF8_FULL)
|
||||
.set_content_arrangement(ContentArrangement::Dynamic)
|
||||
.set_width(80)
|
||||
// .set_width(80)
|
||||
.set_header(vec![
|
||||
Cell::new("Name").add_attribute(Attribute::Bold),
|
||||
Cell::new("UUID").add_attribute(Attribute::Bold),
|
||||
Cell::new("Locked").add_attribute(Attribute::Bold),
|
||||
Cell::new("Roles").add_attribute(Attribute::Bold),
|
||||
]);
|
||||
|
||||
for user_info in user_infos {
|
||||
@ -174,12 +193,175 @@ pub async fn handle_user_command(
|
||||
("no", Color::White)
|
||||
};
|
||||
row.add_cell(Cell::new(lock_str).fg(lock_colour));
|
||||
row.add_cell(Cell::new(user_info.roles.join(", ")));
|
||||
table.add_row(row);
|
||||
}
|
||||
|
||||
println!("{}", table);
|
||||
}
|
||||
}
|
||||
UserCommand::RoleAdd { role, usernames } => {
|
||||
let missing_opt = store
|
||||
.txn(|mut txn| {
|
||||
Box::pin(async move {
|
||||
let user_ids = match resolve_usernames(&mut txn, &usernames).await? {
|
||||
Ok(found) => found,
|
||||
Err(missing) => {
|
||||
return Ok(Some(missing));
|
||||
}
|
||||
};
|
||||
|
||||
for user_id in user_ids {
|
||||
txn.add_user_to_role(user_id, &role).await?;
|
||||
}
|
||||
|
||||
Ok(None)
|
||||
})
|
||||
})
|
||||
.await?;
|
||||
if let Some(missing) = missing_opt {
|
||||
bail!("Unknown users: {missing:?}");
|
||||
}
|
||||
}
|
||||
UserCommand::RoleRemove { role, usernames } => {
|
||||
let missing_opt = store
|
||||
.txn(|mut txn| {
|
||||
Box::pin(async move {
|
||||
let user_ids = match resolve_usernames(&mut txn, &usernames).await? {
|
||||
Ok(found) => found,
|
||||
Err(missing) => {
|
||||
return Ok(Some(missing));
|
||||
}
|
||||
};
|
||||
|
||||
for user_id in user_ids {
|
||||
txn.remove_user_from_role(user_id, &role).await?;
|
||||
}
|
||||
|
||||
Ok(None)
|
||||
})
|
||||
})
|
||||
.await?;
|
||||
if let Some(missing) = missing_opt {
|
||||
bail!("Unknown users: {missing:?}");
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Resolves usernames to UUIDs, returning them (in the same order).
|
||||
///
|
||||
/// If any of the usernames don't exist, returns a set of the usernames that don't exist instead.
|
||||
async fn resolve_usernames(
|
||||
txn: &mut IdCoopStoreTxn<'_, '_>,
|
||||
usernames: &[String],
|
||||
) -> eyre::Result<Result<Vec<Uuid>, BTreeSet<String>>> {
|
||||
let mut missing = BTreeSet::new();
|
||||
let mut found = Vec::new();
|
||||
|
||||
// Find user IDs
|
||||
for user_name in usernames {
|
||||
match txn.lookup_user_by_name(user_name.clone()).await? {
|
||||
Some(user) => {
|
||||
found.push(user.user_id);
|
||||
}
|
||||
None => {
|
||||
missing.insert(user_name.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !missing.is_empty() {
|
||||
return Ok(Err(missing));
|
||||
}
|
||||
|
||||
Ok(Ok(found))
|
||||
}
|
||||
|
||||
/// Commands for user management.
|
||||
#[derive(Clone, Parser)]
|
||||
pub enum RoleCommand {
|
||||
/// Add a role.
|
||||
#[clap(alias = "new", alias = "create")]
|
||||
Add {
|
||||
/// The role ID. Must only consist of alphanumeric characters.
|
||||
role: String,
|
||||
|
||||
/// Human-readable name for the role/
|
||||
#[clap(long = "name")]
|
||||
name: Option<String>,
|
||||
},
|
||||
/// Deletes a role.
|
||||
#[clap(alias = "remove", alias = "rm", alias = "del")]
|
||||
Delete {
|
||||
/// The role ID.
|
||||
role: String,
|
||||
},
|
||||
/// List all roles.
|
||||
#[clap(alias = "ls")]
|
||||
List {},
|
||||
}
|
||||
|
||||
/// Handles a role command from the command-line interface.
|
||||
pub async fn handle_role_command(
|
||||
command: RoleCommand,
|
||||
_config: &Configuration,
|
||||
store: &IdCoopStore,
|
||||
) -> eyre::Result<()> {
|
||||
match command {
|
||||
RoleCommand::Add { role, name } => {
|
||||
let name = name.unwrap_or_else(|| role.clone());
|
||||
|
||||
store
|
||||
.txn(|mut txn| {
|
||||
Box::pin(async move {
|
||||
txn.create_role(CreateRole {
|
||||
role_id: role,
|
||||
role_name: name,
|
||||
})
|
||||
.await
|
||||
})
|
||||
})
|
||||
.await?;
|
||||
}
|
||||
RoleCommand::Delete { role } => {
|
||||
store
|
||||
.txn(|mut txn| Box::pin(async move { txn.delete_role(&role).await }))
|
||||
.await?;
|
||||
}
|
||||
RoleCommand::List {} => {
|
||||
let role_infos = store
|
||||
.txn(|mut txn| Box::pin(async move { txn.list_role_info().await }))
|
||||
.await?;
|
||||
|
||||
let mut table = Table::new();
|
||||
table
|
||||
.load_preset(UTF8_FULL)
|
||||
.set_content_arrangement(ContentArrangement::Dynamic)
|
||||
// .set_width(80)
|
||||
.set_header(vec![
|
||||
Cell::new("Role ID").add_attribute(Attribute::Bold),
|
||||
Cell::new("Name").add_attribute(Attribute::Bold),
|
||||
Cell::new("Users").add_attribute(Attribute::Bold),
|
||||
]);
|
||||
|
||||
for role_info in role_infos {
|
||||
let mut row = Row::new();
|
||||
row.add_cell(Cell::new(role_info.role_id));
|
||||
row.add_cell(Cell::new(role_info.role_name).fg(Color::Grey));
|
||||
row.add_cell(
|
||||
Cell::new(role_info.num_users).fg(if role_info.num_users == 0 {
|
||||
Color::Red
|
||||
} else {
|
||||
Color::White
|
||||
}),
|
||||
);
|
||||
table.add_row(row);
|
||||
}
|
||||
|
||||
println!("{}", table);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -110,6 +110,7 @@ pub struct OidcClientConfiguration {
|
||||
pub name: String,
|
||||
|
||||
/// User roles to allow to access this application.
|
||||
/// Users must satisfy ONE OF the specified roles in order to proceed.
|
||||
///
|
||||
/// The `*` 'role' can be used to allow all users to access this application.
|
||||
///
|
||||
|
||||
101
src/store.rs
101
src/store.rs
@ -7,6 +7,7 @@ use std::collections::BTreeSet;
|
||||
use chrono::DateTime;
|
||||
use chrono::NaiveDateTime;
|
||||
use chrono::Utc;
|
||||
use eyre::ensure;
|
||||
use eyre::eyre;
|
||||
use eyre::Context;
|
||||
use futures::future::BoxFuture;
|
||||
@ -98,6 +99,14 @@ pub struct CreateUser {
|
||||
pub locked: bool,
|
||||
}
|
||||
|
||||
/// Representation of the action of creating a role.
|
||||
pub struct CreateRole {
|
||||
/// Is alphanumeric.
|
||||
pub role_id: String,
|
||||
/// Human-readable name of the role.
|
||||
pub role_name: String,
|
||||
}
|
||||
|
||||
/// Basic information about a user
|
||||
pub struct UserInfo {
|
||||
/// The unique system name for the user.
|
||||
@ -108,6 +117,21 @@ pub struct UserInfo {
|
||||
pub user_id: Uuid,
|
||||
/// Whether the user is locked and is therefore not allowed to log in.
|
||||
pub locked: bool,
|
||||
/// List of role IDs the user is in
|
||||
pub roles: Vec<String>,
|
||||
}
|
||||
|
||||
/// Basic information about a role
|
||||
pub struct RoleInfo {
|
||||
/// The ID of the role.
|
||||
/// Is alphanumeric.
|
||||
pub role_id: String,
|
||||
|
||||
/// The human-readable name of the role.
|
||||
pub role_name: String,
|
||||
|
||||
/// How many users are in the role.
|
||||
pub num_users: i64,
|
||||
}
|
||||
|
||||
/// A wrapper around a database transaction with some database methods on it.
|
||||
@ -298,7 +322,9 @@ impl IdCoopStoreTxn<'_, '_> {
|
||||
pub async fn list_user_info(&mut self) -> eyre::Result<Vec<UserInfo>> {
|
||||
sqlx::query_as!(
|
||||
UserInfo,
|
||||
"SELECT user_name, user_id, locked FROM users ORDER BY user_name"
|
||||
r#"SELECT user_name, user_id, locked, COALESCE((
|
||||
SELECT array_agg(role_id ORDER BY role_id) FROM users_roles ur WHERE ur.user_id = u.user_id
|
||||
), ARRAY[]::text[]) AS "roles!" FROM users u ORDER BY user_name"#
|
||||
)
|
||||
.fetch_all(&mut **self.txn)
|
||||
.await
|
||||
@ -430,4 +456,77 @@ impl IdCoopStoreTxn<'_, '_> {
|
||||
.context("failed to fetch roleset of user")
|
||||
.map(|v| v.into_iter().collect())
|
||||
}
|
||||
|
||||
/// Creates a role.
|
||||
pub async fn create_role(&mut self, cr: CreateRole) -> eyre::Result<()> {
|
||||
ensure!(
|
||||
cr.role_id.chars().all(|c| c.is_ascii_alphanumeric()),
|
||||
"attempted to create role {} with non-alphanum ID",
|
||||
cr.role_id
|
||||
);
|
||||
|
||||
// TODO(prettiness): handle the case where the role already exists
|
||||
// in a nicer way
|
||||
sqlx::query!(
|
||||
"INSERT INTO roles (role_id, role_name) VALUES ($1, $2)",
|
||||
&cr.role_id,
|
||||
&cr.role_name
|
||||
)
|
||||
.execute(&mut **self.txn)
|
||||
.await
|
||||
.context("failed to create role in DB")?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Deletes a role. Silently no-ops if the role doesn't exist.
|
||||
pub async fn delete_role(&mut self, role_id: &str) -> eyre::Result<()> {
|
||||
sqlx::query!("DELETE FROM roles WHERE role_id = $1", role_id)
|
||||
.execute(&mut **self.txn)
|
||||
.await
|
||||
.context("failed to delete role in DB")?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Lists all roles and provides some information about them.
|
||||
pub async fn list_role_info(&mut self) -> eyre::Result<Vec<RoleInfo>> {
|
||||
sqlx::query_as!(
|
||||
RoleInfo,
|
||||
r#"SELECT role_id, role_name, (
|
||||
SELECT COUNT(1) FROM users_roles ur WHERE ur.role_id = r.role_id
|
||||
) AS "num_users!" FROM roles r ORDER BY role_id"#
|
||||
)
|
||||
.fetch_all(&mut **self.txn)
|
||||
.await
|
||||
.context("failed to list roles")
|
||||
}
|
||||
|
||||
/// Adds a user to a role.
|
||||
pub async fn add_user_to_role(&mut self, user_id: Uuid, role_id: &str) -> eyre::Result<()> {
|
||||
sqlx::query!(
|
||||
"INSERT INTO users_roles (user_id, role_id, granted_at_utc) VALUES ($1, $2, NOW())",
|
||||
user_id,
|
||||
role_id
|
||||
)
|
||||
.execute(&mut **self.txn)
|
||||
.await
|
||||
.context("failed to add user to role")?;
|
||||
Ok(())
|
||||
}
|
||||
/// Removes a user to a role.
|
||||
pub async fn remove_user_from_role(
|
||||
&mut self,
|
||||
user_id: Uuid,
|
||||
role_id: &str,
|
||||
) -> eyre::Result<()> {
|
||||
sqlx::query!(
|
||||
"DELETE FROM users_roles WHERE user_id = $1 AND role_id = $2",
|
||||
user_id,
|
||||
role_id
|
||||
)
|
||||
.execute(&mut **self.txn)
|
||||
.await
|
||||
.context("failed to remove user from role")?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@ -30,6 +30,8 @@ use crate::{
|
||||
|
||||
use super::ext_codes::VolatileCodeStore;
|
||||
|
||||
/// Role string that always identifies anyone.
|
||||
/// Not a real role.
|
||||
pub const EVERYONE_ROLE: &str = "*";
|
||||
|
||||
/// Query string parameters for the OIDC authorisation request.
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user