You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

ServerUserSession.java 15KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406
  1. /*
  2. * SonarQube
  3. * Copyright (C) 2009-2023 SonarSource SA
  4. * mailto:info AT sonarsource DOT com
  5. *
  6. * This program is free software; you can redistribute it and/or
  7. * modify it under the terms of the GNU Lesser General Public
  8. * License as published by the Free Software Foundation; either
  9. * version 3 of the License, or (at your option) any later version.
  10. *
  11. * This program is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  14. * Lesser General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU Lesser General Public License
  17. * along with this program; if not, write to the Free Software Foundation,
  18. * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  19. */
  20. package org.sonar.server.user;
  21. import java.util.Arrays;
  22. import java.util.Collection;
  23. import java.util.Collections;
  24. import java.util.HashMap;
  25. import java.util.HashSet;
  26. import java.util.List;
  27. import java.util.Map;
  28. import java.util.Objects;
  29. import java.util.Optional;
  30. import java.util.Set;
  31. import java.util.stream.Collectors;
  32. import javax.annotation.CheckForNull;
  33. import javax.annotation.Nullable;
  34. import org.sonar.api.resources.Qualifiers;
  35. import org.sonar.api.resources.Scopes;
  36. import org.sonar.db.DbClient;
  37. import org.sonar.db.DbSession;
  38. import org.sonar.db.component.BranchDto;
  39. import org.sonar.db.component.ComponentDto;
  40. import org.sonar.db.component.ComponentTreeQuery;
  41. import org.sonar.db.component.ComponentTreeQuery.Strategy;
  42. import org.sonar.db.entity.EntityDto;
  43. import org.sonar.db.permission.GlobalPermission;
  44. import org.sonar.db.user.GroupDto;
  45. import org.sonar.db.user.UserDto;
  46. import static java.util.Collections.singleton;
  47. import static java.util.Optional.of;
  48. import static java.util.Optional.ofNullable;
  49. import static java.util.stream.Collectors.toSet;
  50. import static org.sonar.api.resources.Qualifiers.SUBVIEW;
  51. import static org.sonar.api.resources.Qualifiers.VIEW;
  52. import static org.sonar.api.web.UserRole.PUBLIC_PERMISSIONS;
  53. /**
  54. * Implementation of {@link UserSession} used in web server
  55. */
  56. public class ServerUserSession extends AbstractUserSession {
  57. private static final Set<String> QUALIFIERS = Set.of(VIEW, SUBVIEW);
  58. @CheckForNull
  59. private final UserDto userDto;
  60. private final DbClient dbClient;
  61. private final Map<String, String> entityUuidByComponentUuid = new HashMap<>();
  62. private final Map<String, Set<String>> permissionsByEntityUuid = new HashMap<>();
  63. private Collection<GroupDto> groups;
  64. private Boolean isSystemAdministrator;
  65. private Set<GlobalPermission> permissions;
  66. public ServerUserSession(DbClient dbClient, @Nullable UserDto userDto) {
  67. this.dbClient = dbClient;
  68. this.userDto = userDto;
  69. }
  70. private Collection<GroupDto> loadGroups() {
  71. if (this.userDto == null) {
  72. return Collections.emptyList();
  73. }
  74. try (DbSession dbSession = dbClient.openSession(false)) {
  75. return dbClient.groupDao().selectByUserLogin(dbSession, userDto.getLogin());
  76. }
  77. }
  78. @Override
  79. @CheckForNull
  80. public Long getLastSonarlintConnectionDate() {
  81. return userDto == null ? null : userDto.getLastSonarlintConnectionDate();
  82. }
  83. @Override
  84. @CheckForNull
  85. public String getLogin() {
  86. return userDto == null ? null : userDto.getLogin();
  87. }
  88. @Override
  89. @CheckForNull
  90. public String getUuid() {
  91. return userDto == null ? null : userDto.getUuid();
  92. }
  93. @Override
  94. @CheckForNull
  95. public String getName() {
  96. return userDto == null ? null : userDto.getName();
  97. }
  98. @Override
  99. public Collection<GroupDto> getGroups() {
  100. if (groups == null) {
  101. groups = loadGroups();
  102. }
  103. return groups;
  104. }
  105. @Override
  106. public boolean shouldResetPassword() {
  107. return userDto != null && userDto.isResetPassword();
  108. }
  109. @Override
  110. public boolean isLoggedIn() {
  111. return userDto != null;
  112. }
  113. @Override
  114. public Optional<IdentityProvider> getIdentityProvider() {
  115. return ofNullable(userDto).map(d -> computeIdentity(d).getIdentityProvider());
  116. }
  117. @Override
  118. public Optional<ExternalIdentity> getExternalIdentity() {
  119. return ofNullable(userDto).map(d -> computeIdentity(d).getExternalIdentity());
  120. }
  121. @Override
  122. protected boolean hasPermissionImpl(GlobalPermission permission) {
  123. if (permissions == null) {
  124. permissions = loadGlobalPermissions();
  125. }
  126. return permissions.contains(permission);
  127. }
  128. @Override
  129. protected Optional<String> componentUuidToEntityUuid(String componentUuid) {
  130. String entityUuid = entityUuidByComponentUuid.get(componentUuid);
  131. if (entityUuid != null) {
  132. return of(entityUuid);
  133. }
  134. try (DbSession dbSession = dbClient.openSession(false)) {
  135. Optional<ComponentDto> component = dbClient.componentDao().selectByUuid(dbSession, componentUuid);
  136. if (component.isEmpty()) {
  137. return Optional.empty();
  138. }
  139. // permissions must be checked on the project
  140. entityUuid = getEntityUuid(dbSession, component.get());
  141. entityUuidByComponentUuid.put(componentUuid, entityUuid);
  142. return of(entityUuid);
  143. }
  144. }
  145. @Override
  146. protected boolean hasEntityUuidPermission(String permission, String entityUuid) {
  147. return hasPermission(permission, entityUuid);
  148. }
  149. @Override
  150. protected boolean hasChildProjectsPermission(String permission, String applicationUuid) {
  151. Set<String> childProjectUuids = loadChildProjectUuids(applicationUuid);
  152. Set<String> projectsWithPermission = keepEntitiesUuidsByPermission(permission, childProjectUuids);
  153. return projectsWithPermission.containsAll(childProjectUuids);
  154. }
  155. @Override
  156. protected boolean hasPortfolioChildProjectsPermission(String permission, String portfolioUuid) {
  157. Set<ComponentDto> portfolioHierarchyComponents = resolvePortfolioHierarchyComponents(portfolioUuid);
  158. Set<String> branchUuids = findBranchUuids(portfolioHierarchyComponents);
  159. Set<String> projectUuids = findProjectUuids(branchUuids);
  160. Set<String> projectsWithPermission = keepEntitiesUuidsByPermission(permission, projectUuids);
  161. return projectsWithPermission.containsAll(projectUuids);
  162. }
  163. @Override
  164. public <T extends EntityDto> List<T> keepAuthorizedEntities(String permission, Collection<T> entities) {
  165. Set<String> projectsUuids = entities.stream().map(EntityDto::getUuid).collect(Collectors.toSet());
  166. // TODO in SONAR-19445
  167. Set<String> authorizedEntitiesUuids = keepEntitiesUuidsByPermission(permission, projectsUuids);
  168. return entities.stream()
  169. .filter(project -> authorizedEntitiesUuids.contains(project.getUuid()))
  170. .toList();
  171. }
  172. private Set<String> keepEntitiesUuidsByPermission(String permission, Collection<String> entityUuids) {
  173. try (DbSession dbSession = dbClient.openSession(false)) {
  174. String userUuid = userDto == null ? null : userDto.getUuid();
  175. return dbClient.authorizationDao().keepAuthorizedEntityUuids(dbSession, entityUuids, userUuid, permission);
  176. }
  177. }
  178. private static Set<String> findBranchUuids(Set<ComponentDto> portfolioHierarchyComponents) {
  179. return portfolioHierarchyComponents.stream()
  180. .map(ComponentDto::getCopyComponentUuid)
  181. .collect(toSet());
  182. }
  183. private Set<String> findProjectUuids(Set<String> branchesComponentsUuid) {
  184. try (DbSession dbSession = dbClient.openSession(false)) {
  185. List<ComponentDto> componentDtos = dbClient.componentDao().selectByUuids(dbSession, branchesComponentsUuid);
  186. return getProjectUuids(dbSession, componentDtos);
  187. }
  188. }
  189. private String getEntityUuid(DbSession dbSession, ComponentDto componentDto) {
  190. // Portfolio & subPortfolio don't have branch, so branchUuid represents the portfolio uuid.
  191. // technical project store root portfolio uuid in branchUuid
  192. if (isPortfolioOrSubPortfolio(componentDto) || isTechnicalProject(componentDto)) {
  193. return componentDto.branchUuid();
  194. }
  195. Optional<BranchDto> branchDto = dbClient.branchDao().selectByUuid(dbSession, componentDto.branchUuid());
  196. return branchDto.map(BranchDto::getProjectUuid).orElseThrow(() -> new IllegalStateException("No branch found for component : " + componentDto));
  197. }
  198. private Set<String> getProjectUuids(DbSession dbSession, Collection<ComponentDto> components) {
  199. Set<String> mainProjectUuids = new HashSet<>();
  200. // the result of following stream could be project or application
  201. Collection<String> componentsWithBranch = components.stream()
  202. .filter(c -> !(isTechnicalProject(c) || isPortfolioOrSubPortfolio(c)))
  203. .map(ComponentDto::branchUuid)
  204. .toList();
  205. dbClient.branchDao().selectByUuids(dbSession, componentsWithBranch).stream()
  206. .map(BranchDto::getProjectUuid).forEach(mainProjectUuids::add);
  207. components.stream()
  208. .filter(c -> isTechnicalProject(c) || isPortfolioOrSubPortfolio(c))
  209. .map(ComponentDto::branchUuid)
  210. .forEach(mainProjectUuids::add);
  211. return mainProjectUuids;
  212. }
  213. private static boolean isTechnicalProject(ComponentDto componentDto) {
  214. return Qualifiers.PROJECT.equals(componentDto.qualifier()) && Scopes.FILE.equals(componentDto.scope());
  215. }
  216. private static boolean isPortfolioOrSubPortfolio(ComponentDto componentDto) {
  217. return !Objects.isNull(componentDto.qualifier()) && QUALIFIERS.contains(componentDto.qualifier());
  218. }
  219. private boolean hasPermission(String permission, String entityUuid) {
  220. Set<String> entityPermissions = permissionsByEntityUuid.computeIfAbsent(entityUuid, this::loadEntityPermissions);
  221. return entityPermissions.contains(permission);
  222. }
  223. private Set<String> loadEntityPermissions(String entityUuid) {
  224. try (DbSession dbSession = dbClient.openSession(false)) {
  225. Optional<EntityDto> entity = dbClient.entityDao().selectByUuid(dbSession, entityUuid);
  226. if (entity.isEmpty()) {
  227. return Collections.emptySet();
  228. }
  229. if (entity.get().isPrivate()) {
  230. return loadDbPermissions(dbSession, entityUuid);
  231. }
  232. Set<String> projectPermissions = new HashSet<>();
  233. projectPermissions.addAll(PUBLIC_PERMISSIONS);
  234. projectPermissions.addAll(loadDbPermissions(dbSession, entityUuid));
  235. return Collections.unmodifiableSet(projectPermissions);
  236. }
  237. }
  238. private Set<String> loadChildProjectUuids(String applicationUuid) {
  239. try (DbSession dbSession = dbClient.openSession(false)) {
  240. BranchDto branchDto = dbClient.branchDao().selectMainBranchByProjectUuid(dbSession, applicationUuid)
  241. .orElseThrow();
  242. Set<String> projectBranchesUuid = dbClient.componentDao()
  243. .selectDescendants(dbSession, ComponentTreeQuery.builder()
  244. .setBaseUuid(branchDto.getUuid())
  245. .setQualifiers(singleton(Qualifiers.PROJECT))
  246. .setScopes(singleton(Scopes.FILE))
  247. .setStrategy(Strategy.CHILDREN).build())
  248. .stream()
  249. .map(ComponentDto::getCopyComponentUuid)
  250. .collect(toSet());
  251. return dbClient.branchDao().selectByUuids(dbSession, projectBranchesUuid).stream()
  252. .map(BranchDto::getProjectUuid)
  253. .collect(toSet());
  254. }
  255. }
  256. private List<ComponentDto> getDirectChildComponents(String portfolioUuid) {
  257. try (DbSession dbSession = dbClient.openSession(false)) {
  258. return dbClient.componentDao().selectDescendants(dbSession, ComponentTreeQuery.builder()
  259. .setBaseUuid(portfolioUuid)
  260. .setQualifiers(Arrays.asList(Qualifiers.PROJECT, Qualifiers.SUBVIEW))
  261. .setStrategy(Strategy.CHILDREN).build());
  262. }
  263. }
  264. private Set<ComponentDto> resolvePortfolioHierarchyComponents(String parentComponentUuid) {
  265. Set<ComponentDto> portfolioHierarchyProjects = new HashSet<>();
  266. resolvePortfolioHierarchyComponents(parentComponentUuid, portfolioHierarchyProjects);
  267. return portfolioHierarchyProjects;
  268. }
  269. private void resolvePortfolioHierarchyComponents(String parentComponentUuid, Set<ComponentDto> hierarchyChildComponents) {
  270. List<ComponentDto> childComponents = getDirectChildComponents(parentComponentUuid);
  271. if (childComponents.isEmpty()) {
  272. return;
  273. }
  274. childComponents.forEach(c -> {
  275. if (c.getCopyComponentUuid() != null) {
  276. hierarchyChildComponents.add(c);
  277. }
  278. if (Qualifiers.SUBVIEW.equals(c.qualifier())) {
  279. resolvePortfolioHierarchyComponents(c.uuid(), hierarchyChildComponents);
  280. }
  281. });
  282. }
  283. private Set<GlobalPermission> loadGlobalPermissions() {
  284. Set<String> permissionKeys;
  285. try (DbSession dbSession = dbClient.openSession(false)) {
  286. if (userDto != null && userDto.getUuid() != null) {
  287. permissionKeys = dbClient.authorizationDao().selectGlobalPermissions(dbSession, userDto.getUuid());
  288. } else {
  289. permissionKeys = dbClient.authorizationDao().selectGlobalPermissionsOfAnonymous(dbSession);
  290. }
  291. }
  292. return permissionKeys.stream()
  293. .map(GlobalPermission::fromKey)
  294. .collect(toSet());
  295. }
  296. private Set<String> loadDbPermissions(DbSession dbSession, String entityUuid) {
  297. if (userDto != null && userDto.getUuid() != null) {
  298. return dbClient.authorizationDao().selectEntityPermissions(dbSession, entityUuid, userDto.getUuid());
  299. }
  300. return dbClient.authorizationDao().selectEntityPermissionsOfAnonymous(dbSession, entityUuid);
  301. }
  302. @Override
  303. protected List<ComponentDto> doKeepAuthorizedComponents(String permission, Collection<ComponentDto> components) {
  304. try (DbSession dbSession = dbClient.openSession(false)) {
  305. Set<String> projectUuids = getProjectUuids(dbSession, components);
  306. Map<String, ComponentDto> originalComponents = findComponentsByCopyComponentUuid(components,
  307. dbSession);
  308. Set<String> originalComponentsProjectUuids = getProjectUuids(dbSession, originalComponents.values());
  309. Set<String> allProjectUuids = new HashSet<>(projectUuids);
  310. allProjectUuids.addAll(originalComponentsProjectUuids);
  311. Set<String> authorizedProjectUuids = dbClient.authorizationDao().keepAuthorizedEntityUuids(dbSession, allProjectUuids, getUuid(), permission);
  312. return components.stream()
  313. .filter(c -> {
  314. if (c.getCopyComponentUuid() != null) {
  315. var componentDto = originalComponents.get(c.getCopyComponentUuid());
  316. return componentDto != null && authorizedProjectUuids.contains(getEntityUuid(dbSession, componentDto));
  317. }
  318. return authorizedProjectUuids.contains(c.branchUuid()) || authorizedProjectUuids.contains(
  319. getEntityUuid(dbSession, c));
  320. })
  321. .toList();
  322. }
  323. }
  324. private Map<String, ComponentDto> findComponentsByCopyComponentUuid(Collection<ComponentDto> components, DbSession dbSession) {
  325. Set<String> copyComponentsUuid = components.stream()
  326. .map(ComponentDto::getCopyComponentUuid)
  327. .filter(Objects::nonNull)
  328. .collect(toSet());
  329. return dbClient.componentDao().selectByUuids(dbSession, copyComponentsUuid).stream()
  330. .collect(Collectors.toMap(ComponentDto::uuid, componentDto -> componentDto));
  331. }
  332. @Override
  333. public boolean isSystemAdministrator() {
  334. if (isSystemAdministrator == null) {
  335. isSystemAdministrator = loadIsSystemAdministrator();
  336. }
  337. return isSystemAdministrator;
  338. }
  339. @Override
  340. public boolean isActive() {
  341. return userDto.isActive();
  342. }
  343. private boolean loadIsSystemAdministrator() {
  344. return hasPermission(GlobalPermission.ADMINISTER);
  345. }
  346. }