diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..fc47934 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,27 @@ +name: Build Verification + +on: + push: + +jobs: + build: + runs-on: ubuntu-latest + strategy: + matrix: + java-version: [17, 21, 23] + + steps: + - name: Checkout code + uses: actions/checkout@v2 + + - name: Set up JDK ${{ matrix.java-version }} + uses: actions/setup-java@v2 + with: + java-version: ${{ matrix.java-version }} + distribution: 'temurin' + + - name: Grant execute permission for gradlew + run: chmod +x gradlew + + - name: Build with JDK ${{ matrix.java-version }} + run: ./gradlew build diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index ba79d55..db168cc 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -1,13 +1,15 @@ name: Publish to Maven Central Repository on: - push: - branches: - - master + workflow_run: + workflows: ["Verify"] + types: + - completed jobs: publish: runs-on: ubuntu-latest + if: ${{ github.event.workflow_run.conclusion == 'success' && github.ref == 'refs/heads/master' }} steps: - name: Checkout code @@ -52,4 +54,4 @@ jobs: GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} GPG_KEY_ID: ${{ secrets.GPG_KEY_ID }} run: | - ./gradlew publishAllPublicationsToMavenCentralRepository --stacktrace --debug \ No newline at end of file + ./gradlew publishAllPublicationsToMavenCentralRepository --stacktrace --debug diff --git a/.gitignore b/.gitignore index f13fb54..9931ea6 100644 --- a/.gitignore +++ b/.gitignore @@ -41,3 +41,4 @@ out test-output atlassian-ide-plugin.xml .gradletasknamecache +/src/test/java/io/codef/api/EasyCodefTokenTest.java diff --git a/README.md b/README.md index 946097b..9af3b91 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,5 @@ +

EasyCodef Java V2

+

@@ -9,31 +11,30 @@
-# easycodef-java-v2 - -`easycodef-java-v2`는 codef API를 더욱 더 편리하게 연동할 수 있도록 돕는 오픈소스 라이브러리입니다. +`easycodef-java-v2`는 codef API를 JDK 환경에서 더욱 더 편리하게 연동할 수 있도록 돕는 오픈소스 라이브러리입니다. 현재 알파 버전 개발중으로 v2.0.0-ALPHA-002 버전으로 Maven Central Repository를 통해 배포중입니다. -## Release +2024년 상반기 실제 고객사 대상으로 릴리즈 예정입니다. -


+

+ +## Release [![Build Status](https://img.shields.io/github/actions/workflow/status/codef-io/easycodef-java-v2/publish.yml?style=for-the-badge&logo=gradle&color=02303A)](https://github.com/codef-io/easycodef-java-v2/actions?query=branch%3Amaster)
[![Last Commit](https://img.shields.io/github/last-commit/codef-io/easycodef-java-v2/master?style=for-the-badge&label=LAST%20BUILD&logo=Github&color=181717)](https://github.com/codef-io/easycodef-java-v2)
[![Maven Central](https://img.shields.io/maven-central/v/io.codef.api/easycodef-java-v2.svg?style=for-the-badge&label=Maven%20Central&logo=apache-maven&color=C71A36)](https://central.sonatype.com/artifact/io.codef.api/easycodef-java-v2)
- -### Snippets +## Snippets - Gradle(Kotlin) ```gradle - implementation("io.codef.api:easycodef-java-v2:2.0.0-alpha-002") + implementation("io.codef.api:easycodef-java-v2:2.0.0-alpha-003") ``` - Gradle(short) ```gradle - implementation 'io.codef.api:easycodef-java-v2:2.0.0-alpha-002' + implementation 'io.codef.api:easycodef-java-v2:2.0.0-alpha-003' ``` - Maven @@ -41,12 +42,40 @@ io.codef.api easycodef-java-v2 - 2.0.0-alpha-002 + 2.0.0-alpha-003 ``` -## LISENCE +## Get It ! -헥토데이터 +- 예제 코드 + ```java + EasyCodef easyCodef = EasyCodefBuilder.builder() + .clientType(CodefClientType.DEMO) + .clientId("your-client-id") + .clientSecret("your-client-secret") + .publicKey("your-public-key") + .build(); + + EasyCodefRequest request = EasyCodefRequestBuilder.builder() + .path("/v1/kr/public/hw/nip-cdc-list/my-vaccination") + .organization("0011") + .requestBody("loginType", "1") + .requestBody("userId", "your-nhis-id") + .secureRequestBody("userPassword", "your-nhis-password") + .secureWith(easyCodef) + .build(); + + EasyCodefResponse easyCodefResponse = easyCodef.requestProduct(request); + + final EasyCodefResponse.Result result = easyCodefResponse.result(); + final Object data = easyCodefResponse.data(); + ``` + +--- + +

+헥토데이터
+MIT © | LICENSE +

-MIT © Hectodata Co,. Ltd [LICENSE](https://github.com/codef-io/easycodef-java-v2/blob/master/LICENSE) 파일을 참고하세요. \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts index e818dc4..bab98e5 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -10,7 +10,7 @@ plugins { group = "io.codef.api" -version = "2.0.0-alpha-002" +version = "2.0.0-alpha-003" signing { useInMemoryPgpKeys( @@ -72,6 +72,11 @@ dependencies { */ implementation("com.fasterxml.jackson.core:jackson-databind:2.18.1") + /** + * 2024-10-17 Latest + */ + implementation("com.alibaba:fastjson:2.0.53") + /** * 2024-06-12 Latest * https://mvnrepository.com/artifact/commons-codec/commons-codec diff --git a/src/main/java/io/codef/api/CodefError.java b/src/main/java/io/codef/api/CodefError.java deleted file mode 100644 index d33f05a..0000000 --- a/src/main/java/io/codef/api/CodefError.java +++ /dev/null @@ -1,45 +0,0 @@ -package io.codef.api; - -public enum CodefError { - INVALID_CLIENT_ID( - "clientId must be a properly formatted UUID string. Please check your clientId and ensure it matches the UUID format.", - EasyCodefReferenceUrl.KEY - ), - INVALID_CLIENT_SECRET( - "clientSecret must be a properly formatted UUID string. Please check your clientSecret and ensure it matches the UUID format.", - EasyCodefReferenceUrl.KEY - ), - INVALID_PUBLIC_KEY( - "publicKey is required and cannot be null.", - EasyCodefReferenceUrl.KEY - ), - OAUTH_UNAUTHORIZED( - "Failed to authenticate with the Codef OAuth server. Please verify your clientId and clientSecret values.", - EasyCodefReferenceUrl.KEY - ), - OAUTH_INTERNAL_ERROR( - "An error occurred on the Codef OAuth server (500 Internal Server Error). Please try again later, or contact support if the issue persists.", - EasyCodefReferenceUrl.KEY - ), - OAUTH_CONNECTION_ERROR( - "The connection to the OAUTH server failed. Please check if `https://oauth.codef.io` is accessible.", - EasyCodefReferenceUrl.DEV_GUIDE_REST_API - ); - - private final String message; - private final EasyCodefReferenceUrl referenceUrl; - - CodefError( - String message, - EasyCodefReferenceUrl referenceUrl - ) { - this.message = message; - this.referenceUrl = referenceUrl; - } - - private static final String MESSAGE_FORMAT = "[EasyCodef] %s\n%s"; - - public String getMessage() { - return String.format(MESSAGE_FORMAT, message, referenceUrl.getUrl()); - } -} \ No newline at end of file diff --git a/src/main/java/io/codef/api/CodefUrl.java b/src/main/java/io/codef/api/CodefUrl.java deleted file mode 100644 index 4506d2d..0000000 --- a/src/main/java/io/codef/api/CodefUrl.java +++ /dev/null @@ -1,5 +0,0 @@ -package io.codef.api; - -public class CodefUrl { - public static final String CODEF_OAUTH_SERVER = "https://oauth.codef.io"; -} diff --git a/src/main/java/io/codef/api/CodefValidator.java b/src/main/java/io/codef/api/CodefValidator.java index 6079c18..757237a 100644 --- a/src/main/java/io/codef/api/CodefValidator.java +++ b/src/main/java/io/codef/api/CodefValidator.java @@ -1,9 +1,12 @@ package io.codef.api; +import io.codef.api.error.CodefError; +import io.codef.api.error.CodefException; + import java.util.Optional; import java.util.UUID; -public class CodefValidator { +final class CodefValidator { private CodefValidator() { } diff --git a/src/main/java/io/codef/api/EasyCodef.java b/src/main/java/io/codef/api/EasyCodef.java index cf721e7..afb505e 100644 --- a/src/main/java/io/codef/api/EasyCodef.java +++ b/src/main/java/io/codef/api/EasyCodef.java @@ -1,16 +1,38 @@ package io.codef.api; +import io.codef.api.constants.CodefClientType; +import io.codef.api.dto.EasyCodefRequest; +import io.codef.api.dto.EasyCodefResponse; +import io.codef.api.util.RsaUtil; -public class EasyCodef { +import java.security.PublicKey; - private EasyCodefToken easyCodefToken; - private EasyCodefProperty property; +public class EasyCodef { + private final PublicKey publicKey; + private final CodefClientType clientType; + private final EasyCodefToken easyCodefToken; protected EasyCodef( - EasyCodefToken easyCodefToken, - EasyCodefProperty property + EasyCodefBuilder builder, + EasyCodefToken easyCodefToken ) { + this.publicKey = RsaUtil.generatePublicKey(builder.getPublicKey()); + this.clientType = builder.getClientType(); this.easyCodefToken = easyCodefToken; - this.property = property; + } + + public EasyCodefResponse requestProduct(EasyCodefRequest request) { + final String requestUrl = generateRequestUrl(request); + easyCodefToken.validateAndRefreshToken(); + + return EasyCodefConnector.requestProduct(request, easyCodefToken, requestUrl); + } + + private String generateRequestUrl(EasyCodefRequest request) { + return clientType.getHost() + request.path(); + } + + public PublicKey getPublicKey() { + return publicKey; } } \ No newline at end of file diff --git a/src/main/java/io/codef/api/EasyCodefBuilder.java b/src/main/java/io/codef/api/EasyCodefBuilder.java new file mode 100644 index 0000000..f173e6f --- /dev/null +++ b/src/main/java/io/codef/api/EasyCodefBuilder.java @@ -0,0 +1,79 @@ +package io.codef.api; + +import io.codef.api.constants.CodefClientType; +import io.codef.api.error.CodefError; +import io.codef.api.error.CodefException; + +import java.util.UUID; + +public class EasyCodefBuilder { + private String publicKey; + private UUID clientId; + private UUID clientSecret; + private CodefClientType clientType; + + public static EasyCodefBuilder builder() { + return new EasyCodefBuilder(); + } + + public EasyCodefBuilder publicKey(String publicKey) { + this.publicKey = CodefValidator.requireNonNullElseThrow(publicKey, CodefError.INVALID_PUBLIC_KEY); + return this; + } + + public EasyCodefBuilder clientId(String clientId) { + this.clientId = parseUUID(clientId, CodefError.INVALID_CLIENT_ID); + return this; + } + + public EasyCodefBuilder clientSecret(String clientSecret) { + this.clientSecret = parseUUID(clientSecret, CodefError.INVALID_CLIENT_SECRET); + return this; + } + + public EasyCodefBuilder clientType(CodefClientType clientType) { + this.clientType = clientType; + return this; + } + + public EasyCodef build() { + validatePropertyArguments(); + EasyCodefToken easyCodefToken = new EasyCodefToken(this); + + return new EasyCodef(this, easyCodefToken); + } + + private void validatePropertyArguments() { + CodefValidator.requireNonNullElseThrow(publicKey, CodefError.NULL_PUBLIC_KEY); + CodefValidator.requireNonNullElseThrow(clientId, CodefError.NULL_CLIENT_ID); + CodefValidator.requireNonNullElseThrow(clientSecret, CodefError.NULL_CLIENT_SECRET); + CodefValidator.requireNonNullElseThrow(clientType, CodefError.NULL_CLIENT_TYPE); + } + + private UUID parseUUID(String value, CodefError error) { + CodefValidator.requireNonNullElseThrow(value, error); + CodefValidator.requireValidUUIDPattern(value, error); + + try { + return UUID.fromString(value); + } catch (Exception exception) { + throw CodefException.of(error, exception); + } + } + + protected String getPublicKey() { + return publicKey; + } + + protected UUID getClientId() { + return clientId; + } + + protected UUID getClientSecret() { + return clientSecret; + } + + protected CodefClientType getClientType() { + return clientType; + } +} \ No newline at end of file diff --git a/src/main/java/io/codef/api/EasyCodefConnector.java b/src/main/java/io/codef/api/EasyCodefConnector.java index 4229a28..0cce18a 100644 --- a/src/main/java/io/codef/api/EasyCodefConnector.java +++ b/src/main/java/io/codef/api/EasyCodefConnector.java @@ -1,20 +1,34 @@ package io.codef.api; +import com.alibaba.fastjson2.JSON; +import com.alibaba.fastjson2.JSONObject; +import io.codef.api.constants.CodefHost; +import io.codef.api.constants.CodefPath; +import io.codef.api.dto.EasyCodefRequest; +import io.codef.api.dto.EasyCodefResponse; +import io.codef.api.error.CodefError; +import io.codef.api.error.CodefException; import org.apache.hc.client5.http.classic.methods.HttpPost; import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; import org.apache.hc.client5.http.impl.classic.HttpClientBuilder; import org.apache.hc.core5.http.io.entity.EntityUtils; +import org.apache.hc.core5.http.io.entity.StringEntity; + +import java.net.URLDecoder; import static org.apache.hc.client5.http.auth.StandardAuthScheme.BASIC; +import static org.apache.hc.client5.http.auth.StandardAuthScheme.BEARER; import static org.apache.hc.core5.http.HttpHeaders.AUTHORIZATION; final class EasyCodefConnector { static String issueToken(String codefOAuthToken) { + System.out.println("issue Token !!!\n\n"); final String BASIC_TOKEN_FORMAT = BASIC + " %s"; + final String accessTokenParameter = "access_token"; try (CloseableHttpClient httpClient = HttpClientBuilder.create().build()) { - HttpPost httpPost = new HttpPost(CodefUrl.CODEF_OAUTH_SERVER + CodefUri.ISSUE_TOKEN); + HttpPost httpPost = new HttpPost(CodefHost.CODEF_OAUTH_SERVER + CodefPath.ISSUE_TOKEN); httpPost.addHeader(AUTHORIZATION, String.format(BASIC_TOKEN_FORMAT, codefOAuthToken)); return httpClient.execute(httpPost, response -> { @@ -29,8 +43,42 @@ static String issueToken(String codefOAuthToken) { } String httpResponse = EntityUtils.toString(response.getEntity()); - return ObjectMapperUtil.getInstance().readTree(httpResponse).path("access_token").asText(); + + return JSON.parseObject(httpResponse).getString(accessTokenParameter); + }); + } catch (CodefException exception) { + throw exception; + } catch (Exception exception) { + throw CodefException.of(CodefError.OAUTH_CONNECTION_ERROR, exception); + } + } + + static EasyCodefResponse requestProduct( + EasyCodefRequest request, + EasyCodefToken token, + String requestUrl + ) { + final String BEARER_TOKEN_FORMAT = BEARER + " %s"; + + try (CloseableHttpClient httpClient = HttpClientBuilder.create().build()) { + HttpPost httpPost = new HttpPost(requestUrl); + httpPost.addHeader(AUTHORIZATION, String.format(BEARER_TOKEN_FORMAT, token.getAccessToken())); + String rawRequest = JSON.toJSONString(request.requestParams()); + httpPost.setEntity(new StringEntity(rawRequest)); + + return httpClient.execute(httpPost, response -> { + String httpResponse = EntityUtils.toString(response.getEntity()); + String decodedResponse = URLDecoder.decode(httpResponse, "UTF-8"); + + // TODO {"error":"invalid_token","error_description":"Cannot convert access token to JSON","code":"CF-09990","message":"OAUTH2.0 토큰 에러입니다. 메시지를 확인하세요."} + JSONObject jsonResponseObject = JSON.parseObject(decodedResponse); + + EasyCodefResponse.Result resultResponse = jsonResponseObject.getJSONObject("result").to(EasyCodefResponse.Result.class); + Object dataResponse = jsonResponseObject.getJSONObject("data").to(Object.class); + return new EasyCodefResponse(resultResponse, dataResponse); }); + } catch (CodefException exception) { + throw exception; } catch (Exception exception) { throw CodefException.of(CodefError.OAUTH_CONNECTION_ERROR, exception); } diff --git a/src/main/java/io/codef/api/EasyCodefProperty.java b/src/main/java/io/codef/api/EasyCodefProperty.java deleted file mode 100644 index f0c83a1..0000000 --- a/src/main/java/io/codef/api/EasyCodefProperty.java +++ /dev/null @@ -1,61 +0,0 @@ -package io.codef.api; - -import java.util.UUID; - -public class EasyCodefProperty { - - private String publicKey; - private UUID clientId; - private UUID clientSecret; - - public static EasyCodefProperty builder() { - return new EasyCodefProperty(); - } - - public EasyCodefProperty publicKey(String publicKey) { - this.publicKey = CodefValidator.requireNonNullElseThrow(publicKey, CodefError.INVALID_PUBLIC_KEY); - return this; - } - - public EasyCodefProperty clientId(String clientId) { - this.clientId = parseUUID(clientId, CodefError.INVALID_CLIENT_ID); - return this; - } - - public EasyCodefProperty clientSecret(String clientSecret) { - this.clientSecret = parseUUID(clientSecret, CodefError.INVALID_CLIENT_SECRET); - return this; - } - - public EasyCodef build() { - CodefValidator.requireNonNullElseThrow(publicKey, CodefError.INVALID_PUBLIC_KEY); - CodefValidator.requireNonNullElseThrow(clientId, CodefError.INVALID_CLIENT_ID); - CodefValidator.requireNonNullElseThrow(clientSecret, CodefError.INVALID_CLIENT_SECRET); - - EasyCodefToken easyCodefToken = EasyCodefToken.of(this); - return new EasyCodef(easyCodefToken, this); - } - - private UUID parseUUID( - String value, - CodefError error - ) { - CodefValidator.requireNonNullElseThrow(value, error); - CodefValidator.requireValidUUIDPattern(value, error); - - try { - return UUID.fromString(value); - } catch (Exception exception) { - throw CodefException.of(error, exception); - } - } - - protected String getPublicKey() { - return publicKey; - } - - protected String getCombinedKey() { - final String KEY_FORMAT = "%s:%s"; - return String.format(KEY_FORMAT, clientId, clientSecret); - } -} \ No newline at end of file diff --git a/src/main/java/io/codef/api/EasyCodefServiceType.java b/src/main/java/io/codef/api/EasyCodefServiceType.java deleted file mode 100644 index f3cd5fb..0000000 --- a/src/main/java/io/codef/api/EasyCodefServiceType.java +++ /dev/null @@ -1,12 +0,0 @@ -package io.codef.api; - -public enum EasyCodefServiceType { - API("https://api.codef.io"), - DEMO("https://development.codef.io"); - - private final String uri; - - EasyCodefServiceType(String uri) { - this.uri = uri; - } -} diff --git a/src/main/java/io/codef/api/EasyCodefToken.java b/src/main/java/io/codef/api/EasyCodefToken.java index 9ecb959..47ff82c 100644 --- a/src/main/java/io/codef/api/EasyCodefToken.java +++ b/src/main/java/io/codef/api/EasyCodefToken.java @@ -1,26 +1,32 @@ package io.codef.api; +import java.time.LocalDateTime; import java.util.Base64; public class EasyCodefToken { + private final String oauthToken; + private String accessToken; + private LocalDateTime expiresAt; - private String codefOAuthToken; - private String codefAccessToken; - private EasyCodefServiceType serviceType; + protected EasyCodefToken(EasyCodefBuilder builder) { + final int VALIDITY_PERIOD_DAYS = 7; + final String DELIMITER = ":"; - private EasyCodefToken( - EasyCodefProperty property - ) { - this.codefOAuthToken = generateBase64OAuthToken(property); - this.codefAccessToken = EasyCodefConnector.issueToken(codefOAuthToken); + String combinedKey = String.join(DELIMITER, builder.getClientId().toString(), builder.getClientSecret().toString()); + this.oauthToken = Base64.getEncoder().encodeToString(combinedKey.getBytes()); + this.accessToken = EasyCodefConnector.issueToken(oauthToken); + this.expiresAt = LocalDateTime.now().plusDays(VALIDITY_PERIOD_DAYS); } - protected static EasyCodefToken of(EasyCodefProperty property) { - return new EasyCodefToken(property); + public EasyCodefToken validateAndRefreshToken() { + if (expiresAt.isBefore(LocalDateTime.now().plusHours(24))) { + this.accessToken = EasyCodefConnector.issueToken(oauthToken); + this.expiresAt = LocalDateTime.now().plusDays(7); + } + return this; } - private static String generateBase64OAuthToken(EasyCodefProperty property) { - String combinedKey = property.getCombinedKey(); - return Base64.getEncoder().encodeToString(combinedKey.getBytes()); + public String getAccessToken() { + return accessToken; } } diff --git a/src/main/java/io/codef/api/ObjectMapperUtil.java b/src/main/java/io/codef/api/ObjectMapperUtil.java deleted file mode 100644 index c56e29d..0000000 --- a/src/main/java/io/codef/api/ObjectMapperUtil.java +++ /dev/null @@ -1,17 +0,0 @@ -package io.codef.api; - -import com.fasterxml.jackson.databind.ObjectMapper; - -public class ObjectMapperUtil { - - private ObjectMapperUtil() { - } - - private static class ObjectMapperInitializer { - private static final ObjectMapper INSTANCE = new ObjectMapper(); - } - - public static ObjectMapper getInstance() { - return ObjectMapperInitializer.INSTANCE; - } -} diff --git a/src/main/java/io/codef/api/constants/CodefClientType.java b/src/main/java/io/codef/api/constants/CodefClientType.java new file mode 100644 index 0000000..65e112f --- /dev/null +++ b/src/main/java/io/codef/api/constants/CodefClientType.java @@ -0,0 +1,16 @@ +package io.codef.api.constants; + +public enum CodefClientType { + API(CodefHost.CODEF_API), + DEMO(CodefHost.CODEF_API_DEMO); + + private final String host; + + CodefClientType(String host) { + this.host = host; + } + + public String getHost() { + return host; + } +} diff --git a/src/main/java/io/codef/api/constants/CodefHost.java b/src/main/java/io/codef/api/constants/CodefHost.java new file mode 100644 index 0000000..d7c8fba --- /dev/null +++ b/src/main/java/io/codef/api/constants/CodefHost.java @@ -0,0 +1,7 @@ +package io.codef.api.constants; + +public final class CodefHost { + public static final String CODEF_OAUTH_SERVER = "https://oauth.codef.io"; + public static final String CODEF_API_DEMO = "https://development.codef.io"; + public static final String CODEF_API = "https://api.codef.io"; +} diff --git a/src/main/java/io/codef/api/CodefUri.java b/src/main/java/io/codef/api/constants/CodefPath.java similarity index 62% rename from src/main/java/io/codef/api/CodefUri.java rename to src/main/java/io/codef/api/constants/CodefPath.java index ff6b787..fcb55a0 100644 --- a/src/main/java/io/codef/api/CodefUri.java +++ b/src/main/java/io/codef/api/constants/CodefPath.java @@ -1,5 +1,5 @@ -package io.codef.api; +package io.codef.api.constants; -public class CodefUri { +public final class CodefPath { public static final String ISSUE_TOKEN = "/oauth/token?grant_type=client_credentials&scope=read"; } diff --git a/src/main/java/io/codef/api/EasyCodefReferenceUrl.java b/src/main/java/io/codef/api/constants/CodefReferenceUrl.java similarity index 61% rename from src/main/java/io/codef/api/EasyCodefReferenceUrl.java rename to src/main/java/io/codef/api/constants/CodefReferenceUrl.java index 7e10252..483c302 100644 --- a/src/main/java/io/codef/api/EasyCodefReferenceUrl.java +++ b/src/main/java/io/codef/api/constants/CodefReferenceUrl.java @@ -1,12 +1,14 @@ -package io.codef.api; +package io.codef.api.constants; -public enum EasyCodefReferenceUrl { +public enum CodefReferenceUrl { KEY("https://codef.io/account/keys"), - DEV_GUIDE_REST_API("https://developer.codef.io/common-guide/rest-api"); + DEV_GUIDE_REST_API("https://developer.codef.io/common-guide/rest-api"), + GITHUB("https://github.com/codef-io/easycodef-java-v2"), + PRODUCT("https://developer.codef.io/product/api"); private final String url; - EasyCodefReferenceUrl(String url) { + CodefReferenceUrl(String url) { this.url = url; } diff --git a/src/main/java/io/codef/api/dto/EasyCodefRequest.java b/src/main/java/io/codef/api/dto/EasyCodefRequest.java new file mode 100644 index 0000000..11319b3 --- /dev/null +++ b/src/main/java/io/codef/api/dto/EasyCodefRequest.java @@ -0,0 +1,9 @@ +package io.codef.api.dto; + +import java.util.HashMap; + +public record EasyCodefRequest( + String path, + HashMap requestParams +) { +} \ No newline at end of file diff --git a/src/main/java/io/codef/api/dto/EasyCodefRequestBuilder.java b/src/main/java/io/codef/api/dto/EasyCodefRequestBuilder.java new file mode 100644 index 0000000..509d793 --- /dev/null +++ b/src/main/java/io/codef/api/dto/EasyCodefRequestBuilder.java @@ -0,0 +1,88 @@ +package io.codef.api.dto; + +import io.codef.api.EasyCodef; +import io.codef.api.error.CodefError; +import io.codef.api.error.CodefException; +import io.codef.api.util.RsaUtil; + +import java.util.HashMap; + +public class EasyCodefRequestBuilder { + + private final HashMap generalRequestBody; + private final HashMap secureRequestBody; + private String path; + private EasyCodef easyCodef; + + private EasyCodefRequestBuilder() { + this.generalRequestBody = new HashMap<>(); + this.secureRequestBody = new HashMap<>(); + } + + public static EasyCodefRequestBuilder builder() { + return new EasyCodefRequestBuilder(); + } + + public EasyCodefRequestBuilder organization(Object value) { + generalRequestBody.put("organization", value); + return this; + } + + public EasyCodefRequestBuilder path(String path) { + this.path = path; + + if(!path.startsWith("/v1")) { + throw CodefException.from(CodefError.INVALID_PATH_REQUESTED); + } + return this; + } + + public EasyCodefRequestBuilder requestBody( + String param, + Object value + ) { + generalRequestBody.put(param, value); + return this; + } + + public EasyCodefRequestBuilder secureRequestBody( + String param, + String value + ) { + secureRequestBody.put(param, value); + return this; + } + + public EasyCodefRequestBuilder secureWith(EasyCodef easyCodef) { + this.easyCodef = easyCodef; + return this; + } + + public EasyCodefRequest build() { + final HashMap requests = new HashMap<>(); + final String EASY_CODEF_JAVA_FLAG = "easyCodefJavaV2"; + + if (!secureRequestBody.isEmpty()) { + if (easyCodef == null) { + throw CodefException.from(CodefError.NEED_TO_SECURE_WITH_METHOD); + } else { + secureRequestBody.forEach((key, value) -> { + String encryptedValue = RsaUtil.encryptRSA(value, easyCodef.getPublicKey()); + secureRequestBody.put(key, encryptedValue); + }); + } + } + + if (path == null) { + throw CodefException.from(CodefError.NEED_TO_PATH_METHOD); + } + + if (generalRequestBody.get("organization") == null) { + throw CodefException.from(CodefError.NEED_TO_ORGANIZATION_METHOD); + } + + this.requestBody(EASY_CODEF_JAVA_FLAG, true); + this.generalRequestBody.putAll(secureRequestBody); + return new EasyCodefRequest(path, generalRequestBody); + } +} diff --git a/src/main/java/io/codef/api/dto/EasyCodefResponse.java b/src/main/java/io/codef/api/dto/EasyCodefResponse.java new file mode 100644 index 0000000..2a91a08 --- /dev/null +++ b/src/main/java/io/codef/api/dto/EasyCodefResponse.java @@ -0,0 +1,15 @@ +package io.codef.api.dto; + +public record EasyCodefResponse( + Result result, + Object data +) { + + public record Result( + String code, + String extraMessage, + String message, + String transactionId + ) { + } +} \ No newline at end of file diff --git a/src/main/java/io/codef/api/error/CodefError.java b/src/main/java/io/codef/api/error/CodefError.java new file mode 100644 index 0000000..b99c9de --- /dev/null +++ b/src/main/java/io/codef/api/error/CodefError.java @@ -0,0 +1,83 @@ +package io.codef.api.error; + +import io.codef.api.constants.CodefReferenceUrl; + +public enum CodefError { + INVALID_CLIENT_ID( + "clientId must be a properly formatted UUID string. Please check your clientId and ensure it matches the UUID format.", + CodefReferenceUrl.KEY + ), + INVALID_CLIENT_SECRET( + "clientSecret must be a properly formatted UUID string. Please check your clientSecret and ensure it matches the UUID format.", + CodefReferenceUrl.KEY + ), + INVALID_PUBLIC_KEY( + "publicKey is required and cannot be null.", + CodefReferenceUrl.KEY + ), + NULL_CLIENT_ID( + "clientId is required and cannot be null.", + CodefReferenceUrl.KEY + ), + NULL_CLIENT_SECRET( + "clientSecret is required and cannot be null.", + CodefReferenceUrl.KEY + ), + NULL_PUBLIC_KEY( + "publicKey is required and cannot be null.", + CodefReferenceUrl.KEY + ), + NULL_CLIENT_TYPE( + "clientType is required and cannot be null.", + CodefReferenceUrl.KEY + ), + OAUTH_UNAUTHORIZED( + "Failed to authenticate with the Codef OAuth server (401 Unauthorized). Please verify your clientId and clientSecret values.", + CodefReferenceUrl.KEY + ), + OAUTH_INTERNAL_ERROR( + "An error occurred on the Codef OAuth server (500 Internal Server Error). Please try again later, or contact support if the issue persists.", + CodefReferenceUrl.KEY + ), + OAUTH_CONNECTION_ERROR( + "The connection to the OAUTH server failed. Please check if `https://oauth.codef.io` is accessible.", + CodefReferenceUrl.DEV_GUIDE_REST_API + ), + RSA_ENCRYPTION_ERROR( + "An error occurred on RSA Encryption. Please check your publicKey", + CodefReferenceUrl.KEY + ), + NEED_TO_SECURE_WITH_METHOD( + "To encrypt the parameters, you must call the following method: EasyCodefRequestBuilder.builder().secureWith(easyCodef).", + CodefReferenceUrl.GITHUB + ), + NEED_TO_PATH_METHOD( + "To request codef product, you must call the following method: EasyCodefRequestBuilder.builder().path(\"/v1/kr/***/***...\").", + CodefReferenceUrl.GITHUB + ), + NEED_TO_ORGANIZATION_METHOD( + "To request codef product, you must call the following method: EasyCodefRequestBuilder.builder().organization(\"0xxx\").", + CodefReferenceUrl.GITHUB + ), + INVALID_PATH_REQUESTED( + "The path should be requested in the following format: `/v1/kr/***/***/...`", + CodefReferenceUrl.PRODUCT + ); + + private final String message; + private final CodefReferenceUrl referenceUrl; + + CodefError( + String message, + CodefReferenceUrl referenceUrl + ) { + this.message = message; + this.referenceUrl = referenceUrl; + } + + private static final String MESSAGE_FORMAT = "[EasyCodef] %s\n%s"; + + public String getMessage() { + return String.format(MESSAGE_FORMAT, message, referenceUrl.getUrl()); + } +} \ No newline at end of file diff --git a/src/main/java/io/codef/api/CodefException.java b/src/main/java/io/codef/api/error/CodefException.java similarity index 97% rename from src/main/java/io/codef/api/CodefException.java rename to src/main/java/io/codef/api/error/CodefException.java index a21f2b5..75a98b1 100644 --- a/src/main/java/io/codef/api/CodefException.java +++ b/src/main/java/io/codef/api/error/CodefException.java @@ -1,4 +1,4 @@ -package io.codef.api; +package io.codef.api.error; import java.io.Serial; diff --git a/src/main/java/io/codef/api/util/RsaUtil.java b/src/main/java/io/codef/api/util/RsaUtil.java new file mode 100644 index 0000000..36b4502 --- /dev/null +++ b/src/main/java/io/codef/api/util/RsaUtil.java @@ -0,0 +1,44 @@ +package io.codef.api.util; + +import io.codef.api.error.CodefError; +import io.codef.api.error.CodefException; + +import javax.crypto.Cipher; +import java.security.KeyFactory; +import java.security.PublicKey; +import java.security.spec.X509EncodedKeySpec; +import java.util.Base64; + +public class RsaUtil { + public static String encryptRSA(String plainText, PublicKey publicKey) { + try { + Cipher cipher = initializeCipher(publicKey); + + byte[] bytePlain = cipher.doFinal(plainText.getBytes()); + return Base64.getEncoder().encodeToString(bytePlain); + } catch (Exception exception) { + throw CodefException.of(CodefError.RSA_ENCRYPTION_ERROR, exception); + } + } + + public static PublicKey generatePublicKey(String publicKey) { + final byte[] decodedPublicKey = Base64.getDecoder().decode(publicKey); + + try { + KeyFactory keyFactory = KeyFactory.getInstance("RSA"); + return keyFactory.generatePublic(new X509EncodedKeySpec(decodedPublicKey)); + } catch (Exception exception) { + throw CodefException.of(CodefError.RSA_ENCRYPTION_ERROR, exception); + } + } + + private static Cipher initializeCipher(PublicKey key) { + try { + Cipher cipher = Cipher.getInstance("RSA"); + cipher.init(Cipher.ENCRYPT_MODE, key); + return cipher; + } catch (Exception exception) { + throw CodefException.of(CodefError.RSA_ENCRYPTION_ERROR, exception); + } + } +} diff --git a/src/test/java/io/codef/api/EasyCodefTokenTest.java b/src/test/java/io/codef/api/EasyCodefTokenTest.java deleted file mode 100644 index dfce55d..0000000 --- a/src/test/java/io/codef/api/EasyCodefTokenTest.java +++ /dev/null @@ -1,13 +0,0 @@ -package io.codef.api; - -import org.junit.jupiter.api.Test; - -/** - * EasyCodef 토큰 발급 사용예시 - */ -public class EasyCodefTokenTest { - - @Test - public void usageExample() { - } -}