diff --git a/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java b/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java index 362a46843e..87ca878fc6 100644 --- a/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java +++ b/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java @@ -176,6 +176,7 @@ import org.opensearch.security.transport.InterClusterRequestEvaluator; import org.opensearch.security.transport.SecurityInterceptor; import org.opensearch.security.user.User; +import org.opensearch.security.user.UserService; import org.opensearch.tasks.Task; import org.opensearch.threadpool.ThreadPool; import org.opensearch.transport.RemoteClusterService; @@ -204,6 +205,7 @@ public final class OpenSearchSecurityPlugin extends OpenSearchSecuritySSLPlugin private volatile SecurityRestFilter securityRestHandler; private volatile SecurityInterceptor si; private volatile PrivilegesEvaluator evaluator; + private volatile UserService userService; private volatile ThreadPool threadPool; private volatile ConfigurationRepository cr; private volatile AdminDNs adminDns; @@ -487,6 +489,7 @@ public List getRestHandlers(Settings settings, RestController restC evaluator, threadPool, Objects.requireNonNull(auditLog), sks, + Objects.requireNonNull(userService), sslCertReloadEnabled) ); log.debug("Added {} rest handler(s)", handlers.size()); @@ -813,9 +816,11 @@ public Collection createComponents(Client localClient, ClusterService cl sslExceptionHandler = new AuditLogSslExceptionHandler(auditLog); adminDns = new AdminDNs(settings); - + cr = ConfigurationRepository.create(settings, this.configPath, threadPool, localClient, clusterService, auditLog); + userService = new UserService(cs, cr, settings, localClient); + final XFFResolver xffResolver = new XFFResolver(threadPool); backendRegistry = new BackendRegistry(settings, adminDns, xffResolver, auditLog, threadPool); @@ -872,6 +877,7 @@ public Collection createComponents(Client localClient, ClusterService cl components.add(evaluator); components.add(si); components.add(dcf); + components.add(userService); return components; @@ -1179,7 +1185,6 @@ public static class GuiceHolder implements LifecycleComponent { private static RepositoriesService repositoriesService; private static RemoteClusterService remoteClusterService; private static IndicesService indicesService; - private static PitService pitService; @Inject @@ -1204,7 +1209,8 @@ public static IndicesService getIndicesService() { } public static PitService getPitService() { return pitService; } - + + @Override public void close() { } diff --git a/src/main/java/org/opensearch/security/dlic/rest/api/InternalUsersApiAction.java b/src/main/java/org/opensearch/security/dlic/rest/api/InternalUsersApiAction.java index 646e1f81d6..0551c108a1 100644 --- a/src/main/java/org/opensearch/security/dlic/rest/api/InternalUsersApiAction.java +++ b/src/main/java/org/opensearch/security/dlic/rest/api/InternalUsersApiAction.java @@ -14,7 +14,6 @@ import java.io.IOException; import java.nio.file.Path; import java.util.List; -import java.util.stream.Collectors; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.ObjectNode; @@ -32,18 +31,18 @@ import org.opensearch.rest.RestController; import org.opensearch.rest.RestRequest; import org.opensearch.rest.RestRequest.Method; -import org.opensearch.security.DefaultObjectMapper; import org.opensearch.security.auditlog.AuditLog; import org.opensearch.security.configuration.AdminDNs; import org.opensearch.security.configuration.ConfigurationRepository; import org.opensearch.security.dlic.rest.validation.AbstractConfigurationValidator; import org.opensearch.security.dlic.rest.validation.InternalUsersValidator; import org.opensearch.security.privileges.PrivilegesEvaluator; -import org.opensearch.security.securityconf.Hashed; import org.opensearch.security.securityconf.impl.CType; import org.opensearch.security.securityconf.impl.SecurityDynamicConfiguration; import org.opensearch.security.ssl.transport.PrincipalExtractor; import org.opensearch.security.support.SecurityJsonNode; +import org.opensearch.security.user.UserService; +import org.opensearch.security.user.UserServiceException; import org.opensearch.threadpool.ThreadPool; import static org.opensearch.security.dlic.rest.support.Utils.addRoutesPrefix; @@ -69,13 +68,16 @@ public class InternalUsersApiAction extends PatchableResourceApiAction { new Route(Method.PATCH, "/internalusers/{name}") )); + UserService userService; + @Inject public InternalUsersApiAction(final Settings settings, final Path configPath, final RestController controller, final Client client, final AdminDNs adminDNs, final ConfigurationRepository cl, final ClusterService cs, final PrincipalExtractor principalExtractor, final PrivilegesEvaluator evaluator, - ThreadPool threadPool, AuditLog auditLog) { + ThreadPool threadPool, UserService userService, AuditLog auditLog) { super(settings, configPath, controller, client, adminDNs, cl, cs, principalExtractor, evaluator, threadPool, auditLog); + this.userService = userService; } @Override @@ -100,22 +102,7 @@ protected void handlePut(RestChannel channel, final RestRequest request, final C final String username = request.param("name"); - if (username == null || username.length() == 0) { - badRequestResponse(channel, "No " + getResourceName() + " specified."); - return; - } - - final List foundRestrictedContents = RESTRICTED_FROM_USERNAME.stream().filter(username::contains).collect(Collectors.toList()); - if (!foundRestrictedContents.isEmpty()) { - final String restrictedContents = foundRestrictedContents.stream().map(s -> "'" + s + "'").collect(Collectors.joining(",")); - badRequestResponse(channel, "Username has restricted characters " + restrictedContents + " that are not permitted."); - return; - } - - // TODO it might be sensible to consolidate this with the overridden method in - // order to minimize duplicated logic - - final SecurityDynamicConfiguration internalUsersConfiguration = load(getConfigName(), false); + SecurityDynamicConfiguration internalUsersConfiguration = load(getConfigName(), false); if (!isWriteable(channel, internalUsersConfiguration, username)) { return; @@ -128,50 +115,35 @@ protected void handlePut(RestChannel channel, final RestRequest request, final C final List securityRoles = securityJsonNode.get("opendistro_security_roles").asList(); if (securityRoles != null) { for (final String role: securityRoles) { - if (!isValidRolesMapping(channel, role)) return; + if (!isValidRolesMapping(channel, role)) { + return; + } } } - // if password is set, it takes precedence over hash - final String plainTextPassword = securityJsonNode.get("password").asString(); - final String origHash = securityJsonNode.get("hash").asString(); - if (plainTextPassword != null && plainTextPassword.length() > 0) { - contentAsNode.remove("password"); - contentAsNode.put("hash", hash(plainTextPassword.toCharArray())); - } else if (origHash != null && origHash.length() > 0) { - contentAsNode.remove("password"); - } else if (plainTextPassword != null && plainTextPassword.isEmpty() && origHash == null) { - contentAsNode.remove("password"); - } - final boolean userExisted = internalUsersConfiguration.exists(username); // when updating an existing user password hash can be blank, which means no // changes - // sanity checks, hash is mandatory for newly created users - if (!userExisted && securityJsonNode.get("hash").asString() == null) { - badRequestResponse(channel, "Please specify either 'hash' or 'password' when creating a new internal user."); + try { + if (request.hasParam("owner")) { + ((ObjectNode) content).put("owner", request.param("owner")); + } + if (request.hasParam("isEnabled")) { + ((ObjectNode) content).put("isEnabled", request.param("isEnabled")); + } + ((ObjectNode) content).put("name", username); + internalUsersConfiguration = userService.createOrUpdateAccount((ObjectNode) content); + } + catch (UserServiceException ex) { + badRequestResponse(channel, ex.getMessage()); return; } - - // for existing users, hash is optional - if (userExisted && securityJsonNode.get("hash").asString() == null) { - // sanity check, this should usually not happen - final String hash = ((Hashed) internalUsersConfiguration.getCEntry(username)).getHash(); - if (hash == null || hash.length() == 0) { - internalErrorResponse(channel, - "Existing user " + username + " has no password, and no new password or hash was specified."); - return; - } - contentAsNode.put("hash", hash); + catch (IOException ex) { + throw new IOException(ex); } - internalUsersConfiguration.remove(username); - - // checks complete, create or update the user - internalUsersConfiguration.putCObject(username, DefaultObjectMapper.readTree(contentAsNode, internalUsersConfiguration.getImplementingClass())); - saveAndUpdateConfigs(this.securityIndexName,client, CType.INTERNALUSERS, internalUsersConfiguration, new OnSucessActionListener(channel) { @Override diff --git a/src/main/java/org/opensearch/security/dlic/rest/api/SecurityRestApiActions.java b/src/main/java/org/opensearch/security/dlic/rest/api/SecurityRestApiActions.java index 9655ba67ea..e73d28e865 100644 --- a/src/main/java/org/opensearch/security/dlic/rest/api/SecurityRestApiActions.java +++ b/src/main/java/org/opensearch/security/dlic/rest/api/SecurityRestApiActions.java @@ -28,6 +28,7 @@ import org.opensearch.security.privileges.PrivilegesEvaluator; import org.opensearch.security.ssl.SecurityKeyStore; import org.opensearch.security.ssl.transport.PrincipalExtractor; +import org.opensearch.security.user.UserService; import org.opensearch.threadpool.ThreadPool; public class SecurityRestApiActions { @@ -44,9 +45,10 @@ public static Collection getHandler(final Settings settings, final ThreadPool threadPool, final AuditLog auditLog, final SecurityKeyStore securityKeyStore, + final UserService userService, final boolean certificatesReloadEnabled) { final List handlers = new ArrayList(16); - handlers.add(new InternalUsersApiAction(settings, configPath, controller, client, adminDns, cr, cs, principalExtractor, evaluator, threadPool, auditLog)); + handlers.add(new InternalUsersApiAction(settings, configPath, controller, client, adminDns, cr, cs, principalExtractor, evaluator, threadPool, userService, auditLog)); handlers.add(new RolesMappingApiAction(settings, configPath, controller, client, adminDns, cr, cs, principalExtractor, evaluator, threadPool, auditLog)); handlers.add(new RolesApiAction(settings, configPath, controller, client, adminDns, cr, cs, principalExtractor, evaluator, threadPool, auditLog)); handlers.add(new ActionGroupsApiAction(settings, configPath, controller, client, adminDns, cr, cs, principalExtractor, evaluator, threadPool, auditLog)); diff --git a/src/main/java/org/opensearch/security/user/UserService.java b/src/main/java/org/opensearch/security/user/UserService.java new file mode 100644 index 0000000000..c6eea8ef4c --- /dev/null +++ b/src/main/java/org/opensearch/security/user/UserService.java @@ -0,0 +1,181 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +package org.opensearch.security.user; + +import java.io.IOException; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.google.common.collect.ImmutableList; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import org.opensearch.client.Client; +import org.opensearch.cluster.service.ClusterService; +import org.opensearch.common.inject.Inject; +import org.opensearch.common.settings.Settings; +import org.opensearch.security.DefaultObjectMapper; +import org.opensearch.security.configuration.ConfigurationRepository; +import org.opensearch.security.securityconf.DynamicConfigFactory; +import org.opensearch.security.securityconf.Hashed; +import org.opensearch.security.securityconf.impl.CType; +import org.opensearch.security.securityconf.impl.SecurityDynamicConfiguration; +import org.opensearch.security.support.ConfigConstants; +import org.opensearch.security.support.SecurityJsonNode; + +import static org.opensearch.security.dlic.rest.support.Utils.hash; + +/** + * This class handles user registration and operations on behalf of the Security Plugin. + */ +public class UserService { + + protected final Logger log = LogManager.getLogger(this.getClass()); + ClusterService clusterService; + static ConfigurationRepository configurationRepository; + String securityIndex; + Client client; + final static String NO_PASSWORD_OR_HASH_MESSAGE = "Please specify either 'hash' or 'password' when creating a new internal user."; + final static String RESTRICTED_CHARACTER_USE_MESSAGE = "A restricted character(s) was detected in the account name. Please remove: "; + + final static String SERVICE_ACCOUNT_PASSWORD_MESSAGE = "A password cannot be provided for a service account. Failed to register service account: "; + + final static String SERVICE_ACCOUNT_HASH_MESSAGE = "A password hash cannot be provided for service account. Failed to register service account: "; + + final static String NO_ACCOUNT_NAME_MESSAGE = "No account name was specified in the request."; + private static CType getConfigName() { + return CType.INTERNALUSERS; + } + + static final List RESTRICTED_FROM_USERNAME = ImmutableList.of( + ":" // Not allowed in basic auth, see https://stackoverflow.com/a/33391003/533057 + ); + + @Inject + public UserService( + ClusterService clusterService, + ConfigurationRepository configurationRepository, + Settings settings, + Client client + ) { + this.clusterService = clusterService; + this.configurationRepository = configurationRepository; + this.securityIndex = settings.get(ConfigConstants.SECURITY_CONFIG_INDEX_NAME, + ConfigConstants.OPENDISTRO_SECURITY_DEFAULT_CONFIG_INDEX); + this.client = client; + } + + /** + * Load data for a given CType + * @param config CType whose data is to be loaded in-memory + * @return configuration loaded with given CType data + */ + protected static final SecurityDynamicConfiguration load(final CType config, boolean logComplianceEvent) { + SecurityDynamicConfiguration loaded = configurationRepository.getConfigurationsFromIndex(Collections.singleton(config), logComplianceEvent).get(config).deepClone(); + return DynamicConfigFactory.addStatics(loaded); + } + + /** + * This function will handle the creation or update of a user account. + * + * @param contentAsNode An object node of different account configurations. + * @return InternalUserConfiguration with the new/updated user + * @throws UserServiceException + * @throws IOException + */ + public SecurityDynamicConfiguration createOrUpdateAccount(ObjectNode contentAsNode) throws IOException { + + + SecurityJsonNode securityJsonNode = new SecurityJsonNode(contentAsNode); + + final SecurityDynamicConfiguration internalUsersConfiguration = load(getConfigName(), false); + String accountName = securityJsonNode.get("name").asString(); + + if (accountName == null || accountName.length() == 0) { // Fail if field is present but empty + throw new UserServiceException(NO_ACCOUNT_NAME_MESSAGE); + } + + if (!securityJsonNode.get("attributes").get("owner").isNull() && !securityJsonNode.get("attributes").get("owner").equals(accountName)) { // If this is a service account + verifyServiceAccount(securityJsonNode, accountName); + String password = generatePassword(); + contentAsNode.put("hash", hash(password.toCharArray())); + } + + securityJsonNode = new SecurityJsonNode(contentAsNode); + final List foundRestrictedContents = RESTRICTED_FROM_USERNAME.stream().filter(accountName::contains).collect(Collectors.toList()); + if (!foundRestrictedContents.isEmpty()) { + final String restrictedContents = foundRestrictedContents.stream().map(s -> "'" + s + "'").collect(Collectors.joining(",")); + throw new UserServiceException(RESTRICTED_CHARACTER_USE_MESSAGE + restrictedContents); + } + + // if password is set, it takes precedence over hash + final String plainTextPassword = securityJsonNode.get("password").asString(); + final String origHash = securityJsonNode.get("hash").asString(); + if (plainTextPassword != null && plainTextPassword.length() > 0) { + contentAsNode.remove("password"); + contentAsNode.put("hash", hash(plainTextPassword.toCharArray())); + } else if (origHash != null && origHash.length() > 0) { + contentAsNode.remove("password"); + } else if (plainTextPassword != null && plainTextPassword.isEmpty() && origHash == null) { + contentAsNode.remove("password"); + } + + final boolean userExisted = internalUsersConfiguration.exists(accountName); + + // sanity checks, hash is mandatory for newly created users + if (!userExisted && securityJsonNode.get("hash").asString() == null) { + throw new UserServiceException(NO_PASSWORD_OR_HASH_MESSAGE); + } + + // for existing users, hash is optional + if (userExisted && securityJsonNode.get("hash").asString() == null) { + // sanity check, this should usually not happen + final String hash = ((Hashed) internalUsersConfiguration.getCEntry(accountName)).getHash(); + if (hash == null || hash.length() == 0) { + throw new UserServiceException("Existing user " + accountName + " has no password, and no new password or hash was specified."); + } + contentAsNode.put("hash", hash); + } + + internalUsersConfiguration.remove(accountName); + contentAsNode.remove("name"); + + internalUsersConfiguration.putCObject(accountName, DefaultObjectMapper.readTree(contentAsNode, internalUsersConfiguration.getImplementingClass())); + return internalUsersConfiguration; + } + + private void verifyServiceAccount(SecurityJsonNode securityJsonNode, String accountName) { + + final String plainTextPassword = securityJsonNode.get("password").asString(); + final String origHash = securityJsonNode.get("hash").asString(); + + if (plainTextPassword != null && plainTextPassword.length() > 0) { + throw new UserServiceException(SERVICE_ACCOUNT_PASSWORD_MESSAGE + accountName); + } + + if (origHash != null && origHash.length() > 0) { + throw new UserServiceException(SERVICE_ACCOUNT_HASH_MESSAGE + accountName); + } + } + + /** + * This will be swapped in for a real solution once one is decided on. + * + * @return A password for a service account. + */ + private String generatePassword() { + String generatedPassword = "superSecurePassword"; + return generatedPassword; + } +} diff --git a/src/main/java/org/opensearch/security/user/UserServiceException.java b/src/main/java/org/opensearch/security/user/UserServiceException.java new file mode 100644 index 0000000000..b8e6843751 --- /dev/null +++ b/src/main/java/org/opensearch/security/user/UserServiceException.java @@ -0,0 +1,20 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +package org.opensearch.security.user; + +public class UserServiceException extends RuntimeException { + + public UserServiceException(String message) { + super(message); + } + +} diff --git a/src/test/java/org/opensearch/security/TransportUserInjectorIntegTest.java b/src/test/java/org/opensearch/security/TransportUserInjectorIntegTest.java index 8fa665e8ba..a3e08d5234 100644 --- a/src/test/java/org/opensearch/security/TransportUserInjectorIntegTest.java +++ b/src/test/java/org/opensearch/security/TransportUserInjectorIntegTest.java @@ -106,11 +106,12 @@ public void testSecurityUserInjection() throws Exception { Assert.fail("Expecting exception"); } catch (OpenSearchSecurityException ex) { exception = ex; - log.warn(ex.toString()); + log.debug(ex.toString()); Assert.assertNotNull(exception); - Assert.assertTrue(exception.getMessage().contains("indices:admin/create")); + Assert.assertTrue(exception.getMessage().toString().contains("no permissions for [indices:admin/create]")); } + // 3. with valid backend roles for injected user UserInjectorPlugin.injectedUser = "injectedadmin|injecttest"; try (Node node = new PluginAwareNode(false, tcSettings, Netty4ModulePlugin.class, @@ -157,6 +158,5 @@ public void testSecurityUserInjectionWithConfigDisabled() throws Exception { // Should pass as the user injection is disabled Assert.assertTrue(cir.isAcknowledged()); } - } } diff --git a/src/test/java/org/opensearch/security/auditlog/integration/TestAuditlogImpl.java b/src/test/java/org/opensearch/security/auditlog/integration/TestAuditlogImpl.java index b91ddee9fe..0502f078d9 100644 --- a/src/test/java/org/opensearch/security/auditlog/integration/TestAuditlogImpl.java +++ b/src/test/java/org/opensearch/security/auditlog/integration/TestAuditlogImpl.java @@ -61,7 +61,7 @@ public static List doThenWaitForMessages(final Runnable action, fi final List missedMessages = new ArrayList<>(); final List messages = new ArrayList<>(); final CountDownLatch latch = resetAuditStorage(expectedCount, messages); - + try { action.run(); final int maxSecondsToWaitForMessages = 1; @@ -104,9 +104,9 @@ public static List doThenWaitForMessages(final Runnable action, fi /** * Resets all of the mechanics for fresh messages to be captured - * + * * @param expectedMessageCount The number of messages before the latch is signalled, indicating all messages have been recieved - * @param message Where messages will be stored after being recieved + * @param messages Where messages will be stored after being recieved */ private static CountDownLatch resetAuditStorage(int expectedMessageCount, List messages) { final CountDownLatch latch = new CountDownLatch(expectedMessageCount); diff --git a/src/test/java/org/opensearch/security/dlic/rest/api/UserApiTest.java b/src/test/java/org/opensearch/security/dlic/rest/api/UserApiTest.java index 1ecd96e148..d755751e54 100644 --- a/src/test/java/org/opensearch/security/dlic/rest/api/UserApiTest.java +++ b/src/test/java/org/opensearch/security/dlic/rest/api/UserApiTest.java @@ -42,6 +42,33 @@ protected String getEndpointPrefix() { return PLUGINS_PREFIX; } + + private static final String ENABLED_SERVICE_ACCOUNT_BODY = "{" + + " \"attributes\": { \"owner\": \"test_owner\", " + + "\"isEnabled\": \"true\"}" + + " }\n"; + + private static final String DISABLED_SERVICE_ACCOUNT_BODY = "{" + + " \"attributes\": { \"owner\": \"test_owner\", " + + "\"isEnabled\": \"false\"}" + + " }\n"; + private static final String ENABLED_NOT_SERVICE_ACCOUNT_BODY = "{" + + " \"attributes\": { \"owner\": \"user_is_owner_1\", " + + "\"isEnabled\": \"true\"}" + + " }\n"; + private static final String PASSWORD_SERVICE = "{ \"password\" : \"test\"," + + " \"attributes\": { \"owner\": \"test_owner\", " + + "\"isEnabled\": \"true\"}" + + " }\n"; + private static final String HASH_SERVICE = "{ \"owner\" : \"test_owner\"," + + " \"attributes\": { \"owner\": \"test_owner\", " + + "\"isEnabled\": \"true\"}" + + " }\n"; + private static final String PASSWORD_HASH_SERVICE = "{ \"password\" : \"test\", \"hash\" : \"123\"," + + " \"attributes\": { \"owner\": \"test_owner\", " + + "\"isEnabled\": \"true\"}" + + " }\n"; + public UserApiTest(){ ENDPOINT = getEndpointPrefix() + "/api"; } @@ -139,6 +166,7 @@ private void verifyGet(final Header... header) throws Exception { // GET, new URL endpoint in security response = rh.executeGetRequest(ENDPOINT + "/user", header); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); + } private void verifyPut(final Header... header) throws Exception { @@ -194,6 +222,7 @@ private void verifyPut(final Header... header) throws Exception { Assert.assertEquals(settings.get("reason"), AbstractConfigurationValidator.ErrorType.INVALID_CONFIGURATION.getMessage()); Assert.assertTrue(settings.get(AbstractConfigurationValidator.INVALID_KEYS_KEY + ".keys").contains("some")); Assert.assertTrue(settings.get(AbstractConfigurationValidator.INVALID_KEYS_KEY + ".keys").contains("other")); + } private void verifyPatch(final boolean sendAdminCert, Header... restAdminHeader) throws Exception { @@ -292,6 +321,40 @@ private void verifyPatch(final boolean sendAdminCert, Header... restAdminHeader) addUserWithHash("nagilum", "$2a$12$n5nubfWATfQjSYHiWtUyeOxMIxFInUHOAx8VMmGmxFNPGpaBmeB.m", HttpStatus.SC_CREATED); + // Add enabled service account then get it + response = rh.executePutRequest(ENDPOINT + "/internalusers/happyServiceLive", + ENABLED_SERVICE_ACCOUNT_BODY, restAdminHeader); + Assert.assertEquals(response.getBody(), HttpStatus.SC_CREATED, response.getStatusCode()); + response = rh.executeGetRequest(ENDPOINT + "/internalusers/happyServiceLive", restAdminHeader); + Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); + + + // Add disabled service account + response = rh.executePutRequest(ENDPOINT + "/internalusers/happyServiceDead", + DISABLED_SERVICE_ACCOUNT_BODY, restAdminHeader); + Assert.assertEquals(response.getBody(), HttpStatus.SC_CREATED, response.getStatusCode()); + + // Add enabled non-service account + response = rh.executePutRequest(ENDPOINT + "/internalusers/user_is_owner_1", + ENABLED_NOT_SERVICE_ACCOUNT_BODY, restAdminHeader); + Assert.assertEquals(HttpStatus.SC_CREATED, response.getStatusCode()); + + // Add service account with password -- Should Fail + + response = rh.executePutRequest(ENDPOINT + "/internalusers/passwordService", + PASSWORD_SERVICE, restAdminHeader); + Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); + + //Add service with hash -- should fail + response = rh.executePutRequest(ENDPOINT + "/internalusers/hashService", + HASH_SERVICE, restAdminHeader); + Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); + + // Add Service account with password & Hash -- should fail + response = rh.executePutRequest(ENDPOINT + "/internalusers/passwordHashService", + PASSWORD_HASH_SERVICE, restAdminHeader); + Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); + // access must be allowed now checkGeneralAccess(HttpStatus.SC_OK, "nagilum", "nagilum"); @@ -417,7 +480,6 @@ private void verifyRoles(final boolean sendAdminCert, Header... header) throws E addUserWithPassword("$1aAAAAAAAAC", "$1aAAAAAAAAC", HttpStatus.SC_CREATED); addUserWithPassword("abc", "abc", HttpStatus.SC_CREATED); - // check tabs in json response = rh.executePutRequest(ENDPOINT + "/internalusers/userwithtabs", "\t{\"hash\": \t \"123\"\t} ", header); Assert.assertEquals(response.getBody(), HttpStatus.SC_CREATED, response.getStatusCode()); @@ -478,7 +540,6 @@ public void testPasswordRules() throws Exception { HttpResponse response = rh .executeGetRequest("_plugins/_security/api/" + CType.INTERNALUSERS.toLCString()); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); - System.out.println(response.getBody()); Settings settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); Assert.assertEquals(133, settings.size());