Skip to content
Draft
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
Original file line number Diff line number Diff line change
Expand Up @@ -338,6 +338,8 @@ public Map<String, RequestContentValidator.DataType> allowedKeys() {
.put("opendistro_security_roles", DataType.ARRAY)
.put("hash", DataType.STRING)
.put("password", DataType.STRING)
.put("service", DataType.BOOLEAN)
.put("enabled", DataType.BOOLEAN)
.build();
}
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -127,11 +127,11 @@ public void setAttributes(Map<String, String> attributes) {
this.attributes = attributes;
}

public boolean enabled() {
public boolean isEnabled() {
return this.enabled;
}

public boolean service() {
public boolean isService() {
return this.service;
}

Expand Down
45 changes: 25 additions & 20 deletions src/main/java/org/opensearch/security/user/UserService.java
Original file line number Diff line number Diff line change
Expand Up @@ -155,18 +155,24 @@ public SecurityDynamicConfiguration<?> createOrUpdateAccount(ObjectNode contentA
throw new UserServiceException(NO_ACCOUNT_NAME_MESSAGE);
}

SecurityJsonNode attributeNode = securityJsonNode.get("attributes");
// Read service flag from top level (boolean)
SecurityJsonNode serviceNode = securityJsonNode.get("service");
boolean isServiceAccount = !serviceNode.isNull() && Boolean.parseBoolean(serviceNode.asString());

if (!attributeNode.get("service").isNull() && attributeNode.get("service").asString().equalsIgnoreCase("true")) { // If this is a
// service account
if (isServiceAccount) {
verifyServiceAccount(securityJsonNode, accountName);
String password = generatePassword();
contentAsNode.put("hash", passwordHasher.hash(password.toCharArray()));
contentAsNode.put("service", "true");
contentAsNode.put("service", true);
} else {
contentAsNode.put("service", "false");
contentAsNode.put("service", false);
}

// Read enabled flag from top level (boolean), default to true
SecurityJsonNode enabledNode = securityJsonNode.get("enabled");
boolean isEnabled = enabledNode.isNull() || Boolean.parseBoolean(enabledNode.asString());
contentAsNode.put("enabled", isEnabled);

securityJsonNode = new SecurityJsonNode(contentAsNode);
final var foundRestrictedContents = restrictedFromUsername(accountName);
if (foundRestrictedContents.isPresent()) {
Expand All @@ -185,10 +191,6 @@ public SecurityDynamicConfiguration<?> createOrUpdateAccount(ObjectNode contentA
contentAsNode.remove("password");
}

if (!attributeNode.get("enabled").isNull()) {
contentAsNode.put("enabled", securityJsonNode.get("enabled").asString());
}

final boolean userExisted = internalUsersConfiguration.exists(accountName);

// sanity checks, hash is mandatory for newly created users
Expand Down Expand Up @@ -273,21 +275,23 @@ public AuthToken generateAuthToken(String accountName) throws IOException {
final ObjectNode contentAsNode = (ObjectNode) accountDetails;
SecurityJsonNode securityJsonNode = new SecurityJsonNode(contentAsNode);

Optional.ofNullable(securityJsonNode.get("attributes").get("service"))
.map(SecurityJsonNode::asString)
.filter("true"::equalsIgnoreCase)
.orElseThrow(() -> new UserServiceException(AUTH_TOKEN_GENERATION_MESSAGE));
var serviceNode = securityJsonNode.get("service");
boolean isService = !serviceNode.isNull() && Boolean.parseBoolean(serviceNode.asString());
if (!isService) {
throw new UserServiceException(AUTH_TOKEN_GENERATION_MESSAGE);
}

Optional.ofNullable(securityJsonNode.get("attributes").get("enabled"))
.map(SecurityJsonNode::asString)
.filter("true"::equalsIgnoreCase)
.orElseThrow(() -> new UserServiceException(AUTH_TOKEN_GENERATION_MESSAGE));
var enabledNode = securityJsonNode.get("enabled");
boolean isEnabled = enabledNode.isNull() || Boolean.parseBoolean(enabledNode.asString());
if (!isEnabled) {
throw new UserServiceException(AUTH_TOKEN_GENERATION_MESSAGE);
}

// Generate a new password for the account and store the hash of it
String plainTextPassword = generatePassword();
contentAsNode.put("hash", passwordHasher.hash(plainTextPassword.toCharArray()));
contentAsNode.put("enabled", "true");
contentAsNode.put("service", "true");
contentAsNode.put("enabled", true);
contentAsNode.put("service", true);

// Update the internal user associated with the auth token
internalUsersConfiguration.remove(accountName);
Expand All @@ -296,7 +300,8 @@ public AuthToken generateAuthToken(String accountName) throws IOException {
accountName,
DefaultObjectMapper.readTree(contentAsNode, internalUsersConfiguration.getImplementingClass())
);
saveAndUpdateConfigs(getUserConfigName().toString(), client, CType.INTERNALUSERS, internalUsersConfiguration);
saveAndUpdateConfigs(securityIndex, client, CType.INTERNALUSERS, internalUsersConfiguration);
configurationRepository.reloadConfiguration(java.util.Set.of(CType.INTERNALUSERS), null);

authToken = Base64.getUrlEncoder().encodeToString((accountName + ":" + plainTextPassword).getBytes(StandardCharsets.UTF_8));
return new BasicAuthToken("Basic " + authToken);
Expand Down