Skip to content

Commit

Permalink
feature(SDK): Add Support for Authentication
Browse files Browse the repository at this point in the history
closes: #76
  • Loading branch information
wba2hi committed Feb 22, 2024
1 parent 26118f7 commit 1d4ff54
Show file tree
Hide file tree
Showing 5 changed files with 127 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import io.grpc.ManagedChannel
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.eclipse.kuksa.authentication.JsonWebToken
import org.eclipse.kuksa.extension.TAG
import org.eclipse.kuksa.extension.copy
import org.eclipse.kuksa.extension.datapoint
Expand All @@ -40,6 +41,7 @@ import org.eclipse.kuksa.vsscore.model.VssProperty
import org.eclipse.kuksa.vsscore.model.VssSpecification
import org.eclipse.kuksa.vsscore.model.heritage
import org.eclipse.kuksa.vsscore.model.vssProperties
import kotlin.properties.Delegates

/**
* The DataBrokerConnection holds an active connection to the DataBroker. The Connection can be use to interact with the
Expand All @@ -54,8 +56,18 @@ class DataBrokerConnection internal constructor(
),
private val dataBrokerSubscriber: DataBrokerSubscriber = DataBrokerSubscriber(dataBrokerTransporter),
) {
/**
* Used to register and unregister multiple [DisconnectListener].
*/
val disconnectListeners = MultiListener<DisconnectListener>()

/**
* A JsonWebToken can be provided to authenticate against the DataBroker.
*/
var jsonWebToken: JsonWebToken? by Delegates.observable(null) { _, _, newValue ->
dataBrokerTransporter.jsonWebToken = newValue
}

init {
val state = managedChannel.getState(false)
managedChannel.notifyWhenStateChanged(state) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.withContext
import org.eclipse.kuksa.authentication.JsonWebToken
import org.eclipse.kuksa.extension.TAG

/**
Expand All @@ -34,6 +35,7 @@ import org.eclipse.kuksa.extension.TAG
*/
class DataBrokerConnector @JvmOverloads constructor(
private val managedChannel: ManagedChannel,
private val jsonWebToken: JsonWebToken? = null,
private val defaultDispatcher: CoroutineDispatcher = Dispatchers.Default,
) {

Expand Down Expand Up @@ -73,6 +75,9 @@ class DataBrokerConnector @JvmOverloads constructor(

if (state == ConnectivityState.READY) {
return@withContext DataBrokerConnection(managedChannel, defaultDispatcher)
.apply {
jsonWebToken = this@DataBrokerConnector.jsonWebToken
}
} else {
managedChannel.shutdownNow()
throw DataBrokerException("timeout")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ import io.grpc.stub.StreamObserver
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.eclipse.kuksa.authentication.JsonWebToken
import org.eclipse.kuksa.authentication.withAuthenticationInterceptor
import org.eclipse.kuksa.extension.TAG
import org.eclipse.kuksa.extension.applyDatapoint
import org.eclipse.kuksa.proto.v1.KuksaValV1
Expand All @@ -49,13 +51,19 @@ internal class DataBrokerTransporter(
private val managedChannel: ManagedChannel,
private val defaultDispatcher: CoroutineDispatcher = Dispatchers.Default,
) {

init {
val state = managedChannel.getState(false)
check(state == ConnectivityState.READY) {
"ManagedChannel needs to be connected to the target"
}
}

/**
* A JsonWebToken can be provided to authenticate against the DataBroker.
*/
var jsonWebToken: JsonWebToken? = null

/**
* Sends a request to the DataBroker to respond with the specified [vssPath] and [fields] values.
*
Expand All @@ -76,7 +84,9 @@ internal class DataBrokerTransporter(
.build()

return@withContext try {
blockingStub.get(request)
blockingStub
.withAuthenticationInterceptor(jsonWebToken)
.get(request)
} catch (e: StatusRuntimeException) {
throw DataBrokerException(e.message, e)
}
Expand Down Expand Up @@ -114,7 +124,9 @@ internal class DataBrokerTransporter(
.build()

return@withContext try {
blockingStub.set(request)
blockingStub
.withAuthenticationInterceptor(jsonWebToken)
.set(request)
} catch (e: StatusRuntimeException) {
throw DataBrokerException(e.message, e)
}
Expand Down Expand Up @@ -171,7 +183,9 @@ internal class DataBrokerTransporter(

cancellableContext.run {
try {
asyncStub.subscribe(request, streamObserver)
asyncStub
.withAuthenticationInterceptor(jsonWebToken)
.subscribe(request, streamObserver)
} catch (e: StatusRuntimeException) {
throw DataBrokerException(e.message, e)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*
* Copyright (c) 2024 Contributors to the Eclipse Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* SPDX-License-Identifier: Apache-2.0
*
*/

package org.eclipse.kuksa.authentication

/**
* A JsonWebToken can be used to authenticate against the DataBroker. For authentication to work the DataBroker must be
* started with authentication enabled first.
* The JsonWebToken is defined by an [authScheme] and [token]. The [authScheme] is set to "Bearer". The [token] should
* contain a valid JsonWebToken.
*
* It will be send to the DataBroker as part of the Header Metadata in the following format:
*
* Headers
* Authorization: [authScheme] [token]
*
*/
data class JsonWebToken(
val token: String,
) {
val authScheme: String
get() = DEFAULT_AUTH_SCHEME

private companion object {
private const val DEFAULT_AUTH_SCHEME = "Bearer"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/*
* Copyright (c) 2024 Contributors to the Eclipse Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* SPDX-License-Identifier: Apache-2.0
*
*/

package org.eclipse.kuksa.authentication

import com.google.common.net.HttpHeaders
import io.grpc.ClientInterceptor
import io.grpc.Metadata
import io.grpc.stub.MetadataUtils
import org.eclipse.kuksa.proto.v1.VALGrpc.VALBlockingStub
import org.eclipse.kuksa.proto.v1.VALGrpc.VALStub

internal fun VALBlockingStub.withAuthenticationInterceptor(jsonWebToken: JsonWebToken?): VALBlockingStub {
if (jsonWebToken == null) return this

val authenticationInterceptor = clientInterceptor(jsonWebToken)
return withInterceptors(authenticationInterceptor)
}

internal fun VALStub.withAuthenticationInterceptor(jsonWebToken: JsonWebToken?): VALStub {
if (jsonWebToken == null) return this

val authenticationInterceptor = clientInterceptor(jsonWebToken)
return withInterceptors(authenticationInterceptor)
}

private fun clientInterceptor(jsonWebToken: JsonWebToken): ClientInterceptor? {
val authorizationHeader = Metadata.Key.of(HttpHeaders.AUTHORIZATION, Metadata.ASCII_STRING_MARSHALLER)

val metadata = Metadata()
metadata.put(authorizationHeader, "${jsonWebToken.authScheme} ${jsonWebToken.token}")

return MetadataUtils.newAttachHeadersInterceptor(metadata)
}

0 comments on commit 1d4ff54

Please sign in to comment.