From 5454194d8a367d1aa7d1df29bb591edefa8319c2 Mon Sep 17 00:00:00 2001 From: Julien Lancelot Date: Mon, 12 Jun 2017 15:25:52 +0200 Subject: [PATCH] SONAR-9356 Refactor api/user/current to use protobuf --- .../sonar/server/user/ws/CurrentAction.java | 119 ++++-------- .../server/user/ws/CurrentActionTest.java | 182 ++++++++++++++---- .../user/ws/CurrentActionTest/anonymous.json | 8 - sonar-ws/src/main/protobuf/ws-users.proto | 18 ++ 4 files changed, 203 insertions(+), 124 deletions(-) delete mode 100644 server/sonar-server/src/test/resources/org/sonar/server/user/ws/CurrentActionTest/anonymous.json diff --git a/server/sonar-server/src/main/java/org/sonar/server/user/ws/CurrentAction.java b/server/sonar-server/src/main/java/org/sonar/server/user/ws/CurrentAction.java index 5cbd3511eb7..36a208ca8ce 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/user/ws/CurrentAction.java +++ b/server/sonar-server/src/main/java/org/sonar/server/user/ws/CurrentAction.java @@ -20,23 +20,26 @@ package org.sonar.server.user.ws; import java.util.Collection; -import java.util.Optional; +import java.util.List; +import java.util.stream.Collectors; import org.sonar.api.server.ws.Request; import org.sonar.api.server.ws.Response; import org.sonar.api.server.ws.WebService.NewController; -import org.sonar.api.utils.text.JsonWriter; import org.sonar.db.DbClient; import org.sonar.db.DbSession; import org.sonar.db.permission.OrganizationPermission; import org.sonar.db.user.UserDto; import org.sonar.server.organization.DefaultOrganizationProvider; import org.sonar.server.user.UserSession; +import org.sonarqube.ws.WsUsers.CurrentWsResponse; -import static com.google.common.base.Strings.isNullOrEmpty; -import static java.util.Collections.emptyList; +import static com.google.common.base.Preconditions.checkState; +import static com.google.common.base.Strings.emptyToNull; import static java.util.Collections.singletonList; -import static org.sonar.server.user.ws.UserJsonWriter.FIELD_EXTERNAL_IDENTITY; -import static org.sonar.server.user.ws.UserJsonWriter.FIELD_EXTERNAL_PROVIDER; +import static org.sonar.core.util.Protobuf.setNullable; +import static org.sonar.server.ws.WsUtils.writeProtobuf; +import static org.sonarqube.ws.WsUsers.CurrentWsResponse.Permissions; +import static org.sonarqube.ws.WsUsers.CurrentWsResponse.newBuilder; public class CurrentAction implements UsersWsAction { private final UserSession userSession; @@ -61,87 +64,43 @@ public class CurrentAction implements UsersWsAction { @Override public void handle(Request request, Response response) throws Exception { - try (DbSession dbSession = dbClient.openSession(false)) { - Optional user = Optional.empty(); - Collection groups = emptyList(); - if (userSession.isLoggedIn()) { - user = selectCurrentUser(dbSession); - groups = selectGroups(dbSession); + if (userSession.isLoggedIn()) { + try (DbSession dbSession = dbClient.openSession(false)) { + writeProtobuf(toWsResponse(dbSession, userSession.getLogin()), request, response); } - writeResponse(response, user, groups); + } else { + writeProtobuf(newBuilder() + .setIsLoggedIn(false) + .setPermissions(Permissions.newBuilder().addAllGlobal(getGlobalPermissions()).build()) + .build(), + request, response); } } - private Optional selectCurrentUser(DbSession dbSession) { - return Optional.ofNullable(dbClient.userDao().selectActiveUserByLogin(dbSession, userSession.getLogin())); + private CurrentWsResponse toWsResponse(DbSession dbSession, String userLogin) { + UserDto user = dbClient.userDao().selectActiveUserByLogin(dbSession, userLogin); + checkState(user != null, "User login '%s' cannot be found", userLogin); + Collection groups = dbClient.groupMembershipDao().selectGroupsByLogins(dbSession, singletonList(userLogin)).get(userLogin); + + CurrentWsResponse.Builder builder = newBuilder() + .setIsLoggedIn(true) + .setLogin(user.getLogin()) + .setName(user.getName()) + .setLocal(user.isLocal()) + .addAllGroups(groups) + .addAllScmAccounts(user.getScmAccountsAsList()) + .setPermissions(Permissions.newBuilder().addAllGlobal(getGlobalPermissions()).build()); + setNullable(emptyToNull(user.getEmail()), builder::setEmail); + setNullable(user.getExternalIdentity(), builder::setExternalIdentity); + setNullable(user.getExternalIdentityProvider(), builder::setExternalProvider); + return builder.build(); } - private Collection selectGroups(DbSession dbSession) { - return dbClient.groupMembershipDao().selectGroupsByLogins(dbSession, singletonList(userSession.getLogin())) - .get(userSession.getLogin()); - } - - private void writeResponse(Response response, Optional user, Collection groups) { - JsonWriter json = response.newJsonWriter().beginObject(); - writeUserDetails(json, user, groups); - json.endObject().close(); - } - - private void writeUserDetails(JsonWriter json, Optional optionalUser, Collection groups) { - json - .prop("isLoggedIn", userSession.isLoggedIn()) - .prop("login", userSession.getLogin()) - .prop("name", userSession.getName()); - if (optionalUser.isPresent()) { - UserDto user = optionalUser.get(); - if (!isNullOrEmpty(user.getEmail())) { - json.prop("email", user.getEmail()); - } - json.prop("local", user.isLocal()); - json.prop(FIELD_EXTERNAL_IDENTITY, user.getExternalIdentity()); - json.prop(FIELD_EXTERNAL_PROVIDER, user.getExternalIdentityProvider()); - } - - writeScmAccounts(json, optionalUser); - writeGroups(json, groups); - writePermissions(json); - } - - private static void writeScmAccounts(JsonWriter json, Optional optionalUser) { - json.name("scmAccounts"); - json.beginArray(); - if (optionalUser.isPresent()) { - for (String scmAccount : optionalUser.get().getScmAccountsAsList()) { - json.value(scmAccount); - } - } - json.endArray(); - } - - private static void writeGroups(JsonWriter json, Collection groups) { - json.name("groups"); - json.beginArray(); - for (String group : groups) { - json.value(group); - } - json.endArray(); - } - - private void writePermissions(JsonWriter json) { - json.name("permissions").beginObject(); - writeGlobalPermissions(json); - json.endObject(); - } - - private void writeGlobalPermissions(JsonWriter json) { - json.name("global").beginArray(); - + private List getGlobalPermissions() { String defaultOrganizationUuid = defaultOrganizationProvider.get().getUuid(); - OrganizationPermission.all() + return OrganizationPermission.all() .filter(permission -> userSession.hasPermission(permission, defaultOrganizationUuid)) - .forEach(permission -> json.value(permission.getKey())); - - json.endArray(); + .map(OrganizationPermission::getKey) + .collect(Collectors.toList()); } - } diff --git a/server/sonar-server/src/test/java/org/sonar/server/user/ws/CurrentActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/user/ws/CurrentActionTest.java index 80fe655edf7..fe623c05cce 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/user/ws/CurrentActionTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/user/ws/CurrentActionTest.java @@ -19,26 +19,27 @@ */ package org.sonar.server.user.ws; -import org.junit.Before; import org.junit.Rule; import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.sonar.api.server.ws.WebService; import org.sonar.api.utils.System2; import org.sonar.db.DbClient; import org.sonar.db.DbTester; -import org.sonar.db.user.GroupDto; import org.sonar.db.user.UserDto; -import org.sonar.db.user.UserGroupDto; import org.sonar.server.organization.DefaultOrganizationProvider; import org.sonar.server.organization.TestDefaultOrganizationProvider; import org.sonar.server.tester.UserSessionRule; import org.sonar.server.ws.WsActionTester; +import org.sonarqube.ws.WsUsers.CurrentWsResponse; import static com.google.common.collect.Lists.newArrayList; -import static org.sonar.db.user.GroupTesting.newGroupDto; -import static org.sonar.db.user.UserTesting.newUserDto; +import static org.assertj.core.api.Assertions.assertThat; import static org.sonar.db.permission.OrganizationPermission.ADMINISTER; import static org.sonar.db.permission.OrganizationPermission.ADMINISTER_QUALITY_PROFILES; +import static org.sonar.db.permission.OrganizationPermission.PROVISION_PROJECTS; import static org.sonar.db.permission.OrganizationPermission.SCAN; +import static org.sonar.db.user.GroupTesting.newGroupDto; import static org.sonar.test.JsonAssert.assertJson; public class CurrentActionTest { @@ -46,56 +47,165 @@ public class CurrentActionTest { public UserSessionRule userSessionRule = UserSessionRule.standalone(); @Rule public DbTester db = DbTester.create(System2.INSTANCE); + @Rule + public ExpectedException expectedException = ExpectedException.none(); private DbClient dbClient = db.getDbClient(); private DefaultOrganizationProvider defaultOrganizationProvider = TestDefaultOrganizationProvider.from(db); - private WsActionTester ws; + private WsActionTester ws = new WsActionTester(new CurrentAction(userSessionRule, dbClient, defaultOrganizationProvider)); + + @Test + public void return_user_info() { + db.users().insertUser(user -> user + .setLogin("obiwan.kenobi") + .setName("Obiwan Kenobi") + .setEmail("obiwan.kenobi@starwars.com") + .setLocal(true) + .setExternalIdentity("obiwan") + .setExternalIdentityProvider("sonarqube") + .setScmAccounts(newArrayList("obiwan:github", "obiwan:bitbucket"))); + userSessionRule.logIn("obiwan.kenobi"); + + CurrentWsResponse response = call(); - @Before - public void before() { - ws = new WsActionTester(new CurrentAction(userSessionRule, dbClient, defaultOrganizationProvider)); + assertThat(response) + .extracting(CurrentWsResponse::getIsLoggedIn, CurrentWsResponse::getLogin, CurrentWsResponse::getName, CurrentWsResponse::getEmail, CurrentWsResponse::getLocal, + CurrentWsResponse::getExternalIdentity, CurrentWsResponse::getExternalProvider, CurrentWsResponse::getScmAccountsList) + .containsExactly(true, "obiwan.kenobi", "Obiwan Kenobi", "obiwan.kenobi@starwars.com", true, "obiwan", "sonarqube", + newArrayList("obiwan:github", "obiwan:bitbucket")); } @Test - public void json_example() { - userSessionRule.logIn("obiwan.kenobi").setName("Obiwan Kenobi"); + public void return_minimal_user_info() { + db.users().insertUser(user -> user + .setLogin("obiwan.kenobi") + .setName("Obiwan Kenobi") + .setEmail(null) + .setLocal(true) + .setExternalIdentity("obiwan") + .setExternalIdentityProvider("sonarqube") + .setScmAccounts((String) null)); + userSessionRule.logIn("obiwan.kenobi"); + + CurrentWsResponse response = call(); + + assertThat(response) + .extracting(CurrentWsResponse::getIsLoggedIn, CurrentWsResponse::getLogin, CurrentWsResponse::getName, CurrentWsResponse::getLocal, + CurrentWsResponse::getExternalIdentity, CurrentWsResponse::getExternalProvider) + .containsExactly(true, "obiwan.kenobi", "Obiwan Kenobi", true, "obiwan", "sonarqube"); + assertThat(response.hasEmail()).isFalse(); + assertThat(response.getScmAccountsList()).isEmpty(); + assertThat(response.getGroupsList()).isEmpty(); + assertThat(response.getPermissions().getGlobalList()).isEmpty(); + } + + @Test + public void convert_empty_email_to_null() { + db.users().insertUser(user -> user + .setLogin("obiwan.kenobi") + .setEmail("")); + userSessionRule.logIn("obiwan.kenobi"); - // permissions on default organization + CurrentWsResponse response = call(); + + assertThat(response.hasEmail()).isFalse(); + } + + @Test + public void return_group_membership() { + UserDto user = db.users().insertUser(); + userSessionRule.logIn(user.getLogin()); + db.users().insertMember(db.users().insertGroup(newGroupDto().setName("Jedi")), user); + db.users().insertMember(db.users().insertGroup(newGroupDto().setName("Rebel")), user); + + CurrentWsResponse response = call(); + + assertThat(response.getGroupsList()).containsOnly("Jedi", "Rebel"); + } + + @Test + public void return_permissions() { + UserDto user = db.users().insertUser(); userSessionRule + .logIn(user.getLogin()) + // permissions on default organization .addPermission(SCAN, db.getDefaultOrganization()) - .addPermission(ADMINISTER_QUALITY_PROFILES, db.getDefaultOrganization()); + .addPermission(ADMINISTER_QUALITY_PROFILES, db.getDefaultOrganization()) + // permissions on other organizations are ignored + .addPermission(ADMINISTER, db.organizations().insert()); - // permissions on other organizations are ignored - userSessionRule.addPermission(ADMINISTER, db.organizations().insert()); - - UserDto obiwan = db.users().insertUser( - newUserDto("obiwan.kenobi", "Obiwan Kenobi", "obiwan.kenobi@starwars.com") - .setLocal(true) - .setExternalIdentity("obiwan.kenobi") - .setExternalIdentityProvider("sonarqube") - .setScmAccounts(newArrayList("obiwan:github", "obiwan:bitbucket"))); - GroupDto jedi = db.users().insertGroup(newGroupDto().setName("Jedi")); - GroupDto rebel = db.users().insertGroup(newGroupDto().setName("Rebel")); - db.users().insertGroup(newGroupDto().setName("Sith")); - dbClient.userGroupDao().insert(db.getSession(), new UserGroupDto() - .setUserId(obiwan.getId()) - .setGroupId(jedi.getId())); - dbClient.userGroupDao().insert(db.getSession(), new UserGroupDto() - .setUserId(obiwan.getId()) - .setGroupId(rebel.getId())); - db.commit(); + CurrentWsResponse response = call(); - String response = ws.newRequest().execute().getInput(); + assertThat(response.getPermissions().getGlobalList()).containsOnly("profileadmin", "scan"); + } - assertJson(response).isSimilarTo(getClass().getResource("current-example.json")); + @Test + public void fail_with_ISE_when_user_login_in_db_does_not_exist() { + db.users().insertUser(usert -> usert.setLogin("another")); + userSessionRule.logIn("obiwan.kenobi"); + + expectedException.expect(IllegalStateException.class); + expectedException.expectMessage("User login 'obiwan.kenobi' cannot be found"); + + call(); } @Test public void anonymous() { - userSessionRule.anonymous(); + userSessionRule + .anonymous() + .addPermission(SCAN, db.getDefaultOrganization()) + .addPermission(PROVISION_PROJECTS, db.getDefaultOrganization()); + + CurrentWsResponse response = call(); + + assertThat(response.getIsLoggedIn()).isFalse(); + assertThat(response.getPermissions().getGlobalList()).containsOnly("scan", "provisioning"); + assertThat(response) + .extracting(CurrentWsResponse::hasLogin, CurrentWsResponse::hasName, CurrentWsResponse::hasEmail, CurrentWsResponse::hasLocal, + CurrentWsResponse::hasExternalIdentity, CurrentWsResponse::hasExternalProvider) + .containsOnly(false); + assertThat(response.getScmAccountsList()).isEmpty(); + assertThat(response.getGroupsList()).isEmpty(); + } + + @Test + public void json_example() { + userSessionRule + .logIn("obiwan.kenobi") + .addPermission(SCAN, db.getDefaultOrganization()) + .addPermission(ADMINISTER_QUALITY_PROFILES, db.getDefaultOrganization()); + UserDto obiwan = db.users().insertUser(user -> user + .setLogin("obiwan.kenobi") + .setName("Obiwan Kenobi") + .setEmail("obiwan.kenobi@starwars.com") + .setLocal(true) + .setExternalIdentity("obiwan.kenobi") + .setExternalIdentityProvider("sonarqube") + .setScmAccounts(newArrayList("obiwan:github", "obiwan:bitbucket"))); + db.users().insertMember(db.users().insertGroup(newGroupDto().setName("Jedi")), obiwan); + db.users().insertMember(db.users().insertGroup(newGroupDto().setName("Rebel")), obiwan); String response = ws.newRequest().execute().getInput(); - assertJson(response).isSimilarTo(getClass().getResource("CurrentActionTest/anonymous.json")); + assertJson(response).isSimilarTo(getClass().getResource("current-example.json")); } + + @Test + public void test_definition() { + WebService.Action definition = ws.getDef(); + assertThat(definition.key()).isEqualTo("current"); + assertThat(definition.description()).isEqualTo("Get the details of the current authenticated user."); + assertThat(definition.since()).isEqualTo("5.2"); + assertThat(definition.isPost()).isFalse(); + assertThat(definition.isInternal()).isTrue(); + assertThat(definition.responseExampleAsString()).isNotEmpty(); + assertThat(definition.params()).isEmpty(); + assertThat(definition.changelog()).isEmpty(); + } + + private CurrentWsResponse call() { + return ws.newRequest().executeProtobuf(CurrentWsResponse.class); + } + } diff --git a/server/sonar-server/src/test/resources/org/sonar/server/user/ws/CurrentActionTest/anonymous.json b/server/sonar-server/src/test/resources/org/sonar/server/user/ws/CurrentActionTest/anonymous.json deleted file mode 100644 index 40a82484800..00000000000 --- a/server/sonar-server/src/test/resources/org/sonar/server/user/ws/CurrentActionTest/anonymous.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "isLoggedIn": false, - "scmAccounts": [], - "groups": [], - "permissions": { - "global": [] - } -} diff --git a/sonar-ws/src/main/protobuf/ws-users.proto b/sonar-ws/src/main/protobuf/ws-users.proto index 21aba165b08..0eddebed940 100644 --- a/sonar-ws/src/main/protobuf/ws-users.proto +++ b/sonar-ws/src/main/protobuf/ws-users.proto @@ -93,4 +93,22 @@ message GroupsWsResponse { } } +// WS api/users/current +message CurrentWsResponse { + optional bool isLoggedIn = 1; + optional string login = 2; + optional string name = 3; + optional string email = 4; + optional bool local = 5; + optional string externalIdentity = 6; + optional string externalProvider = 7; + repeated string scmAccounts = 8; + repeated string groups = 9; + optional Permissions permissions = 10; + + message Permissions { + repeated string global = 1; + } +} + -- 2.39.5