Skip to content

Commit

Permalink
Implement authorization for SYNC product
Browse files Browse the repository at this point in the history
  • Loading branch information
ndkhanh-axonivy committed Jul 19, 2024
1 parent 88b886f commit f271d95
Show file tree
Hide file tree
Showing 13 changed files with 120 additions and 15 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,20 @@ public class GitHubConstants {

@NoArgsConstructor(access = AccessLevel.PRIVATE)
public static class Json {
public static final String ACCESS_TOKEN = "access_token";
public static final String TOKEN = "token";
public static final String CLIENT_ID = "client_id";
public static final String CLIENT_SECRET = "client_secret";
public static final String CODE = "code";
public static final String ERROR = "error";
public static final String ERROR_DESCRIPTION = "error";
public static final String USER_ID = "id";
public static final String USER_NAME = "name";
public static final String USER_AVATAR_URL = "avatar_url";
public static final String USER_LOGIN_NAME = "login";
}

@NoArgsConstructor(access = AccessLevel.PRIVATE)
public static class Url {
private static final String BASE_URL = "https://api.github.com";
public static final String USER = BASE_URL + "/user";
public static final String USER_ORGS = USER + "/orgs";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ public class RequestMappingConstants {
public static final String ROOT = "/";
public static final String API = ROOT + "api";
public static final String SYNC = ROOT + "sync";
public static final String USER_MAPPING = "/user";
public static final String PRODUCT = API + "/product";
public static final String PRODUCT_DETAILS = API + "/product-details";
public static final String FEEDBACK = API + "/feedback";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ public ResponseEntity<FeedbackModel> findFeedbackByUserIdAndProductId(@RequestPa

@PostMapping
public ResponseEntity<Void> createFeedback(@RequestBody @Valid FeedbackModel feedback,
@RequestHeader(value = "Authorization", required = false) String authorizationHeader) {
@RequestHeader(value = "Authorization") String authorizationHeader) {
String token = null;
if (authorizationHeader != null && authorizationHeader.startsWith("Bearer ")) {
token = authorizationHeader.substring(7); // Remove "Bearer " prefix
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import com.axonivy.market.constants.GitHubConstants;
import com.axonivy.market.entity.User;
import com.axonivy.market.github.service.GitHubService;
import com.axonivy.market.model.GitHubAccessTokenResponse;
import com.axonivy.market.github.model.GitHubAccessTokenResponse;
import com.axonivy.market.model.Oauth2AuthorizationCode;
import com.axonivy.market.service.JwtService;
import org.springframework.beans.factory.annotation.Value;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
package com.axonivy.market.controller;

import com.axonivy.market.assembler.ProductModelAssembler;
import com.axonivy.market.constants.GitHubConstants;
import com.axonivy.market.entity.Product;
import com.axonivy.market.enums.ErrorCode;
import com.axonivy.market.github.service.GitHubService;
import com.axonivy.market.github.util.GitHubUtils;
import com.axonivy.market.model.Message;
import com.axonivy.market.model.ProductModel;
import com.axonivy.market.service.ProductService;
Expand All @@ -17,9 +20,11 @@
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.HttpClientErrorException;

import static com.axonivy.market.constants.RequestMappingConstants.PRODUCT;
import static com.axonivy.market.constants.RequestMappingConstants.SYNC;
Expand All @@ -29,12 +34,14 @@
public class ProductController {

private final ProductService productService;
private final GitHubService gitHubService;
private final ProductModelAssembler assembler;
private final PagedResourcesAssembler<Product> pagedResourcesAssembler;

public ProductController(ProductService productService, ProductModelAssembler assembler,
public ProductController(ProductService productService, GitHubService gitHubService, ProductModelAssembler assembler,
PagedResourcesAssembler<Product> pagedResourcesAssembler) {
this.productService = productService;
this.gitHubService = gitHubService;
this.assembler = assembler;
this.pagedResourcesAssembler = pagedResourcesAssembler;
}
Expand All @@ -54,7 +61,13 @@ public ResponseEntity<PagedModel<ProductModel>> findProducts(@RequestParam(name
}

@PutMapping(SYNC)
public ResponseEntity<Message> syncProducts() {
public ResponseEntity<Message> syncProducts(@RequestHeader(value = "Authorization") String authorizationHeader) {
String token = null;
if (authorizationHeader.startsWith("Bearer ")) {
token = authorizationHeader.substring(7); // Remove "Bearer " prefix
}
gitHubService.validateUserOrganization(token, GitHubConstants.AXONIVY_MARKET_ORGANIZATION_NAME);

var stopWatch = new StopWatch();
stopWatch.start();
var isAlreadyUpToDate = productService.syncLatestDataFromMarketRepo();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ public enum ErrorCode {
PRODUCT_SORT_INVALID("1102", "PRODUCT_SORT_INVALID"), PRODUCT_NOT_FOUND("1103", "PRODUCT_NOT_FOUND"),
GH_FILE_STATUS_INVALID("0201", "GIT_HUB_FILE_STATUS_INVALID"),
GH_FILE_TYPE_INVALID("0202", "GIT_HUB_FILE_TYPE_INVALID"), USER_NOT_FOUND("2103", "USER_NOT_FOUND"),
GITHUB_USER_NOT_FOUND("2204", "GITHUB_USER_NOT_FOUND"), FEEDBACK_NOT_FOUND("3103", "FEEDBACK_NOT_FOUND"),
ARGUMENT_BAD_REQUEST("4000", "ARGUMENT_BAD_REQUEST");
GITHUB_USER_NOT_FOUND("2204", "GITHUB_USER_NOT_FOUND"), GITHUB_USER_UNAUTHORIZED("2205", "GITHUB_USER_UNAUTHORIZED"),
FEEDBACK_NOT_FOUND("3103", "FEEDBACK_NOT_FOUND"), ARGUMENT_BAD_REQUEST("4000", "ARGUMENT_BAD_REQUEST");

String code;
String helpText;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import com.axonivy.market.exceptions.model.MissingHeaderException;
import com.axonivy.market.exceptions.model.NotFoundException;
import com.axonivy.market.exceptions.model.Oauth2ExchangeCodeException;
import com.axonivy.market.exceptions.model.UnauthorizedException;
import com.axonivy.market.model.Message;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
Expand Down Expand Up @@ -74,4 +75,13 @@ public ResponseEntity<Object> handleOauth2ExchangeCodeException(
errorMessage.setMessageDetails(oauth2ExchangeCodeException.getErrorDescription());
return new ResponseEntity<>(errorMessage, HttpStatus.BAD_REQUEST);
}
@ExceptionHandler(UnauthorizedException.class)
public ResponseEntity<Object> handleUnauthorizedException(
UnauthorizedException unauthorizedException) {
var errorMessage = new Message();
errorMessage.setHelpCode(unauthorizedException.getError());
errorMessage.setMessageDetails(unauthorizedException.getMessage());
return new ResponseEntity<>(errorMessage, HttpStatus.UNAUTHORIZED);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.axonivy.market.exceptions.model;

import com.axonivy.market.enums.ErrorCode;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.Setter;

import java.io.Serial;

@Getter
@Setter
@AllArgsConstructor
public class UnauthorizedException extends RuntimeException {

@Serial
private static final long serialVersionUID = 6778659812221728814L;

private final String error;
private final String message;
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.axonivy.market.model;
package com.axonivy.market.github.model;

import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
package com.axonivy.market.github.service;

import com.axonivy.market.entity.User;
import com.axonivy.market.model.GitHubAccessTokenResponse;
import com.axonivy.market.exceptions.model.UnauthorizedException;
import com.axonivy.market.github.model.GitHubAccessTokenResponse;
import org.kohsuke.github.GHContent;
import org.kohsuke.github.GHOrganization;
import org.kohsuke.github.GHRepository;
Expand All @@ -25,4 +26,6 @@ public interface GitHubService {
GitHubAccessTokenResponse getAccessToken(String code, String clientId, String clientSecret);

User getAndUpdateUser(String accessToken);

void validateUserOrganization(String accessToken, String organization) throws UnauthorizedException;
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@
import com.axonivy.market.enums.ErrorCode;
import com.axonivy.market.exceptions.model.NotFoundException;
import com.axonivy.market.exceptions.model.Oauth2ExchangeCodeException;
import com.axonivy.market.exceptions.model.UnauthorizedException;
import com.axonivy.market.github.model.GitHubAccessTokenResponse;
import com.axonivy.market.github.service.GitHubService;
import com.axonivy.market.model.GitHubAccessTokenResponse;
import com.axonivy.market.github.util.GitHubUtils;
import com.axonivy.market.repository.UserRepository;
import org.kohsuke.github.GHContent;
import org.kohsuke.github.GHOrganization;
Expand All @@ -25,6 +27,7 @@
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.util.ResourceUtils;
import org.springframework.web.client.HttpClientErrorException;
import org.springframework.web.client.RestTemplate;

import java.io.File;
Expand Down Expand Up @@ -105,7 +108,7 @@ public User getAndUpdateUser(String accessToken) {
headers.setBearerAuth(accessToken);
HttpEntity<String> entity = new HttpEntity<>(headers);

ResponseEntity<Map<String, Object>> response = restTemplate.exchange("https://api.github.com/user", HttpMethod.GET,
ResponseEntity<Map<String, Object>> response = restTemplate.exchange(GitHubConstants.Url.USER, HttpMethod.GET,
entity, new ParameterizedTypeReference<>() {
});

Expand Down Expand Up @@ -134,4 +137,33 @@ public User getAndUpdateUser(String accessToken) {

return user;
}

@Override
public void validateUserOrganization(String accessToken, String organization) throws UnauthorizedException {
List<Map<String, Object>> userOrganizations = getUserOrganizations(accessToken);
for (var org : userOrganizations) {
if (org.get("login").equals(organization)) {
return;
}
}
throw new UnauthorizedException(ErrorCode.GITHUB_USER_UNAUTHORIZED.getCode(),
ErrorCode.GITHUB_USER_UNAUTHORIZED.getHelpText()
+ "-User must be a member of the Axon Ivy Market Organization");
}

private List<Map<String, Object>> getUserOrganizations(String accessToken) throws UnauthorizedException {
HttpHeaders headers = new HttpHeaders();
headers.setBearerAuth(accessToken);
HttpEntity<String> entity = new HttpEntity<>(headers);
try {
ResponseEntity<List<Map<String, Object>>> response = restTemplate.exchange(GitHubConstants.Url.USER_ORGS,
HttpMethod.GET, entity, new ParameterizedTypeReference<>() {
});
return response.getBody();
}
catch (HttpClientErrorException exception) {
throw new UnauthorizedException(ErrorCode.GITHUB_USER_UNAUTHORIZED.getCode(),
ErrorCode.GITHUB_USER_UNAUTHORIZED.getHelpText() + "-" + GitHubUtils.extractMessageFromExceptionMessage(exception.getMessage()));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import lombok.NoArgsConstructor;
import lombok.extern.log4j.Log4j2;
import org.apache.commons.lang3.StringUtils;
import org.bson.json.JsonObject;
import org.kohsuke.github.GHCommit;
import org.kohsuke.github.GHContent;
import org.kohsuke.github.PagedIterable;
Expand Down Expand Up @@ -130,4 +131,27 @@ public static String getNonStandardImageFolder(String productId) {
}
return pathToImageFolder;
}

public static String extractMessageFromExceptionMessage(String exceptionMessage) {
String json = extractJson(exceptionMessage);
String key = "\"message\":\"";
int startIndex = json.indexOf(key);
if (startIndex != -1) {
startIndex += key.length();
int endIndex = json.indexOf("\"", startIndex);
if (endIndex != -1) {
return json.substring(startIndex, endIndex);
}
}
return "";
}

private static String extractJson(String text) {
int start = text.indexOf("{");
int end = text.lastIndexOf("}") + 1;
if (start != -1 && end != -1) {
return text.substring(start, end);
}
return "";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import com.axonivy.market.entity.User;
import com.axonivy.market.github.service.GitHubService;
import com.axonivy.market.model.GitHubAccessTokenResponse;
import com.axonivy.market.github.model.GitHubAccessTokenResponse;
import com.axonivy.market.model.Oauth2AuthorizationCode;
import com.axonivy.market.service.JwtService;
import org.junit.jupiter.api.BeforeEach;
Expand Down

0 comments on commit f271d95

Please sign in to comment.