@@ -23,6 +23,12 @@ def set_if_not_none(d: dict, k: str, v):
2323Action = str
2424Resource = Union [dict , str ]
2525
26+ CheckQuery = {
27+ "user" : User ,
28+ "action" : Action ,
29+ "resource" : Resource ,
30+ }
31+
2632
2733class Enforcer :
2834 def __init__ (self , config : PermitConfig ):
@@ -42,6 +48,125 @@ def context_store(self):
4248 """
4349 return self ._context_store
4450
51+ async def bulk_check (
52+ self ,
53+ checks : list [CheckQuery ],
54+ context : Context = {},
55+ ) -> list [bool ]:
56+ """
57+ Checks if a user is authorized to perform an action on a resource within the specified context.
58+
59+ Args:
60+ checks: A list of CheckQuery objects representing the authorization queries to be performed.
61+ context: The context object representing the context in which the action is performed. Defaults to None.
62+
63+ Returns:
64+ list[bool]: A list of booleans indicating whether the user is authorized for each resource.
65+
66+ Raises:
67+ PermitConnectionError: If an error occurs while sending the authorization request to the PDP.
68+
69+ Examples:
70+
71+ # Bulk query of multiple check conventions
72+ await permit.bulk_check([
73+ {
74+ "user": user,
75+ "action": "close",
76+ "resource": {type: "issue", key: "1234"},
77+ },
78+ {
79+ "user": {key: "user"},
80+ "action": "close",
81+ "resource": "issue:1235",
82+ },
83+ {
84+ "user": "user_a",
85+ "action": "close",
86+ "resource": "issue",
87+ },
88+ ])
89+ """
90+
91+ input = []
92+ for check in checks :
93+ normalized_user : UserInput = (
94+ UserInput (key = check ["user" ])
95+ if isinstance (check ["user" ], str )
96+ else UserInput (** check ["user" ])
97+ )
98+ normalized_resource : ResourceInput = self ._normalize_resource (
99+ (
100+ self ._resource_from_string (check ["resource" ])
101+ if isinstance (check ["resource" ], str )
102+ else ResourceInput (** check ["resource" ])
103+ )
104+ )
105+ query_context = self ._context_store .get_derived_context (context )
106+ input .append (
107+ dict (
108+ user = normalized_user .dict (exclude_unset = True ),
109+ action = check ["action" ],
110+ resource = normalized_resource .dict (exclude_unset = True ),
111+ context = query_context ,
112+ )
113+ )
114+
115+ async with aiohttp .ClientSession (headers = self ._headers ) as session :
116+ check_url = f"{ self ._base_url } /allowed/bulk"
117+ try :
118+ async with session .post (
119+ check_url ,
120+ data = json .dumps (input ),
121+ ) as response :
122+ if response .status != 200 :
123+ error_json : dict = await response .json ()
124+ logger .error (
125+ "error in permit.check({}):\n {}\n {}" .format (
126+ (
127+ [
128+ [
129+ check .get ("user" ),
130+ check .get ("action" ),
131+ check .get ("resource" ),
132+ ]
133+ for check in input
134+ ]
135+ ),
136+ f"status code: { response .status } " ,
137+ repr (error_json ),
138+ )
139+ )
140+ raise PermitConnectionError
141+ content : dict = await response .json ()
142+ logger .debug (
143+ f"permit.check() response:\n input: { pformat (input , indent = 2 )} \n response status: { response .status } \n response data: { pformat (content , indent = 2 )} "
144+ )
145+ data = content .get (
146+ "allow" , content .get ("result" , {}).get ("allow" , [])
147+ )
148+ decisions : list [bool ] = [
149+ bool (item .get ("allow" , False )) for item in data
150+ ]
151+ except aiohttp .ClientError as err :
152+ logger .error (
153+ "error in permit.check({}):\n {}" .format (
154+ (
155+ [
156+ [
157+ check .get ("user" ),
158+ check .get ("action" ),
159+ check .get ("resource" ),
160+ ]
161+ for check in input
162+ ]
163+ ),
164+ err ,
165+ )
166+ )
167+ raise PermitConnectionError
168+ return decisions
169+
45170 async def check (
46171 self ,
47172 user : User ,
0 commit comments