Skip to content

Commit

Permalink
More refactoring to allow implementors to implement scope and storage…
Browse files Browse the repository at this point in the history
… operations
  • Loading branch information
augustuswm committed Mar 27, 2024
1 parent caca512 commit e6035cb
Show file tree
Hide file tree
Showing 13 changed files with 113 additions and 69 deletions.
41 changes: 25 additions & 16 deletions v-api-permission-derive/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -631,7 +631,7 @@ fn as_scope_trait_tokens(
quote! { }
};
let variant_ident = variant.ident.clone();
quote! { #permission_type::#variant_ident #fields => #to }
quote! { #permission_type::#variant_ident #fields => Some(#to) }
})
});
let from_scope_mapping = scope_settings
Expand All @@ -658,30 +658,35 @@ fn as_scope_trait_tokens(
});

quote! {
impl v_model::permissions::AsScope for #permission_type {
fn as_scope(&self) -> &str {
match self {
#(#as_scope_mapping,)*
_ => ""
}
impl v_model::permissions::AsScopeInternal for #permission_type {
fn as_scope(&self) -> Option<&str> {
<Self as v_model::permissions::AsScope>::as_scope(self).or_else(|| {
match self {
#(#as_scope_mapping,)*
_ => None,
}
})
}

fn from_scope<S>(
scope: impl Iterator<Item = S>,
) -> Result<Permissions<#permission_type>, v_model::permissions::PermissionError>
fn from_scope<T, S>(
scope: T,
) -> Permissions<#permission_type>
where
T: Iterator<Item = S> + Clone,
S: AsRef<str>,
{
let mut permissions = Permissions::new();
let mut permissions = <Self as v_model::permissions::AsScope>::from_scope(scope.clone());

for entry in scope {
match entry.as_ref() {
#(#from_scope_mapping,)*
other => return Err(v_model::permissions::PermissionError::InvalidScope(other.to_string())),
other => {
// Drop any unrecognized scopes
}
}
}

Ok(permissions)
permissions
}
}
}
Expand All @@ -697,7 +702,7 @@ fn permission_storage_trait_tokens(
let expand_tokens = permission_storage_expand_tokens(permission_type, expand_settings);

quote! {
impl v_model::permissions::PermissionStorage for #permission_type {
impl v_model::permissions::PermissionStorageInternal for #permission_type {
#contract_tokens
#expand_tokens
}
Expand Down Expand Up @@ -765,6 +770,7 @@ fn permission_storage_contract_tokens(

quote! {
fn contract(collection: &Permissions<Self>) -> Permissions<Self> {
let mut base = <Self as PermissionStorage>::contract(collection);
let mut contracted = Vec::new();

#collection_instantiation
Expand All @@ -780,7 +786,8 @@ fn permission_storage_contract_tokens(

#collections_add

contracted.into()
base.append(&mut contracted.into());
base
}
}
}
Expand Down Expand Up @@ -847,6 +854,7 @@ fn permission_storage_expand_tokens(
actor: &newtype_uuid::TypedUuid<v_model::UserId>,
actor_permissions: Option<&Permissions<Self>>,
) -> Permissions<Self> {
let mut base = <Self as PermissionStorage>::expand(collection, actor, actor_permissions.clone());
let mut expanded = Vec::new();

for p in collection.iter() {
Expand All @@ -857,7 +865,8 @@ fn permission_storage_expand_tokens(
}
}

expanded.into()
base.append(&mut expanded.into());
base
}
}
}
6 changes: 4 additions & 2 deletions v-api-permission-derive/tests/derive.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
use newtype_uuid::TypedUuid;
use std::collections::BTreeSet;
use uuid::Uuid;
use v_api::permissions::VPermission;
use v_api_permission_derive::v_api;
use v_model::{Permissions, UserId, ApiKeyId, MapperId, AccessGroupId, OAuthClientId};
use v_model::{permissions::{AsScope, PermissionStorage}, Permissions};

#[test]
fn test_derive() {
Expand All @@ -28,4 +27,7 @@ fn test_derive() {
#[v_api(expand(kind = alias, variant = ReadItem, source = actor), scope(to = "read", from = "read"))]
ReadItemsAssigned,
}

impl AsScope for AppPermissions {}
impl PermissionStorage for AppPermissions {}
}
8 changes: 4 additions & 4 deletions v-api/src/authn/jwt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,13 @@ use serde::{Deserialize, Serialize};
use thiserror::Error;
use tracing::instrument;
use v_model::{
permissions::{Permission, AsScope, PermissionStorage}, AccessTokenId, ApiUser, ApiUserProvider, UserId, UserProviderId,
AccessTokenId, ApiUser, ApiUserProvider, UserId, UserProviderId,
};

use crate::{
config::AsymmetricKey,
context::VContext,
permissions::VPermission,
permissions::VAppPermission,
};

use super::{Signer, SigningKeyError};
Expand Down Expand Up @@ -73,7 +73,7 @@ impl Claims {
expires_at: DateTime<Utc>,
) -> Self
where
T: Permission + From<VPermission> + AsScope + PermissionStorage,
T: VAppPermission,
{
Claims {
iss: ctx.public_url().to_string(),
Expand All @@ -91,7 +91,7 @@ impl Claims {
impl Jwt {
pub async fn new<T>(ctx: &VContext<T>, token: &str) -> Result<Self, JwtError>
where
T: Permission + From<VPermission> + AsScope + PermissionStorage,
T: VAppPermission,
{
tracing::trace!("Decode JWT from headers");

Expand Down
24 changes: 12 additions & 12 deletions v-api/src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ use thiserror::Error;
use tracing::{info_span, instrument, Instrument};
use uuid::Uuid;
use v_model::{
permissions::{AsScope, Caller, Permission, PermissionError, PermissionStorage, Permissions},
permissions::{AsScopeInternal, Caller, Permission, PermissionError, PermissionStorageInternal, Permissions},
schema_ext::LoginAttemptState,
storage::{
AccessGroupFilter, AccessGroupStore, AccessTokenStore, ApiKeyFilter, ApiKeyStore,
Expand Down Expand Up @@ -50,7 +50,7 @@ use crate::{
},
error::{ApiError, AppError},
mapper::{MapperRule, Mapping},
permissions::VPermission,
permissions::{VAppPermission, VPermission},
util::response::{
bad_request, client_error, internal_error, resource_error, resource_restricted,
ResourceError, ResourceResult, ToResourceResult, ToResourceResultOpt,
Expand Down Expand Up @@ -107,13 +107,13 @@ pub struct VContext<T> {
}

pub trait ApiContext: ServerContext {
type AppPermissions: Permission + From<VPermission> + AsScope;
type AppPermissions: VAppPermission;
fn v_ctx(&self) -> &VContext<Self::AppPermissions>;
}

impl<T> ApiContext for VContext<T>
where
T: Permission + From<VPermission> + AsScope,
T: VAppPermission,
{
type AppPermissions = T;
fn v_ctx(&self) -> &VContext<T> {
Expand Down Expand Up @@ -157,7 +157,7 @@ impl From<VContextCallerError> for HttpError {

impl<T, U> VContextWithCaller<T> for RequestContext<U>
where
T: Permission + From<VPermission> + AsScope + PermissionStorage,
T: VAppPermission,
U: ApiContext<AppPermissions = T>,
{
async fn as_ctx(&self) -> Result<(&VContext<T>, Caller<T>), VContextCallerError> {
Expand Down Expand Up @@ -229,7 +229,7 @@ enum BasePermissions<T: Permission> {

impl<T> VContext<T>
where
T: Permission + From<VPermission> + AsScope + PermissionStorage,
T: VAppPermission,
{
pub async fn new(
public_url: String,
Expand Down Expand Up @@ -351,7 +351,7 @@ where
BasePermissions::Full => user_permissions.clone(),
BasePermissions::Restricted(permissions) => {
let token_permissions =
T::expand(permissions, &user.id, Some(&user_permissions));
<T as PermissionStorageInternal>::expand(permissions, &user.id, Some(&user_permissions));
token_permissions.intersect(&user_permissions)
}
};
Expand Down Expand Up @@ -470,7 +470,7 @@ where
AuthToken::Jwt(jwt) => {
// AuthnToken::Jwt can only be generated from a verified JWT
let permissions = match &jwt.claims.scp {
Some(scp) => BasePermissions::Restricted(T::from_scope(scp.iter())?),
Some(scp) => BasePermissions::Restricted(<T as AsScopeInternal>::from_scope(scp.iter())),
None => BasePermissions::Full,
};
Ok((jwt.claims.sub, permissions))
Expand Down Expand Up @@ -508,7 +508,7 @@ where
let permissions = groups
.into_iter()
.fold(Permissions::new(), |mut aggregate, group| {
let mut expanded = T::expand(&group.permissions, &user.id, Some(&user.permissions));
let mut expanded = <T as PermissionStorageInternal>::expand(&group.permissions, &user.id, Some(&user.permissions));

tracing::trace!(group_id = ?group.id, group_name = ?group.name, permissions = ?expanded, "Transformed group into permission set");
aggregate.append(&mut expanded);
Expand Down Expand Up @@ -810,7 +810,7 @@ where
.await
.map(|opt| {
opt.map(|mut user| {
user.permissions = T::expand(&user.permissions, &user.id, None);
user.permissions = <T as PermissionStorageInternal>::expand(&user.permissions, &user.id, None);
user
})
})
Expand Down Expand Up @@ -853,7 +853,7 @@ where
permissions: permissions,
groups: groups,
};
new_user.permissions = T::contract(&new_user.permissions);
new_user.permissions = <T as PermissionStorageInternal>::contract(&new_user.permissions);
ApiUserStore::upsert(&*self.storage, new_user)
.await
.to_resource_result()
Expand All @@ -872,7 +872,7 @@ where
&VPermission::ManageApiUser(api_user.id).into(),
&VPermission::ManageApiUsersAll.into(),
]) {
api_user.permissions = T::contract(&api_user.permissions);
api_user.permissions = <T as PermissionStorageInternal>::contract(&api_user.permissions);
ApiUserStore::upsert(&*self.storage, api_user)
.await
.to_resource_result()
Expand Down
10 changes: 3 additions & 7 deletions v-api/src/endpoints/login/oauth/code.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ use tap::TapFallible;
use tracing::instrument;
use v_model::{
schema_ext::LoginAttemptState, LoginAttempt, LoginAttemptId, NewLoginAttempt, OAuthClient,
OAuthClientId, permissions::{AsScope, PermissionStorage}
OAuthClientId, permissions::PermissionStorage
};

use super::{OAuthProvider, OAuthProviderNameParam, UserInfoProvider};
Expand All @@ -39,7 +39,7 @@ use crate::{
LoginError, UserInfo,
},
error::ApiError,
permissions::{VAppPermission, VPermission},
permissions::VAppPermission,
secrets::OpenApiSecretString,
util::{
request::RequestCookies,
Expand Down Expand Up @@ -182,9 +182,6 @@ where

// Check that the passed in scopes are valid. The scopes are not currently restricted by client
let scope = query.scope.unwrap_or_else(|| DEFAULT_SCOPE.to_string());
let scope_error = VPermission::from_scope_arg(&scope)
.err()
.map(|_| "invalid_scope".to_string());

// Construct a new login attempt with the minimum required values
let mut attempt = NewLoginAttempt::new(
Expand All @@ -202,8 +199,7 @@ where
// TODO: Make this configurable
attempt.expires_at = Some(Utc::now().add(TimeDelta::try_minutes(5).unwrap()));

// Assign any scope errors that arose
attempt.error = scope_error;
// TODO: Assign any scope errors that arose. Currently we drop any unknown or unsupported scopes

// Add in the user defined state and redirect uri
attempt.state = Some(query.state);
Expand Down
4 changes: 2 additions & 2 deletions v-api/src/endpoints/mappers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ use v_model::{permissions::{Permission, AsScope, PermissionStorage}, Mapper, Map
use crate::{
context::ApiContext,
mapper::MappingRules,
permissions::VPermission,
permissions::{VAppPermission, VPermission},
util::{
is_uniqueness_error,
response::{conflict, ResourceError},
Expand Down Expand Up @@ -58,7 +58,7 @@ pub async fn create_mapper_op<T, U>(
) -> Result<HttpResponseCreated<Mapper>, HttpError>
where
T: ApiContext<AppPermissions = U>,
T::AppPermissions: Permission + From<VPermission> + AsScope + PermissionStorage,
T::AppPermissions: VAppPermission,
{
let ctx = rqctx.v_ctx();
let auth = ctx.authn_token(&rqctx).await?;
Expand Down
6 changes: 3 additions & 3 deletions v-api/src/mapper/default.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,15 @@ use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use std::collections::BTreeSet;
use v_model::{
permissions::{Permission, Permissions, AsScope, PermissionStorage},
permissions::Permissions,
storage::StoreError,
AccessGroupId,
};

use crate::{
context::VContext,
endpoints::login::UserInfo,
permissions::VPermission,
permissions::VAppPermission,
util::response::ResourceResult,
};

Expand All @@ -32,7 +32,7 @@ pub struct DefaultMapper<T> {
#[async_trait]
impl<T> MapperRule<T> for DefaultMapper<T>
where
T: Permission + From<VPermission> + AsScope + PermissionStorage,
T: VAppPermission,
{
async fn permissions_for(
&self,
Expand Down
6 changes: 3 additions & 3 deletions v-api/src/mapper/email_address.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,15 @@ use newtype_uuid::TypedUuid;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use v_model::{
permissions::{Permission, Permissions, AsScope, PermissionStorage},
permissions::Permissions,
storage::StoreError,
AccessGroupId,
};

use crate::{
context::VContext,
endpoints::login::UserInfo,
permissions::VPermission,
permissions::VAppPermission,
util::response::ResourceResult,
};

Expand All @@ -34,7 +34,7 @@ pub struct EmailAddressMapper<T> {
#[async_trait]
impl<T> MapperRule<T> for EmailAddressMapper<T>
where
T: Permission + From<VPermission> + AsScope + PermissionStorage,
T: VAppPermission
{
async fn permissions_for(
&self,
Expand Down
6 changes: 3 additions & 3 deletions v-api/src/mapper/email_domain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,15 @@ use newtype_uuid::TypedUuid;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use v_model::{
permissions::{Permission, Permissions, AsScope, PermissionStorage},
permissions::Permissions,
storage::StoreError,
AccessGroupId,
};

use crate::{
context::VContext,
endpoints::login::UserInfo,
permissions::VPermission,
permissions::VAppPermission,
util::response::ResourceResult,
};

Expand All @@ -34,7 +34,7 @@ pub struct EmailDomainMapper<T> {
#[async_trait]
impl<T> MapperRule<T> for EmailDomainMapper<T>
where
T: Permission + From<VPermission> + AsScope + PermissionStorage,
T: VAppPermission,
{
async fn permissions_for(
&self,
Expand Down
Loading

0 comments on commit e6035cb

Please sign in to comment.