Skip to content

Commit

Permalink
feat: support for custom access denied response (DDtKey#32)
Browse files Browse the repository at this point in the history
  • Loading branch information
CuriousCorrelation authored May 30, 2022
1 parent a8318a0 commit 6d58005
Show file tree
Hide file tree
Showing 4 changed files with 60 additions and 7 deletions.
6 changes: 2 additions & 4 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased] - 2022-xx-xx
### Added
- Support for custom access denied response #26

### Changed

## [v3.0.0] - 22-04-03
## [v3.0.0] - 2022-04-03
### Added
- `actix-web: 4.0.1` support #30


## [v3.0.0-beta.6] - 2022-01-08
### Added
- Support custom types for permissions/roles #25
Expand Down
16 changes: 14 additions & 2 deletions proc-macro/src/expand.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,12 @@ impl ToTokens for HasPermissions {
quote!(if _auth_details_.#check_fn(&[#args]))
};

let resp = if let Some(expr) = &self.args.error_fn {
quote!(#expr())
} else {
quote!(actix_web::HttpResponse::Forbidden().finish())
};

let stream = quote! {
#(#fn_attrs)*
#func_vis #fn_async fn #fn_name #fn_generics(
Expand All @@ -91,7 +97,7 @@ impl ToTokens for HasPermissions {
let f = || async move #func_block;
actix_web::Either::Left(f().await)
} else {
actix_web::Either::Right(actix_web::HttpResponse::Forbidden().finish())
actix_web::Either::Right(#resp)
}
}
};
Expand All @@ -104,12 +110,14 @@ struct Args {
permissions: Vec<syn::LitStr>,
secure: Option<syn::Expr>,
type_: Option<syn::Expr>,
error_fn: Option<syn::Ident>,
}

impl Args {
fn new(args: AttributeArgs) -> syn::Result<Self> {
let mut permissions = Vec::with_capacity(args.len());
let mut secure = None;
let mut error_fn = None;
let mut type_ = None;
for arg in args {
match arg {
Expand All @@ -127,10 +135,13 @@ impl Args {
} else if path.is_ident("type") {
let expr = lit_str.parse().unwrap();
type_ = Some(expr);
} else if path.is_ident("error") {
let expr = lit_str.parse().unwrap();
error_fn = Some(expr);
} else {
return Err(syn::Error::new_spanned(
path,
"Unknown identifier. Available: 'secure' and 'type'",
"Unknown identifier. Available: 'secure', 'type' and 'error'",
));
}
}
Expand All @@ -144,6 +155,7 @@ impl Args {
permissions,
secure,
type_,
error_fn,
})
}
}
16 changes: 16 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ pub use middleware::GrantsMiddleware;
/// ```
/// use actix_web::{web, get, HttpResponse};
/// use actix_web_grants::proc_macro::{has_permissions, has_roles};
/// use actix_web::http::StatusCode;
/// use actix_web::body::BoxBody;
///
/// // User should be ADMIN with OP_GET_SECRET permission
/// #[has_permissions["ROLE_ADMIN", "OP_GET_SECRET"]]
Expand All @@ -44,6 +46,20 @@ pub use middleware::GrantsMiddleware;
/// HttpResponse::Ok().body("some secured info")
/// }
///
/// // Custom access denied message.
/// #[has_roles("ADMIN", error = "access_denied")]
/// async fn role_access() -> HttpResponse {
/// HttpResponse::Ok().body("some secured info")
/// }
/// // Non-admin role accessor will receive this response.
/// // The return type of the custom function must be `actix web::HttpResponse`.
/// fn access_denied() -> HttpResponse {
/// HttpResponse::with_body(
/// StatusCode::FORBIDDEN,
/// BoxBody::new("This resource allowed only for ADMIN"),
/// )
/// }
///
/// // Additional security condition to ensure the protection of the endpoint
/// #[has_roles("USER", secure = "user_id.into_inner() == user.id")]
/// #[get("/resource/{user_id}")]
Expand Down
29 changes: 28 additions & 1 deletion tests/proc_macro/different_fn_types.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use crate::common::{self, ROLE_ADMIN, ROLE_MANAGER};
use actix_web::body::BoxBody;
use actix_web::dev::ServiceResponse;
use actix_web::error::ErrorBadRequest;
use actix_web::http::{header::AUTHORIZATION, StatusCode};
Expand Down Expand Up @@ -43,6 +44,19 @@ async fn result_response(payload: web::Query<common::NamePayload>) -> Result<Str
Ok(format!("Welcome {}!", name))
}

fn access_denied() -> HttpResponse {
HttpResponse::with_body(
StatusCode::FORBIDDEN,
BoxBody::new("This resource allowed only for ADMIN"),
)