Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions api/src/org/labkey/api/ApiModule.java
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@
import org.labkey.api.security.SecurityManager;
import org.labkey.api.security.UserManager;
import org.labkey.api.security.ValidEmail;
import org.labkey.api.security.impersonation.ImpersonationTestCase;
import org.labkey.api.settings.AppProps;
import org.labkey.api.settings.AppPropsTestCase;
import org.labkey.api.settings.BaseServerProperties;
Expand Down Expand Up @@ -398,6 +399,7 @@ public void registerServlets(ServletContext servletCtx)
FileUtil.TestCase.class,
GenerateUniqueDataIterator.TestCase.class,
HelpTopic.TestCase.class,
ImpersonationTestCase.class,
InlineInClauseGenerator.TestCase.class,
JSONDataLoader.HeaderMatchTest.class,
JSONDataLoader.MetadataTest.class,
Expand Down
4 changes: 3 additions & 1 deletion api/src/org/labkey/api/admin/AdminUrls.java
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,9 @@ public interface AdminUrls extends UrlProvider
ActionURL getSessionLoggingURL();
ActionURL getTrackedAllocationsViewerURL();
ActionURL getSystemMaintenanceURL();
ActionURL getCspReportToURL(String cspVersion);
ActionURL getCspReportToURL();

ActionURL getAllowedExternalRedirectHostsURL();

/**
* Simply adds an "Admin Console" link to nav trail if invoked in the root container. Otherwise, root is unchanged.
Expand Down
5 changes: 5 additions & 0 deletions api/src/org/labkey/api/audit/query/DefaultAuditTypeTable.java
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,11 @@ protected void initColumns()
@Override
protected SimpleFilter.FilterClause getContainerFilterClause(ContainerFilter filter, FieldKey fieldKey)
{
// TODO: Setting a contextual role on the container filter clause should not be necessary; the user passed
// (separately) to the ContainerFilter should have the appropriate permission. However, some app actions
// (GetTransactionRowIdsAction, maybe GetLocationHistoryAction, etc.) have been relying on this behavior. Clean
// this up soon, but not for 26.3. Note that this is the only code path that passes contextual roles into
// createFilterClause(), so we could eliminate that option during clean up.
User user = (null == getUserSchema()) ? null : getUserSchema().getUser();
Set<Role> roles = SecurityManager.canSeeAuditLog(user) ? RoleManager.roleSet(CanSeeAuditLogRole.class) : null;
return filter.createFilterClause(getSchema(), fieldKey, CanSeeAuditLogPermission.class, roles);
Expand Down
5 changes: 3 additions & 2 deletions api/src/org/labkey/api/data/Container.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Strings;
import org.apache.logging.log4j.Logger;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
Expand Down Expand Up @@ -827,7 +828,7 @@ public static boolean isLegalName(String name, boolean isProject, StringBuilder
return false;
}

if (StringUtils.endsWithIgnoreCase(name, ".view") || StringUtils.endsWithIgnoreCase(name, ".api") || StringUtils.endsWithIgnoreCase(name, ".post"))
if (Strings.CI.endsWith(name, ".view") || Strings.CI.endsWith(name, ".api") || Strings.CI.endsWith(name, ".post"))
{
error.append("Folder name should not end with '.view', '.api', or '.post'.");
return false;
Expand Down Expand Up @@ -1249,7 +1250,7 @@ public boolean getAsBoolean()
}

// always put the required modules in the set
// note that this will pickup the modules from the folder type's getActiveModules()
// note that this will pick up the modules from the folder type's getActiveModules()
Set<Module> modules = new HashSet<>(getRequiredModules());

// add all modules found in user preferences:
Expand Down
4 changes: 2 additions & 2 deletions api/src/org/labkey/api/data/ContainerFilter.java
Original file line number Diff line number Diff line change
Expand Up @@ -169,9 +169,9 @@ public SimpleFilter.FilterClause createFilterClause(DbSchema schema, FieldKey co
}

/** Create a FilterClause that restricts based on the containers that meet the filter and user that meets the permission*/
public SimpleFilter.FilterClause createFilterClause(DbSchema schema, FieldKey containerFilterColumn, Class<? extends Permission> permission, Set<Role> roles)
public SimpleFilter.FilterClause createFilterClause(DbSchema schema, FieldKey containerFilterColumn, Class<? extends Permission> permission, Set<Role> contextualRoles)
{
return new ContainerClause(schema, containerFilterColumn, this, permission, roles);
return new ContainerClause(schema, containerFilterColumn, this, permission, contextualRoles);
}


Expand Down
2 changes: 1 addition & 1 deletion api/src/org/labkey/api/data/RecordFactory.java
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ public RecordFactory(Class<K> clazz)
{
Object[] params = Arrays.stream(_parameters).map(p -> {
Object value = m.get(p.getName());
return ConvertUtils.convert(value, p.getType());
return value != null ? ConvertUtils.convert(value, p.getType()) : null;
}).toArray();

try
Expand Down
1 change: 1 addition & 0 deletions api/src/org/labkey/api/data/SqlScriptExecutor.java
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,7 @@ public void execute()
{
LOG.info("Executing {}", upgradeMethod.getDisplayName());
_moduleContext.invokeUpgradeMethod(upgradeMethod);
LOG.info("Finished executing {}", upgradeMethod.getDisplayName());
}
}
catch (NoSuchMethodException e)
Expand Down
4 changes: 3 additions & 1 deletion api/src/org/labkey/api/data/TempTableTracker.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import java.io.IOException;
import java.io.RandomAccessFile;
import java.lang.ref.Cleaner;
import java.util.ArrayList;
import java.util.Map;
import java.util.TreeMap;
import java.util.TreeSet;
Expand Down Expand Up @@ -286,7 +287,8 @@ public void shutdownStarted()
{
synchronized(createdTableNames)
{
for (TempTableTracker ttt : createdTableNames.values())
// Copy createdTableNames.values() to prevent ConcurrentModificationException
for (TempTableTracker ttt : new ArrayList<>(createdTableNames.values()))
{
ttt.state.run();
}
Expand Down
18 changes: 18 additions & 0 deletions api/src/org/labkey/api/data/dialect/PostgreSqlService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package org.labkey.api.data.dialect;

import org.labkey.api.services.ServiceRegistry;

public interface PostgreSqlService
{
static PostgreSqlService get()
{
return ServiceRegistry.get().getService(PostgreSqlService.class);
}

static void setInstance(PostgreSqlService impl)
{
ServiceRegistry.get().registerService(PostgreSqlService.class, impl);
}

BasePostgreSqlDialect getDialect();
}
2 changes: 1 addition & 1 deletion api/src/org/labkey/api/exp/api/ExpProtocol.java
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ enum Status
String getContact();

List<? extends ExpProtocol> getChildProtocols();
List<? extends ExpExperiment> getBatches();
List<? extends ExpExperiment> getBatches(@Nullable Container c);

void setEntityId(String entityId);
String getEntityId();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
/**
* Designates actions that should not enforce forbidden project checking, which is used to support impersonation
* container restrictions and project locking. Use with caution. Allows project admins to perform actions outside
* of the project they administer and non-admins the ability to invoke actions in locked projects.
* the project they administer and non-admins the ability to invoke actions in locked projects.
*
* @see org.labkey.api.action.PermissionCheckableAction#checkPermissions()
* @see org.labkey.api.data.Container#isForbiddenProject(org.labkey.api.security.User)
Expand Down
2 changes: 1 addition & 1 deletion api/src/org/labkey/api/qc/TsvDataSerializer.java
Original file line number Diff line number Diff line change
Expand Up @@ -91,8 +91,8 @@ private List<String> exportData(DataIteratorBuilder data, List<String> columns,
sep = "\t";
}
pw.println();
writeRow(row, columns, pw, tsvWriter);
}
writeRow(row, columns, pw, tsvWriter); // GitHub Issue #875: write the first row regardless of whether we had a header or not

// write the remaining rows
while (iter.next())
Expand Down
46 changes: 0 additions & 46 deletions api/src/org/labkey/api/query/QueryView.java
Original file line number Diff line number Diff line change
Expand Up @@ -1050,52 +1050,6 @@ public ActionButton createDeleteButton(boolean showConfirmation)
return null;
}

public ActionButton createDeleteAllRowsButton(String tableNoun)
{
ActionButton deleteAllRows = new ActionButton("Delete All Rows");
deleteAllRows.setDisplayPermission(AdminPermission.class);
deleteAllRows.setActionType(ActionButton.Action.SCRIPT);
deleteAllRows.setScript(
"LABKEY.requiresExt4Sandbox(function() {" +
"Ext4.Msg.confirm('Confirm Deletion', 'Are you sure you wish to delete all rows in this " + tableNoun + "? This action cannot be undone and will result in an empty " + tableNoun + ".', function(button){" +
"if (button == 'yes'){" +
"var waitMask = Ext4.Msg.wait('Deleting Rows...', 'Delete Rows'); " +
"Ext4.Ajax.request({ " +
"url : LABKEY.ActionURL.buildURL('query', 'truncateTable'), " +
"method : 'POST', " +
"success: function(response) " +
"{" +
"waitMask.close(); " +
"var data = Ext4.JSON.decode(response.responseText); " +
"Ext4.Msg.show({ " +
"title : 'Success', " +
"buttons : Ext4.MessageBox.OK, " +
"msg : data.deletedRows + ' rows deleted', " +
"fn: function(btn) " +
"{ " +
"if(btn == 'ok') " +
"{ " +
"window.location.reload(); " +
"} " +
"} " +
"})" +
"}, " +
"failure : function(response, opts) " +
"{ " +
"waitMask.close(); " +
"Ext4.getBody().unmask(); " +
"LABKEY.Utils.displayAjaxErrorResponse(response, opts); " +
"}, " +
"jsonData : {schemaName : " + PageFlowUtil.jsString(getQueryDef().getSchema().getName()) + ", queryName : " + PageFlowUtil.jsString(getQueryDef().getName()) + "}, " +
"scope : this " +
"});" +
"}" +
"});" +
"});"
);
return deleteAllRows;
}

public ActionButton createInsertMenuButton()
{
return createInsertMenuButton(null, null);
Expand Down
2 changes: 2 additions & 0 deletions api/src/org/labkey/api/reports/ReportService.java
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@

public interface ReportService
{
String R_REPORT_CUSTOM_SHARING = "rReportCustomSharing";

// this logger is to enable all report loggers in the admin ui (org.labkey.api.reports.*)
@SuppressWarnings({"UnusedDeclaration", "SSBasedInspection"})
Logger packageLogger = LogManager.getLogger(ReportService.class.getPackageName());
Expand Down
11 changes: 9 additions & 2 deletions api/src/org/labkey/api/reports/report/r/RReport.java
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
import org.labkey.api.rstudio.RStudioService;
import org.labkey.api.security.SecurityManager;
import org.labkey.api.security.User;
import org.labkey.api.settings.OptionalFeatureService;
import org.labkey.api.thumbnail.Thumbnail;
import org.labkey.api.util.FileUtil;
import org.labkey.api.util.PageFlowUtil;
Expand Down Expand Up @@ -74,6 +75,8 @@
import java.util.Map;
import java.util.Set;

import static org.labkey.api.reports.ReportService.R_REPORT_CUSTOM_SHARING;

public class RReport extends ExternalScriptEngineReport
{
public static final String TYPE = "ReportService.rReport";
Expand Down Expand Up @@ -925,8 +928,12 @@ public String getEditAreaSyntax()
@Override
public boolean allowShareButton(User user, Container container)
{
// allow sharing if this R report is a DB report and the user canShare
return !getDescriptor().isModuleBased() && canShare(user, container);
if (OptionalFeatureService.get().isFeatureEnabled(R_REPORT_CUSTOM_SHARING))
{
// allow sharing if this R report is a DB report and the user canShare
return !getDescriptor().isModuleBased() && canShare(user, container);
}
return false;
}

public static class TestCase extends Assert
Expand Down
15 changes: 4 additions & 11 deletions api/src/org/labkey/api/security/NormalPermissionsContext.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,7 @@
import org.labkey.api.security.impersonation.ImpersonationContextFactory;
import org.labkey.api.security.impersonation.RoleImpersonationContextFactory;
import org.labkey.api.security.impersonation.UserImpersonationContextFactory;
import org.labkey.api.security.permissions.AdminPermission;
import org.labkey.api.security.permissions.CanImpersonatePrivilegedSiteRolesPermission;
import org.labkey.api.security.permissions.ImpersonatePermission;
import org.labkey.api.util.PageFlowUtil;
import org.labkey.api.view.ActionURL;
import org.labkey.api.view.NavTree;
Expand Down Expand Up @@ -93,22 +92,16 @@ public void addMenu(NavTree menu, Container c, User user, ActionURL currentURL)
{
@Nullable Container project = c.getProject();

// Must be site or project admin (folder admins can't impersonate)
if (user.hasRootAdminPermission() || (null != project && project.hasPermission(user, AdminPermission.class)))
// Site admin, app admin, and impersonating troubleshooter can impersonate anywhere; project admin can
// impersonate in that project. Folder admins can't impersonate.
if (user.hasRootPermission(ImpersonatePermission.class) || (project != null && project.hasPermission(user, ImpersonatePermission.class)))
{
NavTree impersonateMenu = new NavTree("Impersonate");
UserImpersonationContextFactory.addMenu(impersonateMenu);
GroupImpersonationContextFactory.addMenu(impersonateMenu);
RoleImpersonationContextFactory.addMenu(impersonateMenu);
menu.addChild(impersonateMenu);
}
// Or Impersonating Troubleshooter to impersonate site roles only
else if (null == project && user.hasRootPermission(CanImpersonatePrivilegedSiteRolesPermission.class))
{
NavTree impersonateMenu = new NavTree("Impersonate");
RoleImpersonationContextFactory.addMenu(impersonateMenu);
menu.addChild(impersonateMenu);
}
}

NavTree signOut = new NavTree("Sign Out", PageFlowUtil.urlProvider(LoginUrls.class).getLogoutURL(c));
Expand Down
2 changes: 1 addition & 1 deletion api/src/org/labkey/api/security/PermissionsContext.java
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ default Stream<Role> getAssignedRoles(User user, SecurableResource resource)
if (!(role instanceof AbstractRootContainerRole siteRole))
throw new IllegalStateException("Root roles should all be AbstractRootContainerRole");

return siteRole.isAvailableEverywhere() || resource.equals(root);
return siteRole.isApplicableOutsideRoot() || resource.equals(root);
});

if (!resource.equals(root))
Expand Down
8 changes: 4 additions & 4 deletions api/src/org/labkey/api/security/SecurityManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@
import org.labkey.api.security.permissions.AbstractPermission;
import org.labkey.api.security.permissions.AddUserPermission;
import org.labkey.api.security.permissions.AdminPermission;
import org.labkey.api.security.permissions.CanImpersonateSiteRolesPermission;
import org.labkey.api.security.permissions.ImpersonatePermission;
import org.labkey.api.security.permissions.DeletePermission;
import org.labkey.api.security.permissions.InsertPermission;
import org.labkey.api.security.permissions.Permission;
Expand Down Expand Up @@ -822,7 +822,7 @@ public static void impersonateUser(ViewContext viewContext, User impersonatedUse
@Nullable Container project = viewContext.getContainer().getProject();
User user = viewContext.getUser();

if (user.hasRootAdminPermission())
if (user.hasRootPermission(ImpersonatePermission.class))
project = null;

impersonate(viewContext, new UserImpersonationContextFactory(project, user, impersonatedUser, returnUrl));
Expand All @@ -839,7 +839,7 @@ public static void impersonateRoles(ViewContext viewContext, Collection<Role> ne
@Nullable Container project = viewContext.getContainer().getProject();
User user = viewContext.getUser();

if (user.hasRootPermission(CanImpersonateSiteRolesPermission.class))
if (user.hasRootPermission(ImpersonatePermission.class))
project = null;

impersonate(viewContext, new RoleImpersonationContextFactory(project, user, newImpersonationRoles, currentImpersonationRoles, returnUrl));
Expand Down Expand Up @@ -1412,7 +1412,7 @@ public static void ensureAtLeastOneRootAdminExists()
}

// A permission class that uniquely identifies the root admins, of which we insist there must be at least one
public static final Class<? extends Permission> ROOT_ADMIN_PERMISSION = CanImpersonateSiteRolesPermission.class;
public static final Class<? extends Permission> ROOT_ADMIN_PERMISSION = ImpersonatePermission.class;

public static boolean isRootAdmin(User user)
{
Expand Down
4 changes: 2 additions & 2 deletions api/src/org/labkey/api/security/User.java
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
import org.labkey.api.security.permissions.AnalystPermission;
import org.labkey.api.security.permissions.ApplicationAdminPermission;
import org.labkey.api.security.permissions.BrowserDeveloperPermission;
import org.labkey.api.security.permissions.CanImpersonateSiteRolesPermission;
import org.labkey.api.security.permissions.ImpersonatePermission;
import org.labkey.api.security.permissions.DeletePermission;
import org.labkey.api.security.permissions.InsertPermission;
import org.labkey.api.security.permissions.Permission;
Expand Down Expand Up @@ -594,7 +594,7 @@ public static JSONObject getUserProps(User user, User currentUser, @Nullable Con
props.put("isAdmin", nonNullContainer && container.hasPermission(user, AdminPermission.class));
props.put("isRootAdmin", user.hasRootAdminPermission());
props.put("isSystemAdmin", user.hasSiteAdminPermission());
props.put("canImpersonateSiteRoles", user.hasRootPermission(CanImpersonateSiteRolesPermission.class));
props.put("canImpersonateSiteRoles", user.hasRootPermission(ImpersonatePermission.class));
props.put("isGuest", user.isGuest());
props.put("isDeveloper", user.isBrowserDev());
props.put("isAnalyst", user.hasRootPermission(AnalystPermission.class));
Expand Down
10 changes: 5 additions & 5 deletions api/src/org/labkey/api/security/UserCache.java
Original file line number Diff line number Diff line change
Expand Up @@ -83,8 +83,8 @@ private UserCache()
return null != user ? user.cloneUser() : null;
}

// Returns a deep copy of the active users list, allowing callers to interrogate user permissions without affecting
// cached users. Collection is ordered by email... maybe it should be ordered by display name?
// Returns a mutable deep copy of the active users list, allowing callers to interrogate user permissions without
// affecting cached users. Collection is ordered by email... maybe it should be ordered by display name?
static @NotNull Collection<User> getActiveUsers()
{
List<User> activeUsers = getUserCollections().getActiveUsers();
Expand All @@ -95,7 +95,7 @@ private UserCache()
.collect(Collectors.toCollection(LinkedList::new));
}

// Returns a deep copy of the users list including deactivated accounts, allowing callers to interrogate user permissions
// Returns a mutable deep copy of the users list including deactivated accounts, allowing callers to interrogate user permissions
// without affecting cached users. Collection is ordered randomly... maybe it should be ordered by display name?
static @NotNull Collection<User> getActiveAndInactiveUsers()
{
Expand All @@ -104,7 +104,7 @@ private UserCache()
return users
.stream()
.map(User::cloneUser)
.collect(Collectors.toList());
.collect(Collectors.toCollection(LinkedList::new));
}

static @NotNull List<Integer> getUserIds()
Expand All @@ -114,7 +114,7 @@ private UserCache()

static @NotNull Map<ValidEmail, User> getUserEmailMap()
{
return Collections.unmodifiableMap(getUserCollections().getEmailMap());
return getUserCollections().getEmailMap();
}

static int getActiveUserCount()
Expand Down
Loading
Loading