From 95040d06b8ca3350133744a9c36930ab7a846e11 Mon Sep 17 00:00:00 2001 From: Darin Kelkhoff Date: Wed, 8 Feb 2023 14:29:31 -0600 Subject: [PATCH] Initial checkin --- .../Auth0PermissionsHelper.java | 516 ++++++++++++++++++ 1 file changed, 516 insertions(+) create mode 100644 qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/modules/authentication/implementations/Auth0PermissionsHelper.java diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/modules/authentication/implementations/Auth0PermissionsHelper.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/modules/authentication/implementations/Auth0PermissionsHelper.java new file mode 100644 index 00000000..4bd5acc0 --- /dev/null +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/modules/authentication/implementations/Auth0PermissionsHelper.java @@ -0,0 +1,516 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2021-2023. Kingsrook, LLC + * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States + * contact@kingsrook.com + * https://github.com/Kingsrook/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package com.kingsrook.qqq.backend.core.modules.authentication.implementations; + + +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.net.URI; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Collection; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; +import java.util.TreeSet; +import com.kingsrook.qqq.backend.core.actions.permissions.PermissionsHelper; +import com.kingsrook.qqq.backend.core.exceptions.QException; +import com.kingsrook.qqq.backend.core.model.metadata.QInstance; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpEntityEnclosingRequestBase; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.client.methods.HttpPatch; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.entity.StringEntity; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClientBuilder; +import org.apache.http.util.EntityUtils; +import org.json.JSONArray; +import org.json.JSONObject; + + +/******************************************************************************* + ** + *******************************************************************************/ +public class Auth0PermissionsHelper +{ + private String baseUrl; + private String apiName; + private String token; + + + + /******************************************************************************* + ** Constructor + ** + *******************************************************************************/ + public Auth0PermissionsHelper(String baseUrl, String apiName, String token) + { + this.baseUrl = baseUrl; + this.apiName = apiName; + this.token = token; + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + public Set getCurrentAuth0Permissions() throws QException + { + Set rs = new LinkedHashSet<>(); + try(CloseableHttpClient httpClient = HttpClientBuilder.create().build()) + { + HttpGet request = new HttpGet(baseUrl + "/resource-servers/" + URLEncoder.encode(apiName, StandardCharsets.UTF_8)); + request.addHeader("Authorization", "Bearer " + token); + request.addHeader("Content-Type", "application/json"); + + try(CloseableHttpResponse response = httpClient.execute(request)) + { + logResponseStatus(response); + + String body = EntityUtils.toString(response.getEntity()); + JSONObject json = new JSONObject(body); + JSONArray scopes = json.getJSONArray("scopes"); + for(int i = 0; i < scopes.length(); i++) + { + JSONObject scope = scopes.getJSONObject(i); + rs.add(scope.getString("value")); + } + } + } + catch(Exception e) + { + e.printStackTrace(); + throw (new QException("Error getting current auth0 permissions", e)); + } + + return (rs); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + public Set getRoles() throws QException + { + Set rs = new LinkedHashSet<>(); + try(CloseableHttpClient httpClient = HttpClientBuilder.create().build()) + { + HttpGet request = new HttpGet(baseUrl + "roles"); + request.addHeader("Authorization", "Bearer " + token); + request.addHeader("Content-Type", "application/json"); + + try(CloseableHttpResponse response = httpClient.execute(request)) + { + logResponseStatus(response); + + String body = EntityUtils.toString(response.getEntity()); + JSONArray json = new JSONArray(body); + for(int i = 0; i < json.length(); i++) + { + JSONObject role = json.getJSONObject(i); + rs.add(role.getString("id")); + } + } + } + catch(Exception e) + { + e.printStackTrace(); + throw (new QException("Error getting auth0 roles", e)); + } + + return (rs); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + public Set getPermissionsForRole(String roleId) throws QException + { + Set rs = new LinkedHashSet<>(); + try(CloseableHttpClient httpClient = HttpClientBuilder.create().build()) + { + for(int page = 0; ; page++) + { + HttpGet request = new HttpGet(baseUrl + "roles/" + roleId + "/permissions?page=" + page); + request.addHeader("Authorization", "Bearer " + token); + request.addHeader("Content-Type", "application/json"); + + try(CloseableHttpResponse response = httpClient.execute(request)) + { + logResponseStatus(response); + + String body = EntityUtils.toString(response.getEntity()); + JSONArray json = new JSONArray(body); + if(json.isEmpty()) + { + break; + } + + for(int i = 0; i < json.length(); i++) + { + JSONObject permission = json.getJSONObject(i); + rs.add(permission.getString("permission_name")); + } + } + } + } + catch(Exception e) + { + e.printStackTrace(); + throw (new QException("Error getting auth0 permissions for role", e)); + } + + return (rs); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + public void addPermissionsToRole(String roleId, Set permissionsToAdd) throws QException + { + try(CloseableHttpClient httpClient = HttpClientBuilder.create().build()) + { + HttpPost request = new HttpPost(baseUrl + "/roles/" + roleId + "/permissions"); + request.addHeader("Authorization", "Bearer " + token); + request.addHeader("Content-Type", "application/json"); + + JSONArray permissions = new JSONArray(); + for(String permissionName : permissionsToAdd) + { + JSONObject permission = new JSONObject(); + permissions.put(permission); + permission.put("resource_server_identifier", apiName); + permission.put("permission_name", permissionName); + } + JSONObject body = new JSONObject(); + body.put("permissions", permissions); + + request.setEntity(new StringEntity(body.toString())); + + try(CloseableHttpResponse response = httpClient.execute(request)) + { + logResponseStatus(response); + } + } + catch(Exception e) + { + e.printStackTrace(); + throw (new QException("Error storing permissions for role in auth0", e)); + } + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + public void removePermissionsFromRole(String roleId, Set permissionsToRemove) throws QException + { + try(CloseableHttpClient httpClient = HttpClientBuilder.create().build()) + { + HttpDeleteWithBody request = new HttpDeleteWithBody(baseUrl + "/roles/" + roleId + "/permissions"); + request.addHeader("Authorization", "Bearer " + token); + request.addHeader("Content-Type", "application/json"); + + JSONArray permissions = new JSONArray(); + for(String permissionName : permissionsToRemove) + { + JSONObject permission = new JSONObject(); + permissions.put(permission); + permission.put("resource_server_identifier", apiName); + permission.put("permission_name", permissionName); + } + JSONObject body = new JSONObject(); + body.put("permissions", permissions); + + request.setEntity(new StringEntity(body.toString())); + + try(CloseableHttpResponse response = httpClient.execute(request)) + { + logResponseStatus(response); + } + } + catch(Exception e) + { + e.printStackTrace(); + throw (new QException("Error storing permissions for role in auth0", e)); + } + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + public Set getPermissionsInInstanceButNotInAuth0(QInstance qInstance, Collection permissionsCurrentlyInAuth0) + { + List allInstancePermissions = new ArrayList<>(PermissionsHelper.getAllAvailablePermissionNames(qInstance).stream().filter(p -> !p.contains(".bulk")).toList()); + allInstancePermissions.removeAll(permissionsCurrentlyInAuth0); + return new TreeSet<>(allInstancePermissions); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + public Set getPermissionsInAuth0ButNotInInstance(QInstance qInstance, Set currentAuth0Permissions) + { + List allInstancePermissions = new ArrayList<>(PermissionsHelper.getAllAvailablePermissionNames(qInstance).stream().filter(p -> !p.contains(".bulk")).toList()); + Set rs = new TreeSet<>(currentAuth0Permissions); + allInstancePermissions.forEach(rs::remove); + return (rs); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + public void addPermissionsToAuth0(QInstance qInstance, Collection permissionsCurrentlyInAuth0, Collection permissionsToAddToAuth0) throws QException + { + Set permissions = new TreeSet<>(); + permissions.addAll(permissionsCurrentlyInAuth0); + permissions.addAll(permissionsToAddToAuth0); + storePermissionsInAuth0(qInstance, permissions); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + public void removePermissionsToAuth0(QInstance qInstance, Collection permissionsCurrentlyInAuth0, Collection permissionsToRemoveFromAuth0) throws QException + { + Set permissions = new TreeSet<>(permissionsCurrentlyInAuth0); + permissions.removeAll(permissionsToRemoveFromAuth0); + storePermissionsInAuth0(qInstance, permissions); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + public void replaceAuth0PermissionsWithAllFromInstance(QInstance qInstance) throws QException + { + List permissions = PermissionsHelper.getAllAvailablePermissionNames(qInstance).stream().filter(p -> !p.contains(".bulk")).toList(); + storePermissionsInAuth0(qInstance, permissions); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + private void storePermissionsInAuth0(QInstance qInstance, Collection permissions) throws QException + { + try(CloseableHttpClient httpClient = HttpClientBuilder.create().build()) + { + HttpPatch request = new HttpPatch(baseUrl + "/resource-servers/" + URLEncoder.encode(apiName, StandardCharsets.UTF_8)); + request.addHeader("Authorization", "Bearer " + token); + request.addHeader("Content-Type", "application/json"); + + buildRequestBodyWithScopesFromPermissions(qInstance, permissions, request); + + try(CloseableHttpResponse response = httpClient.execute(request)) + { + logResponseStatus(response); + } + } + catch(Exception e) + { + e.printStackTrace(); + throw (new QException("Error storing permissions in auth0", e)); + } + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + private static void logResponseStatus(CloseableHttpResponse response) throws IOException + { + if(response.getStatusLine() != null) + { + Integer statusCode = response.getStatusLine().getStatusCode(); + String statusReasonPhrase = response.getStatusLine().getReasonPhrase(); + System.out.println("Result: " + statusCode + " : " + statusReasonPhrase); + + if(statusCode > 299) + { + System.out.println(EntityUtils.toString(response.getEntity())); + throw (new IllegalStateException("Bad status code: " + statusCode)); + } + } + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + private static void buildRequestBodyWithScopesFromPermissions(QInstance qInstance, Collection permissions, HttpPatch request) throws UnsupportedEncodingException + { + JSONArray scopes = new JSONArray(); + for(String permissionName : permissions) + { + String[] parts = permissionName.split("\\.", 2); + String name = parts[0]; + String permission = parts[1]; + + String object = name; + if(qInstance.getTable(name) != null) + { + object = qInstance.getTable(name).getLabel() + " table"; + } + else if(qInstance.getProcess(name) != null) + { + object = qInstance.getProcess(name).getLabel() + " process"; + } + else if(qInstance.getReport(name) != null) + { + object = qInstance.getReport(name).getLabel() + " report"; + } + else if(qInstance.getWidget(name) != null) + { + object = qInstance.getWidget(name).getLabel() + " widget"; + } + else if(qInstance.getApp(name) != null) + { + object = qInstance.getApp(name).getLabel() + " app"; + } + String verb = permission.equals("hasAccess") ? "access" : permission; + + JSONObject scopeObject = new JSONObject(); + scopeObject.put("value", permissionName); + scopeObject.put("description", "Permission to " + verb + " the " + object); + scopes.put(scopeObject); + } + + JSONObject body = new JSONObject(); + body.put("scopes", scopes); + + request.setEntity(new StringEntity(body.toString())); + } + + + + /******************************************************************************* + ** Getter for baseUrl + *******************************************************************************/ + public String getBaseUrl() + { + return (this.baseUrl); + } + + + + /******************************************************************************* + ** Setter for baseUrl + *******************************************************************************/ + public void setBaseUrl(String baseUrl) + { + this.baseUrl = baseUrl; + } + + + + /******************************************************************************* + ** Fluent setter for baseUrl + *******************************************************************************/ + public Auth0PermissionsHelper withUrl(String baseUrl) + { + this.baseUrl = baseUrl; + return (this); + } + + + + /******************************************************************************* + ** Getter for token + *******************************************************************************/ + public String getToken() + { + return (this.token); + } + + + + /******************************************************************************* + ** Setter for token + *******************************************************************************/ + public void setToken(String token) + { + this.token = token; + } + + + + /******************************************************************************* + ** Fluent setter for token + *******************************************************************************/ + public Auth0PermissionsHelper withToken(String token) + { + this.token = token; + return (this); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + class HttpDeleteWithBody extends HttpEntityEnclosingRequestBase + { + + /******************************************************************************* + ** + *******************************************************************************/ + public String getMethod() + { + return "DELETE"; + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + public HttpDeleteWithBody(final String uri) + { + super(); + setURI(URI.create(uri)); + } + + } + +}