Skip to content

Commit

Permalink
Merge pull request #9 from leanix/feature/CID-1829/validate-configura…
Browse files Browse the repository at this point in the history
…tion

CID-1829: validate configuration
  • Loading branch information
mohamedlajmileanix authored Aug 31, 2023
2 parents 165baea + d467561 commit 9cb05ae
Show file tree
Hide file tree
Showing 13 changed files with 275 additions and 6 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package net.leanix.vsm.gitlab.broker.connector.adapter.feign

import net.leanix.vsm.gitlab.broker.connector.adapter.feign.data.GitlabGroup
import net.leanix.vsm.gitlab.broker.connector.adapter.feign.data.GitlabUser
import net.leanix.vsm.gitlab.broker.shared.auth.adapter.feign.GitlabFeignClientConfiguration
import org.springframework.cloud.openfeign.FeignClient
import org.springframework.web.bind.annotation.GetMapping

@FeignClient(
name = "gitlab",
url = "\${leanix.vsm.connector.gitlab-url}/api/v4",
configuration = [GitlabFeignClientConfiguration::class]
)
interface GitlabClient {
@GetMapping("/user")
fun getCurrentUser(): GitlabUser

@GetMapping("/groups")
fun getAllGroups(): List<GitlabGroup>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package net.leanix.vsm.gitlab.broker.connector.adapter.feign

import net.leanix.vsm.gitlab.broker.connector.adapter.feign.data.GitlabGroup
import net.leanix.vsm.gitlab.broker.connector.adapter.feign.data.GitlabUser

interface GitlabClientProvider {
fun getCurrentUser(): GitlabUser
fun getGroupByFullPath(fullPath: String): GitlabGroup?
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package net.leanix.vsm.gitlab.broker.connector.adapter.feign

import net.leanix.vsm.gitlab.broker.connector.adapter.feign.data.GitlabGroup
import net.leanix.vsm.gitlab.broker.connector.adapter.feign.data.GitlabUser
import net.leanix.vsm.gitlab.broker.shared.exception.InvalidToken
import net.leanix.vsm.gitlab.broker.shared.exception.OrgNameValidationFailed
import org.slf4j.LoggerFactory
import org.springframework.stereotype.Component

@Component
class GitlabFeignClientProvider(
private val gitlabClient: GitlabClient
) : GitlabClientProvider {

private val logger = LoggerFactory.getLogger(GitlabFeignClientProvider::class.java)

override fun getCurrentUser(): GitlabUser {
return runCatching { gitlabClient.getCurrentUser() }.onFailure {
logger.error("Invalid token, could not get current user")
throw InvalidToken()
}.getOrThrow()
}

override fun getGroupByFullPath(fullPath: String): GitlabGroup? {
return runCatching {
gitlabClient.getAllGroups().first { it.fullPath == fullPath }
}.onFailure { throw OrgNameValidationFailed() }.getOrThrow()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package net.leanix.vsm.gitlab.broker.connector.adapter.feign.data

import com.fasterxml.jackson.annotation.JsonProperty

class GitlabGroup(
@JsonProperty("id")
val id: Int,
@JsonProperty("full_path")
val fullPath: String
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package net.leanix.vsm.gitlab.broker.connector.adapter.feign.data

import com.fasterxml.jackson.annotation.JsonProperty

class GitlabUser(
@JsonProperty("id")
val id: Int,
@JsonProperty("is_admin")
val isAdmin: Boolean?
)
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@ import java.util.Locale
open class BaseConnectorService {

@Autowired
private lateinit var loggingService: LoggingService
lateinit var loggingService: LoggingService

@Autowired
private lateinit var messageSource: MessageSource
lateinit var messageSource: MessageSource

private val logger = KotlinLogging.logger {}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package net.leanix.vsm.gitlab.broker.connector.application

import feign.FeignException
import net.leanix.vsm.gitlab.broker.connector.adapter.feign.GitlabClientProvider
import net.leanix.vsm.gitlab.broker.connector.domain.GitLabAssignment
import net.leanix.vsm.gitlab.broker.shared.exception.AccessLevelValidationFailed
import net.leanix.vsm.gitlab.broker.shared.exception.InvalidToken
import net.leanix.vsm.gitlab.broker.shared.exception.OrgNameValidationFailed
import org.springframework.stereotype.Component

@Component
class ValidationService(
private val gitlabClientProvider: GitlabClientProvider
) : BaseConnectorService() {

fun validateConfiguration(gitLabAssignment: GitLabAssignment) {
val orgName = gitLabAssignment.connectorConfiguration.orgName
runCatching {
validateUserAccess()
validateGroupPath(orgName)
}.onSuccess {
logInfoMessages("vsm.configuration.validation.successful", arrayOf(orgName), gitLabAssignment)
}.onFailure { exception ->
handleExceptions(exception, orgName, gitLabAssignment)
}
}

private fun validateUserAccess() {
if (gitlabClientProvider.getCurrentUser().isAdmin != true) throw AccessLevelValidationFailed()
}

private fun validateGroupPath(fullPath: String) {
if (gitlabClientProvider.getGroupByFullPath(fullPath) == null) throw OrgNameValidationFailed()
}

private fun handleExceptions(
exception: Throwable,
orgName: String,
gitLabAssignment: GitLabAssignment
) {
when (exception) {
is InvalidToken -> {
logFailedMessages("vsm.configuration.invalid_token", arrayOf(orgName), gitLabAssignment)
}

is AccessLevelValidationFailed -> {
logFailedMessages("vsm.configuration.access_level", arrayOf(orgName), gitLabAssignment)
}

is OrgNameValidationFailed -> {
logFailedMessages("vsm.configuration.invalid_org_name", arrayOf(orgName), gitLabAssignment)
}

is FeignException -> {
logFailedMessages("vsm.configuration.validation.failed", arrayOf(orgName), gitLabAssignment)
}
}
logFailedStatus(exception.message, gitLabAssignment)
throw exception
}
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
package net.leanix.vsm.gitlab.broker.webhook.adapter.feign
package net.leanix.vsm.gitlab.broker.shared.auth.adapter.feign

import feign.RequestInterceptor
import org.springframework.beans.factory.annotation.Value
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration

@Configuration
class GitlabWebhookFeignClientConfiguration(
class GitlabFeignClientConfiguration(

@Value("\${leanix.vsm.connector.gitlab-token}") private val gitlabAccessToken: String
) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,9 @@ sealed class VsmException(message: String? = null) : RuntimeException(message)
class NoRepositoriesFound : VsmException()

class GraphqlException(message: String?) : VsmException(message)

class InvalidToken : VsmException()

class AccessLevelValidationFailed : VsmException()

class OrgNameValidationFailed : VsmException()
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package net.leanix.vsm.gitlab.broker.webhook.adapter.feign

import net.leanix.vsm.gitlab.broker.shared.auth.adapter.feign.GitlabFeignClientConfiguration
import net.leanix.vsm.gitlab.broker.webhook.domain.GitlabWebhook
import org.springframework.cloud.openfeign.FeignClient
import org.springframework.web.bind.annotation.DeleteMapping
Expand All @@ -11,7 +12,7 @@ import org.springframework.web.bind.annotation.RequestParam
@FeignClient(
name = "gitlabWebhookClient",
url = "\${leanix.vsm.connector.gitlab-url}/api/v4/hooks",
configuration = [GitlabWebhookFeignClientConfiguration::class]
configuration = [GitlabFeignClientConfiguration::class]
)
interface GitlabWebhookClient {

Expand Down
7 changes: 6 additions & 1 deletion src/main/resources/messages.properties
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
vsm.repos.not_found=Zero repositories found in {0} GitHub organisation. Hint: In case organisation is valid, check if the inclusion list has at least one valid repository name.
vsm.repos.total=Fetched Org Repositories Ids. Result : {0} repos
vsm.repos.imported=Repository Imported
vsm.repos.imported=Repository Imported
vsm.configuration.validation.successful=Validation successful for configuration with group/subgroup path {0}
vsm.configuration.validation.failed=Validation failed for configuration with group/subgroup path {0}
vsm.configuration.invalid_token=Invalid token in configuration with group/subgroup path {0}
vsm.configuration.access_level=Access level insufficient for configuration with group/subgroup path {0}
vsm.configuration.invalid_org_name=Invalid group/subgroup path in configuration with group/subgroup path {0}
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
package net.leanix.vsm.gitlab.broker.connector.application

import io.mockk.every
import io.mockk.mockk
import io.mockk.verify
import net.leanix.vsm.gitlab.broker.connector.adapter.feign.GitlabClient
import net.leanix.vsm.gitlab.broker.connector.adapter.feign.GitlabFeignClientProvider
import net.leanix.vsm.gitlab.broker.connector.shared.DataBuilder
import net.leanix.vsm.gitlab.broker.logs.application.LoggingService
import net.leanix.vsm.gitlab.broker.shared.exception.AccessLevelValidationFailed
import net.leanix.vsm.gitlab.broker.shared.exception.InvalidToken
import net.leanix.vsm.gitlab.broker.shared.exception.OrgNameValidationFailed
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.assertThrows
import org.springframework.context.MessageSource

class ValidationServiceTest {

private val messageSource = mockk<MessageSource>()
private val loggingService = mockk<LoggingService>()
private val gitlabClient = mockk<GitlabClient>()
private val gitlabFeignClientProvider = GitlabFeignClientProvider(gitlabClient)
private val validationService = ValidationService(gitlabFeignClientProvider)

@BeforeEach
fun setUp() {
validationService.messageSource = messageSource
validationService.loggingService = loggingService

every { messageSource.getMessage(allAny(), allAny(), allAny()) } returns "mock-message"
every { loggingService.sendAdminLog(any()) } returns Unit
every { loggingService.sendStatusLog(any()) } returns Unit
}

@Test
fun `it should validate the configuration`() {
every { gitlabClient.getCurrentUser() } returns DataBuilder.getGitlabCurrentUser(true)
every { gitlabClient.getAllGroups() } returns DataBuilder.getAllGroups()

validationService.validateConfiguration(DataBuilder.getGitlabAssignment())

verify(exactly = 1) { gitlabClient.getCurrentUser() }
verify(exactly = 1) { gitlabClient.getAllGroups() }
}

@Test
fun `it should not validate the configuration if token is invalid`() {
every { gitlabClient.getCurrentUser() } throws Exception()
every { gitlabClient.getAllGroups() } returns DataBuilder.getAllGroups()

assertThrows<InvalidToken> {
validationService.validateConfiguration(DataBuilder.getGitlabAssignment())
}

verify(exactly = 1) { gitlabClient.getCurrentUser() }
verify(exactly = 0) { gitlabClient.getAllGroups() }
}

@Test
fun `it should not validate the configuration if user is not admin`() {
every { gitlabClient.getCurrentUser() } returns DataBuilder.getGitlabCurrentUser(false)
every { gitlabClient.getAllGroups() } returns DataBuilder.getAllGroups()

assertThrows<AccessLevelValidationFailed> {
validationService.validateConfiguration(DataBuilder.getGitlabAssignment())
}

verify(exactly = 1) { gitlabClient.getCurrentUser() }
verify(exactly = 0) { gitlabClient.getAllGroups() }
}

@Test
fun `it should not validate the configuration if group name is invalid`() {
every { gitlabClient.getCurrentUser() } returns DataBuilder.getGitlabCurrentUser(true)
every { gitlabClient.getAllGroups() } throws Exception()

assertThrows<OrgNameValidationFailed> {
validationService.validateConfiguration(DataBuilder.getGitlabAssignment())
}

verify(exactly = 1) { gitlabClient.getCurrentUser() }
verify(exactly = 1) { gitlabClient.getAllGroups() }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package net.leanix.vsm.gitlab.broker.connector.shared

import net.leanix.vsm.gitlab.broker.connector.adapter.feign.data.GitlabGroup
import net.leanix.vsm.gitlab.broker.connector.adapter.feign.data.GitlabUser
import net.leanix.vsm.gitlab.broker.connector.domain.GitLabAssignment
import net.leanix.vsm.gitlab.broker.connector.domain.GitLabConfiguration
import java.util.*

object DataBuilder {

fun getGitlabAssignment() = GitLabAssignment(
runId = UUID.randomUUID(),
workspaceId = UUID.randomUUID(),
configurationId = UUID.randomUUID(),
connectorConfiguration = GitLabConfiguration("group-1")
)

fun getGitlabCurrentUser(isAdmin: Boolean) = GitlabUser(
id = 1,
isAdmin = isAdmin
)

fun getAllGroups() = listOf(
GitlabGroup(
id = 1,
fullPath = "group-1",
),
GitlabGroup(
id = 2,
fullPath = "group-2",
)
)
}

0 comments on commit 9cb05ae

Please sign in to comment.