Skip to content

Commit

Permalink
Merge pull request #53 from CMIPT/kaiser-crud
Browse files Browse the repository at this point in the history
Finish delete and update repo, and delete user.
  • Loading branch information
Kaiser-Yang authored Sep 26, 2024
2 parents ca8da81 + e9f5518 commit 0f40356
Show file tree
Hide file tree
Showing 18 changed files with 346 additions and 59 deletions.
4 changes: 2 additions & 2 deletions script/deploy_helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -377,8 +377,8 @@ def deploy_on_ubuntu(config):
command_checker(res, message)
create_or_update_user(config.gitUserName, config.gitUserPassword)
create_or_update_user(config.serviceUser, config.serviceUserPassword)
# let the service user can use git and tee commands as the git user without password
sudoers_entry = f"{config.serviceUser} ALL=(git) NOPASSWD: /usr/bin/git, /usr/bin/tee"
# let the service user can use git, rm and tee commands as the git user without password
sudoers_entry = f"{config.serviceUser} ALL=(git) NOPASSWD: /usr/bin/git, /usr/bin/tee, /usr/bin/rm"
res = subprocess.run(f"echo '{sudoers_entry}' | {sudo_cmd} tee /etc/sudoers.d/{config.serviceUser}", shell=True);
command_checker(res.returncode, f"Failed to create /etc/sudoers.d/{config.serviceUser}")
res = subprocess.run(f"{sudo_cmd} chmod 440 /etc/sudoers.d/{config.serviceUser}", shell=True)
Expand Down
4 changes: 4 additions & 0 deletions src/main/java/edu/cmipt/gcs/constant/ApiPathConstant.java
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@ public class ApiPathConstant {
public static final String REPOSITORY_API_PREFIX = ALL_API_PREFIX + "/repository";
public static final String REPOSITORY_CREATE_REPOSITORY_API_PATH =
REPOSITORY_API_PREFIX + "/create";
public static final String REPOSITORY_DELETE_REPOSITORY_API_PATH =
REPOSITORY_API_PREFIX + "/delete";
public static final String REPOSITORY_UPDATE_REPOSITORY_API_PATH =
REPOSITORY_API_PREFIX + "/update";

public static final String SSH_KEY_API_PREFIX = ALL_API_PREFIX + "/ssh";
public static final String SSH_KEY_UPLOAD_SSH_KEY_API_PATH = SSH_KEY_API_PREFIX + "/upload";
Expand Down
113 changes: 113 additions & 0 deletions src/main/java/edu/cmipt/gcs/controller/RepositoryController.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,28 +8,37 @@
import edu.cmipt.gcs.exception.GenericException;
import edu.cmipt.gcs.pojo.repository.RepositoryDTO;
import edu.cmipt.gcs.pojo.repository.RepositoryPO;
import edu.cmipt.gcs.pojo.repository.RepositoryVO;
import edu.cmipt.gcs.service.RepositoryService;
import edu.cmipt.gcs.util.JwtUtil;
import edu.cmipt.gcs.validation.group.CreateGroup;
import edu.cmipt.gcs.validation.group.UpdateGroup;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.Parameters;
import io.swagger.v3.oas.annotations.enums.ParameterIn;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.tags.Tag;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
@Tag(name = "Repository", description = "Repository Related APIs")
public class RepositoryController {
private static final Logger logger = LoggerFactory.getLogger(SshKeyController.class);
@Autowired private RepositoryService repositoryService;

@PostMapping(ApiPathConstant.REPOSITORY_CREATE_REPOSITORY_API_PATH)
Expand All @@ -49,6 +58,11 @@ public class RepositoryController {
public void createRepository(
@Validated(CreateGroup.class) @RequestBody RepositoryDTO repository,
@RequestHeader(HeaderParameter.ACCESS_TOKEN) String accessToken) {
if (repository.isPrivate() != null && repository.isPrivate()) {
throw new GenericException(
ErrorCodeEnum.OPERATION_NOT_IMPLEMENTED,
"private repository is not implemented");
}
String userId = JwtUtil.getId(accessToken);
RepositoryPO repositoryPO = new RepositoryPO(repository, userId);
QueryWrapper<RepositoryPO> queryWrapper = new QueryWrapper<>();
Expand All @@ -61,4 +75,103 @@ public void createRepository(
throw new GenericException(ErrorCodeEnum.REPOSITORY_CREATE_FAILED, repository);
}
}

@DeleteMapping(ApiPathConstant.REPOSITORY_DELETE_REPOSITORY_API_PATH)
@Operation(
summary = "Delete a repository",
description = "Delete a repository with the given id",
tags = {"Repository", "Delete Method"})
@Parameters({
@Parameter(
name = HeaderParameter.ACCESS_TOKEN,
description = "Access token",
required = true,
in = ParameterIn.HEADER,
schema = @Schema(implementation = String.class)),
@Parameter(
name = "id",
description = "Repository id",
required = true,
in = ParameterIn.QUERY,
schema = @Schema(implementation = Long.class))
})
@ApiResponses({
@ApiResponse(responseCode = "200", description = "Repository deleted successfully"),
@ApiResponse(responseCode = "403", description = "Access denied"),
@ApiResponse(responseCode = "404", description = "Repository not found")
})
public void deleteRepository(
@RequestHeader(HeaderParameter.ACCESS_TOKEN) String accessToken,
@RequestParam("id") Long id) {
var repository = repositoryService.getById(id);
if (repository == null) {
throw new GenericException(ErrorCodeEnum.REPOSITORY_NOT_FOUND, id);
}
String userId = JwtUtil.getId(accessToken);
if (!userId.equals(repository.getUserId().toString())) {
logger.info(
"User[{}] tried to delete repository of user[{}]",
userId,
repository.getUserId());
throw new GenericException(ErrorCodeEnum.ACCESS_DENIED);
}
if (!repositoryService.removeById(id)) {
throw new GenericException(ErrorCodeEnum.REPOSITORY_DELETE_FAILED, id);
}
}

@PostMapping(ApiPathConstant.REPOSITORY_UPDATE_REPOSITORY_API_PATH)
@Operation(
summary = "Update a repository",
description = "Update a repository with the given information",
tags = {"Repository", "Post Method"})
@Parameter(
name = HeaderParameter.ACCESS_TOKEN,
description = "Access token",
required = true,
in = ParameterIn.HEADER,
schema = @Schema(implementation = String.class))
@ApiResponses({
@ApiResponse(responseCode = "200", description = "Repository updated successfully"),
@ApiResponse(responseCode = "403", description = "Access denied"),
@ApiResponse(responseCode = "404", description = "Repository not found"),
@ApiResponse(
responseCode = "501",
description = "Update repository name is not implemented")
})
public ResponseEntity<RepositoryVO> updateRepository(
@Validated(UpdateGroup.class) @RequestBody RepositoryDTO repository,
@RequestHeader(HeaderParameter.ACCESS_TOKEN) String accessToken) {
Long id = null;
try {
id = Long.valueOf(repository.id());
} catch (NumberFormatException e) {
logger.error(e.getMessage());
throw new GenericException(ErrorCodeEnum.MESSAGE_CONVERSION_ERROR);
}
var repositoryPO = repositoryService.getById(id);
if (repositoryPO == null) {
throw new GenericException(ErrorCodeEnum.REPOSITORY_NOT_FOUND, id);
}
String userId = JwtUtil.getId(accessToken);
if (!userId.equals(repositoryPO.getUserId().toString())) {
logger.info(
"User[{}] tried to update repository of user[{}]",
userId,
repositoryPO.getUserId());
throw new GenericException(ErrorCodeEnum.ACCESS_DENIED);
}
if (repository.repositoryName() != null
&& !repository
.repositoryName()
.equals(repositoryService.getById(id).getRepositoryName())) {
throw new GenericException(
ErrorCodeEnum.OPERATION_NOT_IMPLEMENTED,
"update repository name is not implemented");
}
if (!repositoryService.updateById(new RepositoryPO(repository))) {
throw new GenericException(ErrorCodeEnum.REPOSITORY_UPDATE_FAILED, repository);
}
return ResponseEntity.ok().body(new RepositoryVO(repositoryService.getById(id)));
}
}
36 changes: 29 additions & 7 deletions src/main/java/edu/cmipt/gcs/controller/SshKeyController.java
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ public class SshKeyController {
public void uploadSshKey(
@Validated(CreateGroup.class) @RequestBody SshKeyDTO sshKeyDTO,
@RequestHeader(HeaderParameter.ACCESS_TOKEN) String accessToken) {
if (!sshKeyService.save(new SshKeyPO(sshKeyDTO))) {
if (!sshKeyService.save(new SshKeyPO(sshKeyDTO, JwtUtil.getId(accessToken)))) {
throw new GenericException(ErrorCodeEnum.SSH_KEY_UPLOAD_FAILED, sshKeyDTO);
}
}
Expand Down Expand Up @@ -101,14 +101,16 @@ public void uploadSshKey(
public void deleteSshKey(
@RequestHeader(HeaderParameter.ACCESS_TOKEN) String accessToken,
@RequestParam("id") Long id) {
var res = sshKeyService.getById(id);
if (res == null) {
var sshKeyPO = sshKeyService.getById(id);
if (sshKeyPO == null) {
throw new GenericException(ErrorCodeEnum.SSH_KEY_NOT_FOUND, id);
}
String idInToken = JwtUtil.getId(accessToken);
String idInRes = res.getUserId().toString();
if (!idInRes.equals(idInToken)) {
logger.info("User[{}] tried to get SSH key of user[{}]", idInToken, idInRes);
if (!idInToken.equals(sshKeyPO.getUserId().toString())) {
logger.info(
"User[{}] tried to delete SSH key of user[{}]",
idInToken,
sshKeyPO.getUserId());
throw new GenericException(ErrorCodeEnum.ACCESS_DENIED);
}
if (!sshKeyService.removeById(id)) {
Expand Down Expand Up @@ -137,7 +139,27 @@ public void deleteSshKey(
content = @Content(schema = @Schema(implementation = ErrorVO.class)))
})
public ResponseEntity<SshKeyVO> updateSshKey(
@Validated(UpdateGroup.class) @RequestBody SshKeyDTO sshKeyDTO) {
@Validated(UpdateGroup.class) @RequestBody SshKeyDTO sshKeyDTO,
@RequestHeader(HeaderParameter.ACCESS_TOKEN) String accessToken) {
Long id = null;
try {
id = Long.valueOf(sshKeyDTO.id());
} catch (NumberFormatException e) {
logger.error(e.getMessage());
throw new GenericException(ErrorCodeEnum.MESSAGE_CONVERSION_ERROR);
}
var sshKeyPO = sshKeyService.getById(id);
if (sshKeyPO == null) {
throw new GenericException(ErrorCodeEnum.SSH_KEY_NOT_FOUND, id);
}
String idInToken = JwtUtil.getId(accessToken);
if (!idInToken.equals(sshKeyPO.getUserId().toString())) {
logger.info(
"User[{}] tried to update SSH key of user[{}]",
idInToken,
sshKeyPO.getUserId());
throw new GenericException(ErrorCodeEnum.ACCESS_DENIED);
}
if (!sshKeyService.updateById(new SshKeyPO(sshKeyDTO))) {
throw new GenericException(ErrorCodeEnum.SSH_KEY_UPDATE_FAILED, sshKeyDTO);
}
Expand Down
8 changes: 6 additions & 2 deletions src/main/java/edu/cmipt/gcs/enumeration/ErrorCodeEnum.java
Original file line number Diff line number Diff line change
Expand Up @@ -48,12 +48,14 @@ public enum ErrorCodeEnum {
REPOSITORYDTO_WATCHER_MIN("RepositoryDTO.watcher.Min"),

REPOSITORYNAME_PATTERN_MISMATCH("REPOSITORYNAME_PATTERN_MISMATCH"),
REPOSITORY_NOT_FOUND("REPOSITORY_NOT_FOUND"),
REPOSITORY_ALREADY_EXISTS("REPOSITORY_ALREADY_EXISTS"),
REPOSITORY_CREATE_FAILED("REPOSITORY_CREATE_FAILED"),
REPOSITORY_UPDATE_FAILED("REPOSITORY_UPDATE_FAILED"),
REPOSITORY_DELETE_FAILED("REPOSITORY_DELETE_FAILED"),

SSHKEYDTO_ID_NULL("SshKeyDTO.id.Null"),
SSHKEYDTO_ID_NOTNULL("SshKeyDTO.id.NotNull"),
SSHKEYDTO_USERID_NOTBLANK("SshKeyDTO.userId.NotBlank"),
SSHKEYDTO_NAME_NOTBLANK("SshKeyDTO.name.NotBlank"),
SSHKEYDTO_NAME_SIZE("SshKeyDTO.name.Size"),
SSHKEYDTO_PUBLICKEY_NOTBLANK("SshKeyDTO.publicKey.NotBlank"),
Expand All @@ -62,7 +64,9 @@ public enum ErrorCodeEnum {
SSH_KEY_UPLOAD_FAILED("SSH_KEY_UPLOAD_FAILED"),
SSH_KEY_UPDATE_FAILED("SSH_KEY_UPDATE_FAILED"),
SSH_KEY_DELETE_FAILED("SSH_KEY_DELETE_FAILED"),
SSH_KEY_NOT_FOUND("SSH_KEY_NOT_FOUND");
SSH_KEY_NOT_FOUND("SSH_KEY_NOT_FOUND"),

OPERATION_NOT_IMPLEMENTED("OPERATION_NOT_IMPLEMENTED");

// code means the error code in the message.properties
private String code;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,8 +74,13 @@ public ResponseEntity<ErrorVO> handleGenericException(
return ResponseEntity.status(HttpStatus.FORBIDDEN)
.body(new ErrorVO(e.getCode(), e.getMessage()));
case USER_NOT_FOUND:
case SSH_KEY_NOT_FOUND:
case REPOSITORY_NOT_FOUND:
return ResponseEntity.status(HttpStatus.NOT_FOUND)
.body(new ErrorVO(e.getCode(), e.getMessage()));
case OPERATION_NOT_IMPLEMENTED:
return ResponseEntity.status(HttpStatus.NOT_IMPLEMENTED)
.body(new ErrorVO(e.getCode(), e.getMessage()));
default:
return ResponseEntity.status(HttpStatus.BAD_REQUEST)
.body(new ErrorVO(e.getCode(), e.getMessage()));
Expand Down
29 changes: 11 additions & 18 deletions src/main/java/edu/cmipt/gcs/filter/JwtFilter.java
Original file line number Diff line number Diff line change
Expand Up @@ -198,26 +198,15 @@ private void authorize(HttpServletRequest request, String accessToken, String re
// pass
} else if (request.getRequestURI()
.equals(ApiPathConstant.SSH_KEY_UPLOAD_SSH_KEY_API_PATH)) {
String idInToken = JwtUtil.getId(accessToken);
String idInBody = getFromRequestBody(request, "userId");
if (!idInToken.equals(idInBody)) {
logger.info(
"User[{}] tried to upload SSH key of user[{}]",
idInToken,
idInBody);
throw new GenericException(ErrorCodeEnum.ACCESS_DENIED);
}
// pass
} else if (request.getRequestURI()
.equals(ApiPathConstant.SSH_KEY_UPDATE_SSH_KEY_API_PATH)) {
String idInToken = JwtUtil.getId(accessToken);
String idInBody = getFromRequestBody(request, "userId");
if (!idInToken.equals(idInBody)) {
logger.info(
"User[{}] tried to update SSH key of user[{}]",
idInToken,
idInBody);
throw new GenericException(ErrorCodeEnum.ACCESS_DENIED);
}
// this will be checked in controller
// because we must query the database to get the user id of the ssh key
} else if (request.getRequestURI()
.equals(ApiPathConstant.REPOSITORY_UPDATE_REPOSITORY_API_PATH)) {
// this will be checked in controller
// because we must query the database to get the user id of the repository
} else {
throw new GenericException(ErrorCodeEnum.ACCESS_DENIED);
}
Expand All @@ -242,6 +231,10 @@ private void authorize(HttpServletRequest request, String accessToken, String re
.equals(ApiPathConstant.SSH_KEY_DELETE_SSH_KEY_API_PATH)) {
// this will be checked in controller
// because we must query the database to get the user id of the ssh key
} else if (request.getRequestURI()
.equals(ApiPathConstant.REPOSITORY_DELETE_REPOSITORY_API_PATH)) {
// this will be checked in controller
// because we must query the database to get the user id of the repository
} else {
throw new GenericException(ErrorCodeEnum.ACCESS_DENIED);
}
Expand Down
4 changes: 4 additions & 0 deletions src/main/java/edu/cmipt/gcs/pojo/repository/RepositoryPO.java
Original file line number Diff line number Diff line change
Expand Up @@ -45,4 +45,8 @@ public RepositoryPO(RepositoryDTO repositoryDTO, String userId) {
this.fork = repositoryDTO.fork();
this.watcher = repositoryDTO.watcher();
}

public RepositoryPO(RepositoryDTO repositoryDTO) {
this(repositoryDTO, null);
}
}
2 changes: 2 additions & 0 deletions src/main/java/edu/cmipt/gcs/pojo/repository/RepositoryVO.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ public record RepositoryVO(
@Schema(description = "Repository Name") String repositoryName,
@Schema(description = "Repository Description") String repositoryDescription,
@Schema(description = "Whether or Not Private Repo") Boolean isPrivate,
@Schema(description = "Owner ID") Long userId,
@Schema(description = "Star Count") Integer star,
@Schema(description = "Fork Count") Integer fork,
@Schema(description = "Watcher Count") Integer watcher) {
Expand All @@ -16,6 +17,7 @@ public RepositoryVO(RepositoryPO repositoryPO) {
repositoryPO.getRepositoryName(),
repositoryPO.getRepositoryDescription(),
repositoryPO.getIsPrivate(),
repositoryPO.getUserId(),
repositoryPO.getStar(),
repositoryPO.getFork(),
repositoryPO.getWatcher());
Expand Down
5 changes: 0 additions & 5 deletions src/main/java/edu/cmipt/gcs/pojo/ssh/SshKeyDTO.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,6 @@ public record SshKeyDTO(
groups = UpdateGroup.class,
message = "SSHKEYDTO_ID_NOTNULL {SshKeyDTO.id.NotNull}")
String id,
@Schema(description = "User ID")
@NotBlank(
groups = {CreateGroup.class, UpdateGroup.class},
message = "SSHKEYDTO_USERID_NOTBLANK {SshKeyDTO.userId.NotBlank}")
String userId,
@Schema(description = "Name", example = "My SSH Key")
@NotBlank(
groups = {CreateGroup.class, UpdateGroup.class},
Expand Down
8 changes: 6 additions & 2 deletions src/main/java/edu/cmipt/gcs/pojo/ssh/SshKeyPO.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,18 +20,22 @@ public class SshKeyPO {
private LocalDateTime gmtUpdated;
@TableLogic private LocalDateTime gmtDeleted;

public SshKeyPO(SshKeyDTO sshKeyDTO) {
public SshKeyPO(SshKeyDTO sshKeyDTO, String userId) {
try {
this.id = Long.valueOf(sshKeyDTO.id());
} catch (NumberFormatException e) {
this.id = null;
}
try {
this.userId = Long.valueOf(sshKeyDTO.userId());
this.userId = Long.valueOf(userId);
} catch (NumberFormatException e) {
this.userId = null;
}
this.name = sshKeyDTO.name();
this.publicKey = sshKeyDTO.publicKey();
}

public SshKeyPO(SshKeyDTO sshKeyDTO) {
this(sshKeyDTO, null);
}
}
Loading

0 comments on commit 0f40356

Please sign in to comment.