diff --git a/api/src/org/labkey/api/security/ApiKeyManager.java b/api/src/org/labkey/api/security/ApiKeyManager.java index 69684001985..b4b6cace88d 100644 --- a/api/src/org/labkey/api/security/ApiKeyManager.java +++ b/api/src/org/labkey/api/security/ApiKeyManager.java @@ -22,6 +22,7 @@ import org.jetbrains.annotations.Nullable; import org.junit.Assert; import org.junit.Test; +import org.labkey.api.data.ContainerManager; import org.labkey.api.data.CoreSchema; import org.labkey.api.data.DbScope; import org.labkey.api.data.DbScope.Transaction; @@ -39,6 +40,15 @@ import org.labkey.api.query.FieldKey; import org.labkey.api.security.UserManager.SessionHandler; import org.labkey.api.security.ValidEmail.InvalidEmailException; +import org.labkey.api.security.permissions.AdminPermission; +import org.labkey.api.security.permissions.DeletePermission; +import org.labkey.api.security.permissions.InsertPermission; +import org.labkey.api.security.permissions.ReadPermission; +import org.labkey.api.security.permissions.UpdatePermission; +import org.labkey.api.security.roles.EditorRole; +import org.labkey.api.security.roles.ReaderRole; +import org.labkey.api.security.roles.Role; +import org.labkey.api.security.roles.RoleManager; import org.labkey.api.settings.AppProps; import org.labkey.api.settings.LenientStartupPropertyHandler; import org.labkey.api.settings.StartupProperty; @@ -60,6 +70,7 @@ import java.util.Map; import java.util.Objects; import java.util.Set; +import java.util.stream.Stream; import static org.labkey.api.util.IntegerUtils.asInteger; @@ -85,9 +96,9 @@ private ApiKeyManager() * @param user User to be associated with the new API key. * @return An API key that expires after the admin-configured duration */ - public @NotNull String createKey(@NotNull User user, @Nullable String description) + public @NotNull String createKey(@NotNull User user, @Nullable String description, @Nullable Class restrictionRole) { - return createKey(user, AppProps.getInstance().getApiKeyExpirationSeconds(), description); + return createKey(user, AppProps.getInstance().getApiKeyExpirationSeconds(), description, restrictionRole); } /** @@ -97,6 +108,18 @@ private ApiKeyManager() * @return An API key that expires after the specified number of seconds */ public @NotNull String createKey(@NotNull User user, int expirationSeconds, @Nullable String description) + { + return createKey(user, expirationSeconds, description, null); + } + + /** + * Create an API key associated with a user and persist it in the database. + * @param user User to be associated with the new API key. + * @param expirationSeconds Number of seconds until expiration. -1 means no expiration. + * @param restrictionRole Role class that limits this API key's permissions. null means no restrictions. + * @return An API key that expires after the specified number of seconds + */ + public @NotNull String createKey(@NotNull User user, int expirationSeconds, @Nullable String description, @Nullable Class restrictionRole) { if (user.isGuest()) throw new IllegalStateException("Can't create an API key for a guest"); @@ -120,6 +143,9 @@ private ApiKeyManager() if (description != null) map.put("Description", StringUtils.abbreviate(description.trim(), 256)); + if (restrictionRole != null) + map.put("RestrictionRole", restrictionRole.getName()); + try (Transaction t = CoreSchema.getInstance().getScope().beginTransaction(TRANSACTION_KIND)) { Table.insert(user, CoreSchema.getInstance().getTableAPIKeys(), map); @@ -183,17 +209,35 @@ public void updateLastUsed(String apikey) try (Transaction t = scope.beginTransaction(TRANSACTION_KIND)) { - SQLFragment sql = new SQLFragment("UPDATE " + CoreSchema.getInstance().getTableAPIKeys() + " SET LastUsed = ? WHERE Crypt = ?", new Date(), crypt(apikey)); + SQLFragment sql = new SQLFragment("UPDATE ") + .append(CoreSchema.getInstance().getTableAPIKeys()) + .append(" SET LastUsed = ? WHERE Crypt = ?") + .add(new Date()) + .add(crypt(apikey)); new SqlExecutor(scope).execute(sql); t.commit(); } } - public record ApiKeyAuthentication(int createdBy, int rowId) + public record ApiKeyAuthentication(int createdBy, int rowId, @Nullable Class restrictionRole) { - public User getUser() + public @Nullable User getUser() { - return UserManager.getUser(createdBy()); + User user = UserManager.getUser(createdBy()); + if (restrictionRole != null) + { + Role role = RoleManager.getRole(restrictionRole); + if (role == null) + { + LOG.error("API key for {} specifies a restriction role {} that was not found", user, restrictionRole.getName()); + user = null; + } + else + { + user = new PermissionsRestrictedUser(user, role.getPermissions()); + } + } + return user; } } @@ -212,7 +256,7 @@ public User getUser() try (Transaction t = CoreSchema.getInstance().getScope().beginTransaction(TRANSACTION_KIND)) { - ret = new TableSelector(CoreSchema.getInstance().getTableAPIKeys(), Set.of("CreatedBy", "RowId"), filter, null).getObject(ApiKeyAuthentication.class); + ret = new TableSelector(CoreSchema.getInstance().getTableAPIKeys(), Set.of("CreatedBy", "RowId", "RestrictionRole"), filter, null).getObject(ApiKeyAuthentication.class); t.commit(); } @@ -327,6 +371,53 @@ public void testTransaction() ApiKeyManager.get().deleteKey(apikey); assertNull(ApiKeyManager.get().authenticateFromApiKey(apikey)); } + + private record UserAndKey(User user, String apiKey){} + + @Test + public void testRoleRestrictions() + { + User admin = TestContext.get().getUser(); + UserAndKey readerUAK = createApiKeyAndRetrieveUser(admin, ReaderRole.class); + User reader = readerUAK.user(); + UserAndKey editorUAK = createApiKeyAndRetrieveUser(admin, EditorRole.class); + User editor = editorUAK.user(); + + ContainerManager.getAllChildren(ContainerManager.getRoot(), admin, AdminPermission.class).stream() + .limit(5) + .forEach(child -> { + assertTrue(child.hasPermission(admin, AdminPermission.class)); + assertFalse(child.hasPermission(editor, AdminPermission.class)); + assertFalse(child.hasPermission(reader, AdminPermission.class)); + Stream.of(DeletePermission.class, UpdatePermission.class, InsertPermission.class) + .forEach(perm -> { + assertTrue(child.hasPermission(admin, perm)); + assertTrue(child.hasPermission(editor, perm)); + assertFalse(child.hasPermission(reader, perm)); + }); + assertTrue(child.hasPermission(admin, ReadPermission.class)); + assertTrue(child.hasPermission(editor, ReadPermission.class)); + assertTrue(child.hasPermission(reader, ReadPermission.class)); + }); + + ApiKeyManager.get().deleteKey(readerUAK.apiKey()); + ApiKeyManager.get().deleteKey(editorUAK.apiKey()); + assertNull(ApiKeyManager.get().authenticateFromApiKey(readerUAK.apiKey())); + assertNull(ApiKeyManager.get().authenticateFromApiKey(editorUAK.apiKey())); + } + + private UserAndKey createApiKeyAndRetrieveUser(User user, Class restrictionRole) + { + String apiKey = ApiKeyManager.get().createKey(user, 10, "Created by ApiKeyManager.TestCase", restrictionRole); + ApiKeyAuthentication auth = ApiKeyManager.get().authenticateFromApiKey(apiKey); + assertNotNull(auth); + User restrictedUser = auth.getUser(); + assertNotNull(restrictedUser); + assertEquals(user.getUserId(), restrictedUser.getUserId()); + assertTrue(restrictedUser instanceof PermissionsRestrictedUser); + + return new UserAndKey(restrictedUser, apiKey); + } } public static class ApiKeyMaintenanceTask implements MaintenanceTask diff --git a/api/src/org/labkey/api/security/ClonedUser.java b/api/src/org/labkey/api/security/ClonedUser.java index b5870e4bf36..591d3511db3 100644 --- a/api/src/org/labkey/api/security/ClonedUser.java +++ b/api/src/org/labkey/api/security/ClonedUser.java @@ -45,7 +45,7 @@ protected ClonedUser(String email, int userId, String displayName, String firstN setPhone(phone); setLastActivity(lastActivity); - setImpersonationContext(ctx); + setPermissionsContext(ctx); } // Map a stream of role classes to a set of roles diff --git a/api/src/org/labkey/api/security/ElevatedUser.java b/api/src/org/labkey/api/security/ElevatedUser.java index c65bd223d75..88915061029 100644 --- a/api/src/org/labkey/api/security/ElevatedUser.java +++ b/api/src/org/labkey/api/security/ElevatedUser.java @@ -15,6 +15,7 @@ */ package org.labkey.api.security; +import com.google.common.collect.Streams; import org.labkey.api.audit.permissions.CanSeeAuditLogPermission; import org.labkey.api.data.Container; import org.labkey.api.security.permissions.Permission; @@ -26,6 +27,7 @@ import java.util.Collection; import java.util.Set; import java.util.stream.Collectors; +import java.util.stream.Stream; /** * A wrapped user that possesses all the security properties (groups, roles, impersonation status, etc.) of the @@ -35,9 +37,26 @@ */ public class ElevatedUser extends ClonedUser { + private static class ElevatedUserContext extends WrappedPermissionsContext + { + private final Set _additionalRoles; + + private ElevatedUserContext(PermissionsContext delegate, Set additionalRoles) + { + super(delegate); + _additionalRoles = additionalRoles; + } + + @Override + public Stream getAssignedRoles(User user, SecurableResource resource) + { + return Streams.concat(_additionalRoles.stream(), super.getAssignedRoles(user, resource)); + } + } + private ElevatedUser(User user, Set rolesToAdd) { - super(user, new WrappedPermissionsContext(user.getPermissionsContext(), rolesToAdd)); + super(user, new ElevatedUserContext(user.getPermissionsContext(), rolesToAdd)); } private ElevatedUser(User user, PermissionsContext ctx) diff --git a/api/src/org/labkey/api/security/LimitedUser.java b/api/src/org/labkey/api/security/LimitedUser.java index cf618ce638c..0febeaf697e 100644 --- a/api/src/org/labkey/api/security/LimitedUser.java +++ b/api/src/org/labkey/api/security/LimitedUser.java @@ -97,7 +97,7 @@ private LimitedUser( @JsonProperty("_lastLogin") Date lastLogin, @JsonProperty("_phone") String phone, @JsonProperty("_lastActivity") Date lastActivity, - @JsonProperty("_impersonationContext") PermissionsContext ctx + @JsonProperty("_permissionsContext") PermissionsContext ctx ) { super(name, userId, displayName, firstName, lastName, active, lastLogin, phone, lastActivity, ctx); diff --git a/api/src/org/labkey/api/security/PermissionsContext.java b/api/src/org/labkey/api/security/PermissionsContext.java index 9c6eeb86095..f4fc81021e9 100644 --- a/api/src/org/labkey/api/security/PermissionsContext.java +++ b/api/src/org/labkey/api/security/PermissionsContext.java @@ -16,6 +16,7 @@ package org.labkey.api.security; import com.google.common.collect.Streams; +import jakarta.servlet.http.HttpSession; import org.jetbrains.annotations.Nullable; import org.labkey.api.data.Container; import org.labkey.api.data.ContainerManager; @@ -87,4 +88,9 @@ default Stream> filterPermissions(Stream> _allowedPermissions; + + private RoleRestrictedPermissionsContext(PermissionsContext ctx, Set> allowedPermissions) + { + super(ctx); + _allowedPermissions = allowedPermissions; + } + + @Override + public Stream> filterPermissions(Stream> perms) + { + return super.filterPermissions(perms).filter(_allowedPermissions::contains); + } + + @Override + public void modifySession(HttpSession session) + { + super.modifySession(session); + session.setAttribute(ALLOWED_PERMISSIONS_KEY, _allowedPermissions); + } + } + + public PermissionsRestrictedUser(User user, @NotNull Set> allowedPermissions) + { + super(user, new RoleRestrictedPermissionsContext(user.getPermissionsContext(), allowedPermissions)); + } +} diff --git a/api/src/org/labkey/api/security/RoleSet.java b/api/src/org/labkey/api/security/RoleSet.java index 58276a200ef..80fac9341b6 100644 --- a/api/src/org/labkey/api/security/RoleSet.java +++ b/api/src/org/labkey/api/security/RoleSet.java @@ -166,7 +166,7 @@ private void testImpersonateRoles(User adminUser, @Nullable Container project, C assertEquals(roles, reconstitutedRoleSet.getRoles()); RoleImpersonationContextFactory factory = new RoleImpersonationContextFactory(project, adminUser, roles, Collections.emptySet(), null); - impersonatingUser.setImpersonationContext(factory.getImpersonationContext()); + impersonatingUser.setPermissionsContext(factory.getImpersonationContext()); if (null == project) assertEquals(roles, impersonatingUser.getSiteRoles(ContainerManager.getRoot()).collect(Collectors.toSet())); diff --git a/api/src/org/labkey/api/security/SecurityManager.java b/api/src/org/labkey/api/security/SecurityManager.java index 84059487c84..1f9cc19f85a 100644 --- a/api/src/org/labkey/api/security/SecurityManager.java +++ b/api/src/org/labkey/api/security/SecurityManager.java @@ -148,6 +148,7 @@ import java.util.stream.Stream; import static org.labkey.api.action.SpringActionController.ERROR_MSG; +import static org.labkey.api.security.PermissionsRestrictedUser.ALLOWED_PERMISSIONS_KEY; import static org.labkey.api.util.IntegerUtils.asInteger; /** @@ -517,8 +518,13 @@ public static User getSessionUser(HandshakeRequest request) if (null != factory) { - sessionUser.setImpersonationContext(factory.getImpersonationContext()); + sessionUser.setPermissionsContext(factory.getImpersonationContext()); } + + //noinspection unchecked + Set> allowedPermissions = (Set>) session.getAttribute(ALLOWED_PERMISSIONS_KEY); + if (allowedPermissions != null) + sessionUser = new PermissionsRestrictedUser(sessionUser, allowedPermissions); } } @@ -586,7 +592,7 @@ public static Pair attemptAuthentication(HttpServletRe if (!sessionUser.isImpersonated() && "true".equalsIgnoreCase(request.getHeader("LabKey-Disallow-Global-Roles"))) { - sessionUser.setImpersonationContext(DisallowPrivilegedRolesContext.get()); + sessionUser.setPermissionsContext(DisallowPrivilegedRolesContext.get()); } HttpSession session = request.getSession(false); @@ -816,6 +822,7 @@ public static HttpSession setAuthenticatedUser(HttpServletRequest request, @Null newSession.setAttribute(PRIMARY_AUTHENTICATION_CONFIGURATION, configuration.getRowId()); newSession.setAttribute(USER_ATTRIBUTES_KEY, response.getUserAttributeMap()); newSession.setAttribute(AUTHENTICATION_PROPERTIES, response.getAuthenticationProperties()); + user.getPermissionsContext().modifySession(newSession); } return newSession; @@ -1121,7 +1128,7 @@ else if (email.getEmailAddress().indexOf("@") > 0) // Issue 25813: if two users are being inserted at the same time through the addUser method above, we can get deadlock on SQLServer so we // actively lock when doing this select to prevent it from promoting a lock up the chain. - SQLFragment select = new SQLFragment("SELECT UserId FROM " + core.getTableInfoUsersData()); + SQLFragment select = new SQLFragment("SELECT UserId FROM ").append(core.getTableInfoUsersData()); if (core.getSchema().getScope().getSqlDialect().isSqlServer()) select.append(" WITH (UPDLOCK)"); select.append(" WHERE DisplayName = ? AND UserId != ?"); @@ -3343,7 +3350,7 @@ public void testReadOnlyImpersonate() throws InvalidEmailException, UserManageme final User testUser = TestContext.get().getUser(); // this user is subsetted to only permit read permissions (see AllowedForReadOnlyUser) User user = addUser(new ValidEmail("impersonate@test.net"), null, false).getUser(); - user.setImpersonationContext(new ReadOnlyPermissionsContext()); + user.setPermissionsContext(new ReadOnlyPermissionsContext()); Container testFolder = null; diff --git a/api/src/org/labkey/api/security/User.java b/api/src/org/labkey/api/security/User.java index 32d8cfbd45d..394112bebd2 100644 --- a/api/src/org/labkey/api/security/User.java +++ b/api/src/org/labkey/api/security/User.java @@ -33,8 +33,8 @@ 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.ImpersonatePermission; import org.labkey.api.security.permissions.DeletePermission; +import org.labkey.api.security.permissions.ImpersonatePermission; import org.labkey.api.security.permissions.ImpersonatePrivilegedSiteRolesPermission; import org.labkey.api.security.permissions.InsertPermission; import org.labkey.api.security.permissions.Permission; @@ -409,7 +409,7 @@ public void setLastActivity(Date lastActivity) _lastActivity = lastActivity; } - void setImpersonationContext(PermissionsContext permissionsContext) + void setPermissionsContext(PermissionsContext permissionsContext) { _permissionsContext = permissionsContext; } diff --git a/api/src/org/labkey/api/security/WrappedPermissionsContext.java b/api/src/org/labkey/api/security/WrappedPermissionsContext.java index a33506a12af..eeffb655a6e 100644 --- a/api/src/org/labkey/api/security/WrappedPermissionsContext.java +++ b/api/src/org/labkey/api/security/WrappedPermissionsContext.java @@ -15,7 +15,7 @@ */ package org.labkey.api.security; -import com.google.common.collect.Streams; +import jakarta.servlet.http.HttpSession; import org.jetbrains.annotations.Nullable; import org.labkey.api.data.Container; import org.labkey.api.security.impersonation.ImpersonationContextFactory; @@ -24,26 +24,18 @@ import org.labkey.api.view.ActionURL; import org.labkey.api.view.NavTree; -import java.util.Set; import java.util.stream.Stream; /** - * Do not use this class directly; use ElevatedUser instead. + * See subclasses ElevatedUser and RoleRestrictedUser */ -public class WrappedPermissionsContext implements PermissionsContext +abstract class WrappedPermissionsContext implements PermissionsContext { private final PermissionsContext _delegate; - private final Set _additionalRoles; - public WrappedPermissionsContext(PermissionsContext delegate, Set additionalRoles) + public WrappedPermissionsContext(PermissionsContext delegate) { _delegate = delegate; - _additionalRoles = additionalRoles; - } - - public WrappedPermissionsContext(PermissionsContext delegate, Role additionalRole) - { - this(delegate, Set.of(additionalRole)); } @Override @@ -86,7 +78,7 @@ public PrincipalArray getGroups(User user) @Override public Stream getAssignedRoles(User user, SecurableResource resource) { - return Streams.concat(_additionalRoles.stream(), _delegate.getAssignedRoles(user, resource)); + return _delegate.getAssignedRoles(user, resource); } @Override @@ -106,4 +98,10 @@ public Stream> filterPermissions(Stream + diff --git a/core/resources/schemas/dbscripts/postgresql/core-26.006-26.007.sql b/core/resources/schemas/dbscripts/postgresql/core-26.006-26.007.sql new file mode 100644 index 00000000000..e761ac28830 --- /dev/null +++ b/core/resources/schemas/dbscripts/postgresql/core-26.006-26.007.sql @@ -0,0 +1 @@ +ALTER TABLE core.ApiKeys ADD RestrictionRole VARCHAR(256); \ No newline at end of file diff --git a/core/resources/schemas/dbscripts/sqlserver/core-26.006-26.007.sql b/core/resources/schemas/dbscripts/sqlserver/core-26.006-26.007.sql new file mode 100644 index 00000000000..1e2671c861b --- /dev/null +++ b/core/resources/schemas/dbscripts/sqlserver/core-26.006-26.007.sql @@ -0,0 +1 @@ +ALTER TABLE core.ApiKeys ADD RestrictionRole NVARCHAR(256); \ No newline at end of file diff --git a/core/src/org/labkey/core/login/DbLoginAuthenticationProvider.java b/core/src/org/labkey/core/login/DbLoginAuthenticationProvider.java index f9f9283cf48..0208d853245 100644 --- a/core/src/org/labkey/core/login/DbLoginAuthenticationProvider.java +++ b/core/src/org/labkey/core/login/DbLoginAuthenticationProvider.java @@ -115,24 +115,21 @@ public boolean isFicamApproved() if (API_KEY.equals(id)) { ApiKeyAuthentication auth = ApiKeyManager.get().authenticateFromApiKey(password); - final AuthenticationResponse ret; if (auth != null) { - // API keys are exempt from secondary authentication, Issue 48764 - ret = AuthenticationResponse.success(configuration, auth.getUser()).setSuccessDetails(UserManager.UserAuditEvent.API_KEY) - .setRequireSecondary(false) - .setAuthenticationProperties(Map.of(ApiKeyManager.API_KEY_ROW_ID, auth.rowId())); - - // Update core.ApiKeys.LastUsed (throttled) - API_KEY_LAST_USED_THROTTLE.execute(password); - } - else - { - ret = AuthenticationResponse.failure(configuration, FailureReason.badApiKey); + User user = auth.getUser(); + if (user != null) + { + // Update core.ApiKeys.LastUsed (throttled) + API_KEY_LAST_USED_THROTTLE.execute(password); + return AuthenticationResponse.success(configuration, user).setSuccessDetails(UserManager.UserAuditEvent.API_KEY) + .setRequireSecondary(false) // API keys are exempt from secondary authentication, Issue 48764 + .setAuthenticationProperties(Map.of(ApiKeyManager.API_KEY_ROW_ID, auth.rowId())); + } } - return ret; + return AuthenticationResponse.failure(configuration, FailureReason.badApiKey); } else { diff --git a/core/src/org/labkey/core/query/ApiKeysTableInfo.java b/core/src/org/labkey/core/query/ApiKeysTableInfo.java index bbdc93a622e..e47848d189f 100644 --- a/core/src/org/labkey/core/query/ApiKeysTableInfo.java +++ b/core/src/org/labkey/core/query/ApiKeysTableInfo.java @@ -44,6 +44,7 @@ public ApiKeysTableInfo(@NotNull CoreQuerySchema schema, boolean filterToCurrent addWrapColumn(getRealTable().getColumn("Expiration")); addWrapColumn(getRealTable().getColumn("LastUsed")); addWrapColumn(getRealTable().getColumn("Description")); + addWrapColumn(getRealTable().getColumn("RestrictionRole")); if (filterToCurrentUser) { diff --git a/core/src/org/labkey/core/security/SecurityController.java b/core/src/org/labkey/core/security/SecurityController.java index 023e4c30eab..da5d42b50d0 100644 --- a/core/src/org/labkey/core/security/SecurityController.java +++ b/core/src/org/labkey/core/security/SecurityController.java @@ -47,6 +47,7 @@ import org.labkey.api.audit.provider.GroupAuditProvider; import org.labkey.api.audit.provider.GroupAuditProvider.GroupAuditEvent; import org.labkey.api.collections.CaseInsensitiveHashMap; +import org.labkey.api.collections.LabKeyCollectors; import org.labkey.api.compliance.ComplianceService; import org.labkey.api.data.ColumnInfo; import org.labkey.api.data.Container; @@ -115,9 +116,13 @@ import org.labkey.api.security.permissions.UpdateUserPermission; import org.labkey.api.security.permissions.UserManagementPermission; import org.labkey.api.security.roles.ApplicationAdminRole; +import org.labkey.api.security.roles.AuthorRole; +import org.labkey.api.security.roles.EditorRole; +import org.labkey.api.security.roles.EditorWithoutDeleteRole; import org.labkey.api.security.roles.FolderAdminRole; import org.labkey.api.security.roles.NoPermissionsRole; import org.labkey.api.security.roles.ProjectAdminRole; +import org.labkey.api.security.roles.ReaderRole; import org.labkey.api.security.roles.Role; import org.labkey.api.security.roles.RoleManager; import org.labkey.api.security.roles.SiteAdminRole; @@ -164,6 +169,7 @@ import java.util.function.BiConsumer; import java.util.function.Consumer; import java.util.stream.Collectors; +import java.util.stream.Stream; import static org.apache.commons.lang3.StringUtils.trimToNull; @@ -2288,10 +2294,38 @@ public void addNavTrail(NavTree root) } } + record RestrictionRole(Class roleClass) + { + String displayName() + { + return RoleManager.getRole(roleClass).getDisplayName(); + } + } + + private static final Map RESTRICTION_ROLE_MAP = Stream.of( + ReaderRole.class, + AuthorRole.class, + EditorWithoutDeleteRole.class, + EditorRole.class + ).collect(LabKeyCollectors.toLinkedMap(Class::getName, RestrictionRole::new)); + + @RequiresLogin + public static class GetApiKeyRolesAction extends ReadOnlyApiAction + { + @Override + public Object execute(Object o, BindException errors) throws Exception + { + return RESTRICTION_ROLE_MAP.entrySet().stream() + .map(e -> new JSONObject(Map.of("uniqueName", e.getKey(), "displayName", e.getValue().displayName()))) + .collect(LabKeyCollectors.toJSONArray()); + } + } + public static class CreateApiKeyForm { private String _type; private String _description; + private String _role; public String getType() { @@ -2314,6 +2348,16 @@ public void setDescription(String description) { _description = description; } + + public String getRole() + { + return _role; + } + + public void setRole(String role) + { + _role = role; + } } @RequiresLogin @@ -2330,7 +2374,16 @@ public Object execute(CreateApiKeyForm form, BindException errors) if (!AppProps.getInstance().isAllowApiKeys()) throw new NotFoundException("Creation of API keys is disabled"); - apiKey = ApiKeyManager.get().createKey(getUser(), form.getDescription()); + RestrictionRole rr = null; + + if (form.getRole() != null) + { + rr = RESTRICTION_ROLE_MAP.get(form.getRole()); + if (rr == null) + throw new NotFoundException("Restriction role was not found."); + } + + apiKey = ApiKeyManager.get().createKey(getUser(), form.getDescription(), rr != null ? rr.roleClass() : null); break; case "session": if (!AppProps.getInstance().isAllowSessionKeys()) @@ -2365,29 +2418,29 @@ public void testActionPermissions() // @RequiresPermission(ReadPermission.class) assertForReadPermission(user, false, - new CompleteUserReadAction() + new CompleteUserReadAction() ); // @RequiresPermission(AdminPermission.class) assertForAdminPermission(user, - new PermissionsAction(), - new StandardDeleteGroupAction(), + new PermissionsAction(), + new StandardDeleteGroupAction(), controller.new GroupAction(), - new CompleteMemberAction(), - new CompleteUserAction(), - new GroupExportAction(), - new GroupPermissionAction(), - new UpdatePermissionsAction(), - new ShowRegistrationEmailAction(), - new GroupDiagramAction(), - new FolderAccessAction() + new CompleteMemberAction(), + new CompleteUserAction(), + new GroupExportAction(), + new GroupPermissionAction(), + new UpdatePermissionsAction(), + new ShowRegistrationEmailAction(), + new GroupDiagramAction(), + new FolderAccessAction() ); // @RequiresPermission(UserManagementPermission.class) assertForUserPermissions(user, controller.new AddUsersAction(), - new ShowResetEmailAction(), - new AdminResetPasswordAction() + new ShowResetEmailAction(), + new AdminResetPasswordAction() ); } diff --git a/experiment/src/org/labkey/experiment/api/property/DomainPropertyManager.java b/experiment/src/org/labkey/experiment/api/property/DomainPropertyManager.java index fc85c94eb97..dbc7137c295 100644 --- a/experiment/src/org/labkey/experiment/api/property/DomainPropertyManager.java +++ b/experiment/src/org/labkey/experiment/api/property/DomainPropertyManager.java @@ -173,7 +173,7 @@ public List getConditionalFormats(Container con // upgrade code that cleared out obsolete 'urn:lsid:labkey.com:PropertyValidator:length' rows. if (PropertyService.get().getValidatorKind(pv.getTypeURI()) == null) { - LOG.error("Invalid property validator typeUri: {}", pv.getTypeURI()); + LOG.warn("Invalid property validator typeUri: {}", pv.getTypeURI()); } else {