11package io .permit .sdk .enforcement ;
22
3+ import com .google .common .primitives .Booleans ;
34import com .google .gson .Gson ;
45import io .permit .sdk .PermitConfig ;
56import io .permit .sdk .api .HttpLoggingInterceptor ;
67import io .permit .sdk .util .Context ;
78import io .permit .sdk .util .ContextStore ;
89
910import java .io .IOException ;
11+ import java .util .ArrayList ;
12+ import java .util .Arrays ;
1013import java .util .HashMap ;
14+ import java .util .List ;
15+ import java .util .stream .Collectors ;
1116
1217import okhttp3 .MediaType ;
1318import okhttp3 .OkHttpClient ;
@@ -26,7 +31,7 @@ class EnforcerInput {
2631 public final User user ;
2732 public final String action ;
2833 public final Resource resource ;
29- public final HashMap < String , Object > context ;
34+ public final Context context ;
3035
3136 /**
3237 * Constructs a new instance of the {@code EnforcerInput} class with the specified data.
@@ -36,7 +41,7 @@ class EnforcerInput {
3641 * @param resource The resource on which the action is performed.
3742 * @param context The context for the authorization check.
3843 */
39- EnforcerInput (User user , String action , Resource resource , HashMap < String , Object > context ) {
44+ EnforcerInput (User user , String action , Resource resource , Context context ) {
4045 this .user = user ;
4146 this .action = action ;
4247 this .resource = resource ;
@@ -76,6 +81,48 @@ class OpaResult {
7681 }
7782}
7883
84+
85+ /**
86+ * The {@code TenantResult} class represents a single tenant returned by the checkInAllTenants query.
87+ */
88+ class TenantResult {
89+ public final Boolean allow ;
90+
91+ public final TenantDetails tenant ;
92+
93+ public TenantResult (Boolean allow , TenantDetails tenant ) {
94+ this .allow = allow ;
95+ this .tenant = tenant ;
96+ }
97+ }
98+
99+ /**
100+ * The {@code AllTenantsResult} class represents the result of the checkInAllTenants query.
101+ */
102+ class AllTenantsResult {
103+ public final TenantResult [] allowed_tenants ;
104+
105+ public AllTenantsResult (TenantResult [] allowed_tenants ) {
106+ this .allowed_tenants = allowed_tenants ;
107+ }
108+ }
109+
110+ /**
111+ * The {@code OpaBulkResult} class represents the result of a Permit bulk enforcement check returned by the policy agent.
112+ */
113+ class OpaBulkResult {
114+ public final List <OpaResult > allow ;
115+
116+ /**
117+ * Constructs a new instance of the {@code OpaResult} class with the specified result.
118+ *
119+ * @param allow {@code true} if the action is allowed, {@code false} otherwise.
120+ */
121+ OpaBulkResult (List <OpaResult > allow ) {
122+ this .allow = allow ;
123+ }
124+ }
125+
79126/**
80127 * The {@code Enforcer} class is responsible for performing permission checks against the PDP.
81128 * It implements the {@link IEnforcerApi} interface.
@@ -257,8 +304,215 @@ public boolean checkUrl(User user, String httpMethod, String url, String tenant,
257304 }
258305 }
259306
307+ @ Override
308+ public boolean [] bulkCheck (List <CheckQuery > checks ) throws IOException {
309+ List <EnforcerInput > inputs = new ArrayList <>();
310+
311+ for (CheckQuery check : checks ) {
312+ Resource normalizedResource = check .resource .normalize (this .config );
313+ inputs .add (new EnforcerInput (check .user , check .action , normalizedResource , check .context ));
314+ }
315+
316+ // request body
317+ Gson gson = new Gson ();
318+ String requestBody = gson .toJson (inputs );
319+ RequestBody body = RequestBody .create (requestBody , MediaType .parse ("application/json" ));
320+
321+ // create the request
322+ String url = String .format ("%s/allowed/bulk" , this .config .getPdpAddress ());
323+ Request request = new Request .Builder ()
324+ .url (url )
325+ .post (body )
326+ .addHeader ("Content-Type" , "application/json" )
327+ .addHeader ("Authorization" , String .format ("Bearer %s" , this .config .getToken ()))
328+ .addHeader ("X-Permit-SDK-Version" , String .format ("java:%s" , this .config .version ))
329+ .build ();
330+
331+ try (Response response = client .newCall (request ).execute ()) {
332+ if (!response .isSuccessful ()) {
333+ String errorMessage = String .format (
334+ "Error in %s: got unexpected status code %d" ,
335+ bulkCheckRepr (inputs ),
336+ response .code ()
337+ );
338+ logger .error (errorMessage );
339+ throw new IOException (errorMessage );
340+ }
341+ ResponseBody responseBody = response .body ();
342+ if (responseBody == null ) {
343+ String errorMessage = String .format (
344+ "Error in %s: got empty response" ,
345+ bulkCheckRepr (inputs )
346+ );
347+ logger .error (errorMessage );
348+ throw new IOException (errorMessage );
349+ }
350+
351+ String responseString = responseBody .string ();
352+ OpaBulkResult result = gson .fromJson (responseString , OpaBulkResult .class );
353+ if (this .config .isDebugMode ()) {
354+ for (int i = 0 ; i < result .allow .size (); i ++) {
355+ logger .info (String .format (
356+ "permit.bulkCheck[%d/%d](%s, %s, %s) = %s" ,
357+ i + 1 ,
358+ result .allow .size (),
359+ inputs .get (i ).user ,
360+ inputs .get (i ).action ,
361+ inputs .get (i ).resource ,
362+ result .allow .get (i ).allow
363+ ));
364+ }
365+
366+ }
367+ return Booleans .toArray (result .allow .stream ().map (r -> r .allow ).collect (Collectors .toList ()));
368+ }
369+ }
370+
371+ @ Override
372+ public List <TenantDetails > checkInAllTenants (User user , String action , Resource resource , Context context ) throws IOException {
373+ Resource normalizedResource = resource .normalize (this .config );
374+ Context queryContext = this .contextStore .getDerivedContext (context );
375+
376+ EnforcerInput input = new EnforcerInput (
377+ user ,
378+ action ,
379+ normalizedResource ,
380+ queryContext
381+ );
382+
383+ // request body
384+ Gson gson = new Gson ();
385+ String requestBody = gson .toJson (input );
386+ RequestBody body = RequestBody .create (requestBody , MediaType .parse ("application/json" ));
387+
388+ // create the request
389+ String url = String .format ("%s/allowed/all-tenants" , this .config .getPdpAddress ());
390+ Request request = new Request .Builder ()
391+ .url (url )
392+ .post (body )
393+ .addHeader ("Content-Type" , "application/json" )
394+ .addHeader ("Authorization" , String .format ("Bearer %s" , this .config .getToken ()))
395+ .addHeader ("X-Permit-SDK-Version" , String .format ("java:%s" , this .config .version ))
396+ .build ();
397+
398+ try (Response response = client .newCall (request ).execute ()) {
399+ if (!response .isSuccessful ()) {
400+ String errorMessage = String .format (
401+ "Error in permit.checkInAllTenants(%s, %s, %s): got unexpected status code %d" ,
402+ user .toString (),
403+ action ,
404+ resource ,
405+ response .code ()
406+ );
407+ logger .error (errorMessage );
408+ throw new IOException (errorMessage );
409+ }
410+ ResponseBody responseBody = response .body ();
411+ if (responseBody == null ) {
412+ String errorMessage = String .format (
413+ "Error in permit.checkInAllTenants(%s, %s, %s): got empty response" ,
414+ user ,
415+ action ,
416+ resource
417+ );
418+ logger .error (errorMessage );
419+ throw new IOException (errorMessage );
420+ }
421+ String responseString = responseBody .string ();
422+ AllTenantsResult result = gson .fromJson (responseString , AllTenantsResult .class );
423+ List <TenantDetails > tenants = Arrays .stream (result .allowed_tenants ).map (r -> r .tenant ).collect (Collectors .toList ());
424+ if (this .config .isDebugMode ()) {
425+ logger .info (String .format (
426+ "permit.checkInAllTenants(%s, %s, %s) => allowed in: [%s]" ,
427+ user ,
428+ action ,
429+ resource ,
430+ tenants .stream ().map (t -> t .key ).collect (Collectors .joining (", " ))
431+ ));
432+ }
433+ return tenants ;
434+ }
435+ }
436+
437+ @ Override
438+ public List <TenantDetails > checkInAllTenants (User user , String action , Resource resource ) throws IOException {
439+ return checkInAllTenants (user , action , resource , new Context ());
440+ }
441+
442+ @ Override
443+ public UserPermissions getUserPermissions (GetUserPermissionsQuery input ) throws IOException {
444+ // request body
445+ Gson gson = new Gson ();
446+ String requestBody = gson .toJson (input );
447+ RequestBody body = RequestBody .create (requestBody , MediaType .parse ("application/json" ));
448+
449+ // create the request
450+ String url = String .format ("%s/user-permissions" , this .config .getPdpAddress ());
451+ Request request = new Request .Builder ()
452+ .url (url )
453+ .post (body )
454+ .addHeader ("Content-Type" , "application/json" )
455+ .addHeader ("Authorization" , String .format ("Bearer %s" , this .config .getToken ()))
456+ .addHeader ("X-Permit-SDK-Version" , String .format ("java:%s" , this .config .version ))
457+ .build ();
458+
459+ try (Response response = client .newCall (request ).execute ()) {
460+ if (!response .isSuccessful ()) {
461+ String errorMessage = String .format (
462+ "Error in permit.getUserPermissions(%s, %s, %s, %s): got unexpected status code %d" ,
463+ input .user .toString (),
464+ input .tenants .toString (),
465+ input .resource_types .toString (),
466+ input .resources .toString (),
467+ response .code ()
468+ );
469+ logger .error (errorMessage );
470+ throw new IOException (errorMessage );
471+ }
472+ ResponseBody responseBody = response .body ();
473+ if (responseBody == null ) {
474+ String errorMessage = String .format (
475+ "Error in permit.getUserPermissions(%s, %s, %s, %s): got empty response" ,
476+ input .user .toString (),
477+ input .tenants .toString (),
478+ input .resource_types .toString (),
479+ input .resources .toString ()
480+ );
481+ logger .error (errorMessage );
482+ throw new IOException (errorMessage );
483+ }
484+ String responseString = responseBody .string ();
485+ UserPermissions result = gson .fromJson (responseString , UserPermissions .class );
486+ if (this .config .isDebugMode ()) {
487+ logger .info (String .format (
488+ "permit.getUserPermissions(%s, %s, %s, %s) => returned %d permissions on %d objects" ,
489+ input .user .toString (),
490+ input .tenants != null ? input .tenants .toString () : "null" ,
491+ input .resource_types != null ? input .resource_types .toString () : "null" ,
492+ input .resources != null ? input .resources .toString () : "null" ,
493+ result .values ().stream ().map (obj -> obj .permissions .size ()).reduce (0 , Integer ::sum ),
494+ result .keySet ().size ()
495+ ));
496+ }
497+ return result ;
498+ }
499+ }
500+
260501 @ Override
261502 public boolean checkUrl (User user , String httpMethod , String url , String tenant ) throws IOException {
262503 return this .checkUrl (user , httpMethod , url , tenant , new Context ());
263504 }
505+
506+ private String bulkCheckRepr (List <EnforcerInput > inputs ) {
507+ return String .format (
508+ "permit.bulkCheck(%s)" ,
509+ inputs .stream ().map (i -> String .format (
510+ "%s, %s, %s, %s" ,
511+ i .user ,
512+ i .action ,
513+ i .resource ,
514+ i .context
515+ )).collect (Collectors .toList ())
516+ );
517+ }
264518}
0 commit comments