diff --git a/src/prerna/auth/utils/AbstractSecurityUtils.java b/src/prerna/auth/utils/AbstractSecurityUtils.java index 1a36609994..3c5891c5dc 100644 --- a/src/prerna/auth/utils/AbstractSecurityUtils.java +++ b/src/prerna/auth/utils/AbstractSecurityUtils.java @@ -1194,10 +1194,12 @@ public static void initialize() throws Exception { // USERENGINEPREFERENCE // a person's own settings on an engine, such as favorite and visibility, kept // out of ENGINEPERMISSION so that setting one adds no access row - colNames = new String[] { "USERID", "TYPE", "ENGINEID", "FAVORITE", "VISIBILITY" }; + // USERDISCOVERABLE marks it as one the user lets agents find and use; off + // unless the user turns it on, and a null is off + colNames = new String[] { "USERID", "TYPE", "ENGINEID", "FAVORITE", "VISIBILITY", "USERDISCOVERABLE" }; types = new String[] { VARCHAR_255, VARCHAR_255, VARCHAR_255, BOOLEAN_DATATYPE_NAME, - BOOLEAN_DATATYPE_NAME }; - defaultValues = new Object[] { null, null, null, false, true }; + BOOLEAN_DATATYPE_NAME, BOOLEAN_DATATYPE_NAME }; + defaultValues = new Object[] { null, null, null, false, true, false }; if (allowIfExistsTable) { String sql = queryUtil.createTableIfNotExistsWithDefaults("USERENGINEPREFERENCE", colNames, types, defaultValues); @@ -1542,10 +1544,12 @@ public static void initialize() throws Exception { // USERPROJECTPREFERENCE // a person's own settings on a project, such as favorite and visibility, kept // out of PROJECTPERMISSION so that setting one adds no access row - colNames = new String[] { "USERID", "TYPE", "PROJECTID", "FAVORITE", "VISIBILITY" }; + // USERDISCOVERABLE marks it as one the user lets agents find and use; off + // unless the user turns it on, and a null is off + colNames = new String[] { "USERID", "TYPE", "PROJECTID", "FAVORITE", "VISIBILITY", "USERDISCOVERABLE" }; types = new String[] { VARCHAR_255, VARCHAR_255, VARCHAR_255, BOOLEAN_DATATYPE_NAME, - BOOLEAN_DATATYPE_NAME }; - defaultValues = new Object[] { null, null, null, false, true }; + BOOLEAN_DATATYPE_NAME, BOOLEAN_DATATYPE_NAME }; + defaultValues = new Object[] { null, null, null, false, true, false }; if (allowIfExistsTable) { String sql = queryUtil.createTableIfNotExistsWithDefaults("USERPROJECTPREFERENCE", colNames, types, defaultValues); diff --git a/src/prerna/auth/utils/SecurityEngineUtils.java b/src/prerna/auth/utils/SecurityEngineUtils.java index cb6cc1b0a5..0cf13db44c 100644 --- a/src/prerna/auth/utils/SecurityEngineUtils.java +++ b/src/prerna/auth/utils/SecurityEngineUtils.java @@ -1407,6 +1407,29 @@ public static void setEngineFavorite(User user, String engineId, boolean isFavor isFavorite); } + /** + * Let agents find and use an engine for the user, or stop them, for each of + * the user's logins. The choice is saved in USERENGINEPREFERENCE, apart from + * the user's permissions, and changes only this engine. An engine is not + * discoverable to the user's agents until the user turns this on. + * + * @param user the user + * @param engineId the engine + * @param discoverable true to let the user's agents find the engine, false to + * stop them + * @throws IllegalAccessException when the engine is not public and the user + * cannot view it + * @throws Exception when the preference cannot be saved + */ + public static void setEngineUserDiscoverable(User user, String engineId, boolean discoverable) throws Exception { + if (!engineIsGlobal(engineId) && !userCanViewEngine(user, engineId)) { + throw new IllegalAccessException( + "The user doesn't have the permission to change whether this engine is discoverable to their agents."); + } + setUserPreference(user, "USERENGINEPREFERENCE", List.of(Pair.with("ENGINEID", engineId)), "USERDISCOVERABLE", + discoverable); + } + /** * update the database name * @@ -2003,6 +2026,8 @@ public static List> getUserEngineList(User user, List> getUserEngineList(User user, List> getUserEngineList(User user, List> getUserProjectList(User user, List> getUserProjectList(User user, String pro qs.addSelector(new QueryColumnSelector("PROJECT__REACTORSCOMPILEDTYPE", "project_reactors_compiled_user_type")); qs.addSelector( QueryFunctionSelector.makeCol2ValCoalesceSelector("USER_PREFERENCES__FAVORITE", 0, "project_favorite")); + qs.addSelector(QueryFunctionSelector.makeCol2ValCoalesceSelector("USER_PREFERENCES__USERDISCOVERABLE", 0, + "project_user_discoverable")); // for sorting qs.addSelector(QueryFunctionSelector.makeFunctionSelector(QueryFunctionHelper.LOWER, QueryFunctionSelector .makeCoalesceSelector("PROJECT__PROJECTDISPLAYNAME", "PROJECT__PROJECTNAME", "display_name"), @@ -3670,9 +3674,10 @@ public static List> getUserProjectList(User user, String pro /** * A left join, as USER_PREFERENCES, to the user's preferences on each project: - * FAVORITE is 1 when any of the user's logins favorited it, and VISIBILITY is 1 - * when any of them shows it and 0 when they all hide it, both cast to int. A - * project the user has no preferences on joins to null. + * FAVORITE is 1 when any of the user's logins favorited it, VISIBILITY is 1 + * when any of them shows it and 0 when they all hide it, and USERDISCOVERABLE + * is 1 when any of them lets agents find it, all cast to int. A project the + * user has no preferences on joins to null. * * @param securityDb the security database * @param user the user; their logins come from the session @@ -3692,6 +3697,11 @@ private static IRelation getUserPreferencesSubquery(IRDBMSEngine securityDb, Use castVisibility.setDataType(securityDb.getQueryUtil().getIntegerDataTypeName()); prefQs.addSelector( QueryFunctionSelector.makeFunctionSelector(QueryFunctionHelper.MAX, castVisibility, "VISIBILITY")); + QueryFunctionSelector castUserDiscoverable = QueryFunctionSelector.makeFunctionSelector( + QueryFunctionHelper.CAST, "USERPROJECTPREFERENCE__USERDISCOVERABLE", "castUserDiscoverable"); + castUserDiscoverable.setDataType(securityDb.getQueryUtil().getIntegerDataTypeName()); + prefQs.addSelector(QueryFunctionSelector.makeFunctionSelector(QueryFunctionHelper.MAX, castUserDiscoverable, + "USERDISCOVERABLE")); prefQs.addGroupBy(new QueryColumnSelector("USERPROJECTPREFERENCE__PROJECTID", "PROJECTID")); prefQs.addExplicitFilter( getUserIdAndTypeFilter(user, "USERPROJECTPREFERENCE__USERID", "USERPROJECTPREFERENCE__TYPE")); @@ -3927,6 +3937,30 @@ public static void setProjectFavorite(User user, String projectId, boolean isFav isFavorite); } + /** + * Let agents find and use a project for the user, or stop them, for each of + * the user's logins. The choice is saved in USERPROJECTPREFERENCE, apart from + * the user's permissions, and changes only this project. A project is not + * discoverable to the user's agents until the user turns this on. + * + * @param user the user + * @param projectId the project + * @param discoverable true to let the user's agents find the project, false to + * stop them + * @throws IllegalAccessException when the project is not public and the user + * cannot view it + * @throws Exception when the preference cannot be saved + */ + public static void setProjectUserDiscoverable(User user, String projectId, boolean discoverable) + throws Exception { + if (!projectIsGlobal(projectId) && !userCanViewProject(user, projectId)) { + throw new IllegalAccessException( + "The user doesn't have the permission to change whether this project is discoverable to their agents."); + } + setUserPreference(user, "USERPROJECTPREFERENCE", List.of(Pair.with("PROJECTID", projectId)), + "USERDISCOVERABLE", discoverable); + } + /////////////////////////////////////////////// /////////////////////////////////////////////// ///////////////// PROJECTS///////////////////// diff --git a/src/prerna/reactor/project/MyProjectsReactor.java b/src/prerna/reactor/project/MyProjectsReactor.java index 4361b3cb60..4eef6d260a 100644 --- a/src/prerna/reactor/project/MyProjectsReactor.java +++ b/src/prerna/reactor/project/MyProjectsReactor.java @@ -249,6 +249,10 @@ private static JSONObject responseSchema() { new JSONObject().put("type", "integer").put("enum", new JSONArray().put(0).put(1)).put("description", "1 if user has favorited this project, 0 otherwise")); + itemProperties.put("project_user_discoverable", + new JSONObject().put("type", "integer").put("enum", new JSONArray().put(0).put(1)).put("description", + "1 if the user has made this project discoverable to their agents, 0 otherwise (the default)")); + itemProperties.put("project_cost", new JSONObject().put("type", "string").put("description", "Cost metadata, may be empty string")); diff --git a/src/prerna/reactor/security/MyEnginesReactor.java b/src/prerna/reactor/security/MyEnginesReactor.java index 16a41069aa..dd300b31ff 100644 --- a/src/prerna/reactor/security/MyEnginesReactor.java +++ b/src/prerna/reactor/security/MyEnginesReactor.java @@ -203,7 +203,7 @@ public ReactorDefinition getReactorDefinition() { Returns a list of engines that the user has access to. Inputs: filterWord, limit, offset, onlyFavorites, engineType, engine, permissionFilters, metaKeys, metaFilters, noMeta, includeUserTracking, sort. - Response keys: prefer engine_* fields (engine_id, engine_name, engine_display_name, engine_type, engine_subtype, engine_cost, engine_discoverable, engine_global, engine_tool_app, engine_created_by, engine_created_by_type, engine_date_created, low_engine_name, engine_user_permission, engine_group_permission, engine_favorite). + Response keys: prefer engine_* fields (engine_id, engine_name, engine_display_name, engine_type, engine_subtype, engine_cost, engine_discoverable, engine_global, engine_tool_app, engine_created_by, engine_created_by_type, engine_date_created, low_engine_name, engine_user_permission, engine_group_permission, engine_favorite, engine_user_discoverable). Model engines also include a capabilities object containing modelId, capability, inputModalities, outputModalities, contextWindow, maxOutputTokens, and builtinTools when model metadata is available and noMeta is false. Any response key prefixed with app_* or database_* is legacy and should not be used. """) @@ -260,6 +260,9 @@ private static JSONObject responseSchema() { "Group-derived permission level for this engine")); itemProperties.put("engine_favorite", new JSONObject().put("type", "integer") .put("enum", new JSONArray().put(0).put(1)).put("description", "1 if favorited, otherwise 0")); + itemProperties.put("engine_user_discoverable", new JSONObject().put("type", "integer") + .put("enum", new JSONArray().put(0).put(1)).put("description", + "1 if the user has made this engine discoverable to their agents, otherwise 0 (the default)")); itemProperties.put("permission", new JSONObject().put("type", "integer").put("description", "Effective permission level for this engine")); itemProperties.put("upvotes", diff --git a/src/prerna/reactor/security/SetEngineUserDiscoverableReactor.java b/src/prerna/reactor/security/SetEngineUserDiscoverableReactor.java new file mode 100644 index 0000000000..8ef59c8848 --- /dev/null +++ b/src/prerna/reactor/security/SetEngineUserDiscoverableReactor.java @@ -0,0 +1,84 @@ +/******************************************************************************* + * Copyright 2015 Defense Health Agency (DHA) + * + * If your use of this software does not include any GPLv2 components: + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ---------------------------------------------------------------------------- + * If your use of this software includes any GPLv2 components: + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * 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 General Public License for more details. + *******************************************************************************/ +package prerna.reactor.security; + +import prerna.auth.utils.SecurityEngineUtils; +import prerna.reactor.AbstractReactor; +import prerna.sablecc2.om.PixelDataType; +import prerna.sablecc2.om.ReactorKeysEnum; +import prerna.sablecc2.om.nounmeta.NounMetadata; +import prerna.util.UploadInputUtility; + +/** + * Let agents find and use an engine for the signed in user, or stop them. This + * is the user's own preference, saved for each of their logins, and is off + * until the user turns it on. + */ +public class SetEngineUserDiscoverableReactor extends AbstractReactor { + + private static final String DISCOVERABLE = "discoverable"; + + public SetEngineUserDiscoverableReactor() { + this.keysToGet = new String[] { ReactorKeysEnum.ENGINE.getKey(), DISCOVERABLE }; + this.keyRequired = new int[] { 1, 1 }; + } + + @Override + public NounMetadata execute() { + organizeKeys(); + String engineId = UploadInputUtility.getEngineNameOrId(this.store, + this.keyValue.get(ReactorKeysEnum.ENGINE.getKey())); + boolean discoverable = SetProjectUserDiscoverableReactor.parseDiscoverable(this.keyValue.get(DISCOVERABLE)); + try { + SecurityEngineUtils.setEngineUserDiscoverable(this.insight.getUser(), engineId, discoverable); + } catch (IllegalAccessException e) { + throw new IllegalArgumentException(e.getMessage()); + } catch (Exception e) { + throw new IllegalArgumentException("Could not save whether the engine is discoverable to agents", e); + } + NounMetadata noun = new NounMetadata(true, PixelDataType.BOOLEAN); + noun.addAdditionalReturn(NounMetadata.getSuccessNounMessage(discoverable + ? "The engine is now discoverable to your agents" + : "The engine is no longer discoverable to your agents")); + return noun; + } + + @Override + public String getReactorDescription() { + return "Set whether an engine is discoverable to the user's agents. Engines are not discoverable until the user turns this on"; + } + + @Override + protected String getDescriptionForKey(String key) { + if (key.equals(DISCOVERABLE)) { + return "true to let the user's agents find and use the engine, false to stop them"; + } + return super.getDescriptionForKey(key); + } + +} diff --git a/src/prerna/reactor/security/SetProjectUserDiscoverableReactor.java b/src/prerna/reactor/security/SetProjectUserDiscoverableReactor.java new file mode 100644 index 0000000000..923c912c37 --- /dev/null +++ b/src/prerna/reactor/security/SetProjectUserDiscoverableReactor.java @@ -0,0 +1,101 @@ +/******************************************************************************* + * Copyright 2015 Defense Health Agency (DHA) + * + * If your use of this software does not include any GPLv2 components: + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ---------------------------------------------------------------------------- + * If your use of this software includes any GPLv2 components: + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * 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 General Public License for more details. + *******************************************************************************/ +package prerna.reactor.security; + +import prerna.auth.utils.SecurityProjectUtils; +import prerna.reactor.AbstractReactor; +import prerna.sablecc2.om.PixelDataType; +import prerna.sablecc2.om.ReactorKeysEnum; +import prerna.sablecc2.om.nounmeta.NounMetadata; +import prerna.util.UploadInputUtility; + +/** + * Let agents find and use a project for the signed in user, or stop them. This + * is the user's own preference, saved for each of their logins, and is off + * until the user turns it on. + */ +public class SetProjectUserDiscoverableReactor extends AbstractReactor { + + private static final String DISCOVERABLE = "discoverable"; + + public SetProjectUserDiscoverableReactor() { + this.keysToGet = new String[] { ReactorKeysEnum.PROJECT.getKey(), DISCOVERABLE }; + this.keyRequired = new int[] { 1, 1 }; + } + + @Override + public NounMetadata execute() { + organizeKeys(); + String projectId = UploadInputUtility.getProjectNameOrId(this.store); + projectId = SecurityProjectUtils.testUserProjectIdForAlias(this.insight.getUser(), projectId); + boolean discoverable = parseDiscoverable(this.keyValue.get(DISCOVERABLE)); + try { + SecurityProjectUtils.setProjectUserDiscoverable(this.insight.getUser(), projectId, discoverable); + } catch (IllegalAccessException e) { + throw new IllegalArgumentException(e.getMessage()); + } catch (Exception e) { + throw new IllegalArgumentException("Could not save whether the project is discoverable to agents", e); + } + NounMetadata noun = new NounMetadata(true, PixelDataType.BOOLEAN); + noun.addAdditionalReturn(NounMetadata.getSuccessNounMessage(discoverable + ? "The project is now discoverable to your agents" + : "The project is no longer discoverable to your agents")); + return noun; + } + + /** + * Read the discoverable input, which must be true or false. + * + * @param value the input + * @return the value + * @throws IllegalArgumentException when the input is not true or false + */ + static boolean parseDiscoverable(String value) { + if ("true".equalsIgnoreCase(value)) { + return true; + } + if ("false".equalsIgnoreCase(value)) { + return false; + } + throw new IllegalArgumentException("discoverable must be true or false"); + } + + @Override + public String getReactorDescription() { + return "Set whether a project is discoverable to the user's agents. Projects are not discoverable until the user turns this on"; + } + + @Override + protected String getDescriptionForKey(String key) { + if (key.equals(DISCOVERABLE)) { + return "true to let the user's agents find and use the project, false to stop them"; + } + return super.getDescriptionForKey(key); + } + +} diff --git a/test/prerna/auth/utils/SecurityEngineUtilsUnitTests.java b/test/prerna/auth/utils/SecurityEngineUtilsUnitTests.java index 89ecadd459..047305c032 100644 --- a/test/prerna/auth/utils/SecurityEngineUtilsUnitTests.java +++ b/test/prerna/auth/utils/SecurityEngineUtilsUnitTests.java @@ -1174,6 +1174,65 @@ void testEngineVisibility_onlyChangesThatEngine() throws Exception { assertEquals(2, SecurityEngineUtils.getVisibleUserDatabaseIds(user).size()); } + /// + /// setEngineUserDiscoverable + /// + + @Test + void testEngineUserDiscoverable() throws Exception { + User user = UnitTestSecurityAuthUtils.createUser("admin", true); + UnitTestSecurityAuthUtils.createEngine("testId", "testAlias", user); + UnitTestSecurityAuthUtils.createEngine("testId2", "testAlias2", user); + + User user2 = UnitTestSecurityAuthUtils.createUser("admin2", true); + + IllegalAccessException ex = assertThrows(IllegalAccessException.class, + () -> SecurityEngineUtils.setEngineUserDiscoverable(user2, "testId", true)); + assertEquals( + "The user doesn't have the permission to change whether this engine is discoverable to their agents.", + ex.getMessage()); + + // no preference saved yet, so it is off + for (Map engine : SecurityEngineUtils.getUserEngineList(user, null, null, false, null, null, + null, null, null, null)) { + assertEquals("0", engine.get("engine_user_discoverable").toString()); + } + + SecurityEngineUtils.setEngineFavorite(user, "testId", true); + SecurityEngineUtils.setEngineUserDiscoverable(user, "testId", true); + for (Map engine : SecurityEngineUtils.getUserEngineList(user, null, null, false, null, null, + null, null, null, null)) { + boolean changed = "testId".equals(engine.get("engine_id")); + assertEquals(changed ? "1" : "0", engine.get("engine_user_discoverable").toString()); + // turning it on keeps the favorite + assertEquals(changed ? "1" : "0", engine.get("engine_favorite").toString()); + } + + SecurityEngineUtils.setEngineUserDiscoverable(user, "testId", false); + for (Map engine : SecurityEngineUtils.getUserEngineList(user, null, null, false, null, null, + null, null, null, null)) { + assertEquals("0", engine.get("engine_user_discoverable").toString()); + } + } + + @Test + void testEngineUserDiscoverable_publicEngineWithoutAccess() throws Exception { + User user = UnitTestSecurityAuthUtils.createUser("admin", true); + UnitTestSecurityAuthUtils.createEngine("testId", "testAlias", user); + SecurityEngineUtils.setEngineGlobal(user, "testId", true); + + User user2 = UnitTestSecurityAuthUtils.createUser("user2", false); + SecurityEngineUtils.setEngineUserDiscoverable(user2, "testId", true); + + List> user2Engines = SecurityEngineUtils.getUserEngineList(user2, null, null, false, null, + null, null, null, null, null); + assertEquals(1, user2Engines.size()); + assertEquals("1", user2Engines.getFirst().get("engine_user_discoverable").toString()); + // the preference belongs to user2 alone + assertEquals("0", SecurityEngineUtils.getUserEngineList(user, null, null, false, null, null, null, null, null, + null).getFirst().get("engine_user_discoverable").toString()); + } + /// /// setEngineFavorite /// diff --git a/test/prerna/auth/utils/SecurityProjectUtilsUnitTests.java b/test/prerna/auth/utils/SecurityProjectUtilsUnitTests.java index aabfcd75f7..ebf3758206 100644 --- a/test/prerna/auth/utils/SecurityProjectUtilsUnitTests.java +++ b/test/prerna/auth/utils/SecurityProjectUtilsUnitTests.java @@ -1067,6 +1067,74 @@ void testSetProjectVisibility_onlyChangesThatProject() throws Exception { SecurityProjectUtils.getUserProjectList(user, null, null, false, null, null, null, null, null).size()); } + /// + /// setProjectUserDiscoverable + /// + + @Test + void testSetProjectUserDiscoverable_noAccess() { + User user = UnitTestSecurityAuthUtils.createUser("admin", true); + User user2 = UnitTestSecurityAuthUtils.createUser("user2", false); + UnitTestSecurityAuthUtils.createProject("testProjectId", "testProjectName", user); + + IllegalAccessException ex = assertThrows(IllegalAccessException.class, + () -> SecurityProjectUtils.setProjectUserDiscoverable(user2, "testProjectId", true)); + assertEquals( + "The user doesn't have the permission to change whether this project is discoverable to their agents.", + ex.getMessage()); + } + + @Test + void testSetProjectUserDiscoverable_offUntilTurnedOn() throws Exception { + User user = UnitTestSecurityAuthUtils.createUser("admin", true); + UnitTestSecurityAuthUtils.createProject("testProjectId", "testProjectName", user); + UnitTestSecurityAuthUtils.createProject("testProjectId2", "testProjectName2", user); + + // no preference saved yet, so it is off + assertEquals("0", SecurityProjectUtils.getUserProjectList(user, "testProjectId").getFirst() + .get("project_user_discoverable").toString()); + // another preference on the row leaves it off + SecurityProjectUtils.setProjectFavorite(user, "testProjectId", true); + assertEquals("0", SecurityProjectUtils.getUserProjectList(user, "testProjectId").getFirst() + .get("project_user_discoverable").toString()); + + SecurityProjectUtils.setProjectUserDiscoverable(user, "testProjectId", true); + for (Map project : SecurityProjectUtils.getUserProjectList(user, null, null, false, null, + null, null, null, null)) { + String expected = "testProjectId".equals(project.get("project_id")) ? "1" : "0"; + assertEquals(expected, project.get("project_user_discoverable").toString()); + } + // turning it on keeps the favorite + assertEquals("1", SecurityProjectUtils.getUserProjectList(user, "testProjectId").getFirst() + .get("project_favorite").toString()); + + SecurityProjectUtils.setProjectUserDiscoverable(user, "testProjectId", false); + assertEquals("0", SecurityProjectUtils.getUserProjectList(user, "testProjectId").getFirst() + .get("project_user_discoverable").toString()); + } + + @Test + void testSetProjectUserDiscoverable_publicProjectWithoutAccess() throws Exception { + User user = UnitTestSecurityAuthUtils.createUser("admin", true); + UnitTestSecurityAuthUtils.createProject("testProjectId", "testProjectName", user); + SecurityProjectUtils.setProjectGlobal(user, "testProjectId", true); + + User user2 = UnitTestSecurityAuthUtils.createUser("user2", false); + String user2Id = user2.getPrimaryLoginToken().getId(); + SecurityProjectUtils.setProjectUserDiscoverable(user2, "testProjectId", true); + + assertEquals("1", SecurityProjectUtils.getUserProjectList(user2, "testProjectId").getFirst() + .get("project_user_discoverable").toString()); + // the preference belongs to user2 alone + assertEquals("0", SecurityProjectUtils.getUserProjectList(user, "testProjectId").getFirst() + .get("project_user_discoverable").toString()); + // kept apart from the permissions, so no access row is added + assertEquals(0L, + ((Number) UnitTestGroupTables.queryValue(securityDb, + "SELECT COUNT(*) FROM PROJECTPERMISSION WHERE USERID=? AND PROJECTID=?", user2Id, + "testProjectId")).longValue()); + } + /// /// getAllMetakeys ///