]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-8715 Allow provisioning of non-local users
authorJulien Lancelot <julien.lancelot@sonarsource.com>
Thu, 2 Feb 2017 16:00:22 +0000 (17:00 +0100)
committerJulien Lancelot <julien.lancelot@sonarsource.com>
Fri, 3 Feb 2017 10:59:06 +0000 (11:59 +0100)
16 files changed:
it/it-tests/src/test/java/it/user/BaseIdentityProviderTest.java
it/it-tests/src/test/java/it/user/OAuth2IdentityProviderTest.java
it/it-tests/src/test/java/it/user/RealmAuthenticationTest.java
it/it-tests/src/test/java/util/user/Users.java
server/sonar-server/src/main/java/org/sonar/server/authentication/RealmAuthenticator.java
server/sonar-server/src/main/java/org/sonar/server/authentication/SsoAuthenticator.java
server/sonar-server/src/main/java/org/sonar/server/user/ExternalIdentity.java
server/sonar-server/src/main/java/org/sonar/server/user/ws/CreateAction.java
server/sonar-server/src/test/java/org/sonar/server/user/UserUpdaterTest.java
server/sonar-server/src/test/java/org/sonar/server/user/ws/CreateActionTest.java
server/sonar-server/src/test/java/org/sonar/server/user/ws/UsersWsTest.java
sonar-ws/src/main/java/org/sonarqube/ws/client/user/CreateRequest.java
sonar-ws/src/main/java/org/sonarqube/ws/client/user/UsersService.java
sonar-ws/src/main/java/org/sonarqube/ws/client/user/UsersWsParameters.java
sonar-ws/src/test/java/org/sonarqube/ws/client/user/CreateRequestTest.java
sonar-ws/src/test/java/org/sonarqube/ws/client/user/UsersServiceTest.java

index 7a8a51f2d2ccac0b1d9c696ffdbba41efda2b7d4..6b5f43f512c931942663550e954c2c2be8742be8 100644 (file)
@@ -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();
   }
 
index 41dddd73041a85f222ff74930e7e95dc6bd7e2ad..89b2abe040ed28847bac2378a758de81ac42b1fc 100644 (file)
@@ -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)));
index 504842ce2c5a732e7dbbd02a35de1d2ef6259a69..bbf78cb077eecbd0700ff53db2c018919873c6fb 100644 (file)
@@ -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"));
   }
 
 }
index 965815fca2163ca259efa261e4ff3052ce906786..3848d18176d3ec136bc4b14804156adfce2895f9 100644 (file)
@@ -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<String> groups;
     private final List<String> scmAccounts;
     private final boolean active;
     private final boolean local;
     private int tokensCount;
 
-    private User(String login, String name, String email, List<String> groups, List<String> scmAccounts, boolean active, boolean local, int tokensCount) {
+    private User(String login, String name, String email, String externalIdentity, String externalProvider, List<String> groups, List<String> 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;
+    }
+  }
+}
index 02f515e70ae8fa7388f6724ae2dda7ed220b65f9..e8b7b0cd9ddbfbc412f2081f48c76d6cded22c8f 100644 (file)
@@ -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 {
 
index 953447485de7c9d26ce5f55ccc7cad5e523eb01c..c3271005de6661e2d5253e0139698541a32b7b4c 100644 (file)
@@ -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 {
 
index bc597b9c9470cfe4b8784232d41c24b0f62488dc..1dcbec63410ded50fce53a60e3f48f675c63bf46 100644 (file)
@@ -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;
 
index 2ad3abf1dbaf2f98bfe405699ae33a652af0aaca..5612f0aef5a761d82483c605bda18ea8a6774fe8 100644 (file)
@@ -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.<br/>" +
         "If a deactivated user account exists with the given login, it will be reactivated.<br/>" +
         "Requires Administer System permission<br/>" +
+        "Since 6.3, the password is only mandatory when creating local users, and should not be set on non local users<br/>" +
         "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();
   }
 
index 245260961c57f189d272bbe944c9610022b99304..30abfb55606d7275257a6c283eebf98be7010ef7 100644 (file)
@@ -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);
index ffe4849f0df453b7f6a1640ff728d6e3279b7171..24484efde746f17a0fe0698f2857ecd9e0116e7a 100644 (file)
@@ -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) {
index 5e6c4991f146d66cd85454b834c40a69430bcb06..fb42f4eb1a69e014fdd8afbd754e75abdf38642c 100644 (file)
@@ -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
index eb316558e2289c49a081f173b06df44e64910836..c2b8a7fbba6232f603a87a1f25458fea99735e3e 100644 (file)
@@ -37,6 +37,7 @@ public class CreateRequest {
   private final String name;
   private final String email;
   private final List<String> 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<String> 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);
     }
   }
index 3393adb5c07f0565e3e39c072e3cb66bfeabe1b2..4df97cae1546cf814f349cfae03aa6ea93835340 100644 (file)
@@ -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());
   }
 
index ea79758067c0750d2161f9727b22ee0d73be1d98..efad150c1f8d0634e6edc7eb2b9fb9fddc5d7ac7 100644 (file)
@@ -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
index 0ba49c20a98e399c7913c50b51fd46de4d1788aa..ddcfeef94f7ad39c51981afe6b96974fa1d1fb8a 100644 (file)
@@ -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();
+  }
+
 }
index b652dc308693a8ab91429db7e943b5c75d68a0dd..b772983d52dc3c6b7d7270946adea4a65b7d38af 100644 (file)
@@ -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();
   }