Skip to content

Commit

Permalink
Merge pull request #283 from Cognifide/feature/token-auth-sonarqube
Browse files Browse the repository at this point in the history
Sonarqube Widget - token authentication support
  • Loading branch information
yahorm authored Oct 23, 2020
2 parents 33a799e + 5d0f227 commit 30c8957
Show file tree
Hide file tree
Showing 21 changed files with 258 additions and 219 deletions.
2 changes: 1 addition & 1 deletion api-mocks/__files/zabbix/uptime.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@
"evaltype": "0",
"lastclock": "{{now format='epoch'}}",
"lastns": "521184209",
"lastvalue": "{{randomValue length=9 type='NUMERIC'}}",
"lastvalue": "{{randomValue length=7 type='NUMERIC'}}",
"prevvalue": "23292221"
}
],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ class CogboardConstants {
const val PROP_HEADERS = "headers"
const val PROP_CONTENT = "content"
const val PROP_WIDGET_TYPE = "type"
const val PROP_AUTHENTICATION_TYPES = "authenticationTypes"
const val PROP_SCHEDULE_PERIOD = "schedulePeriod"
const val PROP_SCHEDULE_PERIOD_DEFAULT = 120L // 120 seconds
const val PROP_SCHEDULE_DELAY_DEFAULT = 10L // 10 seconds
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,13 @@ package com.cognifide.cogboard.http
import com.cognifide.cogboard.CogboardConstants
import com.cognifide.cogboard.CogboardConstants.Companion.PROP_STATUS_CODE
import com.cognifide.cogboard.CogboardConstants.Companion.PROP_STATUS_MESSAGE
import com.cognifide.cogboard.http.auth.AuthenticationFactory
import com.cognifide.cogboard.http.auth.AuthenticationType
import io.vertx.core.AbstractVerticle
import io.vertx.core.buffer.Buffer
import io.vertx.core.json.DecodeException
import io.vertx.core.json.Json
import io.vertx.core.json.JsonArray
import io.vertx.core.json.JsonObject
import io.vertx.core.logging.Logger
import io.vertx.core.logging.LoggerFactory
Expand Down Expand Up @@ -88,34 +92,68 @@ class HttpClient : AbstractVerticle() {
val pass = config.getString(CogboardConstants.PROP_PASSWORD) ?: ""
val token = config.getString(CogboardConstants.PROP_TOKEN) ?: ""
val headers = config.getJsonObject(CogboardConstants.PROP_HEADERS)
val authenticationTypes = Json.decodeValue(config.getString(CogboardConstants.PROP_AUTHENTICATION_TYPES))
?: JsonArray()

if (user.isNotBlank() && token.isNotBlank()) {
request.basicAuthentication(user, token)
} else if (user.isNotBlank() && pass.isNotBlank()) {
request.basicAuthentication(user, pass)
}
val authenticationType = getAuthenticationType(authenticationTypes as JsonArray, user, token, pass)

request.authenticate(authenticationType, user, token, pass)

applyRequestHeaders(request, headers)

return request
}

private fun HttpRequest<Buffer>.authenticate(
authType: AuthenticationType,
username: String,
token: String,
pass: String
) {
AuthenticationFactory(username, token, pass, this).create(authType)
}

private fun getAuthenticationType(authenticationTypes: JsonArray, user: String, token: String, pass: String): AuthenticationType {

return authenticationTypes.stream()
.map { AuthenticationType.valueOf(it.toString()) }
.filter { hasAuthTypeCorrectCredentials(it, user, token, pass) }
.findFirst()
.orElse(AuthenticationType.NONE)
}

private fun hasAuthTypeCorrectCredentials(
authType: AuthenticationType,
username: String,
token: String,
pass: String
): Boolean {
return when {
authType == AuthenticationType.TOKEN && username.isNotBlank() && token.isNotBlank() -> true
authType == AuthenticationType.TOKEN_AS_USERNAME && token.isNotBlank() -> true
else -> authType == AuthenticationType.BASIC && username.isNotBlank() && pass.isNotBlank()
}
}

private fun applyRequestHeaders(request: HttpRequest<Buffer>, headers: JsonObject?) {
request.putHeader(HttpConstants.HEADER_CONTENT_TYPE, HttpConstants.CONTENT_TYPE_JSON)
headers
?.map { Pair(it.key, it.value as String) }
?.forEach { request.putHeader(it.first, it.second) }
}

private fun toJson(response: HttpResponse<Buffer>): JsonObject {
return try {
response.bodyAsJsonObject()
} catch (e: DecodeException) {
try {
JsonObject().put(CogboardConstants.PROP_ARRAY, response.bodyAsJsonArray())
} catch (e: DecodeException) {
JsonObject().put("body", response.bodyAsString())
private fun executeCheckRequest(request: HttpRequest<Buffer>, address: String?, body: JsonObject?) {
request.sendJsonObject(body) {
val result = JsonObject()
if (it.succeeded()) {
result.put(PROP_STATUS_CODE, it.result().statusCode())
result.put(PROP_STATUS_MESSAGE, it.result().statusMessage())
result.put(CogboardConstants.PROP_BODY, it.result().bodyAsString())
} else {
result.put(PROP_STATUS_MESSAGE, "unsuccessful")
LOGGER.error(it.cause()?.message)
}
vertx.eventBus().send(address, result)
}
}

Expand All @@ -138,18 +176,17 @@ class HttpClient : AbstractVerticle() {
}
}

private fun executeCheckRequest(request: HttpRequest<Buffer>, address: String?, body: JsonObject?) {
request.sendJsonObject(body) {
val result = JsonObject()
if (it.succeeded()) {
result.put(PROP_STATUS_CODE, it.result().statusCode())
result.put(PROP_STATUS_MESSAGE, it.result().statusMessage())
result.put(CogboardConstants.PROP_BODY, it.result().bodyAsString())
} else {
result.put(PROP_STATUS_MESSAGE, "unsuccessful")
LOGGER.error(it.cause()?.message)
private fun toJson(response: HttpResponse<Buffer>): JsonObject {
return try {
response.bodyAsJsonObject()
} catch (e: DecodeException) {
try {
JsonObject().put(CogboardConstants.PROP_ARRAY, response.bodyAsJsonArray())
} catch (e: DecodeException) {
JsonObject().put(CogboardConstants.PROP_BODY, response.bodyAsString())
}
vertx.eventBus().send(address, result)
} catch (e: IllegalStateException) {
JsonObject()
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package com.cognifide.cogboard.http.auth

import io.vertx.core.buffer.Buffer
import io.vertx.ext.web.client.HttpRequest

class AuthenticationFactory(
private val user: String,
private val token: String,
private val password: String,
private val request: HttpRequest<Buffer>
) {

fun create(authType: AuthenticationType): HttpRequest<Buffer> {
return when {
AuthenticationType.TOKEN == authType -> token()
AuthenticationType.TOKEN_AS_USERNAME == authType -> tokenAsUsername()
AuthenticationType.BASIC == authType -> basic()
else -> request
}
}

private fun basic(): HttpRequest<Buffer> = request.basicAuthentication(user, password)

private fun token(): HttpRequest<Buffer> = request.basicAuthentication(user, token)

private fun tokenAsUsername(): HttpRequest<Buffer> = request.basicAuthentication(token, "")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.cognifide.cogboard.http.auth

enum class AuthenticationType {

BASIC,
TOKEN,
TOKEN_AS_USERNAME,
NONE;
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@ package com.cognifide.cogboard.widget

import com.cognifide.cogboard.CogboardConstants
import com.cognifide.cogboard.config.service.BoardsConfigService
import com.cognifide.cogboard.http.auth.AuthenticationType
import io.vertx.core.Vertx
import io.vertx.core.eventbus.MessageConsumer
import io.vertx.core.json.Json
import io.vertx.core.json.JsonObject

/**
Expand Down Expand Up @@ -38,6 +40,14 @@ abstract class AsyncWidget(
return super.stop()
}

/**
* Type of authentication.
* Should be overridden for widgets which use `httpGet(..)` from HttpClient
*/
protected open fun authenticationTypes(): Set<AuthenticationType> {
return setOf(AuthenticationType.BASIC)
}

/**
* Notifies Widget that it is time to update.
* Use `httpGet(..)`, `httpPost(..)` or `httpGetStatus(..)` in order to request new state from 3rd party endpoint.
Expand Down Expand Up @@ -90,5 +100,6 @@ abstract class AsyncWidget(
.put(CogboardConstants.PROP_EVENT_ADDRESS, eventBusAddress)
.put(CogboardConstants.PROP_USER, user)
.put(CogboardConstants.PROP_PASSWORD, password)
.put(CogboardConstants.PROP_AUTHENTICATION_TYPES, Json.encode(authenticationTypes()))
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.cognifide.cogboard.widget.type

import com.cognifide.cogboard.config.service.BoardsConfigService
import com.cognifide.cogboard.http.auth.AuthenticationType
import com.cognifide.cogboard.widget.AsyncWidget
import com.cognifide.cogboard.widget.Widget
import io.vertx.core.Vertx
Expand All @@ -15,6 +16,10 @@ class JenkinsJobWidget(

private val path: String = config.getString("path", "")

override fun authenticationTypes(): Set<AuthenticationType> {
return setOf(AuthenticationType.TOKEN, AuthenticationType.BASIC)
}

override fun handleResponse(responseBody: JsonObject) {
if (checkAuthorized(responseBody)) {
val lastBuild = responseBody.getJsonObject("lastBuild")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import com.cognifide.cogboard.CogboardConstants.Companion.REQUEST_METHOD_DELETE
import com.cognifide.cogboard.CogboardConstants.Companion.REQUEST_METHOD_GET
import com.cognifide.cogboard.CogboardConstants.Companion.REQUEST_METHOD_POST
import com.cognifide.cogboard.CogboardConstants.Companion.REQUEST_METHOD_PUT
import com.cognifide.cogboard.http.auth.AuthenticationType
import com.cognifide.cogboard.widget.AsyncWidget
import com.cognifide.cogboard.widget.Widget
import io.netty.util.internal.StringUtil.EMPTY_STRING
Expand All @@ -29,14 +30,16 @@ class ServiceCheckWidget(vertx: Vertx, config: JsonObject) : AsyncWidget(vertx,
get() = if (publicUrl.isNotBlank()) "$publicUrl${config.getString(PROP_PATH, EMPTY_STRING)}"
else config.getString(PROP_PATH, EMPTY_STRING)

override fun authenticationTypes(): Set<AuthenticationType> {
return setOf(AuthenticationType.TOKEN, AuthenticationType.BASIC)
}

override fun updateState() {
if (urlToCheck.isNotBlank()) {
if (requestMethod == REQUEST_METHOD_GET) {
httpGet(url = urlToCheck)
} else if (requestMethod == REQUEST_METHOD_DELETE) {
httpDelete(url = urlToCheck)
} else {
handlePostPut()
when (requestMethod) {
REQUEST_METHOD_GET -> httpGet(url = urlToCheck)
REQUEST_METHOD_DELETE -> httpDelete(url = urlToCheck)
else -> handlePostPut()
}
} else {
sendConfigurationError("Public URL or Path is blank")
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.cognifide.cogboard.widget.type.sonarqube

import com.cognifide.cogboard.CogboardConstants
import com.cognifide.cogboard.http.auth.AuthenticationType
import com.cognifide.cogboard.widget.AsyncWidget
import com.cognifide.cogboard.widget.Widget
import io.vertx.core.Vertx
Expand All @@ -14,6 +15,10 @@ class SonarQubeWidget(vertx: Vertx, config: JsonObject) : AsyncWidget(vertx, con
private val selectedMetrics: JsonArray = config.getJsonArray("selectedMetrics")
private val version: Version = Version.getVersion(config.getString("sonarQubeVersion", ""))

override fun authenticationTypes(): Set<AuthenticationType> {
return setOf(AuthenticationType.TOKEN_AS_USERNAME, AuthenticationType.BASIC)
}

override fun handleResponse(responseBody: JsonObject) {
if (checkAuthorized(responseBody)) {
val data = getData(responseBody)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@ package com.cognifide.cogboard.widget.type.zabbix

import com.cognifide.cogboard.CogboardConstants
import com.cognifide.cogboard.config.service.BoardsConfigService
import com.cognifide.cogboard.http.auth.AuthenticationType
import com.cognifide.cogboard.widget.AsyncWidget
import com.cognifide.cogboard.widget.Widget
import io.vertx.core.Vertx
import io.vertx.core.json.JsonArray
import io.vertx.core.json.JsonObject
import io.vertx.core.logging.Logger
import io.vertx.core.logging.LoggerFactory
import kotlin.math.roundToLong

class ZabbixWidget(
vertx: Vertx,
Expand All @@ -21,6 +23,10 @@ class ZabbixWidget(
private val maxValue: Int = config.getInteger(MAX_VALUE, 0)
private val range: JsonArray = config.getJsonArray(RANGE, JsonArray())

override fun authenticationTypes(): Set<AuthenticationType> {
return setOf(AuthenticationType.NONE)
}

override fun updateState() {
when {
publicUrl.isBlank() -> sendConfigurationError("Endpoint URL is blank.")
Expand Down Expand Up @@ -87,7 +93,7 @@ class ZabbixWidget(
}

private fun getStatusResponse(lastValue: String): Widget.Status {
val convertedValue = lastValue.toLong()
val convertedValue = lastValue.toFloat().roundToLong()
return when {
metricHasMaxValue() -> status(convertedValue.convertToPercentage(maxValue), range)
metricHasProgress() -> status(convertedValue, range)
Expand Down
Loading

0 comments on commit 30c8957

Please sign in to comment.