Skip to content

Commit

Permalink
Basic group endpoints
Browse files Browse the repository at this point in the history
  • Loading branch information
augustuswm committed Sep 29, 2023
1 parent a3aa0af commit df2f140
Show file tree
Hide file tree
Showing 5 changed files with 192 additions and 4 deletions.
47 changes: 43 additions & 4 deletions rfd-api/src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,10 @@ use rfd_model::{
OAuthClientRedirectUriStore, OAuthClientSecretStore, OAuthClientStore, RfdFilter,
RfdPdfFilter, RfdPdfStore, RfdRevisionFilter, RfdRevisionStore, RfdStore, StoreError,
},
AccessToken, ApiUser, ApiUserProvider, InvalidValueError, Job, LoginAttempt, NewAccessToken,
NewApiKey, NewApiUser, NewApiUserProvider, NewJob, NewLoginAttempt, NewOAuthClient,
NewOAuthClientRedirectUri, NewOAuthClientSecret, OAuthClient, OAuthClientRedirectUri,
OAuthClientSecret,
AccessGroup, AccessToken, ApiUser, ApiUserProvider, InvalidValueError, Job, LoginAttempt,
NewAccessGroup, NewAccessToken, NewApiKey, NewApiUser, NewApiUserProvider, NewJob,
NewLoginAttempt, NewOAuthClient, NewOAuthClientRedirectUri, NewOAuthClientSecret, OAuthClient,
OAuthClientRedirectUri, OAuthClientSecret,
};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
Expand Down Expand Up @@ -758,6 +758,8 @@ impl ApiContext {
AccessTokenStore::upsert(&*self.storage, access_token).await
}

// Login Attempt Operations

pub async fn create_login_attempt(
&self,
attempt: NewLoginAttempt,
Expand Down Expand Up @@ -820,6 +822,8 @@ impl ApiContext {
LoginAttemptStore::upsert(&*self.storage, attempt).await
}

// OAuth Client Operations

pub async fn create_oauth_client(&self) -> Result<OAuthClient, StoreError> {
OAuthClientStore::upsert(&*self.storage, NewOAuthClient { id: Uuid::new_v4() }).await
}
Expand Down Expand Up @@ -886,6 +890,41 @@ impl ApiContext {
) -> Result<Option<OAuthClientRedirectUri>, StoreError> {
OAuthClientRedirectUriStore::delete(&*self.storage, id).await
}

// Group Operations
pub async fn get_groups(&self) -> Result<Vec<AccessGroup<ApiPermission>>, StoreError> {
Ok(AccessGroupStore::list(
&*self.storage,
AccessGroupFilter {
id: None,
name: None,
deleted: false,
},
&ListPagination::default().limit(UNLIMITED),
)
.await?)
}

pub async fn create_group(
&self,
group: NewAccessGroup<ApiPermission>,
) -> Result<AccessGroup<ApiPermission>, StoreError> {
AccessGroupStore::upsert(&*self.storage, &group).await
}

pub async fn update_group(
&self,
group: NewAccessGroup<ApiPermission>,
) -> Result<AccessGroup<ApiPermission>, StoreError> {
AccessGroupStore::upsert(&*self.storage, &group).await
}

pub async fn delete_group(
&self,
group_id: &Uuid,
) -> Result<Option<AccessGroup<ApiPermission>>, StoreError> {
AccessGroupStore::delete(&*self.storage, &group_id).await
}
}

#[cfg(test)]
Expand Down
133 changes: 133 additions & 0 deletions rfd-api/src/endpoints/groups.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
use dropshot::{endpoint, HttpError, HttpResponseOk, Path, RequestContext, TypedBody};
use rfd_model::{AccessGroup, NewAccessGroup};
use schemars::JsonSchema;
use serde::Deserialize;
use trace_request::trace_request;
use tracing::instrument;
use uuid::Uuid;

use crate::{
context::ApiContext, error::ApiError, permissions::ApiPermission, util::response::unauthorized,
ApiPermissions,
};

#[trace_request]
#[endpoint {
method = GET,
path = "/group",
}]
#[instrument(skip(rqctx), fields(request_id = rqctx.request_id), err(Debug))]
pub async fn get_groups(
rqctx: RequestContext<ApiContext>,
) -> Result<HttpResponseOk<Vec<AccessGroup<ApiPermission>>>, HttpError> {
let ctx = rqctx.context();
let auth = ctx.authn_token(&rqctx).await;
let caller = ctx.get_caller(&auth?).await?;

if caller.can(&ApiPermission::ListGroups) {
Ok(HttpResponseOk(
ctx.get_groups().await.map_err(ApiError::Storage)?,
))
} else {
Err(unauthorized())
}
}

#[derive(Debug, Clone, PartialEq, Deserialize, JsonSchema)]
pub struct AccessGroupUpdateParams {
name: String,
permissions: ApiPermissions,
}

#[trace_request]
#[endpoint {
method = POST,
path = "/group",
}]
#[instrument(skip(rqctx), fields(request_id = rqctx.request_id), err(Debug))]
pub async fn create_group(
rqctx: RequestContext<ApiContext>,
body: TypedBody<AccessGroupUpdateParams>,
) -> Result<HttpResponseOk<AccessGroup<ApiPermission>>, HttpError> {
let ctx = rqctx.context();
let auth = ctx.authn_token(&rqctx).await;
let caller = ctx.get_caller(&auth?).await?;

if caller.can(&ApiPermission::CreateGroup) {
let body = body.into_inner();
Ok(HttpResponseOk(
ctx.create_group(NewAccessGroup {
id: Uuid::new_v4(),
name: body.name,
permissions: body.permissions,
})
.await
.map_err(ApiError::Storage)?,
))
} else {
Err(unauthorized())
}
}

#[derive(Debug, Clone, PartialEq, Deserialize, JsonSchema)]
pub struct AccessGroupPath {
group_id: Uuid,
}

#[trace_request]
#[endpoint {
method = PUT,
path = "/group/{group_id}",
}]
#[instrument(skip(rqctx), fields(request_id = rqctx.request_id), err(Debug))]
pub async fn update_group(
rqctx: RequestContext<ApiContext>,
path: Path<AccessGroupPath>,
body: TypedBody<AccessGroupUpdateParams>,
) -> Result<HttpResponseOk<AccessGroup<ApiPermission>>, HttpError> {
let ctx = rqctx.context();
let auth = ctx.authn_token(&rqctx).await;
let caller = ctx.get_caller(&auth?).await?;
let path = path.into_inner();

if caller.can(&ApiPermission::UpdateGroup(path.group_id)) {
let body = body.into_inner();
Ok(HttpResponseOk(
ctx.update_group(NewAccessGroup {
id: path.group_id,
name: body.name,
permissions: body.permissions,
})
.await
.map_err(ApiError::Storage)?,
))
} else {
Err(unauthorized())
}
}

#[trace_request]
#[endpoint {
method = DELETE,
path = "/group/{group_id}",
}]
#[instrument(skip(rqctx), fields(request_id = rqctx.request_id), err(Debug))]
pub async fn delete_group(
rqctx: RequestContext<ApiContext>,
path: Path<AccessGroupPath>,
) -> Result<HttpResponseOk<Option<AccessGroup<ApiPermission>>>, HttpError> {
let ctx = rqctx.context();
let auth = ctx.authn_token(&rqctx).await;
let caller = ctx.get_caller(&auth?).await?;
let path = path.into_inner();

if caller.can(&ApiPermission::DeleteGroup(path.group_id)) {
Ok(HttpResponseOk(
ctx.delete_group(&path.group_id)
.await
.map_err(ApiError::Storage)?,
))
} else {
Err(unauthorized())
}
}
1 change: 1 addition & 0 deletions rfd-api/src/endpoints/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
pub mod api_user;
pub mod groups;
pub mod login;
pub mod rfd;
pub mod webhook;
8 changes: 8 additions & 0 deletions rfd-api/src/permissions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,14 @@ pub enum ApiPermission {
UpdateApiUserSelf,
UpdateApiUserAll,

// Group permissions,
ListGroups,
CreateGroup,
UpdateGroup(Uuid),
AddToGroup(Uuid),
RemoveFromGroup(Uuid),
DeleteGroup(Uuid),

// RFD access permissions
GetRfd(i32),
GetRfds(BTreeSet<i32>),
Expand Down
7 changes: 7 additions & 0 deletions rfd-api/src/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ use crate::{
create_api_user, create_api_user_token, delete_api_user_token, get_api_user,
get_api_user_token, get_self, list_api_user_tokens, update_api_user,
},
groups::{create_group, delete_group, get_groups, update_group},
login::oauth::{
client::{
create_oauth_client, create_oauth_client_redirect_uri, create_oauth_client_secret,
Expand Down Expand Up @@ -88,6 +89,12 @@ pub fn server(
api.register(create_api_user_token).unwrap();
api.register(delete_api_user_token).unwrap();

// Group Management
api.register(get_groups).unwrap();
api.register(create_group).unwrap();
api.register(update_group).unwrap();
api.register(delete_group).unwrap();

// OAuth Client Management
api.register(list_oauth_clients).unwrap();
api.register(create_oauth_client).unwrap();
Expand Down

0 comments on commit df2f140

Please sign in to comment.