From 976f2e9f5b983d264ba9a056ec390dd218eb39b0 Mon Sep 17 00:00:00 2001 From: Kaveh-Vakili Date: Fri, 24 Jul 2026 13:45:24 -0400 Subject: [PATCH 1/2] Allow deployment-level exemptions from ADMIN_ONLY_PROJECT_ADD by project type Adds ADMIN_ONLY_PROJECT_ADD_EXEMPT_TYPES, a comma-separated allowlist of project types (e.g. WORKSPACE,SKILL) that stay creatable by non-admins even when ADMIN_ONLY_PROJECT_ADD is enabled, so deployments can lock down code apps while still letting users build workspaces/skills. Empty/unset preserves today's all-or-nothing behavior. Enforcement is centralized in ProjectHelper.generateNewProject, which covers every standard creation reactor. The two zip-upload reactors (UploadProjectReactor, UploadProjectAppReactor) needed special handling since the project type lives inside the uploaded zip: both now peek the .smss entry directly out of the zip (no disk writes) to learn the type and enforce the check before any temp files are created, so a rejected upload never requires cleanup. --- .../auth/utils/AbstractSecurityUtils.java | 17 ++++++ src/prerna/project/impl/ProjectHelper.java | 3 +- .../project/UploadProjectAppReactor.java | 53 ++++++++++++++++++- .../reactor/project/UploadProjectReactor.java | 53 ++++++++++++++++++- src/prerna/util/Constants.java | 1 + src/prerna/util/Utility.java | 31 +++++++++++ 6 files changed, 153 insertions(+), 5 deletions(-) diff --git a/src/prerna/auth/utils/AbstractSecurityUtils.java b/src/prerna/auth/utils/AbstractSecurityUtils.java index 4d2805a7be..4814a551d0 100644 --- a/src/prerna/auth/utils/AbstractSecurityUtils.java +++ b/src/prerna/auth/utils/AbstractSecurityUtils.java @@ -40,10 +40,12 @@ import java.util.Arrays; import java.util.Collection; import java.util.HashMap; +import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Properties; +import java.util.Set; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -61,6 +63,7 @@ import prerna.engine.api.IHeadersDataRow; import prerna.engine.api.IRDBMSEngine; import prerna.engine.api.IRawSelectWrapper; +import prerna.project.api.IProject; import prerna.query.querystruct.SelectQueryStruct; import prerna.query.querystruct.filters.SimpleQueryFilter; import prerna.query.querystruct.selectors.QueryColumnSelector; @@ -85,6 +88,7 @@ public abstract class AbstractSecurityUtils { static boolean anonymousUsersUploadData = false; static boolean adminOnlyProjectAdd = false; + static Set adminOnlyProjectAddExemptTypes = new HashSet<>(); static boolean adminOnlyProjectDelete = false; static boolean adminOnlyProjectAddAccess = false; static boolean adminOnlyProjectSetPublic = false; @@ -183,6 +187,7 @@ public static void loadSecurityDatabase() throws Exception { } adminOnlyProjectAdd = Utility.getApplicationAdminOnlyProjectAdd(); + adminOnlyProjectAddExemptTypes = Utility.getApplicationAdminOnlyProjectAddExemptTypes(); adminOnlyProjectDelete = Utility.getApplicationAdminOnlyProjectDelete(); adminOnlyProjectAddAccess = Utility.getApplicationAdminOnlyProjectAddAccess(); adminOnlyProjectSetPublic = Utility.getApplicationAdminOnlyProjectSetPublic(); @@ -251,6 +256,18 @@ public static boolean adminOnlyProjectAdd() { return adminOnlyProjectAdd; } + /** + * Determine if the given project type is exempt from + * {@link #adminOnlyProjectAdd()}, i.e. non-admins may still create projects + * of this type even when project creation is otherwise admin-restricted. + * + * @param type the project type being created + * @return true if this type is exempt from the admin-only restriction + */ + public static boolean isProjectAddExemptFromAdminOnly(IProject.PROJECT_TYPE type) { + return adminOnlyProjectAddExemptTypes.contains(type); + } + public static boolean adminOnlyProjectDelete() { return adminOnlyProjectDelete; } diff --git a/src/prerna/project/impl/ProjectHelper.java b/src/prerna/project/impl/ProjectHelper.java index 8ea5a50bbd..647ddef8f4 100644 --- a/src/prerna/project/impl/ProjectHelper.java +++ b/src/prerna/project/impl/ProjectHelper.java @@ -168,7 +168,8 @@ public static IProject generateNewProject(String projectId, String projectName, AbstractReactor.throwUserNotPublisherError(); } - if (AbstractSecurityUtils.adminOnlyProjectAdd() && !SecurityAdminUtils.userIsAdmin(user)) { + if (AbstractSecurityUtils.adminOnlyProjectAdd() && !SecurityAdminUtils.userIsAdmin(user) + && !AbstractSecurityUtils.isProjectAddExemptFromAdminOnly(projectType)) { AbstractReactor.throwFunctionalityOnlyExposedForAdminsError(); } diff --git a/src/prerna/reactor/project/UploadProjectAppReactor.java b/src/prerna/reactor/project/UploadProjectAppReactor.java index f825c96db2..99e8b62269 100644 --- a/src/prerna/reactor/project/UploadProjectAppReactor.java +++ b/src/prerna/reactor/project/UploadProjectAppReactor.java @@ -29,12 +29,16 @@ import java.io.File; import java.io.IOException; +import java.io.InputStream; import java.util.ArrayList; +import java.util.Enumeration; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.UUID; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; import org.apache.commons.io.FileUtils; import org.apache.logging.log4j.LogManager; @@ -123,7 +127,11 @@ public NounMetadata execute() { throwUserNotPublisherError(); } - if (AbstractSecurityUtils.adminOnlyProjectAdd() && !SecurityAdminUtils.userIsAdmin(user)) { + // peek the project type directly out of the zip's smss entry (no disk + // writes) so admin-only enforcement can happen before any temp files are + // created - a rejected upload should never need cleanup + if (AbstractSecurityUtils.adminOnlyProjectAdd() && !SecurityAdminUtils.userIsAdmin(user) + && !AbstractSecurityUtils.isProjectAddExemptFromAdminOnly(peekProjectTypeFromZip(zipFilePath))) { AbstractReactor.throwFunctionalityOnlyExposedForAdminsError(); } @@ -406,7 +414,48 @@ protected boolean deleteIfExisting() { } /** - * + * Read the PROJECT_ENUM_TYPE out of the zip's smss entry without extracting + * anything to disk, so callers can enforce type-aware permissions before any + * temp files exist. Defaults to INSIGHTS if the entry is missing, unreadable, + * or has no/unrecognized type - the same default the rest of this reactor + * uses, and the subsequent unzip step will surface a clearer error if the + * zip itself is invalid. + * + * @param zipFilePath + * @return + */ + private IProject.PROJECT_TYPE peekProjectTypeFromZip(String zipFilePath) { + try (ZipFile zipFile = new ZipFile(zipFilePath)) { + Enumeration entries = zipFile.entries(); + while (entries.hasMoreElements()) { + ZipEntry entry = entries.nextElement(); + String name = entry.getName(); + if (name.startsWith("__MACOSX/") || !name.endsWith(Constants.SEMOSS_EXTENSION)) { + continue; + } + Properties prop = new Properties(); + try (InputStream is = zipFile.getInputStream(entry)) { + prop.load(is); + } + String projectEnumTypeStr = prop.getProperty(Constants.PROJECT_ENUM_TYPE); + if (projectEnumTypeStr == null || (projectEnumTypeStr = projectEnumTypeStr.trim()).isEmpty()) { + return IProject.PROJECT_TYPE.INSIGHTS; + } + try { + return IProject.PROJECT_TYPE.valueOf(projectEnumTypeStr); + } catch (IllegalArgumentException e) { + return IProject.PROJECT_TYPE.INSIGHTS; + } + } + } catch (IOException e) { + classLogger.warn("Unable to peek project type from uploaded zip; deferring to standard unzip validation", + e); + } + return IProject.PROJECT_TYPE.INSIGHTS; + } + + /** + * * @param fileToDelete */ private void cleanUpFolders(File... fileToDelete) { diff --git a/src/prerna/reactor/project/UploadProjectReactor.java b/src/prerna/reactor/project/UploadProjectReactor.java index 90dd0ac337..9c1455a729 100644 --- a/src/prerna/reactor/project/UploadProjectReactor.java +++ b/src/prerna/reactor/project/UploadProjectReactor.java @@ -29,12 +29,16 @@ import java.io.File; import java.io.IOException; +import java.io.InputStream; import java.util.ArrayList; +import java.util.Enumeration; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.UUID; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; import org.apache.commons.io.FileUtils; import org.apache.logging.log4j.LogManager; @@ -110,7 +114,11 @@ public NounMetadata execute() { throwUserNotPublisherError(); } - if (AbstractSecurityUtils.adminOnlyProjectAdd() && !SecurityAdminUtils.userIsAdmin(user)) { + // peek the project type directly out of the zip's smss entry (no disk + // writes) so admin-only enforcement can happen before any temp files are + // created - a rejected upload should never need cleanup + if (AbstractSecurityUtils.adminOnlyProjectAdd() && !SecurityAdminUtils.userIsAdmin(user) + && !AbstractSecurityUtils.isProjectAddExemptFromAdminOnly(peekProjectTypeFromZip(zipFilePath))) { AbstractReactor.throwFunctionalityOnlyExposedForAdminsError(); } @@ -383,7 +391,48 @@ public NounMetadata execute() { } /** - * + * Read the PROJECT_ENUM_TYPE out of the zip's smss entry without extracting + * anything to disk, so callers can enforce type-aware permissions before any + * temp files exist. Defaults to INSIGHTS if the entry is missing, unreadable, + * or has no/unrecognized type - the same default the rest of this reactor + * uses, and the subsequent unzip step will surface a clearer error if the + * zip itself is invalid. + * + * @param zipFilePath + * @return + */ + private IProject.PROJECT_TYPE peekProjectTypeFromZip(String zipFilePath) { + try (ZipFile zipFile = new ZipFile(zipFilePath)) { + Enumeration entries = zipFile.entries(); + while (entries.hasMoreElements()) { + ZipEntry entry = entries.nextElement(); + String name = entry.getName(); + if (name.startsWith("__MACOSX/") || !name.endsWith(Constants.SEMOSS_EXTENSION)) { + continue; + } + Properties prop = new Properties(); + try (InputStream is = zipFile.getInputStream(entry)) { + prop.load(is); + } + String projectEnumTypeStr = prop.getProperty(Constants.PROJECT_ENUM_TYPE); + if (projectEnumTypeStr == null || (projectEnumTypeStr = projectEnumTypeStr.trim()).isEmpty()) { + return IProject.PROJECT_TYPE.INSIGHTS; + } + try { + return IProject.PROJECT_TYPE.valueOf(projectEnumTypeStr); + } catch (IllegalArgumentException e) { + return IProject.PROJECT_TYPE.INSIGHTS; + } + } + } catch (IOException e) { + classLogger.warn("Unable to peek project type from uploaded zip; deferring to standard unzip validation", + e); + } + return IProject.PROJECT_TYPE.INSIGHTS; + } + + /** + * * @param fileToDelete */ private void cleanUpFolders(File... fileToDelete) { diff --git a/src/prerna/util/Constants.java b/src/prerna/util/Constants.java index b5dae95ade..4fd0cae978 100644 --- a/src/prerna/util/Constants.java +++ b/src/prerna/util/Constants.java @@ -574,6 +574,7 @@ public class Constants { public static final String PLAYWRIGHT_EXPORT = "PLAYWRIGHT_EXPORT"; // reduce operations to only admins public static final String ADMIN_ONLY_PROJECT_ADD = "ADMIN_ONLY_PROJECT_ADD"; + public static final String ADMIN_ONLY_PROJECT_ADD_EXEMPT_TYPES = "ADMIN_ONLY_PROJECT_ADD_EXEMPT_TYPES"; public static final String ADMIN_ONLY_PROJECT_DELETE = "ADMIN_ONLY_PROJECT_DELETE"; public static final String ADMIN_ONLY_PROJECT_ADD_ACCESS = "ADMIN_ONLY_PROJECT_ADD_ACCESS"; public static final String ADMIN_ONLY_PROJECT_SET_PUBLIC = "ADMIN_ONLY_PROJECT_SET_PUBLIC"; diff --git a/src/prerna/util/Utility.java b/src/prerna/util/Utility.java index f5e9fcdcd3..7e374b9537 100644 --- a/src/prerna/util/Utility.java +++ b/src/prerna/util/Utility.java @@ -3158,6 +3158,37 @@ public static boolean getApplicationAdminOnlyProjectAdd() { return Boolean.parseBoolean(boolString); } + /** + * Determine which project types are exempt from + * {@link Constants#ADMIN_ONLY_PROJECT_ADD}, i.e. non-admins may still create + * these project types even when project creation is otherwise + * admin-restricted. + * + * @return set of exempt {@link IProject.PROJECT_TYPE} values; empty if none + * are configured (no exemptions) + */ + public static Set getApplicationAdminOnlyProjectAddExemptTypes() { + String csv = Utility.getDIHelperProperty(Constants.ADMIN_ONLY_PROJECT_ADD_EXEMPT_TYPES); + if (csv == null || (csv = csv.trim()).isEmpty()) { + return new HashSet<>(); + } + + Set exemptTypes = new HashSet<>(); + for (String rawType : csv.split(",")) { + String typeName = rawType.trim(); + if (typeName.isEmpty()) { + continue; + } + try { + exemptTypes.add(IProject.PROJECT_TYPE.valueOf(typeName)); + } catch (IllegalArgumentException e) { + classLogger.warn("Ignoring unrecognized PROJECT_TYPE '{}' in {}", typeName, + Constants.ADMIN_ONLY_PROJECT_ADD_EXEMPT_TYPES); + } + } + return exemptTypes; + } + /** * Determine if for this instance only the admin can delete a project * From 94a504279e45b26c55e3768b264f35f47a8cfe69 Mon Sep 17 00:00:00 2001 From: Kunal Patel Date: Wed, 5 Aug 2026 16:16:00 -0400 Subject: [PATCH 2/2] feat: exposing admin only workspace and admin only skill flags --- .../auth/utils/AbstractSecurityUtils.java | 147 ++++++++++++++++-- .../workspaces/DeleteWorkspaceReactor.java | 2 +- src/prerna/project/impl/ProjectHelper.java | 3 +- .../agent/skill/DeleteSkillReactor.java | 2 +- .../project/UploadProjectAppReactor.java | 80 +++------- .../reactor/project/UploadProjectReactor.java | 80 +++------- .../reactor/utils/DeleteProjectReactor.java | 2 +- src/prerna/util/Constants.java | 11 +- src/prerna/util/Utility.java | 71 +++++---- .../utils/AbstractSecurityUtilsUnitTests.java | 63 ++++++++ 10 files changed, 286 insertions(+), 175 deletions(-) diff --git a/src/prerna/auth/utils/AbstractSecurityUtils.java b/src/prerna/auth/utils/AbstractSecurityUtils.java index 152e6537bd..603934a3e3 100644 --- a/src/prerna/auth/utils/AbstractSecurityUtils.java +++ b/src/prerna/auth/utils/AbstractSecurityUtils.java @@ -40,12 +40,10 @@ import java.util.Arrays; import java.util.Collection; import java.util.HashMap; -import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Properties; -import java.util.Set; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -88,11 +86,20 @@ public abstract class AbstractSecurityUtils { static boolean anonymousUsersUploadData = false; static boolean adminOnlyProjectAdd = false; - static Set adminOnlyProjectAddExemptTypes = new HashSet<>(); static boolean adminOnlyProjectDelete = false; static boolean adminOnlyProjectAddAccess = false; static boolean adminOnlyProjectSetPublic = false; static boolean adminOnlyProjectSetDiscoverable = false; + static boolean adminOnlyWorkspaceAdd = false; + static boolean adminOnlyWorkspaceDelete = false; + static boolean adminOnlyWorkspaceAddAccess = false; + static boolean adminOnlyWorkspaceSetPublic = false; + static boolean adminOnlyWorkspaceSetDiscoverable = false; + static boolean adminOnlySkillAdd = false; + static boolean adminOnlySkillDelete = false; + static boolean adminOnlySkillAddAccess = false; + static boolean adminOnlySkillSetPublic = false; + static boolean adminOnlySkillSetDiscoverable = false; static boolean adminOnlyDatabaseAdd = false; static boolean adminOnlyDatabaseDelete = false; @@ -187,11 +194,20 @@ public static void loadSecurityDatabase() throws Exception { } adminOnlyProjectAdd = Utility.getApplicationAdminOnlyProjectAdd(); - adminOnlyProjectAddExemptTypes = Utility.getApplicationAdminOnlyProjectAddExemptTypes(); adminOnlyProjectDelete = Utility.getApplicationAdminOnlyProjectDelete(); adminOnlyProjectAddAccess = Utility.getApplicationAdminOnlyProjectAddAccess(); adminOnlyProjectSetPublic = Utility.getApplicationAdminOnlyProjectSetPublic(); adminOnlyProjectSetDiscoverable = Utility.getApplicationAdminOnlyProjectSetDiscoverable(); + adminOnlyWorkspaceAdd = Utility.getApplicationAdminOnlyWorkspaceAdd(); + adminOnlyWorkspaceDelete = Utility.getApplicationAdminOnlyWorkspaceDelete(); + adminOnlyWorkspaceAddAccess = Utility.getApplicationAdminOnlyWorkspaceAddAccess(); + adminOnlyWorkspaceSetPublic = Utility.getApplicationAdminOnlyWorkspaceSetPublic(); + adminOnlyWorkspaceSetDiscoverable = Utility.getApplicationAdminOnlyWorkspaceSetDiscoverable(); + adminOnlySkillAdd = Utility.getApplicationAdminOnlySkillAdd(); + adminOnlySkillDelete = Utility.getApplicationAdminOnlySkillDelete(); + adminOnlySkillAddAccess = Utility.getApplicationAdminOnlySkillAddAccess(); + adminOnlySkillSetPublic = Utility.getApplicationAdminOnlySkillSetPublic(); + adminOnlySkillSetDiscoverable = Utility.getApplicationAdminOnlySkillSetDiscoverable(); adminOnlyDatabaseAdd = Utility.getApplicationAdminOnlyDbAdd(); adminOnlyDatabaseDelete = Utility.getApplicationAdminOnlyDbDelete(); @@ -256,34 +272,137 @@ public static boolean adminOnlyProjectAdd() { return adminOnlyProjectAdd; } - /** - * Determine if the given project type is exempt from - * {@link #adminOnlyProjectAdd()}, i.e. non-admins may still create projects - * of this type even when project creation is otherwise admin-restricted. - * - * @param type the project type being created - * @return true if this type is exempt from the admin-only restriction - */ - public static boolean isProjectAddExemptFromAdminOnly(IProject.PROJECT_TYPE type) { - return adminOnlyProjectAddExemptTypes.contains(type); + public static boolean adminOnlyProjectAdd(IProject.PROJECT_TYPE type) { + if (IProject.PROJECT_TYPE.WORKSPACE == type) { + return adminOnlyWorkspaceAdd; + } else if (IProject.PROJECT_TYPE.SKILL == type) { + return adminOnlySkillAdd; + } + return adminOnlyProjectAdd; } public static boolean adminOnlyProjectDelete() { return adminOnlyProjectDelete; } + public static boolean adminOnlyProjectDelete(String projectId) { + return adminOnlyProjectDelete(getProjectTypeForAdminOnly(projectId)); + } + + public static boolean adminOnlyProjectDelete(IProject.PROJECT_TYPE type) { + if (IProject.PROJECT_TYPE.WORKSPACE == type) { + return adminOnlyWorkspaceDelete; + } else if (IProject.PROJECT_TYPE.SKILL == type) { + return adminOnlySkillDelete; + } + return adminOnlyProjectDelete; + } + public static boolean adminOnlyProjectAddAccess() { return adminOnlyProjectAddAccess; } + public static boolean adminOnlyProjectAddAccess(String projectId) { + return adminOnlyProjectAddAccess(getProjectTypeForAdminOnly(projectId)); + } + + public static boolean adminOnlyProjectAddAccess(IProject.PROJECT_TYPE type) { + if (IProject.PROJECT_TYPE.WORKSPACE == type) { + return adminOnlyWorkspaceAddAccess; + } else if (IProject.PROJECT_TYPE.SKILL == type) { + return adminOnlySkillAddAccess; + } + return adminOnlyProjectAddAccess; + } + public static boolean adminOnlyProjectSetPublic() { return adminOnlyProjectSetPublic; } + public static boolean adminOnlyProjectSetPublic(String projectId) { + return adminOnlyProjectSetPublic(getProjectTypeForAdminOnly(projectId)); + } + + public static boolean adminOnlyProjectSetPublic(IProject.PROJECT_TYPE type) { + if (IProject.PROJECT_TYPE.WORKSPACE == type) { + return adminOnlyWorkspaceSetPublic; + } else if (IProject.PROJECT_TYPE.SKILL == type) { + return adminOnlySkillSetPublic; + } + return adminOnlyProjectSetPublic; + } + public static boolean adminOnlyProjectSetDiscoverable() { return adminOnlyProjectSetDiscoverable; } + public static boolean adminOnlyProjectSetDiscoverable(String projectId) { + return adminOnlyProjectSetDiscoverable(getProjectTypeForAdminOnly(projectId)); + } + + public static boolean adminOnlyProjectSetDiscoverable(IProject.PROJECT_TYPE type) { + if (IProject.PROJECT_TYPE.WORKSPACE == type) { + return adminOnlyWorkspaceSetDiscoverable; + } else if (IProject.PROJECT_TYPE.SKILL == type) { + return adminOnlySkillSetDiscoverable; + } + return adminOnlyProjectSetDiscoverable; + } + + public static boolean adminOnlyWorkspaceAdd() { + return adminOnlyWorkspaceAdd; + } + + public static boolean adminOnlyWorkspaceDelete() { + return adminOnlyWorkspaceDelete; + } + + public static boolean adminOnlyWorkspaceAddAccess() { + return adminOnlyWorkspaceAddAccess; + } + + public static boolean adminOnlyWorkspaceSetPublic() { + return adminOnlyWorkspaceSetPublic; + } + + public static boolean adminOnlyWorkspaceSetDiscoverable() { + return adminOnlyWorkspaceSetDiscoverable; + } + + public static boolean adminOnlySkillAdd() { + return adminOnlySkillAdd; + } + + public static boolean adminOnlySkillDelete() { + return adminOnlySkillDelete; + } + + public static boolean adminOnlySkillAddAccess() { + return adminOnlySkillAddAccess; + } + + public static boolean adminOnlySkillSetPublic() { + return adminOnlySkillSetPublic; + } + + public static boolean adminOnlySkillSetDiscoverable() { + return adminOnlySkillSetDiscoverable; + } + + private static IProject.PROJECT_TYPE getProjectTypeForAdminOnly(String projectId) { + String projectType = SecurityProjectUtils.getProjectTypeForId(projectId); + if (projectType == null || projectType.trim().isEmpty()) { + return IProject.PROJECT_TYPE.INSIGHTS; + } + try { + return IProject.PROJECT_TYPE.valueOf(projectType.trim()); + } catch (IllegalArgumentException e) { + classLogger.warn("Unknown project type '{}' for project {}; applying project admin limits", projectType, + projectId); + return IProject.PROJECT_TYPE.INSIGHTS; + } + } + public static boolean adminOnlyDatabaseAdd() { return adminOnlyDatabaseAdd; } diff --git a/src/prerna/engine/impl/model/inferencetracking/reactors/workspaces/DeleteWorkspaceReactor.java b/src/prerna/engine/impl/model/inferencetracking/reactors/workspaces/DeleteWorkspaceReactor.java index 739a9f4348..a36ba97e2a 100644 --- a/src/prerna/engine/impl/model/inferencetracking/reactors/workspaces/DeleteWorkspaceReactor.java +++ b/src/prerna/engine/impl/model/inferencetracking/reactors/workspaces/DeleteWorkspaceReactor.java @@ -69,7 +69,7 @@ public NounMetadata execute() { "Workspace " + workspaceId + " is a built-in system agent and cannot be deleted"); } - if (AbstractSecurityUtils.adminOnlyProjectDelete()) { + if (AbstractSecurityUtils.adminOnlyWorkspaceDelete()) { throwFunctionalityOnlyExposedForAdminsError(); } diff --git a/src/prerna/project/impl/ProjectHelper.java b/src/prerna/project/impl/ProjectHelper.java index 647ddef8f4..1014b1375d 100644 --- a/src/prerna/project/impl/ProjectHelper.java +++ b/src/prerna/project/impl/ProjectHelper.java @@ -168,8 +168,7 @@ public static IProject generateNewProject(String projectId, String projectName, AbstractReactor.throwUserNotPublisherError(); } - if (AbstractSecurityUtils.adminOnlyProjectAdd() && !SecurityAdminUtils.userIsAdmin(user) - && !AbstractSecurityUtils.isProjectAddExemptFromAdminOnly(projectType)) { + if (AbstractSecurityUtils.adminOnlyProjectAdd(projectType) && !SecurityAdminUtils.userIsAdmin(user)) { AbstractReactor.throwFunctionalityOnlyExposedForAdminsError(); } diff --git a/src/prerna/reactor/agent/skill/DeleteSkillReactor.java b/src/prerna/reactor/agent/skill/DeleteSkillReactor.java index 3b97de8735..2503bfc1b3 100644 --- a/src/prerna/reactor/agent/skill/DeleteSkillReactor.java +++ b/src/prerna/reactor/agent/skill/DeleteSkillReactor.java @@ -90,7 +90,7 @@ public NounMetadata execute() { throw new IllegalArgumentException("skillId is required"); } - if (AbstractSecurityUtils.adminOnlyProjectDelete()) { + if (AbstractSecurityUtils.adminOnlySkillDelete()) { throwFunctionalityOnlyExposedForAdminsError(); } diff --git a/src/prerna/reactor/project/UploadProjectAppReactor.java b/src/prerna/reactor/project/UploadProjectAppReactor.java index 99e8b62269..c44e0bc73f 100644 --- a/src/prerna/reactor/project/UploadProjectAppReactor.java +++ b/src/prerna/reactor/project/UploadProjectAppReactor.java @@ -29,16 +29,12 @@ import java.io.File; import java.io.IOException; -import java.io.InputStream; import java.util.ArrayList; -import java.util.Enumeration; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.UUID; -import java.util.zip.ZipEntry; -import java.util.zip.ZipFile; import org.apache.commons.io.FileUtils; import org.apache.logging.log4j.LogManager; @@ -127,21 +123,6 @@ public NounMetadata execute() { throwUserNotPublisherError(); } - // peek the project type directly out of the zip's smss entry (no disk - // writes) so admin-only enforcement can happen before any temp files are - // created - a rejected upload should never need cleanup - if (AbstractSecurityUtils.adminOnlyProjectAdd() && !SecurityAdminUtils.userIsAdmin(user) - && !AbstractSecurityUtils.isProjectAddExemptFromAdminOnly(peekProjectTypeFromZip(zipFilePath))) { - AbstractReactor.throwFunctionalityOnlyExposedForAdminsError(); - } - - if (global && (AbstractSecurityUtils.adminOnlyProjectSetPublic() && !SecurityAdminUtils.userIsAdmin(user))) { - SemossPixelException exception = new SemossPixelException( - NounMetadata.getErrorNounMessage("User can upload an app but cannot make the app public")); - exception.setContinueThreadOfExecution(false); - throw exception; - } - // creating a temp folder to unzip project folder and smss String randomIdAsDir = UUID.randomUUID().toString(); String randomTempUnzipFolderPath = this.insight.getInsightFolder() + DIR_SEPARATOR + randomIdAsDir; @@ -152,6 +133,7 @@ public NounMetadata execute() { List fileList = new ArrayList<>(); String smssFileLoc = null; File smssFile = null; + Properties projectProperties = null; // unzip files to temp project folder boolean error = false; try { @@ -182,6 +164,21 @@ public NounMetadata execute() { if (smssFileLoc == null) { throw new SemossPixelException("Unable to find " + Constants.SEMOSS_EXTENSION + " file", false); } + + projectProperties = Utility.loadProperties(smssFileLoc); + String projectTypeString = projectProperties.getProperty(Constants.PROJECT_ENUM_TYPE); + IProject.PROJECT_TYPE projectType = projectTypeString == null ? IProject.PROJECT_TYPE.INSIGHTS + : IProject.PROJECT_TYPE.valueOf(projectTypeString.trim()); + if (AbstractSecurityUtils.adminOnlyProjectAdd(projectType) && !SecurityAdminUtils.userIsAdmin(user)) { + AbstractReactor.throwFunctionalityOnlyExposedForAdminsError(); + } + if (global && AbstractSecurityUtils.adminOnlyProjectSetPublic(projectType) + && !SecurityAdminUtils.userIsAdmin(user)) { + SemossPixelException exception = new SemossPixelException( + NounMetadata.getErrorNounMessage("User can upload an app but cannot make the app public")); + exception.setContinueThreadOfExecution(false); + throw exception; + } } catch (SemossPixelException e) { error = true; throw e; @@ -208,7 +205,7 @@ public NounMetadata execute() { boolean projectAddedToDIHelper = false; try { logger.info(step + ") Reading smss"); - Properties prop = Utility.loadProperties(smssFileLoc); + Properties prop = projectProperties; projectId = prop.getProperty(Constants.PROJECT); projectName = Utility.normalizePath(prop.getProperty(Constants.PROJECT_ALIAS)); @@ -413,47 +410,6 @@ protected boolean deleteIfExisting() { return false; } - /** - * Read the PROJECT_ENUM_TYPE out of the zip's smss entry without extracting - * anything to disk, so callers can enforce type-aware permissions before any - * temp files exist. Defaults to INSIGHTS if the entry is missing, unreadable, - * or has no/unrecognized type - the same default the rest of this reactor - * uses, and the subsequent unzip step will surface a clearer error if the - * zip itself is invalid. - * - * @param zipFilePath - * @return - */ - private IProject.PROJECT_TYPE peekProjectTypeFromZip(String zipFilePath) { - try (ZipFile zipFile = new ZipFile(zipFilePath)) { - Enumeration entries = zipFile.entries(); - while (entries.hasMoreElements()) { - ZipEntry entry = entries.nextElement(); - String name = entry.getName(); - if (name.startsWith("__MACOSX/") || !name.endsWith(Constants.SEMOSS_EXTENSION)) { - continue; - } - Properties prop = new Properties(); - try (InputStream is = zipFile.getInputStream(entry)) { - prop.load(is); - } - String projectEnumTypeStr = prop.getProperty(Constants.PROJECT_ENUM_TYPE); - if (projectEnumTypeStr == null || (projectEnumTypeStr = projectEnumTypeStr.trim()).isEmpty()) { - return IProject.PROJECT_TYPE.INSIGHTS; - } - try { - return IProject.PROJECT_TYPE.valueOf(projectEnumTypeStr); - } catch (IllegalArgumentException e) { - return IProject.PROJECT_TYPE.INSIGHTS; - } - } - } catch (IOException e) { - classLogger.warn("Unable to peek project type from uploaded zip; deferring to standard unzip validation", - e); - } - return IProject.PROJECT_TYPE.INSIGHTS; - } - /** * * @param fileToDelete @@ -494,4 +450,4 @@ protected String getDescriptionForKey(String key) { return super.getDescriptionForKey(key); } -} \ No newline at end of file +} diff --git a/src/prerna/reactor/project/UploadProjectReactor.java b/src/prerna/reactor/project/UploadProjectReactor.java index b1791b56e9..bb85f74b85 100644 --- a/src/prerna/reactor/project/UploadProjectReactor.java +++ b/src/prerna/reactor/project/UploadProjectReactor.java @@ -29,16 +29,12 @@ import java.io.File; import java.io.IOException; -import java.io.InputStream; import java.util.ArrayList; -import java.util.Enumeration; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.UUID; -import java.util.zip.ZipEntry; -import java.util.zip.ZipFile; import org.apache.commons.io.FileUtils; import org.apache.logging.log4j.LogManager; @@ -112,21 +108,6 @@ public NounMetadata execute() { throwUserNotPublisherError(); } - // peek the project type directly out of the zip's smss entry (no disk - // writes) so admin-only enforcement can happen before any temp files are - // created - a rejected upload should never need cleanup - if (AbstractSecurityUtils.adminOnlyProjectAdd() && !SecurityAdminUtils.userIsAdmin(user) - && !AbstractSecurityUtils.isProjectAddExemptFromAdminOnly(peekProjectTypeFromZip(zipFilePath))) { - AbstractReactor.throwFunctionalityOnlyExposedForAdminsError(); - } - - if (global && (AbstractSecurityUtils.adminOnlyProjectSetPublic() && !SecurityAdminUtils.userIsAdmin(user))) { - SemossPixelException exception = new SemossPixelException( - NounMetadata.getErrorNounMessage("User can upload a project but cannot make the project public")); - exception.setContinueThreadOfExecution(false); - throw exception; - } - // creating a temp folder to unzip project folder and smss String randomIdAsDir = UUID.randomUUID().toString(); String projectFolderPath = DIHelper.getInstance().getProperty(Constants.BASE_FOLDER) + DIR_SEPARATOR @@ -139,6 +120,7 @@ public NounMetadata execute() { List fileList = new ArrayList<>(); String smssFileLoc = null; File smssFile = null; + Properties projectProperties = null; // unzip files to temp project folder boolean error = false; try { @@ -169,6 +151,21 @@ public NounMetadata execute() { if (smssFileLoc == null) { throw new SemossPixelException("Unable to find " + Constants.SEMOSS_EXTENSION + " file", false); } + + projectProperties = Utility.loadProperties(smssFileLoc); + String projectTypeString = projectProperties.getProperty(Constants.PROJECT_ENUM_TYPE); + IProject.PROJECT_TYPE projectType = projectTypeString == null ? IProject.PROJECT_TYPE.INSIGHTS + : IProject.PROJECT_TYPE.valueOf(projectTypeString.trim()); + if (AbstractSecurityUtils.adminOnlyProjectAdd(projectType) && !SecurityAdminUtils.userIsAdmin(user)) { + AbstractReactor.throwFunctionalityOnlyExposedForAdminsError(); + } + if (global && AbstractSecurityUtils.adminOnlyProjectSetPublic(projectType) + && !SecurityAdminUtils.userIsAdmin(user)) { + SemossPixelException exception = new SemossPixelException(NounMetadata + .getErrorNounMessage("User can upload a project but cannot make the project public")); + exception.setContinueThreadOfExecution(false); + throw exception; + } } catch (SemossPixelException e) { error = true; throw e; @@ -191,7 +188,7 @@ public NounMetadata execute() { boolean projectAddedToDIHelper = false; try { logger.info("{}) Reading smss", step); - Properties prop = Utility.loadProperties(smssFileLoc); + Properties prop = projectProperties; projectId = prop.getProperty(Constants.PROJECT); projectName = prop.getProperty(Constants.PROJECT_ALIAS); @@ -348,48 +345,7 @@ public NounMetadata execute() { } /** - * Read the PROJECT_ENUM_TYPE out of the zip's smss entry without extracting - * anything to disk, so callers can enforce type-aware permissions before any - * temp files exist. Defaults to INSIGHTS if the entry is missing, unreadable, - * or has no/unrecognized type - the same default the rest of this reactor - * uses, and the subsequent unzip step will surface a clearer error if the - * zip itself is invalid. - * - * @param zipFilePath - * @return - */ - private IProject.PROJECT_TYPE peekProjectTypeFromZip(String zipFilePath) { - try (ZipFile zipFile = new ZipFile(zipFilePath)) { - Enumeration entries = zipFile.entries(); - while (entries.hasMoreElements()) { - ZipEntry entry = entries.nextElement(); - String name = entry.getName(); - if (name.startsWith("__MACOSX/") || !name.endsWith(Constants.SEMOSS_EXTENSION)) { - continue; - } - Properties prop = new Properties(); - try (InputStream is = zipFile.getInputStream(entry)) { - prop.load(is); - } - String projectEnumTypeStr = prop.getProperty(Constants.PROJECT_ENUM_TYPE); - if (projectEnumTypeStr == null || (projectEnumTypeStr = projectEnumTypeStr.trim()).isEmpty()) { - return IProject.PROJECT_TYPE.INSIGHTS; - } - try { - return IProject.PROJECT_TYPE.valueOf(projectEnumTypeStr); - } catch (IllegalArgumentException e) { - return IProject.PROJECT_TYPE.INSIGHTS; - } - } - } catch (IOException e) { - classLogger.warn("Unable to peek project type from uploaded zip; deferring to standard unzip validation", - e); - } - return IProject.PROJECT_TYPE.INSIGHTS; - } - - /** - * + * * @param fileToDelete */ private void cleanUpFolders(File... fileToDelete) { diff --git a/src/prerna/reactor/utils/DeleteProjectReactor.java b/src/prerna/reactor/utils/DeleteProjectReactor.java index f2c1256dfb..75ef1e518c 100644 --- a/src/prerna/reactor/utils/DeleteProjectReactor.java +++ b/src/prerna/reactor/utils/DeleteProjectReactor.java @@ -74,7 +74,7 @@ public NounMetadata execute() { projectId = SecurityProjectUtils.testUserProjectIdForAlias(this.insight.getUser(), projectId); boolean isAdmin = SecurityAdminUtils.userIsAdmin(user); if (!isAdmin) { - if (AbstractSecurityUtils.adminOnlyProjectDelete()) { + if (AbstractSecurityUtils.adminOnlyProjectDelete(projectId)) { throwFunctionalityOnlyExposedForAdminsError(); } diff --git a/src/prerna/util/Constants.java b/src/prerna/util/Constants.java index a5630fe2e1..bf267080e8 100644 --- a/src/prerna/util/Constants.java +++ b/src/prerna/util/Constants.java @@ -591,11 +591,20 @@ public class Constants { public static final String PLAYWRIGHT_EXPORT = "PLAYWRIGHT_EXPORT"; // reduce operations to only admins public static final String ADMIN_ONLY_PROJECT_ADD = "ADMIN_ONLY_PROJECT_ADD"; - public static final String ADMIN_ONLY_PROJECT_ADD_EXEMPT_TYPES = "ADMIN_ONLY_PROJECT_ADD_EXEMPT_TYPES"; public static final String ADMIN_ONLY_PROJECT_DELETE = "ADMIN_ONLY_PROJECT_DELETE"; public static final String ADMIN_ONLY_PROJECT_ADD_ACCESS = "ADMIN_ONLY_PROJECT_ADD_ACCESS"; public static final String ADMIN_ONLY_PROJECT_SET_PUBLIC = "ADMIN_ONLY_PROJECT_SET_PUBLIC"; public static final String ADMIN_ONLY_PROJECT_SET_DISCOVERABLE = "ADMIN_ONLY_PROJECT_SET_DISCOVERABLE"; + public static final String ADMIN_ONLY_WORKSPACE_ADD = "ADMIN_ONLY_WORKSPACE_ADD"; + public static final String ADMIN_ONLY_WORKSPACE_DELETE = "ADMIN_ONLY_WORKSPACE_DELETE"; + public static final String ADMIN_ONLY_WORKSPACE_ADD_ACCESS = "ADMIN_ONLY_WORKSPACE_ADD_ACCESS"; + public static final String ADMIN_ONLY_WORKSPACE_SET_PUBLIC = "ADMIN_ONLY_WORKSPACE_SET_PUBLIC"; + public static final String ADMIN_ONLY_WORKSPACE_SET_DISCOVERABLE = "ADMIN_ONLY_WORKSPACE_SET_DISCOVERABLE"; + public static final String ADMIN_ONLY_SKILL_ADD = "ADMIN_ONLY_SKILL_ADD"; + public static final String ADMIN_ONLY_SKILL_DELETE = "ADMIN_ONLY_SKILL_DELETE"; + public static final String ADMIN_ONLY_SKILL_ADD_ACCESS = "ADMIN_ONLY_SKILL_ADD_ACCESS"; + public static final String ADMIN_ONLY_SKILL_SET_PUBLIC = "ADMIN_ONLY_SKILL_SET_PUBLIC"; + public static final String ADMIN_ONLY_SKILL_SET_DISCOVERABLE = "ADMIN_ONLY_SKILL_SET_DISCOVERABLE"; public static final String ADMIN_ONLY_DB_ADD = "ADMIN_ONLY_DB_ADD"; public static final String ADMIN_ONLY_DB_DELETE = "ADMIN_ONLY_DB_DELETE"; diff --git a/src/prerna/util/Utility.java b/src/prerna/util/Utility.java index 0f0d9ca822..8731a9e0e1 100644 --- a/src/prerna/util/Utility.java +++ b/src/prerna/util/Utility.java @@ -3141,37 +3141,6 @@ public static boolean getApplicationAdminOnlyProjectAdd() { return Boolean.parseBoolean(boolString); } - /** - * Determine which project types are exempt from - * {@link Constants#ADMIN_ONLY_PROJECT_ADD}, i.e. non-admins may still create - * these project types even when project creation is otherwise - * admin-restricted. - * - * @return set of exempt {@link IProject.PROJECT_TYPE} values; empty if none - * are configured (no exemptions) - */ - public static Set getApplicationAdminOnlyProjectAddExemptTypes() { - String csv = Utility.getDIHelperProperty(Constants.ADMIN_ONLY_PROJECT_ADD_EXEMPT_TYPES); - if (csv == null || (csv = csv.trim()).isEmpty()) { - return new HashSet<>(); - } - - Set exemptTypes = new HashSet<>(); - for (String rawType : csv.split(",")) { - String typeName = rawType.trim(); - if (typeName.isEmpty()) { - continue; - } - try { - exemptTypes.add(IProject.PROJECT_TYPE.valueOf(typeName)); - } catch (IllegalArgumentException e) { - classLogger.warn("Ignoring unrecognized PROJECT_TYPE '{}' in {}", typeName, - Constants.ADMIN_ONLY_PROJECT_ADD_EXEMPT_TYPES); - } - } - return exemptTypes; - } - /** * Determine if for this instance only the admin can delete a project * @@ -3248,6 +3217,46 @@ public static boolean getApplicationAdminOnlyProjectSetDiscoverable() { return Boolean.parseBoolean(boolString); } + public static boolean getApplicationAdminOnlyWorkspaceAdd() { + return Boolean.parseBoolean(Utility.getDIHelperProperty(Constants.ADMIN_ONLY_WORKSPACE_ADD)); + } + + public static boolean getApplicationAdminOnlyWorkspaceDelete() { + return Boolean.parseBoolean(Utility.getDIHelperProperty(Constants.ADMIN_ONLY_WORKSPACE_DELETE)); + } + + public static boolean getApplicationAdminOnlyWorkspaceAddAccess() { + return Boolean.parseBoolean(Utility.getDIHelperProperty(Constants.ADMIN_ONLY_WORKSPACE_ADD_ACCESS)); + } + + public static boolean getApplicationAdminOnlyWorkspaceSetPublic() { + return Boolean.parseBoolean(Utility.getDIHelperProperty(Constants.ADMIN_ONLY_WORKSPACE_SET_PUBLIC)); + } + + public static boolean getApplicationAdminOnlyWorkspaceSetDiscoverable() { + return Boolean.parseBoolean(Utility.getDIHelperProperty(Constants.ADMIN_ONLY_WORKSPACE_SET_DISCOVERABLE)); + } + + public static boolean getApplicationAdminOnlySkillAdd() { + return Boolean.parseBoolean(Utility.getDIHelperProperty(Constants.ADMIN_ONLY_SKILL_ADD)); + } + + public static boolean getApplicationAdminOnlySkillDelete() { + return Boolean.parseBoolean(Utility.getDIHelperProperty(Constants.ADMIN_ONLY_SKILL_DELETE)); + } + + public static boolean getApplicationAdminOnlySkillAddAccess() { + return Boolean.parseBoolean(Utility.getDIHelperProperty(Constants.ADMIN_ONLY_SKILL_ADD_ACCESS)); + } + + public static boolean getApplicationAdminOnlySkillSetPublic() { + return Boolean.parseBoolean(Utility.getDIHelperProperty(Constants.ADMIN_ONLY_SKILL_SET_PUBLIC)); + } + + public static boolean getApplicationAdminOnlySkillSetDiscoverable() { + return Boolean.parseBoolean(Utility.getDIHelperProperty(Constants.ADMIN_ONLY_SKILL_SET_DISCOVERABLE)); + } + /** * Determine if for this instance only the admin can share insight * diff --git a/test/prerna/auth/utils/AbstractSecurityUtilsUnitTests.java b/test/prerna/auth/utils/AbstractSecurityUtilsUnitTests.java index 2db46d073e..9771909b8e 100644 --- a/test/prerna/auth/utils/AbstractSecurityUtilsUnitTests.java +++ b/test/prerna/auth/utils/AbstractSecurityUtilsUnitTests.java @@ -60,6 +60,7 @@ import prerna.date.SemossDate; import prerna.engine.api.IEngine; import prerna.engine.api.IRDBMSEngine; +import prerna.project.api.IProject; import prerna.query.querystruct.SelectQueryStruct; import prerna.query.querystruct.selectors.QueryColumnSelector; import prerna.util.Constants; @@ -96,6 +97,16 @@ void testDefaultVariables() { assertFalse(AbstractSecurityUtils.adminOnlyProjectAddAccess()); assertFalse(AbstractSecurityUtils.adminOnlyProjectSetPublic()); assertFalse(AbstractSecurityUtils.adminOnlyProjectSetDiscoverable()); + assertFalse(AbstractSecurityUtils.adminOnlyWorkspaceAdd()); + assertFalse(AbstractSecurityUtils.adminOnlyWorkspaceDelete()); + assertFalse(AbstractSecurityUtils.adminOnlyWorkspaceAddAccess()); + assertFalse(AbstractSecurityUtils.adminOnlyWorkspaceSetPublic()); + assertFalse(AbstractSecurityUtils.adminOnlyWorkspaceSetDiscoverable()); + assertFalse(AbstractSecurityUtils.adminOnlySkillAdd()); + assertFalse(AbstractSecurityUtils.adminOnlySkillDelete()); + assertFalse(AbstractSecurityUtils.adminOnlySkillAddAccess()); + assertFalse(AbstractSecurityUtils.adminOnlySkillSetPublic()); + assertFalse(AbstractSecurityUtils.adminOnlySkillSetDiscoverable()); assertFalse(AbstractSecurityUtils.adminOnlyDatabaseAdd()); assertFalse(AbstractSecurityUtils.adminOnlyDatabaseDelete()); assertFalse(AbstractSecurityUtils.adminOnlyDatabaseAddAccess()); @@ -129,6 +140,58 @@ void testDefaultVariables() { assertFalse(AbstractSecurityUtils.adminOnlyInsightShare()); } + @Test + void testAdminOnlyProjectFlagsRouteByProjectType() { + AbstractSecurityUtils.adminOnlyProjectAdd = true; + AbstractSecurityUtils.adminOnlyProjectDelete = false; + AbstractSecurityUtils.adminOnlyProjectAddAccess = true; + AbstractSecurityUtils.adminOnlyProjectSetPublic = false; + AbstractSecurityUtils.adminOnlyProjectSetDiscoverable = true; + AbstractSecurityUtils.adminOnlyWorkspaceAdd = false; + AbstractSecurityUtils.adminOnlyWorkspaceDelete = true; + AbstractSecurityUtils.adminOnlyWorkspaceAddAccess = false; + AbstractSecurityUtils.adminOnlyWorkspaceSetPublic = true; + AbstractSecurityUtils.adminOnlyWorkspaceSetDiscoverable = false; + AbstractSecurityUtils.adminOnlySkillAdd = true; + AbstractSecurityUtils.adminOnlySkillDelete = true; + AbstractSecurityUtils.adminOnlySkillAddAccess = false; + AbstractSecurityUtils.adminOnlySkillSetPublic = false; + AbstractSecurityUtils.adminOnlySkillSetDiscoverable = true; + + try { + assertAdminOnlyProjectFlags(IProject.PROJECT_TYPE.BLOCKS, true, false, true, false, true); + assertAdminOnlyProjectFlags(IProject.PROJECT_TYPE.CODE, true, false, true, false, true); + assertAdminOnlyProjectFlags(IProject.PROJECT_TYPE.INSIGHTS, true, false, true, false, true); + assertAdminOnlyProjectFlags(IProject.PROJECT_TYPE.WORKSPACE, false, true, false, true, false); + assertAdminOnlyProjectFlags(IProject.PROJECT_TYPE.SKILL, true, true, false, false, true); + } finally { + AbstractSecurityUtils.adminOnlyProjectAdd = false; + AbstractSecurityUtils.adminOnlyProjectDelete = false; + AbstractSecurityUtils.adminOnlyProjectAddAccess = false; + AbstractSecurityUtils.adminOnlyProjectSetPublic = false; + AbstractSecurityUtils.adminOnlyProjectSetDiscoverable = false; + AbstractSecurityUtils.adminOnlyWorkspaceAdd = false; + AbstractSecurityUtils.adminOnlyWorkspaceDelete = false; + AbstractSecurityUtils.adminOnlyWorkspaceAddAccess = false; + AbstractSecurityUtils.adminOnlyWorkspaceSetPublic = false; + AbstractSecurityUtils.adminOnlyWorkspaceSetDiscoverable = false; + AbstractSecurityUtils.adminOnlySkillAdd = false; + AbstractSecurityUtils.adminOnlySkillDelete = false; + AbstractSecurityUtils.adminOnlySkillAddAccess = false; + AbstractSecurityUtils.adminOnlySkillSetPublic = false; + AbstractSecurityUtils.adminOnlySkillSetDiscoverable = false; + } + } + + private static void assertAdminOnlyProjectFlags(IProject.PROJECT_TYPE type, boolean add, boolean delete, + boolean addAccess, boolean setPublic, boolean setDiscoverable) { + assertEquals(add, AbstractSecurityUtils.adminOnlyProjectAdd(type)); + assertEquals(delete, AbstractSecurityUtils.adminOnlyProjectDelete(type)); + assertEquals(addAccess, AbstractSecurityUtils.adminOnlyProjectAddAccess(type)); + assertEquals(setPublic, AbstractSecurityUtils.adminOnlyProjectSetPublic(type)); + assertEquals(setDiscoverable, AbstractSecurityUtils.adminOnlyProjectSetDiscoverable(type)); + } + @Test void testAdminOnlyEngineAdd_EngineId() { User user = UnitTestSecurityAuthUtils.createUser("admin", true);