From 78c4df6e1ed3b87d8b3a13a216794ea99169fffd Mon Sep 17 00:00:00 2001 From: Szymon Owczarzak Date: Fri, 26 Mar 2021 13:14:18 +0100 Subject: [PATCH] #345 | Admin session handling improved (#355) * All POST and DELETE requests should now refresh session token token expiration increased to 5 hours * Small fix for version service Co-authored-by: szymon.owczarzak --- .../cogboard/config/EndpointLoader.kt | 2 +- .../config/controller/EndpointsController.kt | 2 +- .../cogboard/config/handler/HandlerUtil.kt | 12 ++- .../cogboard/config/handler/VersionHandler.kt | 3 +- .../config/service/CredentialsService.kt | 6 +- .../config/service/EndpointsService.kt | 6 +- .../cogboard/config/service/VersionService.kt | 9 +- .../security/JwtAuthHandlerFactory.kt | 5 +- .../cognifide/cogboard/security/JwtCommon.kt | 49 +++++++++++ .../cogboard/security/LoginHandler.kt | 51 ++++------- .../cogboard/security/SessionHandler.kt | 50 +++++++++++ .../ExtensionFunctions.kt} | 23 ++++- .../com/cognifide/cogboard/widget/Util.kt | 9 -- .../cogboard/widget/handler/DeleteWidget.kt | 8 +- .../cogboard/widget/handler/UpdateWidget.kt | 8 +- .../cogboard/widget/type/JenkinsJobWidget.kt | 2 +- ...x.server.api.handler.RoutingHandlerFactory | 3 +- .../cogboard/utils/ExtensionFunctionsTest.kt | 84 +++++++++++++++++++ .../com/cognifide/cogboard/widget/UtilTest.kt | 68 --------------- cogboard-webapp/src/actions/helpers.js | 9 +- cogboard-webapp/src/actions/thunks.js | 4 +- .../src/components/WidgetForm/index.js | 5 +- .../cypress/support/dashboards.js | 2 +- .../cypress-tests/cypress/support/user.js | 47 +++++------ knotx/conf/openapi.yaml | 6 ++ knotx/conf/routes/operations.conf | 12 +++ 26 files changed, 304 insertions(+), 181 deletions(-) create mode 100644 cogboard-app/src/main/kotlin/com/cognifide/cogboard/security/JwtCommon.kt create mode 100644 cogboard-app/src/main/kotlin/com/cognifide/cogboard/security/SessionHandler.kt rename cogboard-app/src/main/kotlin/com/cognifide/cogboard/{config/utils/JsonUtils.kt => utils/ExtensionFunctions.kt} (65%) delete mode 100644 cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/Util.kt create mode 100644 cogboard-app/src/test/kotlin/com/cognifide/cogboard/utils/ExtensionFunctionsTest.kt delete mode 100644 cogboard-app/src/test/kotlin/com/cognifide/cogboard/widget/UtilTest.kt diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/config/EndpointLoader.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/config/EndpointLoader.kt index 346334720..98529e0fe 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/config/EndpointLoader.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/config/EndpointLoader.kt @@ -6,7 +6,7 @@ import com.cognifide.cogboard.CogboardConstants.Companion.PROP_USER import com.cognifide.cogboard.config.EndpointsConfig.Companion.CREDENTIALS_PROP import com.cognifide.cogboard.config.service.CredentialsService import com.cognifide.cogboard.config.service.EndpointsService -import com.cognifide.cogboard.config.utils.JsonUtils.findById +import com.cognifide.cogboard.utils.ExtensionFunctions.findById import io.vertx.core.json.JsonArray import io.vertx.core.json.JsonObject diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/config/controller/EndpointsController.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/config/controller/EndpointsController.kt index 6a8bd9e4f..cf6971d1d 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/config/controller/EndpointsController.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/config/controller/EndpointsController.kt @@ -3,7 +3,7 @@ package com.cognifide.cogboard.config.controller import com.cognifide.cogboard.CogboardConstants import com.cognifide.cogboard.config.EndpointsConfig.Companion.ENDPOINT_ID_PROP import com.cognifide.cogboard.config.service.EndpointsService -import com.cognifide.cogboard.config.utils.JsonUtils.findAllByKeyValue +import com.cognifide.cogboard.utils.ExtensionFunctions.findAllByKeyValue import io.vertx.core.AbstractVerticle import io.vertx.core.eventbus.Message import io.vertx.core.json.JsonArray diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/config/handler/HandlerUtil.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/config/handler/HandlerUtil.kt index c82535fbe..4b48f6268 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/config/handler/HandlerUtil.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/config/handler/HandlerUtil.kt @@ -1,13 +1,19 @@ package com.cognifide.cogboard.config.handler +import io.vertx.core.http.HttpMethod import io.vertx.reactivex.ext.web.RoutingContext class HandlerUtil { companion object { fun endResponse(body: String, routingContext: RoutingContext) { - routingContext.response() - .putHeader("Content-Type", "application/json") - .end(body) + if (SESSION_REFRESHERS.contains(routingContext.request().method())) { + routingContext.request().headers().add("body", body) + routingContext.reroute(HttpMethod.POST, "/api/session/refresh") + } else routingContext.response() + .putHeader("Content-Type", "application/json") + .end(body) } + + val SESSION_REFRESHERS = setOf(HttpMethod.POST, HttpMethod.DELETE) } } diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/config/handler/VersionHandler.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/config/handler/VersionHandler.kt index e7669fa7f..cfcb1cf9b 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/config/handler/VersionHandler.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/config/handler/VersionHandler.kt @@ -1,6 +1,7 @@ package com.cognifide.cogboard.config.handler import com.cognifide.cogboard.CogboardConstants +import com.cognifide.cogboard.utils.ExtensionFunctions.endEmptyJson import io.knotx.server.api.handler.RoutingHandlerFactory import io.vertx.core.Handler import io.vertx.core.json.JsonObject @@ -17,6 +18,6 @@ class VersionHandler : RoutingHandlerFactory { ?.publish(CogboardConstants.EVENT_VERSION_CONFIG, JsonObject()) event .response() - .end(JsonObject().toString()) + .endEmptyJson() } } diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/config/service/CredentialsService.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/config/service/CredentialsService.kt index b1f6b38b5..085fbf6d5 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/config/service/CredentialsService.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/config/service/CredentialsService.kt @@ -4,9 +4,9 @@ import com.cognifide.cogboard.config.CredentialsConfig.Companion.CREDENTIALS_ARR import com.cognifide.cogboard.config.CredentialsConfig.Companion.CREDENTIAL_ID_PREFIX import com.cognifide.cogboard.config.CredentialsConfig.Companion.CREDENTIAL_ID_PROP import com.cognifide.cogboard.config.CredentialsConfig.Companion.CREDENTIAL_LABEL_PROP -import com.cognifide.cogboard.config.utils.JsonUtils.findById -import com.cognifide.cogboard.config.utils.JsonUtils.getObjectPositionById -import com.cognifide.cogboard.config.utils.JsonUtils.putIfNotExist +import com.cognifide.cogboard.utils.ExtensionFunctions.findById +import com.cognifide.cogboard.utils.ExtensionFunctions.getObjectPositionById +import com.cognifide.cogboard.utils.ExtensionFunctions.putIfNotExist import com.cognifide.cogboard.config.validation.credentials.CredentialsValidator import com.cognifide.cogboard.storage.Storage import com.cognifide.cogboard.storage.VolumeStorageFactory.credentials diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/config/service/EndpointsService.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/config/service/EndpointsService.kt index 6f3fae272..6ca1d85f0 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/config/service/EndpointsService.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/config/service/EndpointsService.kt @@ -5,9 +5,9 @@ import com.cognifide.cogboard.config.EndpointsConfig.Companion.ENDPOINTS_ARRAY import com.cognifide.cogboard.config.EndpointsConfig.Companion.ENDPOINT_ID_PREFIX import com.cognifide.cogboard.config.EndpointsConfig.Companion.ENDPOINT_ID_PROP import com.cognifide.cogboard.config.EndpointsConfig.Companion.ENDPOINT_LABEL_PROP -import com.cognifide.cogboard.config.utils.JsonUtils.findById -import com.cognifide.cogboard.config.utils.JsonUtils.getObjectPositionById -import com.cognifide.cogboard.config.utils.JsonUtils.putIfNotExist +import com.cognifide.cogboard.utils.ExtensionFunctions.findById +import com.cognifide.cogboard.utils.ExtensionFunctions.getObjectPositionById +import com.cognifide.cogboard.utils.ExtensionFunctions.putIfNotExist import com.cognifide.cogboard.config.validation.endpoints.EndpointsValidator import com.cognifide.cogboard.storage.Storage import com.cognifide.cogboard.storage.VolumeStorageFactory.endpoints diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/config/service/VersionService.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/config/service/VersionService.kt index ddb9359c5..8b6a7a29d 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/config/service/VersionService.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/config/service/VersionService.kt @@ -25,21 +25,26 @@ class VersionService { isNewer(latestVersion, runningVersion) fun checkVersion(body: JsonObject): Boolean { - val latestVersion = body.getString("tag_name")?.substring(1) ?: "" + val repoLatestVersion = body.getString("tag_name")?.substring(1) ?: "" + if (repoLatestVersion.isValid()) { + this.latestVersion = repoLatestVersion + } this.lastCheck = LocalDateTime.now() return if (isNewer(latestVersion, runningVersion)) { - this.latestVersion = latestVersion this.latestResponse = body true } else false } + private fun String.isValid() = this.matches(VALID_PATTERN) + companion object { const val YEAR_INIT = 2000 const val DAY_INIT = 1 const val HOUR_INIT = 0 const val MINUTE_INIT = 0 + val VALID_PATTERN = Regex("\\d+\\.\\d+\\.\\d+") fun isNewer(newValue: String, oldValue: String = "0.0.0"): Boolean { val v1parts = newValue.split('.').map { it.toInt() } diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/security/JwtAuthHandlerFactory.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/security/JwtAuthHandlerFactory.kt index ea1807728..839d98e7b 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/security/JwtAuthHandlerFactory.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/security/JwtAuthHandlerFactory.kt @@ -1,9 +1,9 @@ package com.cognifide.cogboard.security +import com.cognifide.cogboard.utils.ExtensionFunctions.toJWT import io.knotx.server.api.security.AuthHandlerFactory import io.vertx.core.json.JsonObject import io.vertx.ext.auth.KeyStoreOptions -import io.vertx.ext.auth.jwt.JWTAuthOptions import io.vertx.reactivex.core.Vertx import io.vertx.reactivex.ext.auth.jwt.JWTAuth import io.vertx.reactivex.ext.web.handler.AuthHandler @@ -15,7 +15,6 @@ class JwtAuthHandlerFactory : AuthHandlerFactory { override fun create(vertx: Vertx?, config: JsonObject?): AuthHandler { val keyStoreOptions = KeyStoreOptions(config) - val jwtAuthOptions = JWTAuthOptions().setKeyStore(keyStoreOptions) - return JWTAuthHandler.create(JWTAuth.create(vertx, jwtAuthOptions)) + return JWTAuthHandler.create(JWTAuth.create(vertx, keyStoreOptions.toJWT())) } } diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/security/JwtCommon.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/security/JwtCommon.kt new file mode 100644 index 000000000..626f27580 --- /dev/null +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/security/JwtCommon.kt @@ -0,0 +1,49 @@ +package com.cognifide.cogboard.security + +import com.cognifide.cogboard.utils.ExtensionFunctions.asJsonObject +import com.cognifide.cogboard.utils.ExtensionFunctions.endEmptyJson +import com.cognifide.cogboard.utils.ExtensionFunctions.toJWT +import io.vertx.core.json.JsonObject +import io.vertx.ext.auth.KeyStoreOptions +import io.vertx.ext.jwt.JWTOptions +import io.vertx.reactivex.core.Vertx +import io.vertx.reactivex.ext.auth.jwt.JWTAuth +import io.vertx.reactivex.ext.web.RoutingContext + +open class JwtCommon { + + protected lateinit var jwtAuth: JWTAuth + + protected fun sendJWT(ctx: RoutingContext, user: String) { + val body = ctx.request().getHeader("body") ?: "" + ctx.response().putHeader("token", generateJWT(user)) + if (body.isNotEmpty()) ctx.response().end(body) + else ctx.response().endEmptyJson() + } + + protected open fun init(vertx: Vertx, config: JsonObject) { + jwtAuth = initJWT(vertx, config) + } + + private fun generateJWT(username: String): String { + val token = jwtAuth.generateToken( + username.asJsonObject("name"), + JWTOptions().setExpiresInSeconds(SESSION_DURATION_IN_SECONDS) + ) ?: "no data" + return "Bearer $token" + } + + private fun initJWT(vertx: Vertx, config: JsonObject): JWTAuth { + val options = KeyStoreOptions() + .setType(config.getString("type", "jceks")) + .setPath(config.getString("path", "keystore.jceks")) + .setPassword(config.getString("password", "secret")) + + return JWTAuth.create(vertx, options.toJWT()) + } + + companion object { + const val SESSION_DURATION_IN_SECONDS = 5 * 60 * 60 // hours * min * sec + const val DEFAULT_ERROR = "Unable to authenticate" + } +} diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/security/LoginHandler.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/security/LoginHandler.kt index fc7d15e88..9fe9f9663 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/security/LoginHandler.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/security/LoginHandler.kt @@ -9,26 +9,23 @@ import com.cognifide.cogboard.storage.Storage import com.cognifide.cogboard.storage.VolumeStorageFactory import io.knotx.server.api.handler.RoutingHandlerFactory import io.vertx.core.Handler +import io.vertx.core.VertxException import io.vertx.core.json.JsonObject -import io.vertx.ext.auth.KeyStoreOptions -import io.vertx.ext.auth.jwt.JWTAuthOptions -import io.vertx.ext.jwt.JWTOptions import io.vertx.reactivex.core.Vertx -import io.vertx.reactivex.ext.auth.jwt.JWTAuth import io.vertx.reactivex.ext.web.RoutingContext -class LoginHandler(val storage: Storage = VolumeStorageFactory.admin()) : RoutingHandlerFactory { +class LoginHandler(val storage: Storage = VolumeStorageFactory.admin()) : RoutingHandlerFactory, JwtCommon() { - private var vertx: Vertx? = null - private lateinit var config: JsonObject + private lateinit var wrongUserMsg: String + private lateinit var wrongPassMsg: String override fun getName(): String = "login-handler" override fun create(vertx: Vertx?, config: JsonObject?): Handler { - this.vertx = vertx - this.config = config ?: JsonObject() - val wrongUserMsg = config?.getString("wrongUserMsg") ?: "Please, enter correct Username" - val wrongPassMsg = config?.getString("wrongPassMsg") ?: "Please, enter correct Password" + if (vertx == null || config == null) { + throw VertxException("Unable to create LoginHandler vertex=$vertx, config=$config") + } + init(vertx, config) return Handler { ctx -> ctx.bodyAsJson?.let { @@ -44,6 +41,12 @@ class LoginHandler(val storage: Storage = VolumeStorageFactory.admin()) : Routin } } + override fun init(vertx: Vertx, config: JsonObject) { + super.init(vertx, config) + wrongUserMsg = config.getString("wrongUserMsg", DEFAULT_ERROR) + wrongPassMsg = config.getString("wrongPassMsg", DEFAULT_ERROR) + } + private fun getAdmin(name: String): Admin? { val admin = storage.loadConfig() val username = admin.getString(PROP_USER) @@ -54,33 +57,9 @@ class LoginHandler(val storage: Storage = VolumeStorageFactory.admin()) : Routin } private fun isAuthorized(admin: Admin, password: String?) = - admin.password.isNotBlank() && admin.password == password + admin.password.isNotBlank() && admin.password == password private fun sendUnauthorized(ctx: RoutingContext, message: String) { ctx.response().setStatusMessage(message).setStatusCode(STATUS_CODE_401).end() } - - private fun sendJWT(ctx: RoutingContext, user: String) { - ctx.response().end(generateJWT(user)) - } - - private fun generateJWT(username: String): String { - val keyStore = KeyStoreOptions() - .setType(config.getString("type", "jceks")) - .setPath(config.getString("path", "keystore.jceks")) - .setPassword(config.getString("password", "secret")) - - val config = JWTAuthOptions().setKeyStore(keyStore) - val jwtAuth = JWTAuth.create(vertx, config) - - val token = jwtAuth?.generateToken( - JsonObject().put("name", username), - JWTOptions().setExpiresInSeconds(SESSION_DURATION_IN_SECONDS) - ) ?: "no data" - return "{\"token\":\"Bearer $token\"}" - } - - companion object { - private const val SESSION_DURATION_IN_SECONDS = 2 * 60 * 60 // hours * min * sec - } } diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/security/SessionHandler.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/security/SessionHandler.kt new file mode 100644 index 000000000..554de2f80 --- /dev/null +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/security/SessionHandler.kt @@ -0,0 +1,50 @@ +package com.cognifide.cogboard.security + +import com.cognifide.cogboard.CogboardConstants.Companion.STATUS_CODE_401 +import com.cognifide.cogboard.utils.ExtensionFunctions.asJsonObject +import com.cognifide.cogboard.storage.Storage +import com.cognifide.cogboard.storage.VolumeStorageFactory +import io.knotx.server.api.handler.RoutingHandlerFactory +import io.vertx.core.Handler +import io.vertx.core.VertxException +import io.vertx.core.json.JsonObject +import io.vertx.reactivex.core.Vertx +import io.vertx.reactivex.ext.web.RoutingContext + +class SessionHandler(val storage: Storage = VolumeStorageFactory.admin()) : RoutingHandlerFactory, JwtCommon() { + + private lateinit var sessionRefreshError: String + + override fun getName(): String = "session-handler" + + override fun create(vertx: Vertx?, config: JsonObject?): Handler { + if (vertx == null || config == null) { + throw VertxException("Unable to create SessionHandler vertex=$vertx, config=$config") + } + init(vertx, config) + + return Handler { ctx -> + val token = ctx + .request() + .getHeader("Authorization") + ?.substringAfter("Bearer ") + ?.asJsonObject("jwt") + + jwtAuth.authenticate(token) { + val username = it.result().delegate.principal().getString("name") ?: "" + if (it.succeeded() && username.isNotBlank()) { + sendJWT(ctx, username) + } else sendUnauthorized(ctx, sessionRefreshError) + } + } + } + + override fun init(vertx: Vertx, config: JsonObject) { + super.init(vertx, config) + sessionRefreshError = config.getString("sessionRefreshError", DEFAULT_ERROR) + } + + private fun sendUnauthorized(ctx: RoutingContext, message: String) { + ctx.response().setStatusMessage(message).setStatusCode(STATUS_CODE_401).end() + } +} diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/config/utils/JsonUtils.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/utils/ExtensionFunctions.kt similarity index 65% rename from cogboard-app/src/main/kotlin/com/cognifide/cogboard/config/utils/JsonUtils.kt rename to cogboard-app/src/main/kotlin/com/cognifide/cogboard/utils/ExtensionFunctions.kt index c2bea7fb6..f907935ff 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/config/utils/JsonUtils.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/utils/ExtensionFunctions.kt @@ -1,16 +1,27 @@ -package com.cognifide.cogboard.config.utils +package com.cognifide.cogboard.utils import com.cognifide.cogboard.CogboardConstants import io.vertx.core.json.JsonArray import io.vertx.core.json.JsonObject +import io.vertx.ext.auth.KeyStoreOptions +import io.vertx.ext.auth.jwt.JWTAuthOptions +import io.vertx.reactivex.core.http.HttpServerResponse import java.util.stream.Collectors -object JsonUtils { +object ExtensionFunctions { fun JsonArray.findById(idValue: String, idKey: String = CogboardConstants.PROP_ID): JsonObject { return this.findByKeyValue(idValue, idKey) } + fun String.asJsonObject(propName: String): JsonObject = JsonObject().put(propName, this) + + fun HttpServerResponse.endEmptyJson() { + this.end(JsonObject().toString()) + } + + fun KeyStoreOptions.toJWT(): JWTAuthOptions = JWTAuthOptions().setKeyStore(this) + private fun JsonArray.findByKeyValue(value: String, key: String): JsonObject { return this.stream() .map { it as JsonObject } @@ -44,4 +55,12 @@ object JsonUtils { } return this } + + fun String.makeUrlPublic(publicDomain: String): String { + if (this == "") return "" + if (publicDomain == "") return this + + val rest = this.substringAfter("//").substringAfter("/") + return "${publicDomain.removeSuffix("/")}/$rest" + } } diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/Util.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/Util.kt deleted file mode 100644 index b6175e735..000000000 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/Util.kt +++ /dev/null @@ -1,9 +0,0 @@ -package com.cognifide.cogboard.widget - -fun String.makeUrlPublic(publicDomain: String): String { - if (this == "") return "" - if (publicDomain == "") return this - - val rest = this.substringAfter("//").substringAfter("/") - return "${publicDomain.removeSuffix("/")}/$rest" -} diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/handler/DeleteWidget.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/handler/DeleteWidget.kt index fbefc4658..5d3dab857 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/handler/DeleteWidget.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/handler/DeleteWidget.kt @@ -15,12 +15,6 @@ class DeleteWidget : RoutingHandlerFactory { vertx ?.eventBus() ?.publish(CogboardConstants.EVENT_DELETE_WIDGET_CONFIG, event.body.toJsonObject()) - event - .response() - .end(config?.getJsonObject("body", DEFAULT_NO_BODY)?.encode()) - } - - companion object { - val DEFAULT_NO_BODY: JsonObject = JsonObject().put("status", "failed") + event.reroute("/api/session/refresh") } } diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/handler/UpdateWidget.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/handler/UpdateWidget.kt index a2f1e4eee..2fb65badb 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/handler/UpdateWidget.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/handler/UpdateWidget.kt @@ -15,12 +15,6 @@ class UpdateWidget : RoutingHandlerFactory { vertx ?.eventBus() ?.publish(CogboardConstants.EVENT_UPDATE_WIDGET_CONFIG, event.body.toJsonObject()) - event - .response() - .end(config?.getJsonObject("body", DEFAULT_NO_BODY)?.encode()) - } - - companion object { - val DEFAULT_NO_BODY: JsonObject = JsonObject().put("status", "failed") + event.reroute("/api/session/refresh") } } diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/JenkinsJobWidget.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/JenkinsJobWidget.kt index 18fd14c5a..1dbc54c2c 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/JenkinsJobWidget.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/widget/type/JenkinsJobWidget.kt @@ -1,10 +1,10 @@ package com.cognifide.cogboard.widget.type import com.cognifide.cogboard.config.service.BoardsConfigService +import com.cognifide.cogboard.utils.ExtensionFunctions.makeUrlPublic import com.cognifide.cogboard.http.auth.AuthenticationType import com.cognifide.cogboard.widget.AsyncWidget import com.cognifide.cogboard.widget.Widget -import com.cognifide.cogboard.widget.makeUrlPublic import io.vertx.core.Vertx import io.vertx.core.json.JsonObject import kotlin.streams.toList diff --git a/cogboard-app/src/main/resources/META-INF/services/io.knotx.server.api.handler.RoutingHandlerFactory b/cogboard-app/src/main/resources/META-INF/services/io.knotx.server.api.handler.RoutingHandlerFactory index 3ca8787b4..a41fb2c60 100644 --- a/cogboard-app/src/main/resources/META-INF/services/io.knotx.server.api.handler.RoutingHandlerFactory +++ b/cogboard-app/src/main/resources/META-INF/services/io.knotx.server.api.handler.RoutingHandlerFactory @@ -3,4 +3,5 @@ com.cognifide.cogboard.config.handler.VersionHandler com.cognifide.cogboard.widget.handler.UpdateWidget com.cognifide.cogboard.widget.handler.DeleteWidget com.cognifide.cogboard.widget.handler.ContentUpdateWidget -com.cognifide.cogboard.security.LoginHandler \ No newline at end of file +com.cognifide.cogboard.security.LoginHandler +com.cognifide.cogboard.security.SessionHandler \ No newline at end of file diff --git a/cogboard-app/src/test/kotlin/com/cognifide/cogboard/utils/ExtensionFunctionsTest.kt b/cogboard-app/src/test/kotlin/com/cognifide/cogboard/utils/ExtensionFunctionsTest.kt new file mode 100644 index 000000000..cf577f20b --- /dev/null +++ b/cogboard-app/src/test/kotlin/com/cognifide/cogboard/utils/ExtensionFunctionsTest.kt @@ -0,0 +1,84 @@ +package com.cognifide.cogboard.utils + +import com.cognifide.cogboard.utils.ExtensionFunctions.asJsonObject +import com.cognifide.cogboard.utils.ExtensionFunctions.makeUrlPublic +import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.TestInstance + +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +class ExtensionFunctionsTest { + + @Test + fun `String ext asJsonObject() Should Create Valid Json Object`() { + val actual = "value".asJsonObject("propName") + + Assertions.assertEquals("{\"propName\":\"value\"}", actual.toString()) + } + + @Test + fun `String ext asJsonObject() Should Contain Value`() { + val actual = "value".asJsonObject("propName") + + Assertions.assertEquals("value", actual.getString("propName")) + } + + @Test + fun `Should Replace Private with Public Secure`() { + Assertions.assertEquals(PUBLIC_URL_SECURE, PRIVATE_URL_SECURE.makeUrlPublic(PUBLIC_DOMAIN)) + } + + @Test + fun `Should Replace Private with Public Secure even when ends with slash`() { + Assertions.assertEquals(PUBLIC_URL_SECURE, PRIVATE_URL_SECURE.makeUrlPublic(PUBLIC_DOMAIN_SLASH_ENDING)) + } + + @Test + fun `Should Replace Private with Public`() { + Assertions.assertEquals(PUBLIC_URL_SECURE, PRIVATE_URL.makeUrlPublic(PUBLIC_DOMAIN)) + } + + @Test + fun `Should Replace Private with Public even when ends with slash`() { + Assertions.assertEquals(PUBLIC_URL_SECURE, PRIVATE_URL.makeUrlPublic(PUBLIC_DOMAIN_SLASH_ENDING)) + } + + @Test + fun `Should Leave as it was when Public`() { + Assertions.assertEquals(PUBLIC_URL_SECURE, PUBLIC_URL.makeUrlPublic(PUBLIC_DOMAIN)) + } + + @Test + fun `Should Leave as it was when Public even when ends with slash`() { + Assertions.assertEquals(PUBLIC_URL_SECURE, PUBLIC_URL.makeUrlPublic(PUBLIC_DOMAIN_SLASH_ENDING)) + } + + @Test + fun `Should Leave as it was when Public Secure`() { + Assertions.assertEquals(PUBLIC_URL_SECURE, PUBLIC_URL_SECURE.makeUrlPublic(PUBLIC_DOMAIN)) + } + + @Test + fun `Should Leave as it was when Public Secure even when ends with slash`() { + Assertions.assertEquals(PUBLIC_URL_SECURE, PUBLIC_URL_SECURE.makeUrlPublic(PUBLIC_DOMAIN_SLASH_ENDING)) + } + + @Test + fun `Should be empty when was empty`() { + Assertions.assertEquals("", "".makeUrlPublic(PUBLIC_DOMAIN)) + } + + @Test + fun `Should leave as it was when Public was empty`() { + Assertions.assertEquals(PRIVATE_URL, PRIVATE_URL.makeUrlPublic("")) + } + + companion object { + const val PUBLIC_DOMAIN = "https://public.com" + const val PUBLIC_DOMAIN_SLASH_ENDING = "https://public.com/" + const val PUBLIC_URL = "http://public.com/someresource" + const val PUBLIC_URL_SECURE = "https://public.com/someresource" + const val PRIVATE_URL = "http://192.168.1.1/someresource" + const val PRIVATE_URL_SECURE = "https://192.168.1.1/someresource" + } +} \ No newline at end of file diff --git a/cogboard-app/src/test/kotlin/com/cognifide/cogboard/widget/UtilTest.kt b/cogboard-app/src/test/kotlin/com/cognifide/cogboard/widget/UtilTest.kt deleted file mode 100644 index a49b3c71d..000000000 --- a/cogboard-app/src/test/kotlin/com/cognifide/cogboard/widget/UtilTest.kt +++ /dev/null @@ -1,68 +0,0 @@ -package com.cognifide.cogboard.widget - -import org.junit.jupiter.api.Assertions.assertEquals -import org.junit.jupiter.api.Test -import org.junit.jupiter.api.TestInstance - -@TestInstance(TestInstance.Lifecycle.PER_CLASS) -class UtilTest { - - @Test - fun `Should Replace Private with Public Secure`() { - assertEquals(PUBLIC_URL_SECURE, PRIVATE_URL_SECURE.makeUrlPublic(PUBLIC_DOMAIN)) - } - - @Test - fun `Should Replace Private with Public Secure even when ends with slash`() { - assertEquals(PUBLIC_URL_SECURE, PRIVATE_URL_SECURE.makeUrlPublic(PUBLIC_DOMAIN_SLASH_ENDING)) - } - - @Test - fun `Should Replace Private with Public`() { - assertEquals(PUBLIC_URL_SECURE, PRIVATE_URL.makeUrlPublic(PUBLIC_DOMAIN)) - } - - @Test - fun `Should Replace Private with Public even when ends with slash`() { - assertEquals(PUBLIC_URL_SECURE, PRIVATE_URL.makeUrlPublic(PUBLIC_DOMAIN_SLASH_ENDING)) - } - - @Test - fun `Should Leave as it was when Public`() { - assertEquals(PUBLIC_URL_SECURE, PUBLIC_URL.makeUrlPublic(PUBLIC_DOMAIN)) - } - - @Test - fun `Should Leave as it was when Public even when ends with slash`() { - assertEquals(PUBLIC_URL_SECURE, PUBLIC_URL.makeUrlPublic(PUBLIC_DOMAIN_SLASH_ENDING)) - } - - @Test - fun `Should Leave as it was when Public Secure`() { - assertEquals(PUBLIC_URL_SECURE, PUBLIC_URL_SECURE.makeUrlPublic(PUBLIC_DOMAIN)) - } - - @Test - fun `Should Leave as it was when Public Secure even when ends with slash`() { - assertEquals(PUBLIC_URL_SECURE, PUBLIC_URL_SECURE.makeUrlPublic(PUBLIC_DOMAIN_SLASH_ENDING)) - } - - @Test - fun `Should be empty when was empty`() { - assertEquals("", "".makeUrlPublic(PUBLIC_DOMAIN)) - } - - @Test - fun `Should leave as it was when Public was empty`() { - assertEquals(PRIVATE_URL, PRIVATE_URL.makeUrlPublic("")) - } - - companion object { - const val PUBLIC_DOMAIN = "https://public.com" - const val PUBLIC_DOMAIN_SLASH_ENDING = "https://public.com/" - const val PUBLIC_URL = "http://public.com/someresource" - const val PUBLIC_URL_SECURE = "https://public.com/someresource" - const val PRIVATE_URL = "http://192.168.1.1/someresource" - const val PRIVATE_URL_SECURE = "https://192.168.1.1/someresource" - } -} \ No newline at end of file diff --git a/cogboard-webapp/src/actions/helpers.js b/cogboard-webapp/src/actions/helpers.js index 0ff1e77fa..6a370c092 100644 --- a/cogboard-webapp/src/actions/helpers.js +++ b/cogboard-webapp/src/actions/helpers.js @@ -1,6 +1,6 @@ import { dataChanged, setLogoutReasonMessage } from './actionCreators'; import { logout } from './thunks'; -import { isAuthenticated } from '../utils/auth'; +import { isAuthenticated, setToken } from '../utils/auth'; import { mergeRight, assocPath } from 'ramda'; import { checkResponseStatus } from '../utils/fetch'; @@ -29,7 +29,12 @@ export const fetchData = ( return fetch(url, authenticationConfig) .then(checkResponseStatus) - .then(response => response.json()); + .then(response => { + if (response.headers.get('token')) { + setToken(response.headers.get('token')); + } + return response.json(); + }); }; const makeIdCreator = prefix => allIds => { diff --git a/cogboard-webapp/src/actions/thunks.js b/cogboard-webapp/src/actions/thunks.js index e0cdec241..e19027f96 100644 --- a/cogboard-webapp/src/actions/thunks.js +++ b/cogboard-webapp/src/actions/thunks.js @@ -41,7 +41,6 @@ import { } from './helpers'; import { URL, NOTIFICATIONS } from '../constants'; import { - setToken, removeToken, getToken, getUserRole, @@ -91,8 +90,7 @@ export const login = (credentials, loginAsGuest) => dispatch => { dispatch(pushNotification(NOTIFICATIONS.LOGIN(`Guest: ${guestName}`))); } else { return fetchData(URL.LOGIN, { method: 'POST', data: credentials }).then( - ({ token }) => { - setToken(token); + () => { dispatch(loginSuccess()); dispatch(pushNotification(NOTIFICATIONS.LOGIN(getUserRole()))); }, diff --git a/cogboard-webapp/src/components/WidgetForm/index.js b/cogboard-webapp/src/components/WidgetForm/index.js index dd11d5e0e..e2b7be8dc 100644 --- a/cogboard-webapp/src/components/WidgetForm/index.js +++ b/cogboard-webapp/src/components/WidgetForm/index.js @@ -17,7 +17,8 @@ import { Tab, Button } from '@material-ui/core'; import DynamicForm from '../DynamicForm'; import WidgetTypeForm from '../WidgetTypeForm'; import { StyledCancelButton } from './styled'; -import { StyledTabPanel, StyledTabs } from './../styled'; +import { StyledTabPanel, StyledTabs } from '../styled'; +import { isAuthenticated } from '../../utils/auth'; const WidgetForm = ({ handleSubmit, handleCancel, ...initialFormValues }) => { const boardColumns = useSelector( @@ -67,6 +68,7 @@ const WidgetForm = ({ handleSubmit, handleCancel, ...initialFormValues }) => { const dialogFieldNames = widgetType && widgetType.dialogFields ? widgetType.dialogFields : []; const hasDialogFields = dialogFieldNames.length !== 0; + const isAuth = isAuthenticated(); const handleTabChange = (event, newValue) => { setTabValue(newValue); @@ -116,6 +118,7 @@ const WidgetForm = ({ handleSubmit, handleCancel, ...initialFormValues }) => { )}