From 9c20e9ec5ded65dbe2f63ae9e0eb93a61eb50cb0 Mon Sep 17 00:00:00 2001 From: Julien Lancelot Date: Thu, 2 Feb 2017 17:00:22 +0100 Subject: [PATCH] SONAR-8715 Allow provisioning of non-local users --- .../it/user/BaseIdentityProviderTest.java | 34 +++++++- .../it/user/OAuth2IdentityProviderTest.java | 26 ++++++ .../java/it/user/RealmAuthenticationTest.java | 36 ++++++-- .../src/test/java/util/user/Users.java | 17 +++- .../authentication/RealmAuthenticator.java | 2 +- .../authentication/SsoAuthenticator.java | 2 +- .../sonar/server/user/ExternalIdentity.java | 3 + .../sonar/server/user/ws/CreateAction.java | 20 ++++- .../sonar/server/user/UserUpdaterTest.java | 37 ++++++++ .../server/user/ws/CreateActionTest.java | 85 +++++++++++++++++++ .../org/sonar/server/user/ws/UsersWsTest.java | 2 +- .../ws/client/user/CreateRequest.java | 18 +++- .../ws/client/user/UsersService.java | 4 +- .../ws/client/user/UsersWsParameters.java | 1 + .../ws/client/user/CreateRequestTest.java | 37 +++++++- .../ws/client/user/UsersServiceTest.java | 3 + 16 files changed, 302 insertions(+), 25 deletions(-) diff --git a/it/it-tests/src/test/java/it/user/BaseIdentityProviderTest.java b/it/it-tests/src/test/java/it/user/BaseIdentityProviderTest.java index 7a8a51f2d2c..6b5f43f512c 100644 --- a/it/it-tests/src/test/java/it/user/BaseIdentityProviderTest.java +++ b/it/it-tests/src/test/java/it/user/BaseIdentityProviderTest.java @@ -31,6 +31,7 @@ import org.junit.ClassRule; import org.junit.Test; import org.sonarqube.ws.client.GetRequest; import org.sonarqube.ws.client.WsClient; +import org.sonarqube.ws.client.user.CreateRequest; import util.user.UserRule; import util.user.Users; @@ -124,10 +125,11 @@ public class BaseIdentityProviderTest { setUserCreatedByAuthPlugin(USER_LOGIN, USER_PROVIDER_ID, USER_NAME, USER_EMAIL); userRule.createUser("another", "Another", USER_EMAIL, "another"); - runSelenese(ORCHESTRATOR,"/user/BaseIdentityProviderTest/fail_when_email_already_exists.html"); + runSelenese(ORCHESTRATOR, "/user/BaseIdentityProviderTest/fail_when_email_already_exists.html"); File logFile = ORCHESTRATOR.getServer().getWebLogs(); - assertThat(FileUtils.readFileToString(logFile)).doesNotContain("You can't sign up because email 'john@email.com' is already used by an existing user. This means that you probably already registered with another account"); + assertThat(FileUtils.readFileToString(logFile)) + .doesNotContain("You can't sign up because email 'john@email.com' is already used by an existing user. This means that you probably already registered with another account"); } @Test @@ -198,7 +200,7 @@ public class BaseIdentityProviderTest { setServerProperty(ORCHESTRATOR, "sonar.auth.fake-base-id-provider.throwUnauthorizedMessage", "true"); runSelenese(ORCHESTRATOR, - "/user/BaseIdentityProviderTest/display_message_in_ui_but_not_in_log_when_unauthorized_exception.html"); + "/user/BaseIdentityProviderTest/display_message_in_ui_but_not_in_log_when_unauthorized_exception.html"); File logFile = ORCHESTRATOR.getServer().getWebLogs(); assertThat(FileUtils.readFileToString(logFile)).doesNotContain("A functional error has happened"); @@ -267,6 +269,30 @@ public class BaseIdentityProviderTest { userRule.verifyUserExists(login, USER_NAME, USER_EMAIL, false); } + @Test + public void provision_user_before_authentication() { + enablePlugin(); + setUserCreatedByAuthPlugin(USER_LOGIN, USER_PROVIDER_ID, USER_NAME, USER_EMAIL); + + // Provision none local user in database + newAdminWsClient(ORCHESTRATOR).users().create(CreateRequest.builder() + .setLogin(USER_LOGIN) + .setName(USER_NAME) + .setEmail(USER_EMAIL) + .setLocal(false) + .build()); + assertThat(userRule.getUserByLogin(USER_LOGIN).get()) + .extracting(Users.User::isLocal, Users.User::getExternalIdentity, Users.User::getExternalProvider) + .containsOnly(false, USER_LOGIN, "sonarqube"); + + // Authenticate with external system -> It will update external provider info + authenticateWithFakeAuthProvider(); + + assertThat(userRule.getUserByLogin(USER_LOGIN).get()) + .extracting(Users.User::isLocal, Users.User::getExternalIdentity, Users.User::getExternalProvider) + .containsOnly(false, USER_PROVIDER_ID, FAKE_PROVIDER_KEY); + } + private static void enablePlugin() { setServerProperty(ORCHESTRATOR, "sonar.auth.fake-base-id-provider.enabled", "true"); } @@ -284,7 +310,7 @@ public class BaseIdentityProviderTest { private static void authenticateWithFakeAuthProvider() { adminWsClient.wsConnector().call( - new GetRequest(("/sessions/init/" + FAKE_PROVIDER_KEY))) + new GetRequest("/sessions/init/" + FAKE_PROVIDER_KEY)) .failIfNotSuccessful(); } diff --git a/it/it-tests/src/test/java/it/user/OAuth2IdentityProviderTest.java b/it/it-tests/src/test/java/it/user/OAuth2IdentityProviderTest.java index 41dddd73041..89b2abe040e 100644 --- a/it/it-tests/src/test/java/it/user/OAuth2IdentityProviderTest.java +++ b/it/it-tests/src/test/java/it/user/OAuth2IdentityProviderTest.java @@ -35,7 +35,9 @@ import org.junit.Test; import org.sonarqube.ws.client.GetRequest; import org.sonarqube.ws.client.WsClient; import org.sonarqube.ws.client.WsResponse; +import org.sonarqube.ws.client.user.CreateRequest; import util.user.UserRule; +import util.user.Users; import static org.assertj.core.api.Assertions.assertThat; import static util.ItUtils.newAdminWsClient; @@ -166,6 +168,30 @@ public class OAuth2IdentityProviderTest { assertThat(FileUtils.readFileToString(logFile)).doesNotContain("You can't sign up because email 'john@email.com' is already used by an existing user. This means that you probably already registered with another account"); } + @Test + public void provision_user_before_authentication() { + simulateRedirectionToCallback(); + enablePlugin(); + + // Provision none local user in database + newAdminWsClient(ORCHESTRATOR).users().create(CreateRequest.builder() + .setLogin(USER_LOGIN) + .setName(USER_NAME) + .setEmail(USER_EMAIL) + .setLocal(false) + .build()); + assertThat(userRule.getUserByLogin(USER_LOGIN).get()) + .extracting(Users.User::isLocal, Users.User::getExternalIdentity, Users.User::getExternalProvider) + .containsOnly(false, USER_LOGIN, "sonarqube"); + + // Authenticate with external system -> It will update external provider info + authenticateWithFakeAuthProvider(); + + assertThat(userRule.getUserByLogin(USER_LOGIN).get()) + .extracting(Users.User::isLocal, Users.User::getExternalIdentity, Users.User::getExternalProvider) + .containsOnly(false, USER_PROVIDER_ID, FAKE_PROVIDER_KEY); + } + private void authenticateWithFakeAuthProvider() { WsResponse response = adminWsClient.wsConnector().call( new GetRequest(("/sessions/init/" + FAKE_PROVIDER_KEY))); diff --git a/it/it-tests/src/test/java/it/user/RealmAuthenticationTest.java b/it/it-tests/src/test/java/it/user/RealmAuthenticationTest.java index 504842ce2c5..bbf78cb077e 100644 --- a/it/it-tests/src/test/java/it/user/RealmAuthenticationTest.java +++ b/it/it-tests/src/test/java/it/user/RealmAuthenticationTest.java @@ -38,16 +38,17 @@ import org.sonar.wsclient.connectors.HttpClient4Connector; import org.sonar.wsclient.services.AuthenticationQuery; import org.sonar.wsclient.user.UserParameters; import org.sonarqube.ws.client.GetRequest; -import org.sonarqube.ws.client.HttpConnector; -import org.sonarqube.ws.client.WsClient; -import org.sonarqube.ws.client.WsClientFactories; import org.sonarqube.ws.client.WsResponse; +import org.sonarqube.ws.client.user.CreateRequest; import util.user.UserRule; +import util.user.Users; import static java.net.HttpURLConnection.HTTP_OK; import static java.net.HttpURLConnection.HTTP_UNAUTHORIZED; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.Assert.fail; +import static util.ItUtils.newAdminWsClient; +import static util.ItUtils.newUserWsClient; import static util.ItUtils.pluginArtifact; import static util.ItUtils.setServerProperty; import static util.selenium.Selenese.runSelenese; @@ -165,7 +166,6 @@ public class RealmAuthenticationTest { // Then verifyAuthenticationIsOk(login, password); - // When external system does not work users.remove(login + ".password"); updateUsersInExtAuth(users); @@ -319,10 +319,31 @@ public class RealmAuthenticationTest { updateUsersInExtAuth(ImmutableMap.of(username + ".password", password)); verifyAuthenticationIsOk(username, password); - ; } - protected void verifyHttpException(Exception e, int expectedCode) { + @Test + public void provision_user_before_authentication() { + newAdminWsClient(orchestrator).users().create(CreateRequest.builder() + .setLogin(USER_LOGIN) + .setName("Tester Testerovich") + .setEmail("tester@example.org") + .setLocal(false) + .build()); + // The user is created in SonarQube but doesn't exist yet in external authentication system + verifyAuthenticationIsNotOk(USER_LOGIN, "123"); + + updateUsersInExtAuth(ImmutableMap.of( + USER_LOGIN + ".password", "123", + USER_LOGIN + ".name", "Tester Testerovich", + USER_LOGIN + ".email", "tester@example.org")); + + verifyAuthenticationIsOk(USER_LOGIN, "123"); + assertThat(USER_RULE.getUserByLogin(USER_LOGIN).get()) + .extracting(Users.User::isLocal, Users.User::getExternalIdentity, Users.User::getExternalProvider) + .containsOnly(false, USER_LOGIN, "sonarqube"); + } + + private void verifyHttpException(Exception e, int expectedCode) { assertThat(e).isInstanceOf(HttpException.class); HttpException exception = (HttpException) e; assertThat(exception.status()).isEqualTo(expectedCode); @@ -377,9 +398,8 @@ public class RealmAuthenticationTest { } private WsResponse checkAuthenticationWithWebService(String login, String password) { - WsClient wsClient = WsClientFactories.getDefault().newClient(HttpConnector.newBuilder().url(orchestrator.getServer().getUrl()).credentials(login, password).build()); // Call any WS - return wsClient.wsConnector().call(new GetRequest("api/rules/search")); + return newUserWsClient(orchestrator, login, password).wsConnector().call(new GetRequest("api/rules/search")); } } diff --git a/it/it-tests/src/test/java/util/user/Users.java b/it/it-tests/src/test/java/util/user/Users.java index 965815fca21..3848d18176d 100644 --- a/it/it-tests/src/test/java/util/user/Users.java +++ b/it/it-tests/src/test/java/util/user/Users.java @@ -43,15 +43,20 @@ public class Users { private final String login; private final String name; private final String email; + private final String externalIdentity; + private final String externalProvider; private final List groups; private final List scmAccounts; private final boolean active; private final boolean local; private int tokensCount; - private User(String login, String name, String email, List groups, List scmAccounts, boolean active, boolean local, int tokensCount) { + private User(String login, String name, String email, String externalIdentity, String externalProvider, List groups, List scmAccounts, + boolean active, boolean local, int tokensCount) { this.login = login; this.name = name; + this.externalIdentity = externalIdentity; + this.externalProvider = externalProvider; this.email = email; this.groups = groups; this.scmAccounts = scmAccounts; @@ -91,7 +96,13 @@ public class Users { public int getTokensCount() { return tokensCount; } - } -} + public String getExternalIdentity() { + return externalIdentity; + } + public String getExternalProvider() { + return externalProvider; + } + } +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/authentication/RealmAuthenticator.java b/server/sonar-server/src/main/java/org/sonar/server/authentication/RealmAuthenticator.java index 02f515e70ae..e8b7b0cd9dd 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/authentication/RealmAuthenticator.java +++ b/server/sonar-server/src/main/java/org/sonar/server/authentication/RealmAuthenticator.java @@ -47,7 +47,7 @@ import static org.apache.commons.lang.StringUtils.isEmpty; import static org.apache.commons.lang.StringUtils.trimToNull; import static org.sonar.api.CoreProperties.CORE_AUTHENTICATOR_CREATE_USERS; import static org.sonar.server.authentication.event.AuthenticationEvent.Source; -import static org.sonar.server.user.UserUpdater.SQ_AUTHORITY; +import static org.sonar.server.user.ExternalIdentity.SQ_AUTHORITY; public class RealmAuthenticator implements Startable { diff --git a/server/sonar-server/src/main/java/org/sonar/server/authentication/SsoAuthenticator.java b/server/sonar-server/src/main/java/org/sonar/server/authentication/SsoAuthenticator.java index 953447485de..c3271005de6 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/authentication/SsoAuthenticator.java +++ b/server/sonar-server/src/main/java/org/sonar/server/authentication/SsoAuthenticator.java @@ -48,7 +48,7 @@ import org.sonar.server.exceptions.BadRequestException; import static org.apache.commons.lang.StringUtils.defaultIfBlank; import static org.apache.commons.lang.time.DateUtils.addMinutes; import static org.sonar.server.authentication.event.AuthenticationEvent.Source; -import static org.sonar.server.user.UserUpdater.SQ_AUTHORITY; +import static org.sonar.server.user.ExternalIdentity.SQ_AUTHORITY; public class SsoAuthenticator implements Startable { diff --git a/server/sonar-server/src/main/java/org/sonar/server/user/ExternalIdentity.java b/server/sonar-server/src/main/java/org/sonar/server/user/ExternalIdentity.java index bc597b9c947..1dcbec63410 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/user/ExternalIdentity.java +++ b/server/sonar-server/src/main/java/org/sonar/server/user/ExternalIdentity.java @@ -22,6 +22,9 @@ package org.sonar.server.user; import static java.util.Objects.requireNonNull; public class ExternalIdentity { + + public static final String SQ_AUTHORITY = "sonarqube"; + private String provider; private String id; diff --git a/server/sonar-server/src/main/java/org/sonar/server/user/ws/CreateAction.java b/server/sonar-server/src/main/java/org/sonar/server/user/ws/CreateAction.java index 2ad3abf1dba..5612f0aef5a 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/user/ws/CreateAction.java +++ b/server/sonar-server/src/main/java/org/sonar/server/user/ws/CreateAction.java @@ -26,6 +26,7 @@ import org.sonar.api.server.ws.Response; import org.sonar.api.server.ws.WebService; import org.sonar.core.permission.GlobalPermissions; import org.sonar.db.user.UserDto; +import org.sonar.server.user.ExternalIdentity; import org.sonar.server.user.NewUser; import org.sonar.server.user.UserSession; import org.sonar.server.user.UserUpdater; @@ -34,9 +35,11 @@ import org.sonarqube.ws.client.user.CreateRequest; import static com.google.common.base.Strings.emptyToNull; import static org.sonar.core.util.Protobuf.setNullable; +import static org.sonar.server.user.ExternalIdentity.SQ_AUTHORITY; import static org.sonar.server.ws.WsUtils.writeProtobuf; import static org.sonarqube.ws.client.user.UsersWsParameters.ACTION_CREATE; import static org.sonarqube.ws.client.user.UsersWsParameters.PARAM_EMAIL; +import static org.sonarqube.ws.client.user.UsersWsParameters.PARAM_LOCAL; import static org.sonarqube.ws.client.user.UsersWsParameters.PARAM_LOGIN; import static org.sonarqube.ws.client.user.UsersWsParameters.PARAM_NAME; import static org.sonarqube.ws.client.user.UsersWsParameters.PARAM_PASSWORD; @@ -60,6 +63,7 @@ public class CreateAction implements UsersWsAction { .setDescription("Create a user.
" + "If a deactivated user account exists with the given login, it will be reactivated.
" + "Requires Administer System permission
" + + "Since 6.3, the password is only mandatory when creating local users, and should not be set on non local users
" + "Since 6.3, the 'infos' message is no more returned when a user is reactivated") .setSince("3.7") .setPost(true) @@ -71,8 +75,7 @@ public class CreateAction implements UsersWsAction { .setExampleValue("myuser"); action.createParam(PARAM_PASSWORD) - .setDescription("User password") - .setRequired(true) + .setDescription("User password. Only mandatory when creating local user, otherwise it should not be set") .setExampleValue("mypassword"); action.createParam(PARAM_NAME) @@ -93,6 +96,13 @@ public class CreateAction implements UsersWsAction { action.createParam(PARAM_SCM_ACCOUNT) .setDescription("SCM accounts. To set several values, the parameter must be called once for each value.") .setExampleValue("scmAccount=firstValue&scmAccount=secondValue&scmAccount=thirdValue"); + + action.createParam(PARAM_LOCAL) + .setDescription("Specify if the user should be authenticated from SonarQube server or from an external authentication system. " + + "Password should not be set when local is set to false.") + .setSince("6.3") + .setDefaultValue("true") + .setBooleanPossibleValues(); } @Override @@ -108,6 +118,9 @@ public class CreateAction implements UsersWsAction { .setEmail(request.getEmail()) .setScmAccounts(request.getScmAccounts()) .setPassword(request.getPassword()); + if (!request.isLocal()) { + newUser.setExternalIdentity(new ExternalIdentity(SQ_AUTHORITY, request.getLogin())); + } UserDto userDto = userUpdater.create(newUser.build()); return buildResponse(userDto); } @@ -126,10 +139,11 @@ public class CreateAction implements UsersWsAction { private static CreateRequest toWsRequest(Request request) { return CreateRequest.builder() .setLogin(request.mandatoryParam(PARAM_LOGIN)) - .setPassword(request.mandatoryParam(PARAM_PASSWORD)) + .setPassword(request.param(PARAM_PASSWORD)) .setName(request.param(PARAM_NAME)) .setEmail(request.param(PARAM_EMAIL)) .setScmAccounts(getScmAccounts(request)) + .setLocal(request.mandatoryParamAsBoolean(PARAM_LOCAL)) .build(); } diff --git a/server/sonar-server/src/test/java/org/sonar/server/user/UserUpdaterTest.java b/server/sonar-server/src/test/java/org/sonar/server/user/UserUpdaterTest.java index 245260961c5..30abfb55606 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/user/UserUpdaterTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/user/UserUpdaterTest.java @@ -57,6 +57,7 @@ import static org.mockito.Mockito.when; import static org.sonar.api.CoreProperties.CORE_DEFAULT_GROUP; import static org.sonar.db.user.UserTesting.newDisabledUser; import static org.sonar.db.user.UserTesting.newUserDto; +import static org.sonar.server.user.ExternalIdentity.SQ_AUTHORITY; public class UserUpdaterTest { @@ -139,6 +140,42 @@ public class UserUpdaterTest { assertThat(dto.isLocal()).isTrue(); } + @Test + public void create_user_with_identity_provider() throws Exception { + createDefaultGroup(); + + underTest.create(NewUser.builder() + .setLogin("user") + .setName("User") + .setExternalIdentity(new ExternalIdentity("github", "github-user")) + .build()); + + UserDto dto = dbClient.userDao().selectByLogin(session, "user"); + assertThat(dto.isLocal()).isFalse(); + assertThat(dto.getExternalIdentity()).isEqualTo("github-user"); + assertThat(dto.getExternalIdentityProvider()).isEqualTo("github"); + assertThat(dto.getCryptedPassword()).isNull(); + assertThat(dto.getSalt()).isNull(); + } + + @Test + public void create_user_with_sonarqube_external_identity() throws Exception { + createDefaultGroup(); + + underTest.create(NewUser.builder() + .setLogin("user") + .setName("User") + .setExternalIdentity(new ExternalIdentity(SQ_AUTHORITY, "user")) + .build()); + + UserDto dto = dbClient.userDao().selectByLogin(session, "user"); + assertThat(dto.isLocal()).isFalse(); + assertThat(dto.getExternalIdentity()).isEqualTo("user"); + assertThat(dto.getExternalIdentityProvider()).isEqualTo("sonarqube"); + assertThat(dto.getCryptedPassword()).isNull(); + assertThat(dto.getSalt()).isNull(); + } + @Test public void create_user_with_minimum_fields() { when(system2.now()).thenReturn(1418215735482L); diff --git a/server/sonar-server/src/test/java/org/sonar/server/user/ws/CreateActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/user/ws/CreateActionTest.java index ffe4849f0df..24484efde74 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/user/ws/CreateActionTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/user/ws/CreateActionTest.java @@ -116,6 +116,37 @@ public class CreateActionTest { assertThat(db.users().selectGroupIdsOfUser(dbUser.get())).containsOnly(defaultGroupInDefaultOrg.getId()); } + @Test + public void create_local_user() throws Exception { + authenticateAsAdmin(); + + call(CreateRequest.builder() + .setLogin("john") + .setName("John") + .setPassword("1234") + .setLocal(true) + .build()); + + assertThat(db.users().selectUserByLogin("john").get()) + .extracting(UserDto::isLocal, UserDto::getExternalIdentityProvider, UserDto::getExternalIdentity) + .containsOnly(true, "sonarqube", "john"); + } + + @Test + public void create_none_local_user() throws Exception { + authenticateAsAdmin(); + + call(CreateRequest.builder() + .setLogin("john") + .setName("John") + .setLocal(false) + .build()); + + assertThat(db.users().selectUserByLogin("john").get()) + .extracting(UserDto::isLocal, UserDto::getExternalIdentityProvider, UserDto::getExternalIdentity) + .containsOnly(false, "sonarqube", "john"); + } + @Test public void create_user_with_comma_in_scm_account() throws Exception { authenticateAsAdmin(); @@ -233,6 +264,59 @@ public class CreateActionTest { settings.setProperty("sonar.defaultGroup", adminGroup.getName()); } + @Test + public void fail_when_missing_login() throws Exception { + authenticateAsAdmin(); + + expectedException.expect(IllegalArgumentException.class); + expectedException.expectMessage("Login is mandatory and must not be empty"); + call(CreateRequest.builder() + .setLogin(null) + .setName("John") + .setPassword("1234") + .build()); + } + + @Test + public void fail_when_missing_name() throws Exception { + authenticateAsAdmin(); + + expectedException.expect(IllegalArgumentException.class); + expectedException.expectMessage("Name is mandatory and must not be empty"); + call(CreateRequest.builder() + .setLogin("john") + .setName(null) + .setPassword("1234") + .build()); + } + + @Test + public void fail_when_missing_password() throws Exception { + authenticateAsAdmin(); + + expectedException.expect(IllegalArgumentException.class); + expectedException.expectMessage("Password is mandatory and must not be empty"); + call(CreateRequest.builder() + .setLogin("john") + .setName("John") + .setPassword(null) + .build()); + } + + @Test + public void fail_when_password_is_set_on_none_local_user() throws Exception { + authenticateAsAdmin(); + + expectedException.expect(IllegalArgumentException.class); + expectedException.expectMessage("Password should only be set on local user"); + call(CreateRequest.builder() + .setLogin("john") + .setName("John") + .setPassword("1234") + .setLocal(false) + .build()); + } + @Test public void fail_on_missing_permission() throws Exception { userSessionRule.logIn("not_admin"); @@ -263,6 +347,7 @@ public class CreateActionTest { setNullable(createRequest.getEmail(), e -> request.setParam("email", e)); setNullable(createRequest.getPassword(), e -> request.setParam("password", e)); setNullable(createRequest.getScmAccounts(), e -> request.setMultiParam("scmAccount", e)); + request.setParam("local", createRequest.isLocal() ? "true" : "false"); try { return CreateWsResponse.parseFrom(request.execute().getInputStream()); } catch (IOException e) { diff --git a/server/sonar-server/src/test/java/org/sonar/server/user/ws/UsersWsTest.java b/server/sonar-server/src/test/java/org/sonar/server/user/ws/UsersWsTest.java index 5e6c4991f14..fb42f4eb1a6 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/user/ws/UsersWsTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/user/ws/UsersWsTest.java @@ -71,7 +71,7 @@ public class UsersWsTest { WebService.Action action = controller.action("create"); assertThat(action).isNotNull(); assertThat(action.isPost()).isTrue(); - assertThat(action.params()).hasSize(6); + assertThat(action.params()).hasSize(7); } @Test diff --git a/sonar-ws/src/main/java/org/sonarqube/ws/client/user/CreateRequest.java b/sonar-ws/src/main/java/org/sonarqube/ws/client/user/CreateRequest.java index eb316558e22..c2b8a7fbba6 100644 --- a/sonar-ws/src/main/java/org/sonarqube/ws/client/user/CreateRequest.java +++ b/sonar-ws/src/main/java/org/sonarqube/ws/client/user/CreateRequest.java @@ -37,6 +37,7 @@ public class CreateRequest { private final String name; private final String email; private final List scmAccounts; + private final boolean local; private CreateRequest(Builder builder) { this.login = builder.login; @@ -44,12 +45,14 @@ public class CreateRequest { this.name = builder.name; this.email = builder.email; this.scmAccounts = builder.scmAccounts; + this.local = builder.local; } public String getLogin() { return login; } + @CheckForNull public String getPassword() { return password; } @@ -67,6 +70,10 @@ public class CreateRequest { return scmAccounts; } + public boolean isLocal() { + return local; + } + public static Builder builder() { return new Builder(); } @@ -77,6 +84,7 @@ public class CreateRequest { private String name; private String email; private List scmAccounts = emptyList(); + private boolean local = true; private Builder() { // enforce factory method use @@ -87,7 +95,7 @@ public class CreateRequest { return this; } - public Builder setPassword(String password) { + public Builder setPassword(@Nullable String password) { this.password = password; return this; } @@ -107,10 +115,16 @@ public class CreateRequest { return this; } + public Builder setLocal(boolean local) { + this.local = local; + return this; + } + public CreateRequest build() { checkArgument(!isNullOrEmpty(login), "Login is mandatory and must not be empty"); - checkArgument(!isNullOrEmpty(password), "Password is mandatory and must not be empty"); checkArgument(!isNullOrEmpty(name), "Name is mandatory and must not be empty"); + checkArgument(!local || !isNullOrEmpty(password), "Password is mandatory and must not be empty"); + checkArgument(local || isNullOrEmpty(password), "Password should only be set on local user"); return new CreateRequest(this); } } diff --git a/sonar-ws/src/main/java/org/sonarqube/ws/client/user/UsersService.java b/sonar-ws/src/main/java/org/sonarqube/ws/client/user/UsersService.java index 3393adb5c07..4df97cae154 100644 --- a/sonar-ws/src/main/java/org/sonarqube/ws/client/user/UsersService.java +++ b/sonar-ws/src/main/java/org/sonarqube/ws/client/user/UsersService.java @@ -29,6 +29,7 @@ import static org.sonarqube.ws.client.user.UsersWsParameters.ACTION_CREATE; import static org.sonarqube.ws.client.user.UsersWsParameters.ACTION_UPDATE; import static org.sonarqube.ws.client.user.UsersWsParameters.CONTROLLER_USERS; import static org.sonarqube.ws.client.user.UsersWsParameters.PARAM_EMAIL; +import static org.sonarqube.ws.client.user.UsersWsParameters.PARAM_LOCAL; import static org.sonarqube.ws.client.user.UsersWsParameters.PARAM_LOGIN; import static org.sonarqube.ws.client.user.UsersWsParameters.PARAM_NAME; import static org.sonarqube.ws.client.user.UsersWsParameters.PARAM_PASSWORD; @@ -46,7 +47,8 @@ public class UsersService extends BaseService { .setParam(PARAM_PASSWORD, request.getPassword()) .setParam(PARAM_NAME, request.getName()) .setParam(PARAM_EMAIL, request.getEmail()) - .setParam(PARAM_SCM_ACCOUNT, request.getScmAccounts()), + .setParam(PARAM_SCM_ACCOUNT, request.getScmAccounts()) + .setParam(PARAM_LOCAL, request.isLocal()), CreateWsResponse.parser()); } diff --git a/sonar-ws/src/main/java/org/sonarqube/ws/client/user/UsersWsParameters.java b/sonar-ws/src/main/java/org/sonarqube/ws/client/user/UsersWsParameters.java index ea79758067c..efad150c1f8 100644 --- a/sonar-ws/src/main/java/org/sonarqube/ws/client/user/UsersWsParameters.java +++ b/sonar-ws/src/main/java/org/sonarqube/ws/client/user/UsersWsParameters.java @@ -34,6 +34,7 @@ public class UsersWsParameters { public static final String PARAM_SCM_ACCOUNTS = "scmAccounts"; public static final String PARAM_SCM_ACCOUNTS_DEPRECATED = "scm_accounts"; public static final String PARAM_SCM_ACCOUNT = "scmAccount"; + public static final String PARAM_LOCAL = "local"; private UsersWsParameters() { // Only static stuff diff --git a/sonar-ws/src/test/java/org/sonarqube/ws/client/user/CreateRequestTest.java b/sonar-ws/src/test/java/org/sonarqube/ws/client/user/CreateRequestTest.java index 0ba49c20a98..ddcfeef94f7 100644 --- a/sonar-ws/src/test/java/org/sonarqube/ws/client/user/CreateRequestTest.java +++ b/sonar-ws/src/test/java/org/sonarqube/ws/client/user/CreateRequestTest.java @@ -49,6 +49,30 @@ public class CreateRequestTest { assertThat(result.getName()).isEqualTo("John"); assertThat(result.getEmail()).isEqualTo("john@doo.com"); assertThat(result.getScmAccounts()).containsOnly("jo", "hn"); + assertThat(result.isLocal()).isTrue(); + } + + @Test + public void create_request_for_local_user() { + CreateRequest result = underTest + .setLogin("john") + .setPassword("123456") + .setName("John") + .setLocal(true) + .build(); + + assertThat(result.isLocal()).isTrue(); + } + + @Test + public void create_request_for_none_local_user() { + CreateRequest result = underTest + .setLogin("john") + .setName("John") + .setLocal(false) + .build(); + + assertThat(result.isLocal()).isFalse(); } @Test @@ -74,7 +98,7 @@ public class CreateRequestTest { } @Test - public void fail_when_empty_password() { + public void fail_when_empty_password_on_local_user() { expectedException.expect(IllegalArgumentException.class); underTest .setLogin("john") @@ -93,4 +117,15 @@ public class CreateRequestTest { .build(); } + @Test + public void fail_when_password_is_set_on_none_local_user() { + expectedException.expect(IllegalArgumentException.class); + underTest + .setLogin("john") + .setPassword("12345") + .setName("12345") + .setLocal(false) + .build(); + } + } diff --git a/sonar-ws/src/test/java/org/sonarqube/ws/client/user/UsersServiceTest.java b/sonar-ws/src/test/java/org/sonarqube/ws/client/user/UsersServiceTest.java index b652dc30869..b772983d52d 100644 --- a/sonar-ws/src/test/java/org/sonarqube/ws/client/user/UsersServiceTest.java +++ b/sonar-ws/src/test/java/org/sonarqube/ws/client/user/UsersServiceTest.java @@ -30,6 +30,7 @@ import static java.util.Arrays.asList; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; import static org.sonarqube.ws.client.user.UsersWsParameters.PARAM_EMAIL; +import static org.sonarqube.ws.client.user.UsersWsParameters.PARAM_LOCAL; import static org.sonarqube.ws.client.user.UsersWsParameters.PARAM_LOGIN; import static org.sonarqube.ws.client.user.UsersWsParameters.PARAM_NAME; import static org.sonarqube.ws.client.user.UsersWsParameters.PARAM_PASSWORD; @@ -50,6 +51,7 @@ public class UsersServiceTest { .setName("John") .setEmail("john@doo.com") .setScmAccounts(asList("jo", "hn")) + .setLocal(true) .build()); assertThat(serviceTester.getPostParser()).isSameAs(CreateWsResponse.parser()); @@ -59,6 +61,7 @@ public class UsersServiceTest { .hasParam(PARAM_NAME, "John") .hasParam(PARAM_EMAIL, "john@doo.com") .hasParam(PARAM_SCM_ACCOUNT, asList("jo", "hn")) + .hasParam(PARAM_LOCAL, "true") .andNoOtherParam(); } -- 2.39.5