Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add configurable known groups #963

Merged
merged 11 commits into from
Oct 23, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions .deployment/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,13 @@
name: pgcrypto
db: tobira-{{ id }}

- name: add hstore extension
become: true
become_user: postgres
community.postgresql.postgresql_ext:
name: hstore
db: tobira-{{ id }}

# TOBIRA


Expand Down Expand Up @@ -178,6 +185,23 @@
cmd: /opt/tobira/{{ id }}/tobira search-index update
chdir: /opt/tobira/{{ id }}/

- name: Configure known groups
become: true
become_user: tobira
copy:
src: known-groups.json
dest: /opt/tobira/{{ id }}/
owner: root
group: root
mode: '0644'

- name: Configure known groups
become: true
become_user: tobira
command:
cmd: /opt/tobira/{{ id }}/tobira known-groups upsert known-groups.json
chdir: /opt/tobira/{{ id }}/

- name: install tobira service files
become: true
template:
Expand Down
6 changes: 6 additions & 0 deletions .deployment/files/known-groups.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"ROLE_STUDENT": { "label": { "en": "Students", "de": "Studierende" }, "implies": [], "large": true },
"ROLE_STAFF": { "label": { "en": "Staff", "de": "Angestellte" }, "implies": [], "large": true },
"ROLE_INSTRUCTOR": { "label": { "en": "Lecturers", "de": "Vortragende" }, "implies": ["ROLE_STAFF"], "large": true },
"ROLE_TOBIRA_MODERATOR": { "label": { "en": "Moderators", "de": "Moderierende" }, "implies": ["ROLE_STAFF"], "large": false }
}
2 changes: 2 additions & 0 deletions backend/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions backend/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ cookie = "0.17.0"
deadpool = { version = "0.9.0", default-features = false, features = ["managed", "rt_tokio_1"] }
deadpool-postgres = { version = "0.10", default-features = false, features = ["rt_tokio_1"] }
elliptic-curve = { version = "0.13.4", features = ["jwk", "sec1"] }
fallible-iterator = "0.2.0"
form_urlencoded = "1.1.0"
futures = { version = "0.3.1", default-features = false, features = ["std"] }
hex = "0.4.3"
Expand All @@ -50,6 +51,7 @@ palette = { version = "0.7.1", default-features = false, features = ["std"] }
paste = "1"
pem-rfc7468 = { version = "0.7.0", features = ["std"] }
percent-encoding = "2.1.0"
postgres-protocol = "0.6.6"
postgres-types = { version = "0.2.2", features = ["derive", "array-impls"] }
prometheus-client = "0.20.0"
rand = "0.8.4"
Expand Down
8 changes: 4 additions & 4 deletions backend/src/api/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,17 @@ use self::{
subscription::Subscription,
};

pub(crate) mod mutation;
pub(crate) mod query;
pub(crate) mod subscription;
pub(crate) mod util;
pub(crate) mod model;

mod common;
mod context;
mod err;
mod id;
mod model;
mod jwt;
mod mutation;
mod query;
mod subscription;

pub(crate) use self::{
id::Id,
Expand Down
42 changes: 42 additions & 0 deletions backend/src/api/model/known_roles.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
use crate::{
api::{Context, err::ApiResult, util::TranslatedString},
prelude::*,
db::util::impl_from_db,
};



/// A group selectable in the ACL UI. Basically a mapping from role to a nice
/// label and info about the relationship to other roles/groups.
#[derive(juniper::GraphQLObject)]
pub struct KnownGroup {
pub(crate) role: String,
pub(crate) label: TranslatedString<String>,
pub(crate) implies: Vec<String>,
pub(crate) large: bool,
}

impl_from_db!(
KnownGroup,
select: {
known_groups.{ role, label, implies, large },
},
|row| {
KnownGroup {
role: row.role(),
label: row.label(),
implies: row.implies(),
large: row.large(),
}
},
);

impl KnownGroup {
pub(crate) async fn load_all(context: &Context) -> ApiResult<Vec<Self>> {
let selection = Self::select();
let query = format!("select {selection} from known_groups");
context.db.query_mapped(&query, dbargs![], |row| Self::from_row_start(&row))
.await?
.pipe(Ok)
}
}
1 change: 1 addition & 0 deletions backend/src/api/model/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

pub(crate) mod block;
pub(crate) mod event;
pub(crate) mod known_roles;
pub(crate) mod realm;
pub(crate) mod search;
pub(crate) mod series;
Expand Down
6 changes: 6 additions & 0 deletions backend/src/api/query.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ use super::{
event::{AuthorizedEvent, Event},
series::Series,
search::{self, SearchOutcome, EventSearchOutcome, SeriesSearchOutcome},
known_roles::KnownGroup,
},
jwt::{JwtService, jwt},
};
Expand Down Expand Up @@ -118,4 +119,9 @@ impl Query {
) -> ApiResult<SeriesSearchOutcome> {
search::all_series(&query, writable_only, context).await
}

/// Returns all known groups selectable in the ACL UI.
async fn known_groups(context: &Context) -> ApiResult<Vec<KnownGroup>> {
KnownGroup::load_all(context).await
}
}
85 changes: 85 additions & 0 deletions backend/src/api/util.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,13 @@
use std::{collections::HashMap, fmt};

use bytes::BytesMut;
use fallible_iterator::FallibleIterator;
use postgres_types::{FromSql, ToSql};

use crate::prelude::*;



macro_rules! impl_object_with_dummy_field {
($ty:ident) => {
#[juniper::graphql_object(Context = crate::api::Context)]
Expand All @@ -12,3 +22,78 @@ macro_rules! impl_object_with_dummy_field {
}

pub(crate) use impl_object_with_dummy_field;




#[derive(Debug)]
pub struct TranslatedString<T>(pub(crate) HashMap<T, String>);

impl<T: AsRef<str> + fmt::Debug> ToSql for TranslatedString<T> {
fn to_sql(
&self,
_: &postgres_types::Type,
out: &mut BytesMut,
) -> Result<postgres_types::IsNull, Box<dyn std::error::Error + Sync + Send>> {
let values = self.0.iter().map(|(k, v)| (k.as_ref(), Some(&**v)));
postgres_protocol::types::hstore_to_sql(values, out)?;
Ok(postgres_types::IsNull::No)
}

fn accepts(ty: &postgres_types::Type) -> bool {
ty.name() == "hstore"
}

postgres_types::to_sql_checked!();
}

impl<'a> FromSql<'a> for TranslatedString<String> {
fn from_sql(
_: &postgres_types::Type,
raw: &'a [u8],
) -> Result<Self, Box<dyn std::error::Error + Sync + Send>> {
postgres_protocol::types::hstore_from_sql(raw)?
.map(|(k, v)| {
v.map(|v| (k.to_owned(), v.to_owned()))
.ok_or("translated label contained null value in hstore".into())
})
.collect()
.map(Self)
}

fn accepts(ty: &postgres_types::Type) -> bool {
ty.name() == "hstore"
}
}


#[juniper::graphql_scalar(
name = "TranslatedString",
description = "A string in different languages",
)]
impl<S> GraphQLScalar for TranslatedString<String>
where
S: juniper::ScalarValue + From<&str>
{
fn resolve(&self) -> juniper::Value {
use juniper::Value;

self.0.iter()
.map(|(k, v)| (k, juniper::Value::scalar(v.clone())))
.collect::<juniper::Object<S>>()
.pipe(Value::Object)
}

fn from_input_value(value: &juniper::InputValue) -> Option<Self> {
// I did not want to waste time implementing this now, given that we
// likely never use it.
let _ = value;
todo!("TranslatedString cannot be used as input value yet")
}

fn from_str<'a>(value: juniper::ScalarToken<'a>) -> juniper::ParseScalarResult<'a, S> {
// See `from_input_value`
let _ = value;
todo!()
}
}
9 changes: 9 additions & 0 deletions backend/src/args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,15 @@ pub(crate) enum Command {
#[clap(flatten)]
shared: Shared,
},

/// Listing, adding, and removing known groups.
KnownGroups {
#[clap(subcommand)]
options: cmd::known_groups::Args,

#[clap(flatten)]
shared: Shared,
},
}

#[derive(Debug, clap::Args)]
Expand Down
Loading