Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
9 changes: 9 additions & 0 deletions src/org/labkey/test/components/core/ApiKeyDialog.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import org.labkey.test.Locator;
import org.labkey.test.components.bootstrap.ModalDialog;
import org.labkey.test.components.html.Input;
import org.labkey.test.components.html.OptionSelect;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.ui.ExpectedConditions;
Expand Down Expand Up @@ -151,6 +152,12 @@ public String getDescription()
return elementCache().descriptionDisplay.getText();
}

public ApiKeyDialog setRestrictionRole(String restrictionRole)
{
elementCache().restrictionRoleSelect.selectByVisibleText(restrictionRole);
return this;
}

public String getInputFieldValue()
{
return elementCache().inputField.getValue();
Expand All @@ -172,6 +179,8 @@ protected class ElementCache extends ModalDialog.ElementCache
{
Input descriptionInput = Input.Input(Locator.tagWithId("input", "keyDescription"), getDriver()).findWhenNeeded(this);
WebElement descriptionDisplay = Locator.tagWithClassContaining("div", "api-key__description").findWhenNeeded(this);
OptionSelect<OptionSelect.SelectOption> restrictionRoleSelect = new OptionSelect<>(Locator.tagWithId("select", "keyRole")
.findWhenNeeded(this).withTimeout(2000));
WebElement generateApiKeyButton = Locator.tagWithText("button", "Generate API Key").findWhenNeeded(this);
Input inputField = Input.Input(Locator.tagWithClass("input", "api-key__input"), getDriver()).findWhenNeeded(this);
WebElement copyKeyButton = Locator.tagWithName("button", "copy_apikey_token").findWhenNeeded(this);
Expand Down
11 changes: 8 additions & 3 deletions src/org/labkey/test/components/core/ApiKeyPanel.java
Original file line number Diff line number Diff line change
Expand Up @@ -36,24 +36,29 @@ public static SimpleWebDriverComponentFinder<ApiKeyPanel> panelFinder(WebDriver
return new Panel.PanelFinder(driver).withTitle("API Keys").wrap(ApiKeyPanel::new);
}

public String generateApiKey(@Nullable String description)
public String generateApiKey(@Nullable String description, @Nullable String restrictionRole)
{
ApiKeyDialog apiKeyDialog = clickGenerateApiKey();
if (description != null)
{
apiKeyDialog.setDescription(description);
}
if (restrictionRole != null)
{
apiKeyDialog.setRestrictionRole(restrictionRole);
}
apiKeyDialog.generateApiKey();
Assert.assertEquals("API Key discription", description == null ? "" : description, apiKeyDialog.getDescription());
Assert.assertEquals("API Key description", description == null ? "" : description, apiKeyDialog.getDescription());
String inputFieldValue = apiKeyDialog.getInputFieldValue();
apiKeyDialog.clickDone();
return inputFieldValue;
}

public String generateApiKey()
{
return generateApiKey(null);
return generateApiKey(null, null);
}

public ApiKeyDialog clickGenerateApiKey()
{
elementCache().generateApiKeyButton.click();
Expand Down
68 changes: 67 additions & 1 deletion src/org/labkey/test/tests/ApiKeyTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
import org.labkey.test.pages.core.admin.CustomizeSitePage;
import org.labkey.test.params.FieldDefinition;
import org.labkey.test.params.list.IntListDefinition;
import org.labkey.test.util.ApiPermissionsHelper;
import org.labkey.test.util.Maps;
import org.labkey.test.util.PasswordUtil;
import org.labkey.test.util.TestUser;
Expand Down Expand Up @@ -362,6 +363,35 @@ public void testSessionKeyDisabled() throws IOException
}
}

@Test
public void testRestrictedApiKey() throws IOException
{
List<Map<String, Object>> _generatedApiKeys = new ArrayList<>();

goToAdminConsole()
.clickSiteSettings()
.setAllowApiKeys(true)
.setApiKeyExpiration(CustomizeSitePage.KeyExpirationOptions.ONE_WEEK)
.save();

String apiKey = generateAPIKey(null, "Reader");
_generatedApiKeys.add(getLastAPIKeyRecord());

// Test connection that does not keep a session
log("Verify active API key via basic authentication");
Connection cn = createBasicAuthConnection(apiKey);
verifyReadOnlyAPIKey(cn);

// Test connection that does keep a session
cn = createApiKeyConnection(apiKey);
log("Verify active API key via api authentication");
verifyReadOnlyAPIKey(cn);

log("Verify revoked/deleted api key");
deleteAPIKeys(_generatedApiKeys);
verifyInvalidAPIKey(createApiKeyConnection(apiKey), false);
}

private void verifyValidAPIKey(Connection connection) throws IOException
{
verifyValidAPIKey(connection, PasswordUtil.getUsername());
Expand Down Expand Up @@ -397,6 +427,37 @@ private void verifyValidAPIKey(Connection connection, String userEmail) throws I
}
}

private void verifyReadOnlyAPIKey(Connection connection) throws IOException
{
try
{
WhoAmIResponse whoAmI = new WhoAmICommand().execute(connection, null);
assertEquals("Connection user", PasswordUtil.getUsername(), whoAmI.getEmail());

// Likely multiple permissions (depending on what modules are running), but all should end with "ReadPermission"
List<String> badPerms = new ApiPermissionsHelper(this, () -> connection).getUserPermissions(getProjectName(), null).stream()
.filter(perm -> !perm.endsWith("ReadPermission"))
.toList();

assertFalse("Unexpected permissions: " + badPerms, badPerms.isEmpty());

// Should be able to select rows
QueryApiHelper queryApiHelper = new QueryApiHelper(connection, getProjectName(), "lists", LIST_NAME);
SelectRowsResponse selectResponse = queryApiHelper.selectRows();
assertEquals("Total rows", valueCount.get(), selectResponse.getRowCount());

// Should NOT be able to import, insert, update, or delete
Assert.assertThrows("User does not have permission to insert rows", CommandException.class, () -> queryApiHelper.importData(LIST_VALUE + "\nvalue" + valueCount.get()));
Assert.assertThrows("User does not have permission to perform this operation.", CommandException.class, () -> queryApiHelper.insertRows(List.of()));
Assert.assertThrows("User does not have permission to perform this operation.", CommandException.class, () -> queryApiHelper.updateRows(List.of()));
Assert.assertThrows("User does not have permission to perform this operation.", CommandException.class, () -> queryApiHelper.deleteRows(List.of()));
}
catch (CommandException e)
{
throw new RuntimeException("Response: " + e.getStatusCode(), e);
}
}

private void verifySessionKeyCsrf(Connection connection) throws IOException
{
try
Expand Down Expand Up @@ -484,9 +545,14 @@ private String generateSessionKey()
}

private String generateAPIKey(@Nullable String description)
{
return generateAPIKey(description, null);
}

private String generateAPIKey(@Nullable String description, @Nullable String restrictionRole)
{
goToExternalToolPage();
return ApiKeyPanel.panelFinder(getDriver()).find().generateApiKey(description);
return ApiKeyPanel.panelFinder(getDriver()).find().generateApiKey(description, restrictionRole);
}

private String generateAPIKeyAndRecord(List<Map<String, Object>> _generatedApiKeys) throws IOException
Expand Down
22 changes: 18 additions & 4 deletions src/org/labkey/test/util/ApiPermissionsHelper.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
package org.labkey.test.util;

import org.apache.commons.lang3.StringUtils;
import org.jetbrains.annotations.Nullable;
import org.junit.Assert;
import org.labkey.remoteapi.CommandException;
import org.labkey.remoteapi.CommandResponse;
Expand Down Expand Up @@ -248,7 +249,7 @@ public Integer getGroupId(String groupName)
return id;
}

public Integer getUserId(String user)
public Integer getUserId(@Nullable String user)
{
try
{
Expand Down Expand Up @@ -291,12 +292,12 @@ private List<String> getGroupMembers(String container, String groupName)
return (List)response.getParsedData().get("groupMembers");
}

private List<Map<String, Object>> getUserGroups(String container, String user) throws CommandException
private List<Map<String, Object>> getUserGroups(String container, @Nullable String user) throws CommandException
{
return getUserPerms(container, user).getProperty("container.groups");
}

public List<String> getUserRoles(String container, String user)
public List<String> getUserRoles(String container, @Nullable String user)
{
try
{
Expand All @@ -308,7 +309,20 @@ public List<String> getUserRoles(String container, String user)
}
}

private CommandResponse getUserPerms(String container, String user) throws CommandException
public List<String> getUserPermissions(String container, @Nullable String user)
{
try
{
return getUserPerms(container, user).getProperty("container.effectivePermissions");
}
catch (CommandException e)
{
throw new RuntimeException(e);
}
}

// Pass null for current user
private CommandResponse getUserPerms(String container, @Nullable String user) throws CommandException
{
Connection connection = getConnection();
SimpleGetCommand command = new SimpleGetCommand("security", "getUserPerms");
Expand Down