diff --git a/dms-infrastructure/src/main/kotlin/team/aliens/dms/global/error/GlobalErrorCode.kt b/dms-infrastructure/src/main/kotlin/team/aliens/dms/global/error/GlobalErrorCode.kt index b7572cf60..28e3c386c 100644 --- a/dms-infrastructure/src/main/kotlin/team/aliens/dms/global/error/GlobalErrorCode.kt +++ b/dms-infrastructure/src/main/kotlin/team/aliens/dms/global/error/GlobalErrorCode.kt @@ -14,6 +14,7 @@ enum class GlobalErrorCode( BAD_REQUEST(ErrorStatus.BAD_REQUEST, "Bad Request", 3), INVALID_FILE(ErrorStatus.BAD_REQUEST, "Invalid File", 4), BAD_EXCEL_FORMAT(ErrorStatus.BAD_REQUEST, "%s행 등 %s개 행의 데이터 형식이 잘못되었습니다.", 5), + KEY_MANAGEMENT_SERVICE(ErrorStatus.BAD_REQUEST, "Key processing failed", 6), EXTENSION_MISMATCH(401, "File Extension Mismatch", 1), diff --git a/dms-infrastructure/src/main/kotlin/team/aliens/dms/thirdparty/encrypt/aop/EntityEncryptAspect.kt b/dms-infrastructure/src/main/kotlin/team/aliens/dms/thirdparty/encrypt/aop/EntityEncryptAspect.kt new file mode 100644 index 000000000..801d95b8c --- /dev/null +++ b/dms-infrastructure/src/main/kotlin/team/aliens/dms/thirdparty/encrypt/aop/EntityEncryptAspect.kt @@ -0,0 +1,122 @@ +package team.aliens.dms.thirdparty.encrypt.aop + +import org.aspectj.lang.JoinPoint +import org.aspectj.lang.ProceedingJoinPoint +import org.aspectj.lang.annotation.AfterReturning +import org.aspectj.lang.annotation.Around +import org.aspectj.lang.annotation.Aspect +import org.aspectj.lang.reflect.MethodSignature +import team.aliens.dms.common.annotation.Decrypted +import team.aliens.dms.common.annotation.EncryptType +import team.aliens.dms.common.annotation.EncryptedColumn +import team.aliens.dms.common.annotation.UseCase +import team.aliens.dms.common.spi.EncryptPort +import team.aliens.dms.common.spi.SchoolSecretPort +import team.aliens.dms.common.spi.SecurityPort +import team.aliens.dms.domain.school.exception.SchoolNotFoundException +import java.lang.reflect.Field +import java.util.function.Consumer +import java.util.function.Function + +@UseCase +@Aspect +class EntityEncryptAspect( + private val securityPort: SecurityPort, + private val schoolSecretPort: SchoolSecretPort, + private val encryptPort: EncryptPort +) { + + @AfterReturning( + "@annotation(team.aliens.dms.common.annotation.Encrypt)", + returning = "ret" + ) + fun encryptReturnValue(joinPoint: JoinPoint, ret: Any) { + if (ret is List<*>) ret.map { obj -> + doOnEncryptedColumn(obj!!) { field -> + setEncryptedValue(obj, field) + } + } else { + doOnEncryptedColumn(ret) { field -> + setEncryptedValue(ret, field) + } + } + } + + @AfterReturning( + "@annotation(team.aliens.dms.common.annotation.Decrypt)", + returning = "ret" + ) + fun decryptReturnValue(joinPoint: JoinPoint, ret: Any) { + if (ret is List<*>) ret.map { obj -> + doOnEncryptedColumn(obj!!) { field -> + setDecryptedValue(obj, field) + } + } else { + doOnEncryptedColumn(ret) { field -> + setDecryptedValue(ret, field) + } + } + } + + @Around("execution(* *(.., @team.aliens.dms.common.annotation.Decrypted() *, ..))+") + fun decryptParameterValue(joinPoint: ProceedingJoinPoint) { + (joinPoint.signature as MethodSignature).method.parameters + .forEachIndexed { idx, param -> + if (param.annotations.contains(Decrypted::class as Annotation)) { + doOnEncryptedColumn(joinPoint.args[idx]) { field -> + setDecryptedValue(joinPoint.args[idx], field) + } + } + } + } + + private fun doOnEncryptedColumn(obj: Any, behavior: Consumer) { + obj::class.java.declaredFields.map { field: Field -> + if (field.isAnnotationPresent(EncryptedColumn::class.java)) { + behavior.accept(field) + } + } + } + + private fun setEncryptedValue(obj: Any, field: Field) { + getAnnotationAndSet(obj, field) { f -> + when (f.getAnnotation(EncryptedColumn::class.java).type) { + EncryptType.SYMMETRIC -> encryptPort.symmetricEncrypt( + secretKey = getSchoolKey(), + plainText = f.get(obj) as String + ) + EncryptType.ASYMMETRIC -> encryptPort.asymmetricEncrypt( + plainText = f.get(obj) as String + ) + } + } + } + + private fun setDecryptedValue(obj: Any, field: Field) { + getAnnotationAndSet(obj, field) { f -> + when (f.getAnnotation(EncryptedColumn::class.java).type) { + EncryptType.SYMMETRIC -> encryptPort.symmetricDecrypt( + secretKey = getSchoolKey(), + cipherText = f.get(obj) as String + ) + EncryptType.ASYMMETRIC -> encryptPort.asymmetricDecrypt( + cipherText = f.get(obj) as String + ) + } + } + } + + private fun getAnnotationAndSet(obj: Any, field: Field, function: Function) { + field.run { + check(type == String::class.java) + isAccessible = true + set(obj, function.apply(this)) + } + } + + private fun getSchoolKey(): String { + val schoolId = securityPort.getCurrentUserSchoolId() + val schoolSecret = schoolSecretPort.querySchoolSecretBySchoolId(schoolId) ?: throw SchoolNotFoundException + return encryptPort.asymmetricDecrypt(schoolSecret.secretKey) + } +}