Skip to content

Commit

Permalink
SONAR-10945 Prevent access qgates and rules to none members of paid o…
Browse files Browse the repository at this point in the history
…rganization

* Add membership check for paid organization in api/qualitygates ws
* Add membership check for paid organization in api/rules ws
* Move membership check in UserSession
* Use UserSession#checkMemebership in QGates and Rules ws
  • Loading branch information
julienlancelot authored and SonarTech committed Jul 11, 2018
1 parent f675d58 commit 884cd78
Show file tree
Hide file tree
Showing 32 changed files with 601 additions and 186 deletions.
15 changes: 13 additions & 2 deletions server/sonar-ce/src/main/java/org/sonar/ce/user/CeUserSession.java
Expand Up @@ -23,8 +23,8 @@
import java.util.List; import java.util.List;
import org.sonar.db.component.ComponentDto; import org.sonar.db.component.ComponentDto;
import org.sonar.db.organization.OrganizationDto; import org.sonar.db.organization.OrganizationDto;
import org.sonar.db.user.GroupDto;
import org.sonar.db.permission.OrganizationPermission; import org.sonar.db.permission.OrganizationPermission;
import org.sonar.db.user.GroupDto;
import org.sonar.server.user.UserSession; import org.sonar.server.user.UserSession;


/** /**
Expand All @@ -42,7 +42,8 @@ public String getLogin() {
throw notImplemented(); throw notImplemented();
} }


@Override public String getUuid() { @Override
public String getUuid() {
throw notImplemented(); throw notImplemented();
} }


Expand Down Expand Up @@ -136,6 +137,16 @@ public List<ComponentDto> keepAuthorizedComponents(String permission, Collection
throw notImplemented(); throw notImplemented();
} }


@Override
public boolean hasMembership(OrganizationDto organization) {
throw notImplemented();
}

@Override
public UserSession checkMembership(OrganizationDto organization) {
throw notImplemented();
}

private static RuntimeException notImplemented() { private static RuntimeException notImplemented() {
throw new UnsupportedOperationException(UOE_MESSAGE); throw new UnsupportedOperationException(UOE_MESSAGE);
} }
Expand Down
Expand Up @@ -68,6 +68,13 @@ public final boolean hasComponentUuidPermission(String permission, String compon


protected abstract boolean hasProjectUuidPermission(String permission, String projectUuid); protected abstract boolean hasProjectUuidPermission(String permission, String projectUuid);


@Override
public final boolean hasMembership(OrganizationDto organization) {
return isRoot() || hasMembershipImpl(organization);
}

protected abstract boolean hasMembershipImpl(OrganizationDto organization);

@Override @Override
public final List<ComponentDto> keepAuthorizedComponents(String permission, Collection<ComponentDto> components) { public final List<ComponentDto> keepAuthorizedComponents(String permission, Collection<ComponentDto> components) {
if (isRoot()) { if (isRoot()) {
Expand Down
Expand Up @@ -173,4 +173,20 @@ public interface UserSession {
*/ */
UserSession checkIsSystemAdministrator(); UserSession checkIsSystemAdministrator();


/**
* Returns {@code true} if the user is member of the organization, otherwise {@code false}.
*
* If the organization does not exist, then returns {@code false}.
*
* Always returns {@code true} if {@link #isRoot()} is {@code true}, even if
* organization does not exist.
*/
boolean hasMembership(OrganizationDto organization);

/**
* Ensures that {@link #hasMembership(OrganizationDto)} is {@code true},
* otherwise throws a {@link org.sonar.server.exceptions.ForbiddenException}.
*/
UserSession checkMembership(OrganizationDto organization);

} }
Expand Up @@ -145,6 +145,11 @@ protected boolean hasPermissionImpl(OrganizationPermission permission, String or
throw new UnsupportedOperationException("hasPermissionImpl not implemented"); throw new UnsupportedOperationException("hasPermissionImpl not implemented");
} }


@Override
protected boolean hasMembershipImpl(OrganizationDto organization) {
throw new UnsupportedOperationException("hasMembershipImpl not implemented");
}

@Override @Override
protected Optional<String> componentUuidToProjectUuid(String componentUuid) { protected Optional<String> componentUuidToProjectUuid(String componentUuid) {
return Optional.ofNullable(projectUuidByComponentUuid.get(componentUuid)); return Optional.ofNullable(projectUuidByComponentUuid.get(componentUuid));
Expand Down Expand Up @@ -233,4 +238,9 @@ public boolean isSystemAdministrator() {
public UserSession checkIsSystemAdministrator() { public UserSession checkIsSystemAdministrator() {
throw new UnsupportedOperationException("checkIsSystemAdministrator not implemented"); throw new UnsupportedOperationException("checkIsSystemAdministrator not implemented");
} }

@Override
public UserSession checkMembership(OrganizationDto organization) {
throw new UnsupportedOperationException("checkMembership not implemented");
}
} }
Expand Up @@ -24,6 +24,7 @@
import java.util.Optional; import java.util.Optional;
import javax.annotation.CheckForNull; import javax.annotation.CheckForNull;
import javax.annotation.concurrent.Immutable; import javax.annotation.concurrent.Immutable;
import org.sonar.db.organization.OrganizationDto;
import org.sonar.db.permission.OrganizationPermission; import org.sonar.db.permission.OrganizationPermission;
import org.sonar.db.user.GroupDto; import org.sonar.db.user.GroupDto;
import org.sonar.server.user.AbstractUserSession; import org.sonar.server.user.AbstractUserSession;
Expand Down Expand Up @@ -89,4 +90,9 @@ public boolean isRoot() {
public boolean isSystemAdministrator() { public boolean isSystemAdministrator() {
return false; return false;
} }

@Override
public boolean hasMembershipImpl(OrganizationDto organization) {
return false;
}
} }
Expand Up @@ -39,6 +39,7 @@
import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkArgument;
import static java.lang.String.format; import static java.lang.String.format;
import static org.sonar.api.web.UserRole.ADMIN; import static org.sonar.api.web.UserRole.ADMIN;
import static org.sonar.db.organization.OrganizationDto.Subscription.PAID;
import static org.sonar.db.permission.OrganizationPermission.ADMINISTER_QUALITY_GATES; import static org.sonar.db.permission.OrganizationPermission.ADMINISTER_QUALITY_GATES;
import static org.sonar.server.qualitygate.ws.QualityGatesWsParameters.PARAM_ORGANIZATION; import static org.sonar.server.qualitygate.ws.QualityGatesWsParameters.PARAM_ORGANIZATION;
import static org.sonar.server.user.AbstractUserSession.insufficientPrivilegesException; import static org.sonar.server.user.AbstractUserSession.insufficientPrivilegesException;
Expand Down Expand Up @@ -100,7 +101,9 @@ OrganizationDto getOrganization(DbSession dbSession, Request request) {
String organizationKey = Optional.ofNullable(request.param(PARAM_ORGANIZATION)) String organizationKey = Optional.ofNullable(request.param(PARAM_ORGANIZATION))
.orElseGet(() -> defaultOrganizationProvider.get().getKey()); .orElseGet(() -> defaultOrganizationProvider.get().getKey());
Optional<OrganizationDto> organizationDto = dbClient.organizationDao().selectByKey(dbSession, organizationKey); Optional<OrganizationDto> organizationDto = dbClient.organizationDao().selectByKey(dbSession, organizationKey);
return checkFoundWithOptional(organizationDto, "No organization with key '%s'", organizationKey); OrganizationDto organization = checkFoundWithOptional(organizationDto, "No organization with key '%s'", organizationKey);
checkMembershipOnPaidOrganization(organization);
return organization;
} }


void checkCanEdit(QGateWithOrgDto qualityGate) { void checkCanEdit(QGateWithOrgDto qualityGate) {
Expand All @@ -127,4 +130,11 @@ private static void checkNotBuiltIn(QualityGateDto qualityGate) {
checkArgument(!qualityGate.isBuiltIn(), "Operation forbidden for built-in Quality Gate '%s'", qualityGate.getName()); checkArgument(!qualityGate.isBuiltIn(), "Operation forbidden for built-in Quality Gate '%s'", qualityGate.getName());
} }


private void checkMembershipOnPaidOrganization(OrganizationDto organization) {
if (!organization.getSubscription().equals(PAID)) {
return;
}
userSession.checkMembership(organization);
}

} }
Expand Up @@ -33,7 +33,6 @@
import org.sonar.db.rule.RuleDefinitionDto; import org.sonar.db.rule.RuleDefinitionDto;
import org.sonar.db.user.GroupDto; import org.sonar.db.user.GroupDto;
import org.sonar.db.user.UserDto; import org.sonar.db.user.UserDto;
import org.sonar.server.exceptions.ForbiddenException;
import org.sonar.server.organization.DefaultOrganizationProvider; import org.sonar.server.organization.DefaultOrganizationProvider;
import org.sonar.server.user.UserSession; import org.sonar.server.user.UserSession;


Expand Down Expand Up @@ -74,7 +73,7 @@ public OrganizationDto getOrganization(DbSession dbSession, QProfileDto profile)
String organizationUuid = profile.getOrganizationUuid(); String organizationUuid = profile.getOrganizationUuid();
OrganizationDto organization = dbClient.organizationDao().selectByUuid(dbSession, organizationUuid) OrganizationDto organization = dbClient.organizationDao().selectByUuid(dbSession, organizationUuid)
.orElseThrow(() -> new IllegalStateException("Cannot load organization with uuid=" + organizationUuid)); .orElseThrow(() -> new IllegalStateException("Cannot load organization with uuid=" + organizationUuid));
checkMembershipOnPaidOrganization(dbSession, organization); checkMembershipOnPaidOrganization(organization);
return organization; return organization;
} }


Expand All @@ -84,7 +83,7 @@ public OrganizationDto getOrganizationByKey(DbSession dbSession, @Nullable Strin
OrganizationDto organization = checkFoundWithOptional( OrganizationDto organization = checkFoundWithOptional(
dbClient.organizationDao().selectByKey(dbSession, organizationOrDefaultKey), dbClient.organizationDao().selectByKey(dbSession, organizationOrDefaultKey),
"No organization with key '%s'", organizationOrDefaultKey); "No organization with key '%s'", organizationOrDefaultKey);
checkMembershipOnPaidOrganization(dbSession, organization); checkMembershipOnPaidOrganization(organization);
return organization; return organization;
} }


Expand Down Expand Up @@ -174,15 +173,11 @@ private boolean isMember(DbSession dbSession, OrganizationDto organization, int
return dbClient.organizationMemberDao().select(dbSession, organization.getUuid(), userId).isPresent(); return dbClient.organizationMemberDao().select(dbSession, organization.getUuid(), userId).isPresent();
} }


private void checkMembershipOnPaidOrganization(DbSession dbSession, OrganizationDto organization) { private void checkMembershipOnPaidOrganization(OrganizationDto organization) {
if (!organization.getSubscription().equals(PAID)) { if (!organization.getSubscription().equals(PAID)) {
return; return;
} }
Integer userId = userSession.getUserId(); userSession.checkMembership(organization);
if (userId != null && isMember(dbSession, organization, userId)) {
return;
}
throw new ForbiddenException(String.format("You're not member of organization '%s'", organization.getKey()));
} }


} }
Expand Up @@ -34,16 +34,13 @@


import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkArgument;
import static java.lang.String.format; import static java.lang.String.format;
import static org.sonar.server.util.EnumUtils.toEnums;
import static org.sonar.server.ws.WsUtils.checkFound;
import static org.sonar.server.ws.WsUtils.checkFoundWithOptional;
import static org.sonar.server.rule.ws.RulesWsParameters.PARAM_ACTIVATION; import static org.sonar.server.rule.ws.RulesWsParameters.PARAM_ACTIVATION;
import static org.sonar.server.rule.ws.RulesWsParameters.PARAM_ACTIVE_SEVERITIES; import static org.sonar.server.rule.ws.RulesWsParameters.PARAM_ACTIVE_SEVERITIES;
import static org.sonar.server.rule.ws.RulesWsParameters.PARAM_AVAILABLE_SINCE; import static org.sonar.server.rule.ws.RulesWsParameters.PARAM_AVAILABLE_SINCE;
import static org.sonar.server.rule.ws.RulesWsParameters.PARAM_COMPARE_TO_PROFILE; import static org.sonar.server.rule.ws.RulesWsParameters.PARAM_COMPARE_TO_PROFILE;
import static org.sonar.server.rule.ws.RulesWsParameters.PARAM_INCLUDE_EXTERNAL;
import static org.sonar.server.rule.ws.RulesWsParameters.PARAM_INHERITANCE; import static org.sonar.server.rule.ws.RulesWsParameters.PARAM_INHERITANCE;
import static org.sonar.server.rule.ws.RulesWsParameters.PARAM_IS_TEMPLATE; import static org.sonar.server.rule.ws.RulesWsParameters.PARAM_IS_TEMPLATE;
import static org.sonar.server.rule.ws.RulesWsParameters.PARAM_INCLUDE_EXTERNAL;
import static org.sonar.server.rule.ws.RulesWsParameters.PARAM_LANGUAGES; import static org.sonar.server.rule.ws.RulesWsParameters.PARAM_LANGUAGES;
import static org.sonar.server.rule.ws.RulesWsParameters.PARAM_ORGANIZATION; import static org.sonar.server.rule.ws.RulesWsParameters.PARAM_ORGANIZATION;
import static org.sonar.server.rule.ws.RulesWsParameters.PARAM_QPROFILE; import static org.sonar.server.rule.ws.RulesWsParameters.PARAM_QPROFILE;
Expand All @@ -54,6 +51,9 @@
import static org.sonar.server.rule.ws.RulesWsParameters.PARAM_TAGS; import static org.sonar.server.rule.ws.RulesWsParameters.PARAM_TAGS;
import static org.sonar.server.rule.ws.RulesWsParameters.PARAM_TEMPLATE_KEY; import static org.sonar.server.rule.ws.RulesWsParameters.PARAM_TEMPLATE_KEY;
import static org.sonar.server.rule.ws.RulesWsParameters.PARAM_TYPES; import static org.sonar.server.rule.ws.RulesWsParameters.PARAM_TYPES;
import static org.sonar.server.util.EnumUtils.toEnums;
import static org.sonar.server.ws.WsUtils.checkFound;
import static org.sonar.server.ws.WsUtils.checkFoundWithOptional;


@ServerSide @ServerSide
public class RuleQueryFactory { public class RuleQueryFactory {
Expand Down Expand Up @@ -139,6 +139,7 @@ private void setOrganization(DbSession dbSession, RuleQuery query, Request reque
checkArgument(organization.getUuid().equals(inputOrganization.getUuid()), checkArgument(organization.getUuid().equals(inputOrganization.getUuid()),
format("The specified quality profile '%s' is not part of the specified organization '%s'", profile.getKee(), organizationKey)); format("The specified quality profile '%s' is not part of the specified organization '%s'", profile.getKee(), organizationKey));
} }
wsSupport.checkMembershipOnPaidOrganization(organization);
query.setOrganization(organization); query.setOrganization(organization);
} }


Expand Down
Expand Up @@ -33,11 +33,12 @@
import org.sonar.db.user.UserDto; import org.sonar.db.user.UserDto;
import org.sonar.server.organization.DefaultOrganizationProvider; import org.sonar.server.organization.DefaultOrganizationProvider;
import org.sonar.server.user.UserSession; import org.sonar.server.user.UserSession;
import org.sonar.server.ws.WsUtils;


import static org.sonar.core.util.stream.MoreCollectors.toSet; import static org.sonar.core.util.stream.MoreCollectors.toSet;
import static org.sonar.core.util.stream.MoreCollectors.uniqueIndex; import static org.sonar.core.util.stream.MoreCollectors.uniqueIndex;
import static org.sonar.db.organization.OrganizationDto.Subscription.PAID;
import static org.sonar.db.permission.OrganizationPermission.ADMINISTER_QUALITY_PROFILES; import static org.sonar.db.permission.OrganizationPermission.ADMINISTER_QUALITY_PROFILES;
import static org.sonar.server.ws.WsUtils.checkFoundWithOptional;


@ServerSide @ServerSide
public class RuleWsSupport { public class RuleWsSupport {
Expand All @@ -61,14 +62,23 @@ public void checkQProfileAdminPermissionOnDefaultOrganization() {
public OrganizationDto getOrganizationByKey(DbSession dbSession, @Nullable String organizationKey) { public OrganizationDto getOrganizationByKey(DbSession dbSession, @Nullable String organizationKey) {
String organizationOrDefaultKey = Optional.ofNullable(organizationKey) String organizationOrDefaultKey = Optional.ofNullable(organizationKey)
.orElseGet(defaultOrganizationProvider.get()::getKey); .orElseGet(defaultOrganizationProvider.get()::getKey);
return WsUtils.checkFoundWithOptional( OrganizationDto organization = checkFoundWithOptional(
dbClient.organizationDao().selectByKey(dbSession, organizationOrDefaultKey), dbClient.organizationDao().selectByKey(dbSession, organizationOrDefaultKey),
"No organization with key '%s'", organizationOrDefaultKey); "No organization with key '%s'", organizationOrDefaultKey);
checkMembershipOnPaidOrganization(organization);
return organization;
} }


Map<String, UserDto> getUsersByUuid(DbSession dbSession, List<RuleDto> rules) { Map<String, UserDto> getUsersByUuid(DbSession dbSession, List<RuleDto> rules) {
Set<String> userUuids = rules.stream().map(RuleDto::getNoteUserUuid).filter(Objects::nonNull).collect(toSet()); Set<String> userUuids = rules.stream().map(RuleDto::getNoteUserUuid).filter(Objects::nonNull).collect(toSet());
return dbClient.userDao().selectByUuids(dbSession, userUuids).stream().collect(uniqueIndex(UserDto::getUuid)); return dbClient.userDao().selectByUuids(dbSession, userUuids).stream().collect(uniqueIndex(UserDto::getUuid));
} }


void checkMembershipOnPaidOrganization(OrganizationDto organization) {
if (!organization.getSubscription().equals(PAID)) {
return;
}
userSession.checkMembership(organization);
}

} }
Expand Up @@ -25,6 +25,8 @@
import org.sonar.server.exceptions.ForbiddenException; import org.sonar.server.exceptions.ForbiddenException;
import org.sonar.server.exceptions.UnauthorizedException; import org.sonar.server.exceptions.UnauthorizedException;


import static java.lang.String.format;

public abstract class AbstractUserSession extends BaseUserSession { public abstract class AbstractUserSession extends BaseUserSession {
private static final String INSUFFICIENT_PRIVILEGES_MESSAGE = "Insufficient privileges"; private static final String INSUFFICIENT_PRIVILEGES_MESSAGE = "Insufficient privileges";
private static final ForbiddenException INSUFFICIENT_PRIVILEGES_EXCEPTION = new ForbiddenException(INSUFFICIENT_PRIVILEGES_MESSAGE); private static final ForbiddenException INSUFFICIENT_PRIVILEGES_EXCEPTION = new ForbiddenException(INSUFFICIENT_PRIVILEGES_MESSAGE);
Expand Down Expand Up @@ -86,4 +88,13 @@ public final UserSession checkIsSystemAdministrator() {
} }
return this; return this;
} }

@Override
public UserSession checkMembership(OrganizationDto organization) {
if (!hasMembership(organization)) {
throw new ForbiddenException(format("You're not member of organization '%s'", organization.getKey()));
}
return this;
}

} }
Expand Up @@ -22,6 +22,7 @@
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.Optional; import java.util.Optional;
import org.sonar.db.organization.OrganizationDto;
import org.sonar.db.permission.OrganizationPermission; import org.sonar.db.permission.OrganizationPermission;
import org.sonar.db.user.GroupDto; import org.sonar.db.user.GroupDto;


Expand Down Expand Up @@ -116,11 +117,16 @@ protected Optional<String> componentUuidToProjectUuid(String componentUuid) {
protected boolean hasProjectUuidPermission(String permission, String projectUuid) { protected boolean hasProjectUuidPermission(String permission, String projectUuid) {
return true; return true;
} }

@Override @Override
public boolean isSystemAdministrator() { public boolean isSystemAdministrator() {
return true; return true;
} }

@Override
public boolean hasMembershipImpl(OrganizationDto organization) {
return true;
}
} }


private void start() { private void start() {
Expand Down
Expand Up @@ -25,6 +25,7 @@
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Optional; import java.util.Optional;
Expand All @@ -36,13 +37,15 @@
import org.sonar.db.DbClient; import org.sonar.db.DbClient;
import org.sonar.db.DbSession; import org.sonar.db.DbSession;
import org.sonar.db.component.ComponentDto; import org.sonar.db.component.ComponentDto;
import org.sonar.db.organization.OrganizationDto;
import org.sonar.db.organization.OrganizationMemberDto;
import org.sonar.db.permission.OrganizationPermission; import org.sonar.db.permission.OrganizationPermission;
import org.sonar.db.user.GroupDto; import org.sonar.db.user.GroupDto;
import org.sonar.db.user.UserDto; import org.sonar.db.user.UserDto;
import org.sonar.server.organization.DefaultOrganizationProvider; import org.sonar.server.organization.DefaultOrganizationProvider;
import org.sonar.server.organization.OrganizationFlags; import org.sonar.server.organization.OrganizationFlags;


import static com.google.common.collect.Maps.newHashMap; import static java.util.Objects.requireNonNull;
import static org.apache.commons.lang.StringUtils.defaultIfEmpty; import static org.apache.commons.lang.StringUtils.defaultIfEmpty;


/** /**
Expand All @@ -56,9 +59,10 @@ public class ServerUserSession extends AbstractUserSession {
private final DefaultOrganizationProvider defaultOrganizationProvider; private final DefaultOrganizationProvider defaultOrganizationProvider;
private final Supplier<Collection<GroupDto>> groups = Suppliers.memoize(this::loadGroups); private final Supplier<Collection<GroupDto>> groups = Suppliers.memoize(this::loadGroups);
private final Supplier<Boolean> isSystemAdministratorSupplier = Suppliers.memoize(this::loadIsSystemAdministrator); private final Supplier<Boolean> isSystemAdministratorSupplier = Suppliers.memoize(this::loadIsSystemAdministrator);
private final Map<String, String> projectUuidByComponentUuid = newHashMap(); private final Map<String, String> projectUuidByComponentUuid = new HashMap<>();
private Map<String, Set<OrganizationPermission>> permissionsByOrganizationUuid; private Map<String, Set<OrganizationPermission>> permissionsByOrganizationUuid;
private Map<String, Set<String>> permissionsByProjectUuid; private Map<String, Set<String>> permissionsByProjectUuid;
private Set<String> organizationMembership = new HashSet<>();


ServerUserSession(DbClient dbClient, OrganizationFlags organizationFlags, ServerUserSession(DbClient dbClient, OrganizationFlags organizationFlags,
DefaultOrganizationProvider defaultOrganizationProvider, @Nullable UserDto userDto) { DefaultOrganizationProvider defaultOrganizationProvider, @Nullable UserDto userDto) {
Expand Down Expand Up @@ -222,4 +226,29 @@ private boolean loadIsSystemAdministrator() {
return false; return false;
} }
} }

@Override
public boolean hasMembershipImpl(OrganizationDto organization) {
return isMember(organization);
}

private boolean isMember(OrganizationDto organization) {
if (!isLoggedIn()) {
return false;
}
if (isRoot()) {
return true;
}
String organizationUuid = organization.getUuid();
if (organizationMembership.contains(organizationUuid)) {
return true;
}
try (DbSession dbSession = dbClient.openSession(false)) {
Optional<OrganizationMemberDto> organizationMemberDto = dbClient.organizationMemberDao().select(dbSession, organizationUuid, requireNonNull(getUserId()));
if (organizationMemberDto.isPresent()) {
organizationMembership.add(organizationUuid);
}
return organizationMembership.contains(organizationUuid);
}
}
} }

0 comments on commit 884cd78

Please sign in to comment.