Skip to content

Commit

Permalink
feat: stringify permissions in public API
Browse files Browse the repository at this point in the history
- `Permission` enum
- Iterator over `Permissions`
- I hate it, for the most part.
  • Loading branch information
AlphaKeks committed Jan 15, 2024
1 parent d283643 commit a1dda05
Show file tree
Hide file tree
Showing 7 changed files with 168 additions and 19 deletions.
25 changes: 20 additions & 5 deletions api-spec.json
Original file line number Diff line number Diff line change
Expand Up @@ -1752,7 +1752,10 @@
"in": "query",
"required": false,
"schema": {
"$ref": "#/components/schemas/Permissions"
"type": "array",
"items": {
"$ref": "#/components/schemas/Permission"
}
}
}
],
Expand All @@ -1776,7 +1779,10 @@
"type": "string"
},
"permissions": {
"$ref": "#/components/schemas/Permissions"
"type": "array",
"items": {
"$ref": "#/components/schemas/Permission"
}
}
}
}
Expand Down Expand Up @@ -1877,7 +1883,10 @@
"type": "string"
},
"permissions": {
"$ref": "#/components/schemas/Permissions"
"type": "array",
"items": {
"$ref": "#/components/schemas/Permission"
}
}
}
}
Expand Down Expand Up @@ -1960,7 +1969,10 @@
"type": "string"
},
"permissions": {
"$ref": "#/components/schemas/Permissions"
"type": "array",
"items": {
"$ref": "#/components/schemas/Permission"
}
}
}
},
Expand Down Expand Up @@ -2400,7 +2412,10 @@
"$ref": "#/components/schemas/SteamID"
},
"permissions": {
"$ref": "#/components/schemas/Permissions"
"type": "array",
"items": {
"$ref": "#/components/schemas/Permission"
}
}
}
},
Expand Down
6 changes: 3 additions & 3 deletions src/auth/admins/models.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,17 @@ use cs2kz::SteamID;
use serde::{Deserialize, Serialize};
use utoipa::{IntoParams, ToSchema};

use crate::auth::Permissions;
use crate::auth::Permission;

#[derive(Debug, Serialize, Deserialize, ToSchema)]
pub struct Admin {
pub steam_id: SteamID,
pub name: String,
pub permissions: Permissions,
pub permissions: Vec<Permission>,
}

#[derive(Debug, Serialize, Deserialize, IntoParams, ToSchema)]
pub struct NewAdmin {
pub steam_id: SteamID,
pub permissions: Permissions,
pub permissions: Vec<Permission>,
}
25 changes: 18 additions & 7 deletions src/auth/admins/routes/get_many.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,14 @@ use serde::Deserialize;
use utoipa::IntoParams;

use crate::auth::admins::Admin;
use crate::auth::Permissions;
use crate::auth::{Permission, Permissions};
use crate::extractors::State;
use crate::{responses, Error, Result};

#[derive(Debug, Deserialize, IntoParams)]
pub struct GetAdminsParams {
#[serde(default)]
pub minimum_permissions: Permissions,
pub minimum_permissions: Vec<Permission>,
}

#[tracing::instrument(skip(state))]
Expand All @@ -32,8 +32,12 @@ pub async fn get_many(
state: State,
Query(params): Query<GetAdminsParams>,
) -> Result<Json<Vec<Admin>>> {
let admins = sqlx::query_as! {
Admin,
let minimum_permissions = params
.minimum_permissions
.into_iter()
.collect::<Permissions>();

let admins = sqlx::query! {
r#"
SELECT
p.steam_id `steam_id: SteamID`,
Expand All @@ -45,11 +49,18 @@ pub async fn get_many(
WHERE
(a.permissions & ?) = ?
"#,
params.minimum_permissions,
params.minimum_permissions,
minimum_permissions,
minimum_permissions,
}
.fetch_all(state.database())
.await?;
.await?
.into_iter()
.map(|row| Admin {
steam_id: row.steam_id,
name: row.name,
permissions: Permissions(row.permissions).iter().collect(),
})
.collect::<Vec<_>>();

if admins.is_empty() {
return Err(Error::NoContent);
Expand Down
9 changes: 7 additions & 2 deletions src/auth/admins/routes/get_single.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use axum::Json;
use cs2kz::SteamID;

use crate::auth::admins::Admin;
use crate::auth::Permissions;
use crate::extractors::State;
use crate::{responses, Error, Result};

Expand All @@ -20,8 +21,7 @@ use crate::{responses, Error, Result};
),
)]
pub async fn get_single(state: State, Path(steam_id): Path<SteamID>) -> Result<Json<Admin>> {
sqlx::query_as! {
Admin,
sqlx::query! {
r#"
SELECT
p.steam_id `steam_id: SteamID`,
Expand All @@ -37,6 +37,11 @@ pub async fn get_single(state: State, Path(steam_id): Path<SteamID>) -> Result<J
}
.fetch_optional(state.database())
.await?
.map(|row| Admin {
steam_id: row.steam_id,
name: row.name,
permissions: Permissions(row.permissions).iter().collect(),
})
.map(Json)
.ok_or(Error::NoContent)
}
3 changes: 3 additions & 0 deletions src/auth/admins/routes/update.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use axum::Json;

use crate::auth::admins::NewAdmin;
use crate::auth::Permissions;
use crate::extractors::State;
use crate::responses::Created;
use crate::{responses, Result};
Expand All @@ -25,6 +26,8 @@ pub async fn update(
state: State,
Json(NewAdmin { steam_id, permissions }): Json<NewAdmin>,
) -> Result<Created<()>> {
let permissions = permissions.into_iter().collect::<Permissions>();

sqlx::query! {
r#"
INSERT INTO
Expand Down
2 changes: 1 addition & 1 deletion src/auth/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ pub mod servers;
pub mod steam;

pub mod permissions;
pub use permissions::Permissions;
pub use permissions::{Permission, Permissions};

pub mod jwt;
pub use jwt::JWT;
Expand Down
117 changes: 116 additions & 1 deletion src/auth/permissions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,14 @@ impl Permissions {
pub const fn contains(self, other: Self) -> bool {
(self.0 & other.0) == other.0
}

pub const fn nth_bit(self, n: u64) -> bool {
(self.0 >> n) & 1 == 1
}

pub const fn iter(self) -> PermissionsIter {
PermissionsIter::new(self)
}
}

#[allow(dead_code)]
Expand Down Expand Up @@ -70,9 +78,104 @@ impl BitAnd for Permissions {
}
}

impl IntoIterator for Permissions {
type Item = Permission;
type IntoIter = PermissionsIter;

fn into_iter(self) -> Self::IntoIter {
self.iter()
}
}

impl FromIterator<Permission> for Permissions {
fn from_iter<T: IntoIterator<Item = Permission>>(iter: T) -> Self {
iter.into_iter()
.fold(Self::NONE, |acc, curr| acc | Self(curr as u64))
}
}

#[repr(u64)]
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, ToSchema)]
#[serde(rename_all = "snake_case")]
pub enum Permission {
#[default]
None = u64::MIN,
All = u64::MAX,

MapsView = 1 << 0,
MapsApprove = 1 << 1,
MapsEdit = 1 << 2,
MapsDeglobal = 1 << 3,

ServersApprove = 1 << 10,
ServersEdit = 1 << 11,
ServersDeglobal = 1 << 12,

BansCreate = 1 << 20,
BansEdit = 1 << 21,
BansRemove = 1 << 22,

ManageAdmins = 1 << 63,
}

pub struct PermissionsIter {
permissions: Permissions,
idx: u64,
}

impl PermissionsIter {
const fn new(permissions: Permissions) -> Self {
Self { permissions, idx: 0 }
}
}

impl Iterator for PermissionsIter {
type Item = Permission;

fn next(&mut self) -> Option<Self::Item> {
if self.idx >= 63 {
return None;
}

while self.idx < 63 && !self.permissions.nth_bit(self.idx) {
self.idx += 1;
}

if !self.permissions.nth_bit(self.idx) {
return None;
}

// TODO(AlphaKeks): I hate this.
let next = match Permissions(1_u64 << self.idx) {
Permissions::NONE => Permission::None,
Permissions::ALL => Permission::All,
Permissions::MAPS_VIEW => Permission::MapsView,
Permissions::MAPS_APPROVE => Permission::MapsApprove,
Permissions::MAPS_EDIT => Permission::MapsEdit,
Permissions::MAPS_DEGLOBAL => Permission::MapsDeglobal,
Permissions::SERVERS_APPROVE => Permission::ServersApprove,
Permissions::SERVERS_EDIT => Permission::ServersEdit,
Permissions::SERVERS_DEGLOBAL => Permission::ServersDeglobal,
Permissions::BANS_CREATE => Permission::BansCreate,
Permissions::BANS_EDIT => Permission::BansEdit,
Permissions::BANS_REMOVE => Permission::BansRemove,
Permissions::MANAGE_ADMINS => Permission::ManageAdmins,
_ => {
// Unused bit.
self.idx += 1;
return self.next();
}
};

self.idx += 1;

Some(next)
}
}

#[cfg(test)]
mod tests {
use super::Permissions;
use super::{Permission, Permissions};

#[test]
fn zero() {
Expand Down Expand Up @@ -100,4 +203,16 @@ mod tests {
assert!(a.contains(b));
assert!(!b.contains(a));
}

#[test]
fn iter() {
let perms = Permissions::BANS_EDIT | Permissions::MAPS_APPROVE | Permissions::MAPS_EDIT;
let perms = perms.iter().collect::<Vec<_>>();

assert_eq!(perms, [
Permission::MapsApprove,
Permission::MapsEdit,
Permission::BansEdit
]);
}
}

0 comments on commit a1dda05

Please sign in to comment.