db.getDbClient().almAppInstallDao().insertOrUpdate(db.getSession(), ALM.GITHUB, "the-owner", false, "123456", null);
// could be improved, insertOrUpdate should return the DTO with its uuid
Optional<AlmAppInstallDto> install = db.getDbClient().almAppInstallDao().selectByOwnerId(db.getSession(), ALM.GITHUB, "the-owner");
- db.getDbClient().organizationAlmBindingDao().insert(db.getSession(), organization, install.get(), "xxx", "xxx");
+ db.getDbClient().organizationAlmBindingDao().insert(db.getSession(), organization, install.get(), "xxx", "xxx", true);
db.commit();
assertThat(underTest.selectByOrganization(db.getSession(), GITHUB, organization).get().getUuid()).isEqualTo(install.get().getUuid());
public OrganizationAlmBindingDto insertOrganizationAlmBinding(OrganizationDto organization, AlmAppInstallDto almAppInstall) {
UserDto user = db.users().insertUser();
- db.getDbClient().organizationAlmBindingDao().insert(db.getSession(), organization, almAppInstall, randomAlphabetic(10), user.getUuid());
+ db.getDbClient().organizationAlmBindingDao().insert(db.getSession(), organization, almAppInstall, randomAlphabetic(10), user.getUuid(), true);
db.commit();
return db.getDbClient().organizationAlmBindingDao().selectByOrganization(db.getSession(), organization).get();
}
db.commit();
return db.getDbClient().almAppInstallDao().selectByOwnerId(db.getSession(), dto.getAlm(), dto.getOwnerId()).get();
}
-
}
@ServerSide
public interface OrganizationAlmBinding {
- void bindOrganization(DbSession dbSession, OrganizationDto organization, String installationId);
+ void bindOrganization(DbSession dbSession, OrganizationDto organization, String installationId, boolean enableMembersSync);
}
private final UserIndexer userIndexer;
private final DefaultGroupFinder defaultGroupFinder;
private final AvatarResolver avatarResolver;
+ private final OrganizationsWsSupport wsSupport;
- public AddMemberAction(DbClient dbClient, UserSession userSession, UserIndexer userIndexer, DefaultGroupFinder defaultGroupFinder, AvatarResolver avatarResolver) {
+ public AddMemberAction(DbClient dbClient, UserSession userSession, UserIndexer userIndexer, DefaultGroupFinder defaultGroupFinder,
+ AvatarResolver avatarResolver, OrganizationsWsSupport wsSupport) {
this.dbClient = dbClient;
this.userSession = userSession;
this.userIndexer = userIndexer;
this.defaultGroupFinder = defaultGroupFinder;
this.avatarResolver = avatarResolver;
+ this.wsSupport = wsSupport;
}
@Override
OrganizationDto organization = checkFoundWithOptional(dbClient.organizationDao().selectByKey(dbSession, organizationKey), "Organization '%s' is not found",
organizationKey);
UserDto user = checkFound(dbClient.userDao().selectByLogin(dbSession, login), "User '%s' is not found", login);
+ wsSupport.checkMemberSyncIsDisabled(dbSession, organization);
+
addMember(dbSession, organization, user);
int groups = dbClient.groupMembershipDao().countGroups(dbSession, GroupMembershipQuery.builder()
if (installationId == null) {
return;
}
- organizationAlmBinding.bindOrganization(dbSession, organization, installationId);
+ organizationAlmBinding.bindOrganization(dbSession, organization, installationId, true);
}
@CheckForNull
CreateAction.class,
DeleteAction.class,
RemoveMemberAction.class,
- UpdateAction.class);
+ UpdateAction.class,
+ SetMembersSyncAction.class);
}
}
import javax.annotation.CheckForNull;
import org.sonar.api.server.ws.Request;
import org.sonar.api.server.ws.WebService;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbSession;
import org.sonar.db.organization.OrganizationDto;
import org.sonar.server.organization.OrganizationValidation;
import org.sonarqube.ws.Organizations.Organization;
+import static com.google.common.base.Preconditions.checkArgument;
import static java.util.Optional.ofNullable;
import static org.sonar.server.organization.OrganizationValidation.DESCRIPTION_MAX_LENGTH;
import static org.sonar.server.organization.OrganizationValidation.NAME_MAX_LENGTH;
static final String PARAM_LOGIN = "login";
private final OrganizationValidation organizationValidation;
+ private final DbClient dbClient;
- public OrganizationsWsSupport(OrganizationValidation organizationValidation) {
+ public OrganizationsWsSupport(OrganizationValidation organizationValidation, DbClient dbClient) {
this.organizationValidation = organizationValidation;
+ this.dbClient = dbClient;
}
String getAndCheckMandatoryName(Request request) {
ofNullable(dto.getAvatarUrl()).ifPresent(builder::setAvatar);
return builder;
}
+
+
+ void checkMemberSyncIsDisabled(DbSession dbSession, OrganizationDto organization) {
+ dbClient.organizationAlmBindingDao().selectByOrganization(dbSession, organization).ifPresent(orgAlmBindingDto ->
+ checkArgument(!orgAlmBindingDto.isMembersSyncEnable(), "You can't add or remove members when synchronization of organization with alm is enabled."));
+ }
}
private final DbClient dbClient;
private final UserSession userSession;
private final UserIndexer userIndexer;
+ private final OrganizationsWsSupport wsSupport;
- public RemoveMemberAction(DbClient dbClient, UserSession userSession, UserIndexer userIndexer) {
+ public RemoveMemberAction(DbClient dbClient, UserSession userSession, UserIndexer userIndexer, OrganizationsWsSupport wsSupport) {
this.dbClient = dbClient;
this.userSession = userSession;
this.userIndexer = userIndexer;
+ this.wsSupport = wsSupport;
}
@Override
"Organization '%s' is not found", organizationKey);
UserDto user = checkFound(dbClient.userDao().selectActiveUserByLogin(dbSession, login), "User '%s' is not found", login);
userSession.checkPermission(ADMINISTER, organization);
-
+ wsSupport.checkMemberSyncIsDisabled(dbSession, organization);
dbClient.organizationMemberDao().select(dbSession, organization.getUuid(), user.getId())
.ifPresent(om -> removeMember(dbSession, organization, user));
}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.server.organization.ws;
+
+import java.util.Optional;
+import org.sonar.api.server.ws.Request;
+import org.sonar.api.server.ws.Response;
+import org.sonar.api.server.ws.WebService;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbSession;
+import org.sonar.db.alm.OrganizationAlmBindingDto;
+import org.sonar.db.organization.OrganizationDto;
+import org.sonar.server.user.UserSession;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static org.sonar.db.permission.OrganizationPermission.ADMINISTER;
+import static org.sonar.server.organization.ws.OrganizationsWsSupport.PARAM_ORGANIZATION;
+import static org.sonar.server.ws.WsUtils.checkFoundWithOptional;
+
+public class SetMembersSyncAction implements OrganizationsWsAction {
+
+ private static final String ENABLED = "enabled";
+ private DbClient dbClient;
+ private UserSession userSession;
+
+ public SetMembersSyncAction(DbClient dbClient, UserSession userSession) {
+ this.dbClient = dbClient;
+ this.userSession = userSession;
+ }
+
+ @Override
+ public void define(WebService.NewController context) {
+ WebService.NewAction action = context.createAction("set_members_sync")
+ .setDescription("Enable or disable organization members synchronization.<br/>" +
+ "Requires 'Administer System' permission on the specified organization.")
+ .setSince("7.7")
+ .setPost(true)
+ .setInternal(true)
+ .setHandler(this);
+
+ action.createParam(PARAM_ORGANIZATION)
+ .setDescription("Organization key")
+ .setInternal(true)
+ .setRequired(true);
+
+ action.createParam(ENABLED)
+ .setDescription("True to enable members sync, false otherwise.")
+ .setInternal(true)
+ .setRequired(true)
+ .setBooleanPossibleValues();
+ }
+
+ @Override
+ public void handle(Request request, Response response) throws Exception {
+ String organizationKey = request.mandatoryParam(PARAM_ORGANIZATION);
+
+ try (DbSession dbSession = dbClient.openSession(false)) {
+
+ OrganizationDto organization = checkFoundWithOptional(dbClient.organizationDao().selectByKey(dbSession, organizationKey),
+ "Organization '%s' does not exist", organizationKey);
+
+ userSession.checkPermission(ADMINISTER, organization);
+
+ Optional<OrganizationAlmBindingDto> orgAlmBindingDto = dbClient.organizationAlmBindingDao().selectByOrganization(dbSession, organization);
+ checkArgument(orgAlmBindingDto.isPresent(), "Organization '%s' is not bound to an ALM", organization.getKey());
+
+ dbClient.organizationAlmBindingDao().updateMembersSync(dbSession, orgAlmBindingDto.get(), request.mandatoryParamAsBoolean(ENABLED));
+
+ dbSession.commit();
+ }
+
+ response.noContent();
+ }
+
+}
import org.sonar.server.exceptions.ForbiddenException;
import org.sonar.server.exceptions.NotFoundException;
import org.sonar.server.issue.ws.AvatarResolverImpl;
+import org.sonar.server.organization.OrganizationValidationImpl;
import org.sonar.server.tester.UserSessionRule;
import org.sonar.server.user.index.UserDoc;
import org.sonar.server.user.index.UserIndex;
public DbTester db = DbTester.create();
private DbClient dbClient = db.getDbClient();
private DbSession dbSession = db.getSession();
-
+ private OrganizationsWsSupport wsSupport = new OrganizationsWsSupport(new OrganizationValidationImpl(), dbClient);
private WsActionTester ws = new WsActionTester(
- new AddMemberAction(dbClient, userSession, new UserIndexer(dbClient, es.client()), new DefaultGroupFinder(dbClient), new AvatarResolverImpl()));
+ new AddMemberAction(dbClient, userSession, new UserIndexer(dbClient, es.client()), new DefaultGroupFinder(dbClient), new AvatarResolverImpl(), wsSupport));
@Test
public void add_member_in_db_and_user_index() {
call(organization.getKey(), user.getLogin());
}
+ @Test
+ public void fail_if_org_is_bind_to_alm_and_members_sync_is_enabled() {
+ OrganizationDto organization = db.organizations().insert();
+ db.alm().insertOrganizationAlmBinding(organization, db.alm().insertAlmAppInstall());
+ UserDto user = db.users().insertUser();
+
+ expectedException.expect(IllegalArgumentException.class);
+
+ call(organization.getKey(), user.getLogin());
+ }
+
@Test
public void json_example() {
OrganizationDto organization = db.organizations().insert();
private OrganizationAlmBinding organizationAlmBinding = mock(OrganizationAlmBinding.class);
private WsActionTester wsTester = new WsActionTester(
- new CreateAction(settings.asConfig(), userSession, dbClient, new OrganizationsWsSupport(organizationValidation),
+ new CreateAction(settings.asConfig(), userSession, dbClient, new OrganizationsWsSupport(organizationValidation, dbClient),
organizationValidation,
organizationUpdater, organizationFlags, organizationAlmBinding));
.setParam("installationId", "ABCD")
.execute();
- verify(organizationAlmBinding).bindOrganization(any(DbSession.class), any(OrganizationDto.class), eq("ABCD"));
+ verify(organizationAlmBinding).bindOrganization(any(DbSession.class), any(OrganizationDto.class), eq("ABCD"), eq(true));
}
@Test
public void does_not_bind_organization_when_organizationAlmBinding_is_null() {
wsTester = new WsActionTester(
- new CreateAction(settings.asConfig(), userSession, dbClient, new OrganizationsWsSupport(organizationValidation),
+ new CreateAction(settings.asConfig(), userSession, dbClient, new OrganizationsWsSupport(organizationValidation, dbClient),
organizationValidation, organizationUpdater, organizationFlags, null));
createUserAndLogInAsSystemAdministrator();
db.qualityGates().insertBuiltInQualityGate();
underTest.configure(container);
assertThat(container.getPicoContainer().getComponentAdapters())
- .hasSize(COMPONENTS_IN_EMPTY_COMPONENT_CONTAINER + 11);
+ .hasSize(COMPONENTS_IN_EMPTY_COMPONENT_CONTAINER + 12);
}
}
import org.sonar.server.exceptions.BadRequestException;
import org.sonar.server.exceptions.ForbiddenException;
import org.sonar.server.exceptions.NotFoundException;
+import org.sonar.server.organization.OrganizationValidationImpl;
import org.sonar.server.tester.UserSessionRule;
import org.sonar.server.user.index.UserIndex;
import org.sonar.server.user.index.UserIndexDefinition;
private UserIndex userIndex = new UserIndex(es.client(), System2.INSTANCE);
private UserIndexer userIndexer = new UserIndexer(dbClient, es.client());
+ private OrganizationsWsSupport wsSupport = new OrganizationsWsSupport(new OrganizationValidationImpl(), dbClient);
- private WsActionTester ws = new WsActionTester(new RemoveMemberAction(dbClient, userSession, userIndexer));
+ private WsActionTester ws = new WsActionTester(new RemoveMemberAction(dbClient, userSession, userIndexer, wsSupport));
private OrganizationDto organization;
private ComponentDto project;
call(organization.getKey(), user.getLogin());
}
+ @Test
+ public void fail_if_org_is_bind_to_alm_and_members_sync_is_enabled() {
+ OrganizationDto organization = db.organizations().insert();
+ db.alm().insertOrganizationAlmBinding(organization, db.alm().insertAlmAppInstall());
+ UserDto user = db.users().insertUser();
+
+ expectedException.expect(IllegalArgumentException.class);
+
+ call(organization.getKey(), user.getLogin());
+ }
+
@Test
public void remove_org_admin_is_allowed_when_another_org_admin_exists() {
OrganizationDto anotherOrganization = db.organizations().insert();
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.server.organization.ws;
+
+import java.util.Optional;
+import javax.annotation.Nullable;
+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.db.DbClient;
+import org.sonar.db.DbSession;
+import org.sonar.db.DbTester;
+import org.sonar.db.alm.OrganizationAlmBindingDto;
+import org.sonar.db.organization.OrganizationDto;
+import org.sonar.db.user.UserDto;
+import org.sonar.server.exceptions.ForbiddenException;
+import org.sonar.server.exceptions.NotFoundException;
+import org.sonar.server.tester.UserSessionRule;
+import org.sonar.server.ws.TestRequest;
+import org.sonar.server.ws.TestResponse;
+import org.sonar.server.ws.WsActionTester;
+
+import static java.net.HttpURLConnection.HTTP_NO_CONTENT;
+import static java.util.Optional.ofNullable;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.AssertionsForClassTypes.tuple;
+import static org.sonar.server.organization.ws.OrganizationsWsSupport.PARAM_ORGANIZATION;
+
+public class SetMembersSyncActionTest {
+
+ @Rule
+ public ExpectedException expectedException = ExpectedException.none();
+ @Rule
+ public UserSessionRule userSession = UserSessionRule.standalone().logIn().setRoot();
+ @Rule
+ public DbTester db = DbTester.create();
+
+ private DbClient dbClient = db.getDbClient();
+
+ private DbSession dbSession = db.getSession();
+
+ private WsActionTester ws = new WsActionTester(new SetMembersSyncAction(dbClient, userSession));
+
+ @Test
+ public void definition() {
+ OrganizationDto organization = db.organizations().insert();
+ db.alm().insertOrganizationAlmBinding(organization, db.alm().insertAlmAppInstall());
+
+ WebService.Action definition = ws.getDef();
+
+ assertThat(definition.key()).isEqualTo("set_members_sync");
+ assertThat(definition.since()).isEqualTo("7.7");
+ assertThat(definition.isPost()).isTrue();
+ assertThat(definition.isInternal()).isTrue();
+ assertThat(definition.params())
+ .extracting(WebService.Param::key, WebService.Param::isRequired)
+ .containsExactlyInAnyOrder(tuple("organization", true), tuple("enabled", true));
+ }
+
+ @Test
+ public void update_members_sync() {
+ OrganizationDto organization = db.organizations().insert();
+ db.alm().insertOrganizationAlmBinding(organization, db.alm().insertAlmAppInstall());
+
+ sendRequest(organization.getKey(), true);
+
+ Optional<OrganizationAlmBindingDto> dto = dbClient.organizationAlmBindingDao().selectByOrganization(dbSession, organization);
+ assertThat(dto).isPresent();
+ assertThat(dto.get().isMembersSyncEnable()).isTrue();
+ }
+
+ @Test
+ public void returns_no_content() {
+ OrganizationDto organization = db.organizations().insert();
+ db.alm().insertOrganizationAlmBinding(organization, db.alm().insertAlmAppInstall());
+
+ TestResponse result = sendRequest(organization.getKey(), true);
+
+ assertThat(result.getStatus()).isEqualTo(HTTP_NO_CONTENT);
+ assertThat(result.getInput()).isEmpty();
+ }
+
+ @Test
+ public void fail_if_org_is_not_admin_of_the_org() {
+ OrganizationDto organization = db.organizations().insert();
+ UserDto user = db.users().insertUser();
+ userSession.logIn(user);
+
+ expectedException.expect(ForbiddenException.class);
+ expectedException.expectMessage("Insufficient privileges");
+
+ sendRequest(organization.getKey(), true);
+ }
+
+ @Test
+ public void fail_if_org_is_not_bound_to_an_alm() {
+ OrganizationDto organization = db.organizations().insert();
+
+ expectedException.expect(IllegalArgumentException.class);
+ expectedException.expectMessage(String.format("Organization '%s' is not bound to an ALM", organization.getKey()));
+
+ sendRequest(organization.getKey(), true);
+ }
+
+ @Test
+ public void fail_if_org_does_not_exist() {
+ OrganizationDto organization = db.organizations().insert();
+ db.alm().insertOrganizationAlmBinding(organization, db.alm().insertAlmAppInstall());
+
+ expectedException.expect(NotFoundException.class);
+ expectedException.expectMessage("Organization '1234' does not exist");
+
+ sendRequest("1234", true);
+ }
+
+ private TestResponse sendRequest(@Nullable String organizationKey, @Nullable Boolean enabled) {
+ TestRequest request = ws.newRequest();
+ ofNullable(organizationKey).ifPresent(o -> request.setParam(PARAM_ORGANIZATION, o));
+ ofNullable(enabled).ifPresent(e -> request.setParam("enabled", String.valueOf(e)));
+
+ return request.execute();
+ }
+}
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.organization.OrganizationDto;
import org.sonar.server.exceptions.ForbiddenException;
public UserSessionRule userSession = UserSessionRule.standalone();
@Rule
public ExpectedException expectedException = ExpectedException.none();
+ @Rule
+ public DbTester db = DbTester.create();
+ private DbClient dbClient = db.getDbClient();
private TestOrganizationFlags organizationFlags = TestOrganizationFlags.standalone().setEnabled(true);
- private UpdateAction underTest = new UpdateAction(userSession, new OrganizationsWsSupport(new OrganizationValidationImpl()), dbTester.getDbClient(), organizationFlags);
+ private UpdateAction underTest = new UpdateAction(userSession, new OrganizationsWsSupport(new OrganizationValidationImpl(), dbClient), dbTester.getDbClient(), organizationFlags);
private WsActionTester wsTester = new WsActionTester(underTest);
@Test