Skip to content

Commit 30c8957

Browse files
authored
Merge pull request #283 from Cognifide/feature/token-auth-sonarqube
Sonarqube Widget - token authentication support
2 parents 33a799e + 5d0f227 commit 30c8957

File tree

21 files changed

+258
-219
lines changed

21 files changed

+258
-219
lines changed

api-mocks/__files/zabbix/uptime.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@
5151
"evaltype": "0",
5252
"lastclock": "{{now format='epoch'}}",
5353
"lastns": "521184209",
54-
"lastvalue": "{{randomValue length=9 type='NUMERIC'}}",
54+
"lastvalue": "{{randomValue length=7 type='NUMERIC'}}",
5555
"prevvalue": "23292221"
5656
}
5757
],

cogboard-app/src/main/kotlin/com/cognifide/cogboard/CogboardConstants.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ class CogboardConstants {
2323
const val PROP_HEADERS = "headers"
2424
const val PROP_CONTENT = "content"
2525
const val PROP_WIDGET_TYPE = "type"
26+
const val PROP_AUTHENTICATION_TYPES = "authenticationTypes"
2627
const val PROP_SCHEDULE_PERIOD = "schedulePeriod"
2728
const val PROP_SCHEDULE_PERIOD_DEFAULT = 120L // 120 seconds
2829
const val PROP_SCHEDULE_DELAY_DEFAULT = 10L // 10 seconds

cogboard-app/src/main/kotlin/com/cognifide/cogboard/http/HttpClient.kt

Lines changed: 61 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,13 @@ package com.cognifide.cogboard.http
33
import com.cognifide.cogboard.CogboardConstants
44
import com.cognifide.cogboard.CogboardConstants.Companion.PROP_STATUS_CODE
55
import com.cognifide.cogboard.CogboardConstants.Companion.PROP_STATUS_MESSAGE
6+
import com.cognifide.cogboard.http.auth.AuthenticationFactory
7+
import com.cognifide.cogboard.http.auth.AuthenticationType
68
import io.vertx.core.AbstractVerticle
79
import io.vertx.core.buffer.Buffer
810
import io.vertx.core.json.DecodeException
11+
import io.vertx.core.json.Json
12+
import io.vertx.core.json.JsonArray
913
import io.vertx.core.json.JsonObject
1014
import io.vertx.core.logging.Logger
1115
import io.vertx.core.logging.LoggerFactory
@@ -88,34 +92,68 @@ class HttpClient : AbstractVerticle() {
8892
val pass = config.getString(CogboardConstants.PROP_PASSWORD) ?: ""
8993
val token = config.getString(CogboardConstants.PROP_TOKEN) ?: ""
9094
val headers = config.getJsonObject(CogboardConstants.PROP_HEADERS)
95+
val authenticationTypes = Json.decodeValue(config.getString(CogboardConstants.PROP_AUTHENTICATION_TYPES))
96+
?: JsonArray()
9197

92-
if (user.isNotBlank() && token.isNotBlank()) {
93-
request.basicAuthentication(user, token)
94-
} else if (user.isNotBlank() && pass.isNotBlank()) {
95-
request.basicAuthentication(user, pass)
96-
}
98+
val authenticationType = getAuthenticationType(authenticationTypes as JsonArray, user, token, pass)
99+
100+
request.authenticate(authenticationType, user, token, pass)
97101

98102
applyRequestHeaders(request, headers)
99103

100104
return request
101105
}
102106

107+
private fun HttpRequest<Buffer>.authenticate(
108+
authType: AuthenticationType,
109+
username: String,
110+
token: String,
111+
pass: String
112+
) {
113+
AuthenticationFactory(username, token, pass, this).create(authType)
114+
}
115+
116+
private fun getAuthenticationType(authenticationTypes: JsonArray, user: String, token: String, pass: String): AuthenticationType {
117+
118+
return authenticationTypes.stream()
119+
.map { AuthenticationType.valueOf(it.toString()) }
120+
.filter { hasAuthTypeCorrectCredentials(it, user, token, pass) }
121+
.findFirst()
122+
.orElse(AuthenticationType.NONE)
123+
}
124+
125+
private fun hasAuthTypeCorrectCredentials(
126+
authType: AuthenticationType,
127+
username: String,
128+
token: String,
129+
pass: String
130+
): Boolean {
131+
return when {
132+
authType == AuthenticationType.TOKEN && username.isNotBlank() && token.isNotBlank() -> true
133+
authType == AuthenticationType.TOKEN_AS_USERNAME && token.isNotBlank() -> true
134+
else -> authType == AuthenticationType.BASIC && username.isNotBlank() && pass.isNotBlank()
135+
}
136+
}
137+
103138
private fun applyRequestHeaders(request: HttpRequest<Buffer>, headers: JsonObject?) {
104139
request.putHeader(HttpConstants.HEADER_CONTENT_TYPE, HttpConstants.CONTENT_TYPE_JSON)
105140
headers
106141
?.map { Pair(it.key, it.value as String) }
107142
?.forEach { request.putHeader(it.first, it.second) }
108143
}
109144

110-
private fun toJson(response: HttpResponse<Buffer>): JsonObject {
111-
return try {
112-
response.bodyAsJsonObject()
113-
} catch (e: DecodeException) {
114-
try {
115-
JsonObject().put(CogboardConstants.PROP_ARRAY, response.bodyAsJsonArray())
116-
} catch (e: DecodeException) {
117-
JsonObject().put("body", response.bodyAsString())
145+
private fun executeCheckRequest(request: HttpRequest<Buffer>, address: String?, body: JsonObject?) {
146+
request.sendJsonObject(body) {
147+
val result = JsonObject()
148+
if (it.succeeded()) {
149+
result.put(PROP_STATUS_CODE, it.result().statusCode())
150+
result.put(PROP_STATUS_MESSAGE, it.result().statusMessage())
151+
result.put(CogboardConstants.PROP_BODY, it.result().bodyAsString())
152+
} else {
153+
result.put(PROP_STATUS_MESSAGE, "unsuccessful")
154+
LOGGER.error(it.cause()?.message)
118155
}
156+
vertx.eventBus().send(address, result)
119157
}
120158
}
121159

@@ -138,18 +176,17 @@ class HttpClient : AbstractVerticle() {
138176
}
139177
}
140178

141-
private fun executeCheckRequest(request: HttpRequest<Buffer>, address: String?, body: JsonObject?) {
142-
request.sendJsonObject(body) {
143-
val result = JsonObject()
144-
if (it.succeeded()) {
145-
result.put(PROP_STATUS_CODE, it.result().statusCode())
146-
result.put(PROP_STATUS_MESSAGE, it.result().statusMessage())
147-
result.put(CogboardConstants.PROP_BODY, it.result().bodyAsString())
148-
} else {
149-
result.put(PROP_STATUS_MESSAGE, "unsuccessful")
150-
LOGGER.error(it.cause()?.message)
179+
private fun toJson(response: HttpResponse<Buffer>): JsonObject {
180+
return try {
181+
response.bodyAsJsonObject()
182+
} catch (e: DecodeException) {
183+
try {
184+
JsonObject().put(CogboardConstants.PROP_ARRAY, response.bodyAsJsonArray())
185+
} catch (e: DecodeException) {
186+
JsonObject().put(CogboardConstants.PROP_BODY, response.bodyAsString())
151187
}
152-
vertx.eventBus().send(address, result)
188+
} catch (e: IllegalStateException) {
189+
JsonObject()
153190
}
154191
}
155192

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package com.cognifide.cogboard.http.auth
2+
3+
import io.vertx.core.buffer.Buffer
4+
import io.vertx.ext.web.client.HttpRequest
5+
6+
class AuthenticationFactory(
7+
private val user: String,
8+
private val token: String,
9+
private val password: String,
10+
private val request: HttpRequest<Buffer>
11+
) {
12+
13+
fun create(authType: AuthenticationType): HttpRequest<Buffer> {
14+
return when {
15+
AuthenticationType.TOKEN == authType -> token()
16+
AuthenticationType.TOKEN_AS_USERNAME == authType -> tokenAsUsername()
17+
AuthenticationType.BASIC == authType -> basic()
18+
else -> request
19+
}
20+
}
21+
22+
private fun basic(): HttpRequest<Buffer> = request.basicAuthentication(user, password)
23+
24+
private fun token(): HttpRequest<Buffer> = request.basicAuthentication(user, token)
25+
26+
private fun tokenAsUsername(): HttpRequest<Buffer> = request.basicAuthentication(token, "")
27+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package com.cognifide.cogboard.http.auth
2+
3+
enum class AuthenticationType {
4+
5+
BASIC,
6+
TOKEN,
7+
TOKEN_AS_USERNAME,
8+
NONE;
9+
}

cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/AsyncWidget.kt

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@ package com.cognifide.cogboard.widget
22

33
import com.cognifide.cogboard.CogboardConstants
44
import com.cognifide.cogboard.config.service.BoardsConfigService
5+
import com.cognifide.cogboard.http.auth.AuthenticationType
56
import io.vertx.core.Vertx
67
import io.vertx.core.eventbus.MessageConsumer
8+
import io.vertx.core.json.Json
79
import io.vertx.core.json.JsonObject
810

911
/**
@@ -38,6 +40,14 @@ abstract class AsyncWidget(
3840
return super.stop()
3941
}
4042

43+
/**
44+
* Type of authentication.
45+
* Should be overridden for widgets which use `httpGet(..)` from HttpClient
46+
*/
47+
protected open fun authenticationTypes(): Set<AuthenticationType> {
48+
return setOf(AuthenticationType.BASIC)
49+
}
50+
4151
/**
4252
* Notifies Widget that it is time to update.
4353
* Use `httpGet(..)`, `httpPost(..)` or `httpGetStatus(..)` in order to request new state from 3rd party endpoint.
@@ -90,5 +100,6 @@ abstract class AsyncWidget(
90100
.put(CogboardConstants.PROP_EVENT_ADDRESS, eventBusAddress)
91101
.put(CogboardConstants.PROP_USER, user)
92102
.put(CogboardConstants.PROP_PASSWORD, password)
103+
.put(CogboardConstants.PROP_AUTHENTICATION_TYPES, Json.encode(authenticationTypes()))
93104
}
94105
}

cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/JenkinsJobWidget.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package com.cognifide.cogboard.widget.type
22

33
import com.cognifide.cogboard.config.service.BoardsConfigService
4+
import com.cognifide.cogboard.http.auth.AuthenticationType
45
import com.cognifide.cogboard.widget.AsyncWidget
56
import com.cognifide.cogboard.widget.Widget
67
import io.vertx.core.Vertx
@@ -15,6 +16,10 @@ class JenkinsJobWidget(
1516

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

19+
override fun authenticationTypes(): Set<AuthenticationType> {
20+
return setOf(AuthenticationType.TOKEN, AuthenticationType.BASIC)
21+
}
22+
1823
override fun handleResponse(responseBody: JsonObject) {
1924
if (checkAuthorized(responseBody)) {
2025
val lastBuild = responseBody.getJsonObject("lastBuild")

cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/ServiceCheckWidget.kt

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import com.cognifide.cogboard.CogboardConstants.Companion.REQUEST_METHOD_DELETE
1212
import com.cognifide.cogboard.CogboardConstants.Companion.REQUEST_METHOD_GET
1313
import com.cognifide.cogboard.CogboardConstants.Companion.REQUEST_METHOD_POST
1414
import com.cognifide.cogboard.CogboardConstants.Companion.REQUEST_METHOD_PUT
15+
import com.cognifide.cogboard.http.auth.AuthenticationType
1516
import com.cognifide.cogboard.widget.AsyncWidget
1617
import com.cognifide.cogboard.widget.Widget
1718
import io.netty.util.internal.StringUtil.EMPTY_STRING
@@ -29,14 +30,16 @@ class ServiceCheckWidget(vertx: Vertx, config: JsonObject) : AsyncWidget(vertx,
2930
get() = if (publicUrl.isNotBlank()) "$publicUrl${config.getString(PROP_PATH, EMPTY_STRING)}"
3031
else config.getString(PROP_PATH, EMPTY_STRING)
3132

33+
override fun authenticationTypes(): Set<AuthenticationType> {
34+
return setOf(AuthenticationType.TOKEN, AuthenticationType.BASIC)
35+
}
36+
3237
override fun updateState() {
3338
if (urlToCheck.isNotBlank()) {
34-
if (requestMethod == REQUEST_METHOD_GET) {
35-
httpGet(url = urlToCheck)
36-
} else if (requestMethod == REQUEST_METHOD_DELETE) {
37-
httpDelete(url = urlToCheck)
38-
} else {
39-
handlePostPut()
39+
when (requestMethod) {
40+
REQUEST_METHOD_GET -> httpGet(url = urlToCheck)
41+
REQUEST_METHOD_DELETE -> httpDelete(url = urlToCheck)
42+
else -> handlePostPut()
4043
}
4144
} else {
4245
sendConfigurationError("Public URL or Path is blank")

cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/sonarqube/SonarQubeWidget.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package com.cognifide.cogboard.widget.type.sonarqube
22

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

18+
override fun authenticationTypes(): Set<AuthenticationType> {
19+
return setOf(AuthenticationType.TOKEN_AS_USERNAME, AuthenticationType.BASIC)
20+
}
21+
1722
override fun handleResponse(responseBody: JsonObject) {
1823
if (checkAuthorized(responseBody)) {
1924
val data = getData(responseBody)

cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/zabbix/ZabbixWidget.kt

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,15 @@ package com.cognifide.cogboard.widget.type.zabbix
22

33
import com.cognifide.cogboard.CogboardConstants
44
import com.cognifide.cogboard.config.service.BoardsConfigService
5+
import com.cognifide.cogboard.http.auth.AuthenticationType
56
import com.cognifide.cogboard.widget.AsyncWidget
67
import com.cognifide.cogboard.widget.Widget
78
import io.vertx.core.Vertx
89
import io.vertx.core.json.JsonArray
910
import io.vertx.core.json.JsonObject
1011
import io.vertx.core.logging.Logger
1112
import io.vertx.core.logging.LoggerFactory
13+
import kotlin.math.roundToLong
1214

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

26+
override fun authenticationTypes(): Set<AuthenticationType> {
27+
return setOf(AuthenticationType.NONE)
28+
}
29+
2430
override fun updateState() {
2531
when {
2632
publicUrl.isBlank() -> sendConfigurationError("Endpoint URL is blank.")
@@ -87,7 +93,7 @@ class ZabbixWidget(
8793
}
8894

8995
private fun getStatusResponse(lastValue: String): Widget.Status {
90-
val convertedValue = lastValue.toLong()
96+
val convertedValue = lastValue.toFloat().roundToLong()
9197
return when {
9298
metricHasMaxValue() -> status(convertedValue.convertToPercentage(maxValue), range)
9399
metricHasProgress() -> status(convertedValue, range)

0 commit comments

Comments
 (0)