From c75528240a3d1eeda0049c274270bebd7b1ec454 Mon Sep 17 00:00:00 2001 From: mohamedlajmileanix Date: Fri, 2 Aug 2024 13:30:12 +0200 Subject: [PATCH 1/9] CID-2776: webhook endpoint initial commit --- .../controllers/GitHubWebhookController.kt | 34 ++++++++++ .../githubagent/dto/ManifestFileUpdateDto.kt | 13 ++++ .../githubagent/dto/PushEventPayload.kt | 33 +++++++++ .../githubagent/exceptions/Exceptions.kt | 1 + .../services/GitHubGraphQLService.kt | 29 +++++++- .../githubagent/services/WebhookService.kt | 68 +++++++++++++++++++ .../leanix/githubagent/shared/Constants.kt | 7 ++ .../GetRepositoryWithManifestContent.graphql | 12 ++++ 8 files changed, 196 insertions(+), 1 deletion(-) create mode 100644 src/main/kotlin/net/leanix/githubagent/controllers/GitHubWebhookController.kt create mode 100644 src/main/kotlin/net/leanix/githubagent/dto/ManifestFileUpdateDto.kt create mode 100644 src/main/kotlin/net/leanix/githubagent/dto/PushEventPayload.kt create mode 100644 src/main/kotlin/net/leanix/githubagent/services/WebhookService.kt create mode 100644 src/main/resources/graphql/GetRepositoryWithManifestContent.graphql diff --git a/src/main/kotlin/net/leanix/githubagent/controllers/GitHubWebhookController.kt b/src/main/kotlin/net/leanix/githubagent/controllers/GitHubWebhookController.kt new file mode 100644 index 0000000..db5e81c --- /dev/null +++ b/src/main/kotlin/net/leanix/githubagent/controllers/GitHubWebhookController.kt @@ -0,0 +1,34 @@ +package net.leanix.githubagent.controllers + +import net.leanix.githubagent.exceptions.GitHubEventTypesNotSupported +import net.leanix.githubagent.services.WebhookService +import net.leanix.githubagent.shared.SUPPORTED_EVENT_TYPES +import org.springframework.http.HttpStatus +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.RequestMapping +import org.springframework.web.bind.annotation.ResponseStatus +import org.springframework.web.bind.annotation.RestController + +@RestController +@RequestMapping("/github") +class GitHubWebhookController(private val webhookService: WebhookService) { + + @PostMapping("/webhook") + @ResponseStatus(HttpStatus.ACCEPTED) + fun hook( + @RequestHeader("X-Github-Event") eventType: String, + @RequestBody payload: String + ) { + runCatching { + if (SUPPORTED_EVENT_TYPES.contains(eventType.uppercase())) { + webhookService.consumeWebhookEvent(eventType, payload) + } else { + throw GitHubEventTypesNotSupported("Event Type not supported: $eventType") + } + }.onFailure { + throw it + } + } +} diff --git a/src/main/kotlin/net/leanix/githubagent/dto/ManifestFileUpdateDto.kt b/src/main/kotlin/net/leanix/githubagent/dto/ManifestFileUpdateDto.kt new file mode 100644 index 0000000..a0d34b3 --- /dev/null +++ b/src/main/kotlin/net/leanix/githubagent/dto/ManifestFileUpdateDto.kt @@ -0,0 +1,13 @@ +package net.leanix.githubagent.dto + +data class ManifestFileUpdateDto( + val repositoryFullName: String, + val action: ManifestFileAction, + val manifestContent: String? +) + +enum class ManifestFileAction { + ADDED, + MODIFIED, + REMOVED +} diff --git a/src/main/kotlin/net/leanix/githubagent/dto/PushEventPayload.kt b/src/main/kotlin/net/leanix/githubagent/dto/PushEventPayload.kt new file mode 100644 index 0000000..199c8c6 --- /dev/null +++ b/src/main/kotlin/net/leanix/githubagent/dto/PushEventPayload.kt @@ -0,0 +1,33 @@ +package net.leanix.githubagent.dto + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties +import com.fasterxml.jackson.annotation.JsonProperty + +@JsonIgnoreProperties(ignoreUnknown = true) +data class PushEventPayload( + val ref: String, + val repository: PushEventRepository, + val installation: PushEventInstallation, + @JsonProperty("head_commit") + val headCommit: PushEventCommit +) + +@JsonIgnoreProperties(ignoreUnknown = true) +data class PushEventRepository( + @JsonProperty("full_name") + val fullName: String, + @JsonProperty("default_branch") + val defaultBranch: String +) + +@JsonIgnoreProperties(ignoreUnknown = true) +data class PushEventInstallation( + val id: Int +) + +@JsonIgnoreProperties(ignoreUnknown = true) +data class PushEventCommit( + val added: List, + val removed: List, + val modified: List +) diff --git a/src/main/kotlin/net/leanix/githubagent/exceptions/Exceptions.kt b/src/main/kotlin/net/leanix/githubagent/exceptions/Exceptions.kt index a5aa9b7..ec7fe3d 100644 --- a/src/main/kotlin/net/leanix/githubagent/exceptions/Exceptions.kt +++ b/src/main/kotlin/net/leanix/githubagent/exceptions/Exceptions.kt @@ -11,3 +11,4 @@ class UnableToConnectToGitHubEnterpriseException(message: String) : RuntimeExcep class JwtTokenNotFound : RuntimeException("JWT token not found") class GraphQLApiException(errors: List) : RuntimeException("Errors: ${errors.joinToString(separator = "\n") { it.message }}") +class GitHubEventTypesNotSupported(message: String) : RuntimeException(message) diff --git a/src/main/kotlin/net/leanix/githubagent/services/GitHubGraphQLService.kt b/src/main/kotlin/net/leanix/githubagent/services/GitHubGraphQLService.kt index 28f7ee3..95259a3 100644 --- a/src/main/kotlin/net/leanix/githubagent/services/GitHubGraphQLService.kt +++ b/src/main/kotlin/net/leanix/githubagent/services/GitHubGraphQLService.kt @@ -7,6 +7,7 @@ import net.leanix.githubagent.dto.PagedRepositories import net.leanix.githubagent.dto.RepositoryDto import net.leanix.githubagent.exceptions.GraphQLApiException import net.leanix.githubagent.graphql.data.GetRepositories +import net.leanix.githubagent.graphql.data.GetRepositoryManifestContent import net.leanix.githubagent.graphql.data.getrepositories.Blob import org.slf4j.LoggerFactory import org.springframework.stereotype.Component @@ -20,7 +21,7 @@ class GitHubGraphQLService( companion object { private val logger = LoggerFactory.getLogger(GitHubGraphQLService::class.java) private const val PAGE_COUNT = 20 - private const val MANIFEST_FILE_NAME = "leanix.yaml" + const val MANIFEST_FILE_NAME = "leanix.yaml" } fun getRepositories( @@ -67,6 +68,32 @@ class GitHubGraphQLService( } } + fun getFileContent( + repositoryFullName: String, + filePath: String, + token: String + ): String? { + val client = buildGitHubGraphQLClient(token) + + val query = GetRepositoryManifestContent( + GetRepositoryManifestContent.Variables( + repositoryFullName = repositoryFullName, + filePath = filePath + ) + ) + + val result = runBlocking { + client.execute(query) + } + + return if (result.errors != null && result.errors!!.isNotEmpty()) { + logger.error("Error getting file content: ${result.errors}") + throw GraphQLApiException(result.errors!!) + } else { + (result.data!!.viewer.repository!!.`object` as Blob?)?.text + } + } + private fun buildGitHubGraphQLClient( token: String ) = diff --git a/src/main/kotlin/net/leanix/githubagent/services/WebhookService.kt b/src/main/kotlin/net/leanix/githubagent/services/WebhookService.kt new file mode 100644 index 0000000..908c0cc --- /dev/null +++ b/src/main/kotlin/net/leanix/githubagent/services/WebhookService.kt @@ -0,0 +1,68 @@ +package net.leanix.githubagent.services + +import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper +import com.fasterxml.jackson.module.kotlin.readValue +import net.leanix.githubagent.config.GitHubEnterpriseProperties +import net.leanix.githubagent.dto.ManifestFileAction +import net.leanix.githubagent.dto.ManifestFileUpdateDto +import net.leanix.githubagent.dto.PushEventPayload +import net.leanix.githubagent.services.GitHubGraphQLService.Companion.MANIFEST_FILE_NAME +import net.leanix.githubagent.shared.TOPIC_PREFIX +import org.springframework.stereotype.Service + +@Service +class WebhookService( + private val webSocketService: WebSocketService, + private val gitHubGraphQLService: GitHubGraphQLService, + private val gitHubEnterpriseProperties: GitHubEnterpriseProperties, + private val cachingService: CachingService +) { + private val objectMapper = jacksonObjectMapper() + + fun consumeWebhookEvent(eventType: String, payload: String) { + when (eventType.uppercase()) { + "PUSH" -> handlePushEvent(payload) + else -> webSocketService.sendMessage("$TOPIC_PREFIX/events/other", payload) + } + } + + private fun handlePushEvent(payload: String) { + val pushEventPayload: PushEventPayload = objectMapper.readValue(payload) + val ref = pushEventPayload.ref + val repositoryFullName = pushEventPayload.repository.fullName + val headCommit = pushEventPayload.headCommit + + val installationToken = cachingService.get("installationToken:${pushEventPayload.installation.id}")?.toString() + ?: throw IllegalArgumentException("Installation token not found/ expired") + // TODO refresh token if expired + + if (ref == "refs/heads/${pushEventPayload.repository.defaultBranch}") { + when { + MANIFEST_FILE_NAME in headCommit.added -> { + val fileContent = getFileContent(repositoryFullName, installationToken) + sendManifestData(repositoryFullName, ManifestFileAction.ADDED, fileContent) + } + MANIFEST_FILE_NAME in headCommit.modified -> { + val fileContent = getFileContent(repositoryFullName, installationToken) + sendManifestData(repositoryFullName, ManifestFileAction.MODIFIED, fileContent) + } + MANIFEST_FILE_NAME in headCommit.removed -> { + sendManifestData(repositoryFullName, ManifestFileAction.REMOVED, null) + } + } + } + } + + private fun getFileContent(repositoryFullName: String, token: String): String? { + return gitHubGraphQLService.getFileContent( + repositoryFullName, + "HEAD:${gitHubEnterpriseProperties.manifestFileDirectory}$MANIFEST_FILE_NAME", + token + ) + } + + private fun sendManifestData(repositoryFullName: String, action: ManifestFileAction, manifestContent: String?) { + val manifestFileUpdateDto = ManifestFileUpdateDto(repositoryFullName, action, manifestContent) + webSocketService.sendMessage("$TOPIC_PREFIX/events/manifestFile", manifestFileUpdateDto) + } +} diff --git a/src/main/kotlin/net/leanix/githubagent/shared/Constants.kt b/src/main/kotlin/net/leanix/githubagent/shared/Constants.kt index 98cb945..8b45296 100644 --- a/src/main/kotlin/net/leanix/githubagent/shared/Constants.kt +++ b/src/main/kotlin/net/leanix/githubagent/shared/Constants.kt @@ -1,3 +1,10 @@ package net.leanix.githubagent.shared const val TOPIC_PREFIX = "/app/ghe/" + +val SUPPORTED_EVENT_TYPES = listOf( + "repository", + "push", + "organization", + "installation", +) diff --git a/src/main/resources/graphql/GetRepositoryWithManifestContent.graphql b/src/main/resources/graphql/GetRepositoryWithManifestContent.graphql new file mode 100644 index 0000000..fb67fce --- /dev/null +++ b/src/main/resources/graphql/GetRepositoryWithManifestContent.graphql @@ -0,0 +1,12 @@ +query GetRepositoryManifestContent($repositoryFullName: String!, $filePath: String!) { + viewer{ + repository(name: $repositoryFullName) { + object(expression: $filePath) { + __typename + ... on Blob { + text + } + } + } + } +} \ No newline at end of file From ee41ce7ae5e42c5ccdcdf4edb43dd9d19b02520d Mon Sep 17 00:00:00 2001 From: mohamedlajmileanix Date: Mon, 5 Aug 2024 13:31:45 +0200 Subject: [PATCH 2/9] CID-2776: fix graphql call --- .../githubagent/dto/PushEventPayload.kt | 10 +++++++- .../services/GitHubGraphQLService.kt | 11 ++++++--- .../githubagent/services/WebhookService.kt | 24 ++++++++++++++----- .../leanix/githubagent/shared/Constants.kt | 8 +++---- .../GetRepositoryWithManifestContent.graphql | 16 ++++++------- 5 files changed, 46 insertions(+), 23 deletions(-) diff --git a/src/main/kotlin/net/leanix/githubagent/dto/PushEventPayload.kt b/src/main/kotlin/net/leanix/githubagent/dto/PushEventPayload.kt index 199c8c6..1710784 100644 --- a/src/main/kotlin/net/leanix/githubagent/dto/PushEventPayload.kt +++ b/src/main/kotlin/net/leanix/githubagent/dto/PushEventPayload.kt @@ -14,10 +14,13 @@ data class PushEventPayload( @JsonIgnoreProperties(ignoreUnknown = true) data class PushEventRepository( + @JsonProperty("name") + val name: String, @JsonProperty("full_name") val fullName: String, @JsonProperty("default_branch") - val defaultBranch: String + val defaultBranch: String, + val owner: PushEventOwner ) @JsonIgnoreProperties(ignoreUnknown = true) @@ -31,3 +34,8 @@ data class PushEventCommit( val removed: List, val modified: List ) + +@JsonIgnoreProperties(ignoreUnknown = true) +data class PushEventOwner( + val name: String +) diff --git a/src/main/kotlin/net/leanix/githubagent/services/GitHubGraphQLService.kt b/src/main/kotlin/net/leanix/githubagent/services/GitHubGraphQLService.kt index 95259a3..b2ab413 100644 --- a/src/main/kotlin/net/leanix/githubagent/services/GitHubGraphQLService.kt +++ b/src/main/kotlin/net/leanix/githubagent/services/GitHubGraphQLService.kt @@ -69,7 +69,8 @@ class GitHubGraphQLService( } fun getFileContent( - repositoryFullName: String, + owner: String, + repositoryName: String, filePath: String, token: String ): String? { @@ -77,7 +78,8 @@ class GitHubGraphQLService( val query = GetRepositoryManifestContent( GetRepositoryManifestContent.Variables( - repositoryFullName = repositoryFullName, + owner = owner, + repositoryName = repositoryName, filePath = filePath ) ) @@ -90,7 +92,10 @@ class GitHubGraphQLService( logger.error("Error getting file content: ${result.errors}") throw GraphQLApiException(result.errors!!) } else { - (result.data!!.viewer.repository!!.`object` as Blob?)?.text + ( + result.data!!.repository!!.`object` + as net.leanix.githubagent.graphql.`data`.getrepositorymanifestcontent.Blob? + )?.text } } diff --git a/src/main/kotlin/net/leanix/githubagent/services/WebhookService.kt b/src/main/kotlin/net/leanix/githubagent/services/WebhookService.kt index 908c0cc..f6ae9f4 100644 --- a/src/main/kotlin/net/leanix/githubagent/services/WebhookService.kt +++ b/src/main/kotlin/net/leanix/githubagent/services/WebhookService.kt @@ -8,6 +8,7 @@ import net.leanix.githubagent.dto.ManifestFileUpdateDto import net.leanix.githubagent.dto.PushEventPayload import net.leanix.githubagent.services.GitHubGraphQLService.Companion.MANIFEST_FILE_NAME import net.leanix.githubagent.shared.TOPIC_PREFIX +import org.slf4j.LoggerFactory import org.springframework.stereotype.Service @Service @@ -17,6 +18,8 @@ class WebhookService( private val gitHubEnterpriseProperties: GitHubEnterpriseProperties, private val cachingService: CachingService ) { + + private val logger = LoggerFactory.getLogger(WebhookService::class.java) private val objectMapper = jacksonObjectMapper() fun consumeWebhookEvent(eventType: String, payload: String) { @@ -29,8 +32,10 @@ class WebhookService( private fun handlePushEvent(payload: String) { val pushEventPayload: PushEventPayload = objectMapper.readValue(payload) val ref = pushEventPayload.ref + val repositoryName = pushEventPayload.repository.name val repositoryFullName = pushEventPayload.repository.fullName val headCommit = pushEventPayload.headCommit + val organizationName = pushEventPayload.repository.owner.name val installationToken = cachingService.get("installationToken:${pushEventPayload.installation.id}")?.toString() ?: throw IllegalArgumentException("Installation token not found/ expired") @@ -39,30 +44,37 @@ class WebhookService( if (ref == "refs/heads/${pushEventPayload.repository.defaultBranch}") { when { MANIFEST_FILE_NAME in headCommit.added -> { - val fileContent = getFileContent(repositoryFullName, installationToken) + logger.info("Manifest file added to repository $repositoryFullName") + val fileContent = getFileContent(organizationName, repositoryName, installationToken) sendManifestData(repositoryFullName, ManifestFileAction.ADDED, fileContent) } MANIFEST_FILE_NAME in headCommit.modified -> { - val fileContent = getFileContent(repositoryFullName, installationToken) + logger.info("Manifest file modified in repository $repositoryFullName") + val fileContent = getFileContent(organizationName, repositoryName, installationToken) sendManifestData(repositoryFullName, ManifestFileAction.MODIFIED, fileContent) } MANIFEST_FILE_NAME in headCommit.removed -> { + logger.info("Manifest file removed from repository $repositoryFullName") sendManifestData(repositoryFullName, ManifestFileAction.REMOVED, null) } } } } - private fun getFileContent(repositoryFullName: String, token: String): String? { + private fun getFileContent(organizationName: String, repositoryName: String, token: String): String? { return gitHubGraphQLService.getFileContent( - repositoryFullName, + owner = organizationName, + repositoryName, "HEAD:${gitHubEnterpriseProperties.manifestFileDirectory}$MANIFEST_FILE_NAME", token ) } private fun sendManifestData(repositoryFullName: String, action: ManifestFileAction, manifestContent: String?) { - val manifestFileUpdateDto = ManifestFileUpdateDto(repositoryFullName, action, manifestContent) - webSocketService.sendMessage("$TOPIC_PREFIX/events/manifestFile", manifestFileUpdateDto) + logger.info("Sending manifest file update event for repository $repositoryFullName") + webSocketService.sendMessage( + "$TOPIC_PREFIX/events/manifestFile", + ManifestFileUpdateDto(repositoryFullName, action, manifestContent) + ) } } diff --git a/src/main/kotlin/net/leanix/githubagent/shared/Constants.kt b/src/main/kotlin/net/leanix/githubagent/shared/Constants.kt index 8b45296..8003dac 100644 --- a/src/main/kotlin/net/leanix/githubagent/shared/Constants.kt +++ b/src/main/kotlin/net/leanix/githubagent/shared/Constants.kt @@ -3,8 +3,8 @@ package net.leanix.githubagent.shared const val TOPIC_PREFIX = "/app/ghe/" val SUPPORTED_EVENT_TYPES = listOf( - "repository", - "push", - "organization", - "installation", + "REPOSITORY", + "PUSH", + "ORGANIZATION", + "INSTALLATION", ) diff --git a/src/main/resources/graphql/GetRepositoryWithManifestContent.graphql b/src/main/resources/graphql/GetRepositoryWithManifestContent.graphql index fb67fce..4fbbeee 100644 --- a/src/main/resources/graphql/GetRepositoryWithManifestContent.graphql +++ b/src/main/resources/graphql/GetRepositoryWithManifestContent.graphql @@ -1,12 +1,10 @@ -query GetRepositoryManifestContent($repositoryFullName: String!, $filePath: String!) { - viewer{ - repository(name: $repositoryFullName) { - object(expression: $filePath) { - __typename - ... on Blob { - text - } +query GetRepositoryManifestContent($owner: String!, $repositoryName: String!, $filePath: String!) { + repository(owner: $owner, name: $repositoryName) { + object(expression: $filePath) { + __typename + ... on Blob { + text } } - } + } } \ No newline at end of file From b8765b148e0185188def38569c0fbb7720e4636d Mon Sep 17 00:00:00 2001 From: mohamedlajmileanix Date: Mon, 5 Aug 2024 14:07:45 +0200 Subject: [PATCH 3/9] CID-2776: code refactoring --- .../controllers/GitHubWebhookController.kt | 9 +++++---- .../net/leanix/githubagent/exceptions/Exceptions.kt | 1 - .../githubagent/services/GitHubGraphQLService.kt | 4 ++-- .../githubagent/services/GitHubScanningService.kt | 5 ++--- .../leanix/githubagent/services/WebSocketService.kt | 3 ++- .../net/leanix/githubagent/services/WebhookService.kt | 11 ++++++----- .../graphql/GetRepositoryWithManifestContent.graphql | 2 +- .../githubagent/services/GitHubScanningServiceTest.kt | 5 ++--- 8 files changed, 20 insertions(+), 20 deletions(-) diff --git a/src/main/kotlin/net/leanix/githubagent/controllers/GitHubWebhookController.kt b/src/main/kotlin/net/leanix/githubagent/controllers/GitHubWebhookController.kt index db5e81c..b76d600 100644 --- a/src/main/kotlin/net/leanix/githubagent/controllers/GitHubWebhookController.kt +++ b/src/main/kotlin/net/leanix/githubagent/controllers/GitHubWebhookController.kt @@ -1,8 +1,8 @@ package net.leanix.githubagent.controllers -import net.leanix.githubagent.exceptions.GitHubEventTypesNotSupported import net.leanix.githubagent.services.WebhookService import net.leanix.githubagent.shared.SUPPORTED_EVENT_TYPES +import org.slf4j.LoggerFactory import org.springframework.http.HttpStatus import org.springframework.web.bind.annotation.PostMapping import org.springframework.web.bind.annotation.RequestBody @@ -15,6 +15,8 @@ import org.springframework.web.bind.annotation.RestController @RequestMapping("/github") class GitHubWebhookController(private val webhookService: WebhookService) { + private val logger = LoggerFactory.getLogger(GitHubWebhookController::class.java) + @PostMapping("/webhook") @ResponseStatus(HttpStatus.ACCEPTED) fun hook( @@ -22,13 +24,12 @@ class GitHubWebhookController(private val webhookService: WebhookService) { @RequestBody payload: String ) { runCatching { + logger.info("Received a GitHub event of type $eventType") if (SUPPORTED_EVENT_TYPES.contains(eventType.uppercase())) { webhookService.consumeWebhookEvent(eventType, payload) } else { - throw GitHubEventTypesNotSupported("Event Type not supported: $eventType") + logger.warn("Event type not supported") } - }.onFailure { - throw it } } } diff --git a/src/main/kotlin/net/leanix/githubagent/exceptions/Exceptions.kt b/src/main/kotlin/net/leanix/githubagent/exceptions/Exceptions.kt index ec7fe3d..a5aa9b7 100644 --- a/src/main/kotlin/net/leanix/githubagent/exceptions/Exceptions.kt +++ b/src/main/kotlin/net/leanix/githubagent/exceptions/Exceptions.kt @@ -11,4 +11,3 @@ class UnableToConnectToGitHubEnterpriseException(message: String) : RuntimeExcep class JwtTokenNotFound : RuntimeException("JWT token not found") class GraphQLApiException(errors: List) : RuntimeException("Errors: ${errors.joinToString(separator = "\n") { it.message }}") -class GitHubEventTypesNotSupported(message: String) : RuntimeException(message) diff --git a/src/main/kotlin/net/leanix/githubagent/services/GitHubGraphQLService.kt b/src/main/kotlin/net/leanix/githubagent/services/GitHubGraphQLService.kt index b2ab413..42e436f 100644 --- a/src/main/kotlin/net/leanix/githubagent/services/GitHubGraphQLService.kt +++ b/src/main/kotlin/net/leanix/githubagent/services/GitHubGraphQLService.kt @@ -94,8 +94,8 @@ class GitHubGraphQLService( } else { ( result.data!!.repository!!.`object` - as net.leanix.githubagent.graphql.`data`.getrepositorymanifestcontent.Blob? - )?.text + as net.leanix.githubagent.graphql.`data`.getrepositorymanifestcontent.Blob + ).text } } diff --git a/src/main/kotlin/net/leanix/githubagent/services/GitHubScanningService.kt b/src/main/kotlin/net/leanix/githubagent/services/GitHubScanningService.kt index cc30428..c08366a 100644 --- a/src/main/kotlin/net/leanix/githubagent/services/GitHubScanningService.kt +++ b/src/main/kotlin/net/leanix/githubagent/services/GitHubScanningService.kt @@ -4,7 +4,6 @@ import net.leanix.githubagent.client.GitHubClient import net.leanix.githubagent.dto.Installation import net.leanix.githubagent.dto.OrganizationDto import net.leanix.githubagent.exceptions.JwtTokenNotFound -import net.leanix.githubagent.shared.TOPIC_PREFIX import org.slf4j.LoggerFactory import org.springframework.stereotype.Service import java.util.UUID @@ -65,7 +64,7 @@ class GitHubScanningService( } } logger.info("Sending organizations data") - webSocketService.sendMessage("$TOPIC_PREFIX${cachingService.get("runId")}/organizations", organizations) + webSocketService.sendMessage("${cachingService.get("runId")}/organizations", organizations) } private fun fetchAndSendRepositoriesData(installation: Installation) { @@ -80,7 +79,7 @@ class GitHubScanningService( ) logger.info("Sending page $page of repositories") webSocketService.sendMessage( - "$TOPIC_PREFIX${cachingService.get("runId")}/repositories", + "${cachingService.get("runId")}/repositories", repositoriesPage.repositories ) cursor = repositoriesPage.cursor diff --git a/src/main/kotlin/net/leanix/githubagent/services/WebSocketService.kt b/src/main/kotlin/net/leanix/githubagent/services/WebSocketService.kt index 8959113..48bf42a 100644 --- a/src/main/kotlin/net/leanix/githubagent/services/WebSocketService.kt +++ b/src/main/kotlin/net/leanix/githubagent/services/WebSocketService.kt @@ -1,6 +1,7 @@ package net.leanix.githubagent.services import net.leanix.githubagent.config.WebSocketClientConfig +import net.leanix.githubagent.shared.TOPIC_PREFIX import org.slf4j.LoggerFactory import org.springframework.messaging.simp.stomp.StompSession import org.springframework.stereotype.Service @@ -19,6 +20,6 @@ class WebSocketService( } fun sendMessage(topic: String, data: Any) { - stompSession!!.send(topic, data) + stompSession!!.send("$TOPIC_PREFIX$topic", data) } } diff --git a/src/main/kotlin/net/leanix/githubagent/services/WebhookService.kt b/src/main/kotlin/net/leanix/githubagent/services/WebhookService.kt index f6ae9f4..7f8ab2a 100644 --- a/src/main/kotlin/net/leanix/githubagent/services/WebhookService.kt +++ b/src/main/kotlin/net/leanix/githubagent/services/WebhookService.kt @@ -7,7 +7,6 @@ import net.leanix.githubagent.dto.ManifestFileAction import net.leanix.githubagent.dto.ManifestFileUpdateDto import net.leanix.githubagent.dto.PushEventPayload import net.leanix.githubagent.services.GitHubGraphQLService.Companion.MANIFEST_FILE_NAME -import net.leanix.githubagent.shared.TOPIC_PREFIX import org.slf4j.LoggerFactory import org.springframework.stereotype.Service @@ -25,13 +24,15 @@ class WebhookService( fun consumeWebhookEvent(eventType: String, payload: String) { when (eventType.uppercase()) { "PUSH" -> handlePushEvent(payload) - else -> webSocketService.sendMessage("$TOPIC_PREFIX/events/other", payload) + else -> { + logger.info("Sending event") + webSocketService.sendMessage("/events/other", payload) + } } } private fun handlePushEvent(payload: String) { val pushEventPayload: PushEventPayload = objectMapper.readValue(payload) - val ref = pushEventPayload.ref val repositoryName = pushEventPayload.repository.name val repositoryFullName = pushEventPayload.repository.fullName val headCommit = pushEventPayload.headCommit @@ -41,7 +42,7 @@ class WebhookService( ?: throw IllegalArgumentException("Installation token not found/ expired") // TODO refresh token if expired - if (ref == "refs/heads/${pushEventPayload.repository.defaultBranch}") { + if (pushEventPayload.ref == "refs/heads/${pushEventPayload.repository.defaultBranch}") { when { MANIFEST_FILE_NAME in headCommit.added -> { logger.info("Manifest file added to repository $repositoryFullName") @@ -73,7 +74,7 @@ class WebhookService( private fun sendManifestData(repositoryFullName: String, action: ManifestFileAction, manifestContent: String?) { logger.info("Sending manifest file update event for repository $repositoryFullName") webSocketService.sendMessage( - "$TOPIC_PREFIX/events/manifestFile", + "/events/manifestFile", ManifestFileUpdateDto(repositoryFullName, action, manifestContent) ) } diff --git a/src/main/resources/graphql/GetRepositoryWithManifestContent.graphql b/src/main/resources/graphql/GetRepositoryWithManifestContent.graphql index 4fbbeee..56f21a0 100644 --- a/src/main/resources/graphql/GetRepositoryWithManifestContent.graphql +++ b/src/main/resources/graphql/GetRepositoryWithManifestContent.graphql @@ -7,4 +7,4 @@ query GetRepositoryManifestContent($owner: String!, $repositoryName: String!, $f } } } -} \ No newline at end of file +} diff --git a/src/test/kotlin/net/leanix/githubagent/services/GitHubScanningServiceTest.kt b/src/test/kotlin/net/leanix/githubagent/services/GitHubScanningServiceTest.kt index f42aa0e..f04e0d3 100644 --- a/src/test/kotlin/net/leanix/githubagent/services/GitHubScanningServiceTest.kt +++ b/src/test/kotlin/net/leanix/githubagent/services/GitHubScanningServiceTest.kt @@ -12,7 +12,6 @@ import net.leanix.githubagent.dto.PagedRepositories import net.leanix.githubagent.dto.RepositoryDto import net.leanix.githubagent.exceptions.JwtTokenNotFound import net.leanix.githubagent.graphql.data.enums.RepositoryVisibility -import net.leanix.githubagent.shared.TOPIC_PREFIX import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import org.junit.jupiter.api.assertThrows @@ -54,7 +53,7 @@ class GitHubScanningServiceTest { fun `scanGitHubResources should send organizations over WebSocket`() { every { cachingService.get("runId") } returns runId gitHubScanningService.scanGitHubResources() - verify { webSocketService.sendMessage(eq("$TOPIC_PREFIX$runId/organizations"), any()) } + verify { webSocketService.sendMessage(eq("$runId/organizations"), any()) } } @Test @@ -88,6 +87,6 @@ class GitHubScanningServiceTest { cursor = null ) gitHubScanningService.scanGitHubResources() - verify { webSocketService.sendMessage(eq("$TOPIC_PREFIX$runId/repositories"), any()) } + verify { webSocketService.sendMessage(eq("$runId/repositories"), any()) } } } From c5715ddd774ef936a699dd418232440b6cd6a122 Mon Sep 17 00:00:00 2001 From: mohamedlajmileanix Date: Mon, 5 Aug 2024 15:09:51 +0200 Subject: [PATCH 4/9] CID-2776: code refactoring --- .../net/leanix/githubagent/services/GitHubGraphQLService.kt | 6 +++--- .../net/leanix/githubagent/services/WebhookService.kt | 4 ++-- src/main/kotlin/net/leanix/githubagent/shared/Constants.kt | 2 ++ 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/main/kotlin/net/leanix/githubagent/services/GitHubGraphQLService.kt b/src/main/kotlin/net/leanix/githubagent/services/GitHubGraphQLService.kt index 42e436f..a86d87a 100644 --- a/src/main/kotlin/net/leanix/githubagent/services/GitHubGraphQLService.kt +++ b/src/main/kotlin/net/leanix/githubagent/services/GitHubGraphQLService.kt @@ -9,6 +9,7 @@ import net.leanix.githubagent.exceptions.GraphQLApiException import net.leanix.githubagent.graphql.data.GetRepositories import net.leanix.githubagent.graphql.data.GetRepositoryManifestContent import net.leanix.githubagent.graphql.data.getrepositories.Blob +import net.leanix.githubagent.shared.MANIFEST_FILE_NAME import org.slf4j.LoggerFactory import org.springframework.stereotype.Component import org.springframework.web.reactive.function.client.WebClient @@ -21,7 +22,6 @@ class GitHubGraphQLService( companion object { private val logger = LoggerFactory.getLogger(GitHubGraphQLService::class.java) private const val PAGE_COUNT = 20 - const val MANIFEST_FILE_NAME = "leanix.yaml" } fun getRepositories( @@ -73,7 +73,7 @@ class GitHubGraphQLService( repositoryName: String, filePath: String, token: String - ): String? { + ): String { val client = buildGitHubGraphQLClient(token) val query = GetRepositoryManifestContent( @@ -95,7 +95,7 @@ class GitHubGraphQLService( ( result.data!!.repository!!.`object` as net.leanix.githubagent.graphql.`data`.getrepositorymanifestcontent.Blob - ).text + ).text.toString() } } diff --git a/src/main/kotlin/net/leanix/githubagent/services/WebhookService.kt b/src/main/kotlin/net/leanix/githubagent/services/WebhookService.kt index 7f8ab2a..f619b6e 100644 --- a/src/main/kotlin/net/leanix/githubagent/services/WebhookService.kt +++ b/src/main/kotlin/net/leanix/githubagent/services/WebhookService.kt @@ -6,7 +6,7 @@ import net.leanix.githubagent.config.GitHubEnterpriseProperties import net.leanix.githubagent.dto.ManifestFileAction import net.leanix.githubagent.dto.ManifestFileUpdateDto import net.leanix.githubagent.dto.PushEventPayload -import net.leanix.githubagent.services.GitHubGraphQLService.Companion.MANIFEST_FILE_NAME +import net.leanix.githubagent.shared.MANIFEST_FILE_NAME import org.slf4j.LoggerFactory import org.springframework.stereotype.Service @@ -62,7 +62,7 @@ class WebhookService( } } - private fun getFileContent(organizationName: String, repositoryName: String, token: String): String? { + private fun getFileContent(organizationName: String, repositoryName: String, token: String): String { return gitHubGraphQLService.getFileContent( owner = organizationName, repositoryName, diff --git a/src/main/kotlin/net/leanix/githubagent/shared/Constants.kt b/src/main/kotlin/net/leanix/githubagent/shared/Constants.kt index 8003dac..d41f649 100644 --- a/src/main/kotlin/net/leanix/githubagent/shared/Constants.kt +++ b/src/main/kotlin/net/leanix/githubagent/shared/Constants.kt @@ -2,6 +2,8 @@ package net.leanix.githubagent.shared const val TOPIC_PREFIX = "/app/ghe/" +const val MANIFEST_FILE_NAME = "leanix.yaml" + val SUPPORTED_EVENT_TYPES = listOf( "REPOSITORY", "PUSH", From 158679cc8d45e44385df61fed6948a4e6897fc49 Mon Sep 17 00:00:00 2001 From: mohamedlajmileanix Date: Mon, 5 Aug 2024 15:31:58 +0200 Subject: [PATCH 5/9] CID-2776: refresh tokens if expired --- .../services/GitHubAuthenticationService.kt | 25 ++++++++++++++++++- .../services/GitHubScanningService.kt | 15 +++-------- .../githubagent/services/WebhookService.kt | 12 ++++++--- .../GitHubAuthenticationServiceTest.kt | 5 +++- .../services/GitHubScanningServiceTest.kt | 5 +++- 5 files changed, 43 insertions(+), 19 deletions(-) diff --git a/src/main/kotlin/net/leanix/githubagent/services/GitHubAuthenticationService.kt b/src/main/kotlin/net/leanix/githubagent/services/GitHubAuthenticationService.kt index c76c573..db6ef73 100644 --- a/src/main/kotlin/net/leanix/githubagent/services/GitHubAuthenticationService.kt +++ b/src/main/kotlin/net/leanix/githubagent/services/GitHubAuthenticationService.kt @@ -2,8 +2,11 @@ package net.leanix.githubagent.services import io.jsonwebtoken.Jwts import io.jsonwebtoken.SignatureAlgorithm +import net.leanix.githubagent.client.GitHubClient import net.leanix.githubagent.config.GitHubEnterpriseProperties +import net.leanix.githubagent.dto.Installation import net.leanix.githubagent.exceptions.FailedToCreateJWTException +import net.leanix.githubagent.exceptions.JwtTokenNotFound import org.bouncycastle.jce.provider.BouncyCastleProvider import org.slf4j.LoggerFactory import org.springframework.core.io.ResourceLoader @@ -24,7 +27,8 @@ class GitHubAuthenticationService( private val cachingService: CachingService, private val githubEnterpriseProperties: GitHubEnterpriseProperties, private val resourceLoader: ResourceLoader, - private val gitHubEnterpriseService: GitHubEnterpriseService + private val gitHubEnterpriseService: GitHubEnterpriseService, + private val gitHubClient: GitHubClient, ) { companion object { @@ -34,6 +38,15 @@ class GitHubAuthenticationService( private val logger = LoggerFactory.getLogger(GitHubAuthenticationService::class.java) } + fun refreshTokens() { + generateJwtToken() + val jwtToken = cachingService.get("jwtToken") ?: throw JwtTokenNotFound() + generateAndCacheInstallationTokens( + gitHubClient.getInstallations("Bearer $jwtToken"), + jwtToken.toString() + ) + } + fun generateJwtToken() { runCatching { logger.info("Generating JWT token") @@ -67,6 +80,16 @@ class GitHubAuthenticationService( } } + fun generateAndCacheInstallationTokens( + installations: List, + jwtToken: String + ) { + installations.forEach { installation -> + val installationToken = gitHubClient.createInstallationToken(installation.id, "Bearer $jwtToken").token + cachingService.set("installationToken:${installation.id}", installationToken, 3600L) + } + } + @Throws(IOException::class) private fun readPrivateKey(): String { val pemFile = File(resourceLoader.getResource("file:${githubEnterpriseProperties.pemFile}").uri) diff --git a/src/main/kotlin/net/leanix/githubagent/services/GitHubScanningService.kt b/src/main/kotlin/net/leanix/githubagent/services/GitHubScanningService.kt index c08366a..6223704 100644 --- a/src/main/kotlin/net/leanix/githubagent/services/GitHubScanningService.kt +++ b/src/main/kotlin/net/leanix/githubagent/services/GitHubScanningService.kt @@ -13,7 +13,8 @@ class GitHubScanningService( private val gitHubClient: GitHubClient, private val cachingService: CachingService, private val webSocketService: WebSocketService, - private val gitHubGraphQLService: GitHubGraphQLService + private val gitHubGraphQLService: GitHubGraphQLService, + private val gitHubAuthenticationService: GitHubAuthenticationService ) { private val logger = LoggerFactory.getLogger(GitHubScanningService::class.java) @@ -37,20 +38,10 @@ class GitHubScanningService( private fun getInstallations(jwtToken: String): List { val installations = gitHubClient.getInstallations("Bearer $jwtToken") - generateAndCacheInstallationTokens(installations, jwtToken) + gitHubAuthenticationService.generateAndCacheInstallationTokens(installations, jwtToken) return installations } - private fun generateAndCacheInstallationTokens( - installations: List, - jwtToken: String - ) { - installations.forEach { installation -> - val installationToken = gitHubClient.createInstallationToken(installation.id, "Bearer $jwtToken").token - cachingService.set("installationToken:${installation.id}", installationToken, 3600L) - } - } - private fun fetchAndSendOrganisationsData( installations: List ) { diff --git a/src/main/kotlin/net/leanix/githubagent/services/WebhookService.kt b/src/main/kotlin/net/leanix/githubagent/services/WebhookService.kt index f619b6e..4f12955 100644 --- a/src/main/kotlin/net/leanix/githubagent/services/WebhookService.kt +++ b/src/main/kotlin/net/leanix/githubagent/services/WebhookService.kt @@ -15,7 +15,8 @@ class WebhookService( private val webSocketService: WebSocketService, private val gitHubGraphQLService: GitHubGraphQLService, private val gitHubEnterpriseProperties: GitHubEnterpriseProperties, - private val cachingService: CachingService + private val cachingService: CachingService, + private val gitHubAuthenticationService: GitHubAuthenticationService ) { private val logger = LoggerFactory.getLogger(WebhookService::class.java) @@ -38,9 +39,12 @@ class WebhookService( val headCommit = pushEventPayload.headCommit val organizationName = pushEventPayload.repository.owner.name - val installationToken = cachingService.get("installationToken:${pushEventPayload.installation.id}")?.toString() - ?: throw IllegalArgumentException("Installation token not found/ expired") - // TODO refresh token if expired + var installationToken = cachingService.get("installationToken:${pushEventPayload.installation.id}")?.toString() + if (installationToken == null) { + gitHubAuthenticationService.refreshTokens() + installationToken = cachingService.get("installationToken:${pushEventPayload.installation.id}")?.toString() + require(installationToken != null) { "Installation token not found/ expired" } + } if (pushEventPayload.ref == "refs/heads/${pushEventPayload.repository.defaultBranch}") { when { diff --git a/src/test/kotlin/net/leanix/githubagent/services/GitHubAuthenticationServiceTest.kt b/src/test/kotlin/net/leanix/githubagent/services/GitHubAuthenticationServiceTest.kt index 44b70d1..75c6a27 100644 --- a/src/test/kotlin/net/leanix/githubagent/services/GitHubAuthenticationServiceTest.kt +++ b/src/test/kotlin/net/leanix/githubagent/services/GitHubAuthenticationServiceTest.kt @@ -2,6 +2,7 @@ package net.leanix.githubagent.services import io.mockk.every import io.mockk.mockk +import net.leanix.githubagent.client.GitHubClient import net.leanix.githubagent.config.GitHubEnterpriseProperties import org.junit.jupiter.api.Assertions.assertNotNull import org.junit.jupiter.api.Assertions.assertThrows @@ -16,11 +17,13 @@ class GitHubAuthenticationServiceTest { private val githubEnterpriseProperties = mockk() private val resourceLoader = mockk() private val gitHubEnterpriseService = mockk() + private val gitHubClient = mockk() private val githubAuthenticationService = GitHubAuthenticationService( cachingService, githubEnterpriseProperties, resourceLoader, - gitHubEnterpriseService + gitHubEnterpriseService, + gitHubClient ) @Test diff --git a/src/test/kotlin/net/leanix/githubagent/services/GitHubScanningServiceTest.kt b/src/test/kotlin/net/leanix/githubagent/services/GitHubScanningServiceTest.kt index f04e0d3..e9134ed 100644 --- a/src/test/kotlin/net/leanix/githubagent/services/GitHubScanningServiceTest.kt +++ b/src/test/kotlin/net/leanix/githubagent/services/GitHubScanningServiceTest.kt @@ -23,11 +23,13 @@ class GitHubScanningServiceTest { private val cachingService = mockk() private val webSocketService = mockk(relaxUnitFun = true) private val gitHubGraphQLService = mockk() + private val gitHubAuthenticationService = mockk() private val gitHubScanningService = GitHubScanningService( gitHubClient, cachingService, webSocketService, - gitHubGraphQLService + gitHubGraphQLService, + gitHubAuthenticationService ) private val runId = UUID.randomUUID() @@ -47,6 +49,7 @@ class GitHubScanningServiceTest { cursor = null ) every { cachingService.remove(any()) } returns Unit + every { gitHubAuthenticationService.generateAndCacheInstallationTokens(any(), any()) } returns Unit } @Test From 918f68c127d296224a6e71f8c49efc0e9e77fa48 Mon Sep 17 00:00:00 2001 From: mohamedlajmileanix Date: Tue, 6 Aug 2024 11:10:32 +0200 Subject: [PATCH 6/9] CID-2776: add tests --- .../GitHubWebhookControllerTest.kt | 37 ++++++ .../services/WebhookServiceTest.kt | 121 ++++++++++++++++++ 2 files changed, 158 insertions(+) create mode 100644 src/test/kotlin/net/leanix/githubagent/controllers/GitHubWebhookControllerTest.kt create mode 100644 src/test/kotlin/net/leanix/githubagent/services/WebhookServiceTest.kt diff --git a/src/test/kotlin/net/leanix/githubagent/controllers/GitHubWebhookControllerTest.kt b/src/test/kotlin/net/leanix/githubagent/controllers/GitHubWebhookControllerTest.kt new file mode 100644 index 0000000..7da7b9e --- /dev/null +++ b/src/test/kotlin/net/leanix/githubagent/controllers/GitHubWebhookControllerTest.kt @@ -0,0 +1,37 @@ +package net.leanix.githubagent.controllers + +import com.ninjasquad.springmockk.MockkBean +import io.mockk.verify +import net.leanix.githubagent.services.WebhookService +import org.junit.jupiter.api.Test +import org.mockito.ArgumentMatchers.anyString +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest +import org.springframework.test.web.servlet.MockMvc +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders +import org.springframework.test.web.servlet.result.MockMvcResultMatchers + +@WebMvcTest(GitHubWebhookController::class) +class GitHubWebhookControllerTest { + + @Autowired + private lateinit var mockMvc: MockMvc + + @MockkBean + private lateinit var webhookService: WebhookService + + @Test + fun `should not process not supported events`() { + val eventType = "UNSUPPORTED_EVENT" + val payload = "{}" + + mockMvc.perform( + MockMvcRequestBuilders.post("/github/webhook") + .header("X-GitHub-Event", eventType) + .content(payload) + ) + .andExpect(MockMvcResultMatchers.status().isAccepted) + + verify(exactly = 0) { webhookService.consumeWebhookEvent(anyString(), anyString()) } + } +} diff --git a/src/test/kotlin/net/leanix/githubagent/services/WebhookServiceTest.kt b/src/test/kotlin/net/leanix/githubagent/services/WebhookServiceTest.kt new file mode 100644 index 0000000..704cf26 --- /dev/null +++ b/src/test/kotlin/net/leanix/githubagent/services/WebhookServiceTest.kt @@ -0,0 +1,121 @@ +package net.leanix.githubagent.services + +import com.ninjasquad.springmockk.MockkBean +import io.mockk.every +import io.mockk.verify +import net.leanix.githubagent.dto.ManifestFileAction +import net.leanix.githubagent.dto.ManifestFileUpdateDto +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.test.context.SpringBootTest +import org.springframework.test.context.ActiveProfiles + +@SpringBootTest +@ActiveProfiles("test") +class WebhookServiceTest { + + @MockkBean + private lateinit var webSocketService: WebSocketService + + @MockkBean + private lateinit var gitHubGraphQLService: GitHubGraphQLService + + @MockkBean + private lateinit var cachingService: CachingService + + @MockkBean + private lateinit var gitHubAuthenticationService: GitHubAuthenticationService + + @Autowired + private lateinit var webhookService: WebhookService + + @BeforeEach + fun setUp() { + every { gitHubAuthenticationService.refreshTokens() } returns Unit + every { webSocketService.sendMessage(any(), any()) } returns Unit + } + + @Test + fun `should refresh tokens if expired`() { + val payload = """{ + "repository": { + "name": "repo", + "full_name": "owner/repo", + "owner": {"name": "owner"}, + "default_branch": "main" + }, + "head_commit": { + "added": [], + "modified": [], + "removed": [] + }, + "installation": {"id": 1}, + "ref": "refs/heads/main" + }""" + + every { cachingService.get("installationToken:1") } returns null andThen "token" + + webhookService.consumeWebhookEvent("PUSH", payload) + + verify(exactly = 1) { gitHubAuthenticationService.refreshTokens() } + } + + @Test + fun `should process push event`() { + every { cachingService.get(any()) } returns "token" + every { gitHubGraphQLService.getFileContent(any(), any(), any(), any()) } returns "content" + + val payload = """{ + "repository": { + "name": "repo", + "full_name": "owner/repo", + "owner": {"name": "owner"}, + "default_branch": "main" + }, + "head_commit": { + "added": [], + "modified": ["leanix.yaml"], + "removed": [] + }, + "installation": {"id": 1}, + "ref": "refs/heads/main" + }""" + + webhookService.consumeWebhookEvent("PUSH", payload) + + verify(exactly = 1) { + webSocketService.sendMessage( + "/events/manifestFile", + ManifestFileUpdateDto( + "owner/repo", + ManifestFileAction.MODIFIED, + "content" + ) + ) + } + } + + @Test + fun `should send all events of type other than push to backend without processing`() { + val payload = """{ + "repository": { + "name": "repo", + "full_name": "owner/repo", + "owner": {"name": "owner"}, + "default_branch": "main" + }, + "head_commit": { + "added": [], + "modified": [], + "removed": [] + }, + "installation": {"id": 1}, + "ref": "refs/heads/main" + }""" + + webhookService.consumeWebhookEvent("OTHER", payload) + + verify(exactly = 1) { webSocketService.sendMessage("/events/other", payload) } + } +} From fdc47b0dfa084efd19d378a6f13fdbb2ca594df6 Mon Sep 17 00:00:00 2001 From: mohamedlajmileanix Date: Tue, 6 Aug 2024 11:26:10 +0200 Subject: [PATCH 7/9] CID-2776: remove unnecessary throw statement --- .../leanix/githubagent/services/GitHubAuthenticationService.kt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/kotlin/net/leanix/githubagent/services/GitHubAuthenticationService.kt b/src/main/kotlin/net/leanix/githubagent/services/GitHubAuthenticationService.kt index db6ef73..7e4e4d2 100644 --- a/src/main/kotlin/net/leanix/githubagent/services/GitHubAuthenticationService.kt +++ b/src/main/kotlin/net/leanix/githubagent/services/GitHubAuthenticationService.kt @@ -6,7 +6,6 @@ import net.leanix.githubagent.client.GitHubClient import net.leanix.githubagent.config.GitHubEnterpriseProperties import net.leanix.githubagent.dto.Installation import net.leanix.githubagent.exceptions.FailedToCreateJWTException -import net.leanix.githubagent.exceptions.JwtTokenNotFound import org.bouncycastle.jce.provider.BouncyCastleProvider import org.slf4j.LoggerFactory import org.springframework.core.io.ResourceLoader @@ -40,7 +39,7 @@ class GitHubAuthenticationService( fun refreshTokens() { generateJwtToken() - val jwtToken = cachingService.get("jwtToken") ?: throw JwtTokenNotFound() + val jwtToken = cachingService.get("jwtToken") generateAndCacheInstallationTokens( gitHubClient.getInstallations("Bearer $jwtToken"), jwtToken.toString() From 4e332af1aaf12817cd1f01e0eb853f879ad873b0 Mon Sep 17 00:00:00 2001 From: mohamedlajmileanix Date: Tue, 6 Aug 2024 12:43:46 +0200 Subject: [PATCH 8/9] CID-2776: addressing PR comments --- .../services/GitHubAuthenticationService.kt | 2 +- .../githubagent/services/GitHubGraphQLService.kt | 2 +- .../net/leanix/githubagent/services/WebhookService.kt | 10 +++++----- .../leanix/githubagent/services/WebhookServiceTest.kt | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/main/kotlin/net/leanix/githubagent/services/GitHubAuthenticationService.kt b/src/main/kotlin/net/leanix/githubagent/services/GitHubAuthenticationService.kt index 7e4e4d2..e6291b3 100644 --- a/src/main/kotlin/net/leanix/githubagent/services/GitHubAuthenticationService.kt +++ b/src/main/kotlin/net/leanix/githubagent/services/GitHubAuthenticationService.kt @@ -54,8 +54,8 @@ class GitHubAuthenticationService( val keySpec = PKCS8EncodedKeySpec(Base64.getDecoder().decode(rsaPrivateKey)) val privateKey = KeyFactory.getInstance("RSA").generatePrivate(keySpec) val jwt = createJwtToken(privateKey) - cachingService.set("jwtToken", jwt.getOrThrow(), JWT_EXPIRATION_DURATION) gitHubEnterpriseService.verifyJwt(jwt.getOrThrow()) + cachingService.set("jwtToken", jwt.getOrThrow(), JWT_EXPIRATION_DURATION) }.onFailure { logger.error("Failed to generate/validate JWT token", it) if (it is InvalidKeySpecException) { diff --git a/src/main/kotlin/net/leanix/githubagent/services/GitHubGraphQLService.kt b/src/main/kotlin/net/leanix/githubagent/services/GitHubGraphQLService.kt index a86d87a..91e5243 100644 --- a/src/main/kotlin/net/leanix/githubagent/services/GitHubGraphQLService.kt +++ b/src/main/kotlin/net/leanix/githubagent/services/GitHubGraphQLService.kt @@ -68,7 +68,7 @@ class GitHubGraphQLService( } } - fun getFileContent( + fun getManifestFileContent( owner: String, repositoryName: String, filePath: String, diff --git a/src/main/kotlin/net/leanix/githubagent/services/WebhookService.kt b/src/main/kotlin/net/leanix/githubagent/services/WebhookService.kt index 4f12955..dbf7923 100644 --- a/src/main/kotlin/net/leanix/githubagent/services/WebhookService.kt +++ b/src/main/kotlin/net/leanix/githubagent/services/WebhookService.kt @@ -26,7 +26,7 @@ class WebhookService( when (eventType.uppercase()) { "PUSH" -> handlePushEvent(payload) else -> { - logger.info("Sending event") + logger.debug("Sending event of type: $eventType") webSocketService.sendMessage("/events/other", payload) } } @@ -50,12 +50,12 @@ class WebhookService( when { MANIFEST_FILE_NAME in headCommit.added -> { logger.info("Manifest file added to repository $repositoryFullName") - val fileContent = getFileContent(organizationName, repositoryName, installationToken) + val fileContent = getManifestFileContent(organizationName, repositoryName, installationToken) sendManifestData(repositoryFullName, ManifestFileAction.ADDED, fileContent) } MANIFEST_FILE_NAME in headCommit.modified -> { logger.info("Manifest file modified in repository $repositoryFullName") - val fileContent = getFileContent(organizationName, repositoryName, installationToken) + val fileContent = getManifestFileContent(organizationName, repositoryName, installationToken) sendManifestData(repositoryFullName, ManifestFileAction.MODIFIED, fileContent) } MANIFEST_FILE_NAME in headCommit.removed -> { @@ -66,8 +66,8 @@ class WebhookService( } } - private fun getFileContent(organizationName: String, repositoryName: String, token: String): String { - return gitHubGraphQLService.getFileContent( + private fun getManifestFileContent(organizationName: String, repositoryName: String, token: String): String { + return gitHubGraphQLService.getManifestFileContent( owner = organizationName, repositoryName, "HEAD:${gitHubEnterpriseProperties.manifestFileDirectory}$MANIFEST_FILE_NAME", diff --git a/src/test/kotlin/net/leanix/githubagent/services/WebhookServiceTest.kt b/src/test/kotlin/net/leanix/githubagent/services/WebhookServiceTest.kt index 704cf26..1537294 100644 --- a/src/test/kotlin/net/leanix/githubagent/services/WebhookServiceTest.kt +++ b/src/test/kotlin/net/leanix/githubagent/services/WebhookServiceTest.kt @@ -64,7 +64,7 @@ class WebhookServiceTest { @Test fun `should process push event`() { every { cachingService.get(any()) } returns "token" - every { gitHubGraphQLService.getFileContent(any(), any(), any(), any()) } returns "content" + every { gitHubGraphQLService.getManifestFileContent(any(), any(), any(), any()) } returns "content" val payload = """{ "repository": { From 58618dd09c1a41156bdb4246c7300eace5b7d464 Mon Sep 17 00:00:00 2001 From: mohamedlajmileanix Date: Tue, 6 Aug 2024 13:29:07 +0200 Subject: [PATCH 9/9] CID-2776: addressing PR comments --- .../leanix/githubagent/controllers/GitHubWebhookController.kt | 3 +-- .../net/leanix/githubagent/runners/PostStartupRunner.kt | 2 +- .../githubagent/services/GitHubAuthenticationService.kt | 4 ++-- .../kotlin/net/leanix/githubagent/services/WebhookService.kt | 2 +- .../githubagent/services/GitHubAuthenticationServiceTest.kt | 4 ++-- 5 files changed, 7 insertions(+), 8 deletions(-) diff --git a/src/main/kotlin/net/leanix/githubagent/controllers/GitHubWebhookController.kt b/src/main/kotlin/net/leanix/githubagent/controllers/GitHubWebhookController.kt index b76d600..c218576 100644 --- a/src/main/kotlin/net/leanix/githubagent/controllers/GitHubWebhookController.kt +++ b/src/main/kotlin/net/leanix/githubagent/controllers/GitHubWebhookController.kt @@ -24,11 +24,10 @@ class GitHubWebhookController(private val webhookService: WebhookService) { @RequestBody payload: String ) { runCatching { - logger.info("Received a GitHub event of type $eventType") if (SUPPORTED_EVENT_TYPES.contains(eventType.uppercase())) { webhookService.consumeWebhookEvent(eventType, payload) } else { - logger.warn("Event type not supported") + logger.warn("Received an unsupported event of type: $eventType") } } } diff --git a/src/main/kotlin/net/leanix/githubagent/runners/PostStartupRunner.kt b/src/main/kotlin/net/leanix/githubagent/runners/PostStartupRunner.kt index 41283c4..98cc898 100644 --- a/src/main/kotlin/net/leanix/githubagent/runners/PostStartupRunner.kt +++ b/src/main/kotlin/net/leanix/githubagent/runners/PostStartupRunner.kt @@ -18,7 +18,7 @@ class PostStartupRunner( override fun run(args: ApplicationArguments?) { webSocketService.initSession() - githubAuthenticationService.generateJwtToken() + githubAuthenticationService.generateAndCacheJwtToken() gitHubScanningService.scanGitHubResources() } } diff --git a/src/main/kotlin/net/leanix/githubagent/services/GitHubAuthenticationService.kt b/src/main/kotlin/net/leanix/githubagent/services/GitHubAuthenticationService.kt index e6291b3..1e2bcdd 100644 --- a/src/main/kotlin/net/leanix/githubagent/services/GitHubAuthenticationService.kt +++ b/src/main/kotlin/net/leanix/githubagent/services/GitHubAuthenticationService.kt @@ -38,7 +38,7 @@ class GitHubAuthenticationService( } fun refreshTokens() { - generateJwtToken() + generateAndCacheJwtToken() val jwtToken = cachingService.get("jwtToken") generateAndCacheInstallationTokens( gitHubClient.getInstallations("Bearer $jwtToken"), @@ -46,7 +46,7 @@ class GitHubAuthenticationService( ) } - fun generateJwtToken() { + fun generateAndCacheJwtToken() { runCatching { logger.info("Generating JWT token") Security.addProvider(BouncyCastleProvider()) diff --git a/src/main/kotlin/net/leanix/githubagent/services/WebhookService.kt b/src/main/kotlin/net/leanix/githubagent/services/WebhookService.kt index dbf7923..7966e23 100644 --- a/src/main/kotlin/net/leanix/githubagent/services/WebhookService.kt +++ b/src/main/kotlin/net/leanix/githubagent/services/WebhookService.kt @@ -26,7 +26,7 @@ class WebhookService( when (eventType.uppercase()) { "PUSH" -> handlePushEvent(payload) else -> { - logger.debug("Sending event of type: $eventType") + logger.info("Sending event of type: $eventType") webSocketService.sendMessage("/events/other", payload) } } diff --git a/src/test/kotlin/net/leanix/githubagent/services/GitHubAuthenticationServiceTest.kt b/src/test/kotlin/net/leanix/githubagent/services/GitHubAuthenticationServiceTest.kt index 75c6a27..8543776 100644 --- a/src/test/kotlin/net/leanix/githubagent/services/GitHubAuthenticationServiceTest.kt +++ b/src/test/kotlin/net/leanix/githubagent/services/GitHubAuthenticationServiceTest.kt @@ -34,7 +34,7 @@ class GitHubAuthenticationServiceTest { every { resourceLoader.getResource(any()) } returns ClassPathResource("valid-private-key.pem") every { gitHubEnterpriseService.verifyJwt(any()) } returns Unit - assertDoesNotThrow { githubAuthenticationService.generateJwtToken() } + assertDoesNotThrow { githubAuthenticationService.generateAndCacheJwtToken() } assertNotNull(cachingService.get("jwtToken")) } @@ -44,6 +44,6 @@ class GitHubAuthenticationServiceTest { every { githubEnterpriseProperties.pemFile } returns "invalid-private-key.pem" every { resourceLoader.getResource(any()) } returns ClassPathResource("invalid-private-key.pem") - assertThrows(IllegalArgumentException::class.java) { githubAuthenticationService.generateJwtToken() } + assertThrows(IllegalArgumentException::class.java) { githubAuthenticationService.generateAndCacheJwtToken() } } }