common_proc_macro/
expand.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
use proc_macro2::{Ident, Span, TokenStream as TokenStream2};
use quote::{quote, ToTokens};
use syn::{AttributeArgs, ItemFn, NestedMeta, ReturnType};

pub(crate) struct HasPermissions {
    check_fn: Ident,
    func: ItemFn,
    args: Args,
}

impl HasPermissions {
    pub fn new(check_fn: &str, args: AttributeArgs, func: ItemFn) -> syn::Result<Self> {
        let check_fn: Ident = syn::parse_str(check_fn)?;

        let args = Args::new(args)?;
        if args.permissions.is_empty() {
            return Err(syn::Error::new(
                Span::call_site(),
                "The #[has_permissions(..)] macro requires at least one `permission` argument",
            ));
        }

        Ok(Self {
            check_fn,
            func,
            args,
        })
    }
}

impl ToTokens for HasPermissions {
    fn to_tokens(&self, output: &mut TokenStream2) {
        let func_vis = &self.func.vis;
        let func_block = &self.func.block;

        let fn_sig = &self.func.sig;
        let fn_attrs = &self.func.attrs;
        let fn_name = &fn_sig.ident;
        let fn_generics = &fn_sig.generics;
        let fn_args = &fn_sig.inputs;
        let fn_async = &fn_sig.asyncness.unwrap();
        let fn_output = match &fn_sig.output {
            ReturnType::Type(ref _arrow, ref ty) => ty.to_token_stream(),
            ReturnType::Default => {
                quote! {()}
            }
        };

        let check_fn = &self.check_fn;

        let args = {
            let permissions = &self.args.permissions;

            quote! {
                #(#permissions,)*
            }
        };

        let condition = if let Some(expr) = &self.args.secure {
            quote!(if _auth_details_.#check_fn(vec![#args]) && #expr)
        } else {
            quote!(if _auth_details_.#check_fn(vec![#args]))
        };

        let stream = quote! {
            #(#fn_attrs)*
            #func_vis #fn_async fn #fn_name #fn_generics(
                _auth_details_: common::security::permissions::AuthDetails,
                #fn_args
            ) -> actix_web::Either<#fn_output, actix_web::HttpResponse> {
                use common::security::permissions::{PermissionsCheck, RolesCheck};
                #condition {
                    let f = || async move #func_block;
                    actix_web::Either::Left(f().await)
                } else {
                    actix_web::Either::Right(actix_web::HttpResponse::Forbidden().finish())
                }
            }
        };

        output.extend(stream);
    }
}

struct Args {
    permissions: Vec<syn::LitStr>,
    secure: Option<syn::Expr>,
}

impl Args {
    fn new(args: AttributeArgs) -> syn::Result<Self> {
        let mut permissions = Vec::with_capacity(args.len());
        let mut secure = None;
        for arg in args {
            match arg {
                NestedMeta::Lit(syn::Lit::Str(lit)) => {
                    permissions.push(lit);
                }
                NestedMeta::Meta(syn::Meta::NameValue(syn::MetaNameValue {
                    path,
                    lit: syn::Lit::Str(lit_str),
                    ..
                })) => {
                    if path.is_ident("secure") {
                        let expr = lit_str.parse().unwrap();
                        secure = Some(expr);
                    } else {
                        return Err(syn::Error::new_spanned(
                            path,
                            "Unknown identifier. Available is 'secure'",
                        ));
                    }
                }
                _ => {
                    return Err(syn::Error::new_spanned(arg, "Unknown attribute."));
                }
            }
        }

        Ok(Args {
            permissions,
            secure,
        })
    }
}