1   /*
2    * Copyright 2024 LINE Corporation
3    *
4    * LINE Corporation licenses this file to you under the Apache License,
5    * version 2.0 (the "License"); you may not use this file except in compliance
6    * with the License. You may obtain a copy of the License at:
7    *
8    *   https://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13   * License for the specific language governing permissions and limitations
14   * under the License.
15   */
16  package com.linecorp.centraldogma.server.internal.storage.project;
17  
18  import static com.linecorp.centraldogma.server.internal.storage.project.ProjectInitializer.INTERNAL_PROJECT_DOGMA;
19  
20  import java.time.Instant;
21  import java.util.Collections;
22  import java.util.LinkedHashMap;
23  import java.util.Map;
24  import java.util.concurrent.CompletableFuture;
25  
26  import com.linecorp.centraldogma.common.Author;
27  import com.linecorp.centraldogma.common.Revision;
28  import com.linecorp.centraldogma.server.command.Command;
29  import com.linecorp.centraldogma.server.command.CommandExecutor;
30  import com.linecorp.centraldogma.server.internal.admin.auth.AuthUtil;
31  import com.linecorp.centraldogma.server.metadata.MetadataService;
32  import com.linecorp.centraldogma.server.metadata.ProjectMetadata;
33  import com.linecorp.centraldogma.server.metadata.User;
34  import com.linecorp.centraldogma.server.storage.project.Project;
35  import com.linecorp.centraldogma.server.storage.project.ProjectManager;
36  
37  /**
38   * A wrapper class of {@link ProjectManager} which prevents accessing internal projects
39   * from unprivileged requests.
40   */
41  public final class ProjectApiManager {
42  
43      private final ProjectManager projectManager;
44      private final CommandExecutor commandExecutor;
45      private final MetadataService metadataService;
46  
47      public ProjectApiManager(ProjectManager projectManager, CommandExecutor commandExecutor,
48                               MetadataService metadataService) {
49          this.projectManager = projectManager;
50          this.commandExecutor = commandExecutor;
51          this.metadataService = metadataService;
52      }
53  
54      public Map<String, Project> listProjects() {
55          final Map<String, Project> projects = projectManager.list();
56          if (isAdmin()) {
57              return projects;
58          }
59  
60          return listProjectsWithoutDogma(projects);
61      }
62  
63      private static boolean isAdmin() {
64          final User currentUserOrNull = AuthUtil.currentUserOrNull();
65          if (currentUserOrNull == null) {
66              return false;
67          }
68  
69          return currentUserOrNull.isAdmin();
70      }
71  
72      public static Map<String, Project> listProjectsWithoutDogma(Map<String, Project> projects) {
73          final Map<String, Project> result = new LinkedHashMap<>(projects.size() - 1);
74          for (Map.Entry<String, Project> entry : projects.entrySet()) {
75              if (!INTERNAL_PROJECT_DOGMA.equals(entry.getKey())) {
76                  result.put(entry.getKey(), entry.getValue());
77              }
78          }
79          return Collections.unmodifiableMap(result);
80      }
81  
82      public Map<String, Instant> listRemovedProjects() {
83          return projectManager.listRemoved();
84      }
85  
86      public CompletableFuture<Void> createProject(String projectName, Author author) {
87          checkInternalDogmaProject(projectName, "create");
88          return commandExecutor.execute(Command.createProject(author, projectName));
89      }
90  
91      private static void checkInternalDogmaProject(String projectName, String operation) {
92          if (INTERNAL_PROJECT_DOGMA.equals(projectName)) {
93              throw new IllegalArgumentException("Cannot " + operation + ' ' + projectName);
94          }
95      }
96  
97      public CompletableFuture<ProjectMetadata> getProjectMetadata(String projectName) {
98          return metadataService.getProject(projectName);
99      }
100 
101     public CompletableFuture<Void> removeProject(String projectName, Author author) {
102         checkInternalDogmaProject(projectName, "remove");
103         // Metadata must be updated first because it cannot be updated if the project is removed.
104         return metadataService.removeProject(author, projectName)
105                               .thenCompose(unused -> commandExecutor.execute(
106                                       Command.removeProject(author, projectName)));
107     }
108 
109     public CompletableFuture<Void> purgeProject(String projectName, Author author) {
110         checkInternalDogmaProject(projectName, "purge");
111         return commandExecutor.execute(Command.purgeProject(author, projectName));
112     }
113 
114     public CompletableFuture<Revision> unremoveProject(String projectName, Author author) {
115         checkInternalDogmaProject(projectName, "unremove");
116         // Restore the project first then update its metadata as 'active'.
117         return commandExecutor.execute(Command.unremoveProject(author, projectName))
118                 .thenCompose(unused -> metadataService.restoreProject(author, projectName));
119     }
120 
121     public Project getProject(String projectName) {
122         if (INTERNAL_PROJECT_DOGMA.equals(projectName) && !isAdmin()) {
123             throw new IllegalArgumentException("Cannot access " + projectName);
124         }
125         return projectManager.get(projectName);
126     }
127 }