diff --git a/.github/workflows/build-pull-request.yaml b/.github/workflows/build-pull-request.yaml
index 907f2019..8dc7fcb0 100755
--- a/.github/workflows/build-pull-request.yaml
+++ b/.github/workflows/build-pull-request.yaml
@@ -31,7 +31,7 @@ jobs:
retention-days: 14
- name: Run 'test' with Gradle Wrapper
- run: ./gradlew test -Dkotest.tags="!Secure"
+ run: ./gradlew test -Dkotest.tags="!CustomDatabroker"
- name: Upload Test Reports
uses: actions/upload-artifact@v3
diff --git a/.run/kuksa-sdk_IntegrationSecureTests.run.xml b/.run/kuksa-sdk_IntegrationSecureTests.run.xml
deleted file mode 100644
index 3be2c124..00000000
--- a/.run/kuksa-sdk_IntegrationSecureTests.run.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
- true
- true
- false
-
-
-
\ No newline at end of file
diff --git a/.run/kuksa-sdk_IntegrationTests.run.xml b/.run/kuksa-sdk_IntegrationTests.run.xml
index a0ace44d..12bc49be 100644
--- a/.run/kuksa-sdk_IntegrationTests.run.xml
+++ b/.run/kuksa-sdk_IntegrationTests.run.xml
@@ -13,11 +13,12 @@
-
+
false
true
false
+ false
\ No newline at end of file
diff --git a/app/src/main/java/org/eclipse/kuksa/testapp/databroker/JavaDataBrokerEngine.java b/app/src/main/java/org/eclipse/kuksa/testapp/databroker/JavaDataBrokerEngine.java
index a853735e..6935ac91 100644
--- a/app/src/main/java/org/eclipse/kuksa/testapp/databroker/JavaDataBrokerEngine.java
+++ b/app/src/main/java/org/eclipse/kuksa/testapp/databroker/JavaDataBrokerEngine.java
@@ -20,7 +20,6 @@
package org.eclipse.kuksa.testapp.databroker;
import android.content.Context;
-import android.util.Log;
import androidx.annotation.NonNull;
@@ -29,114 +28,48 @@
import org.eclipse.kuksa.DataBrokerConnector;
import org.eclipse.kuksa.DisconnectListener;
import org.eclipse.kuksa.PropertyListener;
-import org.eclipse.kuksa.TimeoutConfig;
import org.eclipse.kuksa.VssSpecificationListener;
import org.eclipse.kuksa.model.Property;
import org.eclipse.kuksa.proto.v1.KuksaValV1.GetResponse;
import org.eclipse.kuksa.proto.v1.KuksaValV1.SetResponse;
import org.eclipse.kuksa.proto.v1.Types;
import org.eclipse.kuksa.proto.v1.Types.Datapoint;
-import org.eclipse.kuksa.testapp.databroker.model.Certificate;
+import org.eclipse.kuksa.testapp.databroker.connection.DataBrokerConnectorFactory;
import org.eclipse.kuksa.testapp.databroker.model.ConnectionInfo;
import org.eclipse.kuksa.vsscore.model.VssSpecification;
-import org.jetbrains.annotations.NotNull;
-import java.io.IOException;
-import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Set;
-import java.util.concurrent.TimeUnit;
import javax.annotation.Nullable;
-import io.grpc.ChannelCredentials;
-import io.grpc.Grpc;
-import io.grpc.ManagedChannel;
-import io.grpc.ManagedChannelBuilder;
-import io.grpc.TlsChannelCredentials;
-
public class JavaDataBrokerEngine implements DataBrokerEngine {
- private static final String TAG = JavaDataBrokerEngine.class.getSimpleName();
- private static final long TIMEOUT_CONNECTION = 5;
-
@Nullable
private DataBrokerConnection dataBrokerConnection = null;
+ private final DataBrokerConnectorFactory connectorFactory = new DataBrokerConnectorFactory();
private final Set disconnectListeners = new HashSet<>();
+ // Too many to usefully handle: Checked Exceptions: IOE, RuntimeExceptions: UOE, ISE, IAE, ...
+ @SuppressWarnings("TooGenericExceptionCaught")
public void connect(
@NonNull Context context,
@NonNull ConnectionInfo connectionInfo,
@NonNull CoroutineCallback callback
) {
- if (connectionInfo.isTlsEnabled()) {
- connectSecure(context, connectionInfo, callback);
- } else {
- connectInsecure(connectionInfo, callback);
- }
- }
-
- private void connectInsecure(
- @NonNull ConnectionInfo connectInfo,
- @NonNull CoroutineCallback callback
- ) {
- try {
- ManagedChannel managedChannel = ManagedChannelBuilder
- .forAddress(connectInfo.getHost(), connectInfo.getPort())
- .usePlaintext()
- .build();
-
- connect(managedChannel, callback);
- } catch (IllegalArgumentException e) {
- callback.onError(e);
- }
- }
-
- private void connectSecure(
- @NotNull Context context,
- @NotNull ConnectionInfo connectInfo,
- @NotNull CoroutineCallback callback
- ) {
- Certificate certificate = connectInfo.getCertificate();
-
- ChannelCredentials tlsCredentials;
- try {
- InputStream rootCertFile = context.getContentResolver().openInputStream(certificate.getUri());
- if (rootCertFile == null) return;
-
- tlsCredentials = TlsChannelCredentials.newBuilder()
- .trustManager(rootCertFile)
- .build();
- } catch (IOException e) {
- Log.w(TAG, "Could not find file for certificate: " + certificate);
-
- return;
- }
-
try {
- ManagedChannelBuilder> channelBuilder = Grpc
- .newChannelBuilderForAddress(connectInfo.getHost(), connectInfo.getPort(), tlsCredentials);
-
- String overrideAuthority = certificate.getOverrideAuthority().trim();
- boolean hasOverrideAuthority = !overrideAuthority.isEmpty();
- if (hasOverrideAuthority) {
- channelBuilder.overrideAuthority(overrideAuthority);
- }
-
- ManagedChannel managedChannel = channelBuilder.build();
- connect(managedChannel, callback);
- } catch (IllegalArgumentException e) {
+ DataBrokerConnector connector = connectorFactory.create(context, connectionInfo);
+ connect(connector, callback);
+ } catch (Exception e) {
callback.onError(e);
}
}
private void connect(
- @NonNull ManagedChannel managedChannel,
+ @NonNull DataBrokerConnector connector,
@NonNull CoroutineCallback callback
) {
- DataBrokerConnector connector = new DataBrokerConnector(managedChannel);
- connector.setTimeoutConfig(new TimeoutConfig(TIMEOUT_CONNECTION, TimeUnit.SECONDS));
connector.connect(new CoroutineCallback<>() {
@Override
public void onSuccess(@Nullable DataBrokerConnection result) {
diff --git a/app/src/main/kotlin/org/eclipse/kuksa/testapp/databroker/KotlinDataBrokerEngine.kt b/app/src/main/kotlin/org/eclipse/kuksa/testapp/databroker/KotlinDataBrokerEngine.kt
index 2b08977a..30731180 100644
--- a/app/src/main/kotlin/org/eclipse/kuksa/testapp/databroker/KotlinDataBrokerEngine.kt
+++ b/app/src/main/kotlin/org/eclipse/kuksa/testapp/databroker/KotlinDataBrokerEngine.kt
@@ -21,11 +21,6 @@ package org.eclipse.kuksa.testapp.databroker
import android.content.Context
import androidx.lifecycle.LifecycleCoroutineScope
-import io.grpc.ChannelCredentials
-import io.grpc.Grpc
-import io.grpc.ManagedChannel
-import io.grpc.ManagedChannelBuilder
-import io.grpc.TlsChannelCredentials
import kotlinx.coroutines.launch
import org.eclipse.kuksa.CoroutineCallback
import org.eclipse.kuksa.DataBrokerConnection
@@ -33,15 +28,14 @@ import org.eclipse.kuksa.DataBrokerConnector
import org.eclipse.kuksa.DataBrokerException
import org.eclipse.kuksa.DisconnectListener
import org.eclipse.kuksa.PropertyListener
-import org.eclipse.kuksa.TimeoutConfig
import org.eclipse.kuksa.VssSpecificationListener
import org.eclipse.kuksa.model.Property
import org.eclipse.kuksa.proto.v1.KuksaValV1.GetResponse
import org.eclipse.kuksa.proto.v1.KuksaValV1.SetResponse
import org.eclipse.kuksa.proto.v1.Types.Datapoint
+import org.eclipse.kuksa.testapp.databroker.connection.DataBrokerConnectorFactory
import org.eclipse.kuksa.testapp.databroker.model.ConnectionInfo
import org.eclipse.kuksa.vsscore.model.VssSpecification
-import java.io.IOException
@Suppress("complexity:TooManyFunctions")
class KotlinDataBrokerEngine(
@@ -49,78 +43,23 @@ class KotlinDataBrokerEngine(
) : DataBrokerEngine {
override var dataBrokerConnection: DataBrokerConnection? = null
+ private val connectorFactory = DataBrokerConnectorFactory()
private val disconnectListeners = mutableSetOf()
+ // Too many to usefully handle: Checked Exceptions: IOE, RuntimeExceptions: UOE, ISE, IAE, ...
+ @Suppress("TooGenericExceptionCaught")
override fun connect(
context: Context,
connectionInfo: ConnectionInfo,
callback: CoroutineCallback,
) {
- if (connectionInfo.isTlsEnabled) {
- connectSecure(context, connectionInfo, callback)
- } else {
- connectInsecure(connectionInfo, callback)
- }
- }
-
- private fun connectInsecure(
- connectInfo: ConnectionInfo,
- callback: CoroutineCallback,
- ) {
- try {
- val managedChannel = ManagedChannelBuilder
- .forAddress(connectInfo.host, connectInfo.port)
- .usePlaintext()
- .build()
-
- connect(managedChannel, callback)
- } catch (e: IllegalArgumentException) {
- callback.onError(e)
- }
- }
-
- private fun connectSecure(
- context: Context,
- connectInfo: ConnectionInfo,
- callback: CoroutineCallback,
- ) {
- val certificate = connectInfo.certificate
-
- val tlsCredentials: ChannelCredentials
- try {
- val rootCertFile = context.contentResolver.openInputStream(certificate.uri)
- tlsCredentials = TlsChannelCredentials.newBuilder()
- .trustManager(rootCertFile)
- .build()
- } catch (e: IOException) {
+ val connector: DataBrokerConnector = try {
+ connectorFactory.create(context, connectionInfo)
+ } catch (e: Exception) {
callback.onError(e)
return
}
- try {
- val host = connectInfo.host.trim()
- val port = connectInfo.port
- val channelBuilder = Grpc
- .newChannelBuilderForAddress(host, port, tlsCredentials)
-
- val overrideAuthority = certificate.overrideAuthority.trim()
- val hasOverrideAuthority = overrideAuthority.isNotEmpty()
- if (hasOverrideAuthority) {
- channelBuilder.overrideAuthority(overrideAuthority)
- }
-
- val managedChannel = channelBuilder.build()
- connect(managedChannel, callback)
- } catch (e: IllegalArgumentException) {
- callback.onError(e)
- }
- }
-
- private fun connect(managedChannel: ManagedChannel, callback: CoroutineCallback) {
- val connector = DataBrokerConnector(managedChannel).apply {
- timeoutConfig = TimeoutConfig(TIMEOUT_CONNECTION_SEC)
- }
-
lifecycleScope.launch {
try {
dataBrokerConnection = connector.connect()
@@ -206,8 +145,4 @@ class KotlinDataBrokerEngine(
disconnectListeners.remove(listener)
dataBrokerConnection?.disconnectListeners?.unregister(listener)
}
-
- companion object {
- const val TIMEOUT_CONNECTION_SEC = 5L
- }
}
diff --git a/app/src/main/kotlin/org/eclipse/kuksa/testapp/databroker/connection/DataBrokerConnectorFactory.kt b/app/src/main/kotlin/org/eclipse/kuksa/testapp/databroker/connection/DataBrokerConnectorFactory.kt
new file mode 100644
index 00000000..067b9162
--- /dev/null
+++ b/app/src/main/kotlin/org/eclipse/kuksa/testapp/databroker/connection/DataBrokerConnectorFactory.kt
@@ -0,0 +1,101 @@
+/*
+ * 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.testapp.databroker.connection
+
+import android.content.Context
+import android.net.Uri
+import io.grpc.ChannelCredentials
+import io.grpc.Grpc
+import io.grpc.ManagedChannel
+import io.grpc.ManagedChannelBuilder
+import io.grpc.TlsChannelCredentials
+import org.eclipse.kuksa.DataBrokerConnector
+import org.eclipse.kuksa.TimeoutConfig
+import org.eclipse.kuksa.authentication.JsonWebToken
+import org.eclipse.kuksa.testapp.databroker.model.ConnectionInfo
+import org.eclipse.kuksa.testapp.extension.readAsText
+import java.io.IOException
+import java.util.concurrent.TimeUnit
+
+class DataBrokerConnectorFactory {
+ private val timeoutConfig = TimeoutConfig(5, TimeUnit.SECONDS)
+
+ @Throws(IOException::class)
+ fun create(context: Context, connectionInfo: ConnectionInfo): DataBrokerConnector {
+ val managedChannel = if (connectionInfo.isTlsEnabled) {
+ createSecureManagedChannel(context, connectionInfo)
+ } else {
+ createInsecureManagedChannel(connectionInfo)
+ }
+
+ val jsonWebToken = loadJsonWebToken(context, connectionInfo)
+
+ return DataBrokerConnector(managedChannel, jsonWebToken).apply {
+ timeoutConfig = this@DataBrokerConnectorFactory.timeoutConfig
+ }
+ }
+
+ private fun createInsecureManagedChannel(connectionInfo: ConnectionInfo): ManagedChannel {
+ val host = connectionInfo.host.trim()
+ val port = connectionInfo.port
+
+ return ManagedChannelBuilder
+ .forAddress(host, port)
+ .usePlaintext()
+ .build()
+ }
+
+ @Throws(IOException::class)
+ private fun createSecureManagedChannel(context: Context, connectionInfo: ConnectionInfo): ManagedChannel {
+ val certificate = connectionInfo.certificate
+ val rootCertFile = context.contentResolver.openInputStream(certificate.uri)
+
+ val tlsCredentials: ChannelCredentials = TlsChannelCredentials.newBuilder()
+ .trustManager(rootCertFile)
+ .build()
+
+ val host = connectionInfo.host.trim()
+ val port = connectionInfo.port
+ val channelBuilder = Grpc
+ .newChannelBuilderForAddress(host, port, tlsCredentials)
+
+ val overrideAuthority = certificate.overrideAuthority.trim()
+ val hasOverrideAuthority = overrideAuthority.isNotEmpty()
+ if (hasOverrideAuthority) {
+ channelBuilder.overrideAuthority(overrideAuthority)
+ }
+
+ return channelBuilder.build()
+ }
+
+ @Throws(IOException::class)
+ private fun loadJsonWebToken(context: Context, connectionInfo: ConnectionInfo): JsonWebToken? {
+ val isAuthenticationDisabled = !connectionInfo.isAuthenticationEnabled
+ val jwtUriPath = connectionInfo.jwtUriPath
+ if (isAuthenticationDisabled || jwtUriPath.isNullOrEmpty()) {
+ return null
+ }
+
+ val uri: Uri = Uri.parse(jwtUriPath)
+ val token = uri.readAsText(context)
+
+ return JsonWebToken(token)
+ }
+}
diff --git a/app/src/main/kotlin/org/eclipse/kuksa/testapp/databroker/model/ConnectionInfo.kt b/app/src/main/kotlin/org/eclipse/kuksa/testapp/databroker/model/ConnectionInfo.kt
index 1529e0ac..2c4788f7 100644
--- a/app/src/main/kotlin/org/eclipse/kuksa/testapp/databroker/model/ConnectionInfo.kt
+++ b/app/src/main/kotlin/org/eclipse/kuksa/testapp/databroker/model/ConnectionInfo.kt
@@ -28,8 +28,10 @@ import org.eclipse.kuksa.testapp.serialization.JsonSerializer
data class ConnectionInfo(
val host: String = "localhost",
val port: Int = 55556,
- val certificate: Certificate = Certificate.DEFAULT,
val isTlsEnabled: Boolean = false,
+ val certificate: Certificate = Certificate.DEFAULT,
+ val isAuthenticationEnabled: Boolean = false,
+ val jwtUriPath: String? = null,
)
object ConnectionInfoSerializer : JsonSerializer(ConnectionInfo.serializer()) {
diff --git a/app/src/main/kotlin/org/eclipse/kuksa/testapp/databroker/view/DataBrokerView.kt b/app/src/main/kotlin/org/eclipse/kuksa/testapp/databroker/view/DataBrokerView.kt
index 6cc4f379..7e66978b 100644
--- a/app/src/main/kotlin/org/eclipse/kuksa/testapp/databroker/view/DataBrokerView.kt
+++ b/app/src/main/kotlin/org/eclipse/kuksa/testapp/databroker/view/DataBrokerView.kt
@@ -76,6 +76,7 @@ import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch
import org.eclipse.kuksa.proto.v1.Types.Datapoint.ValueCase
import org.eclipse.kuksa.testapp.R
+import org.eclipse.kuksa.testapp.databroker.view.connection.DataBrokerConnectionView
import org.eclipse.kuksa.testapp.databroker.view.suggestions.SuggestionAdapter
import org.eclipse.kuksa.testapp.databroker.view.suggestions.SuggestionTextView
import org.eclipse.kuksa.testapp.databroker.viewmodel.ConnectionViewModel
@@ -121,7 +122,7 @@ fun DataBrokerView(
) {
Column {
if (!connectionViewModel.isConnected) {
- DataBrokerConnection(connectionViewModel)
+ DataBrokerConnectionView(connectionViewModel)
}
val dataBrokerMode = topAppBarViewModel.dataBrokerMode
if (connectionViewModel.isConnected) {
diff --git a/app/src/main/kotlin/org/eclipse/kuksa/testapp/databroker/view/connection/AuthenticationOptionsView.kt b/app/src/main/kotlin/org/eclipse/kuksa/testapp/databroker/view/connection/AuthenticationOptionsView.kt
new file mode 100644
index 00000000..29858205
--- /dev/null
+++ b/app/src/main/kotlin/org/eclipse/kuksa/testapp/databroker/view/connection/AuthenticationOptionsView.kt
@@ -0,0 +1,111 @@
+/*
+ * 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.testapp.databroker.view.connection
+
+import android.net.Uri
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material3.Checkbox
+import androidx.compose.material3.Surface
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import androidx.constraintlayout.compose.ConstraintLayout
+import org.eclipse.kuksa.testapp.databroker.model.ConnectionInfo
+import org.eclipse.kuksa.testapp.databroker.view.DefaultEdgePadding
+import org.eclipse.kuksa.testapp.databroker.view.FileSelectorSettingView
+import org.eclipse.kuksa.testapp.extension.fetchFileName
+
+@Composable
+fun AuthenticationOptionsView(
+ connectionInfo: ConnectionInfo,
+ modifier: Modifier = Modifier,
+ onAuthenticationStateChanged: (Boolean) -> Unit = {},
+ onJwtSelected: (Uri) -> Unit = {},
+) {
+ val context = LocalContext.current
+
+ Column(modifier = modifier.padding(start = DefaultEdgePadding, end = DefaultEdgePadding)) {
+ ConstraintLayout(
+ modifier = Modifier
+ .fillMaxWidth()
+ .clickable {
+ onAuthenticationStateChanged(!connectionInfo.isAuthenticationEnabled)
+ },
+ ) {
+ val (text, checkbox) = createRefs()
+ Text(
+ text = "Authentication",
+ modifier = Modifier.constrainAs(text) {
+ top.linkTo(parent.top)
+ bottom.linkTo(parent.bottom)
+ },
+ )
+ Checkbox(
+ checked = connectionInfo.isAuthenticationEnabled,
+ onCheckedChange = onAuthenticationStateChanged,
+ modifier = Modifier
+ .constrainAs(checkbox) {
+ top.linkTo(parent.top)
+ bottom.linkTo(parent.bottom)
+
+ end.linkTo(parent.end)
+ },
+ )
+ }
+
+ if (connectionInfo.isAuthenticationEnabled) {
+ val uri = Uri.parse(connectionInfo.jwtUriPath ?: "")
+ val fileName = uri.fetchFileName(context) ?: "Select JWT..."
+
+ FileSelectorSettingView(
+ label = "JWT",
+ value = fileName,
+ onResult = onJwtSelected,
+ modifier = Modifier.padding(end = 10.dp),
+ )
+ }
+ }
+}
+
+@Preview
+@Composable
+private fun DisabledTlsOptionsViewPreview() {
+ val connectionInfo = ConnectionInfo(isAuthenticationEnabled = false)
+
+ Surface {
+ AuthenticationOptionsView(connectionInfo)
+ }
+}
+
+@Preview
+@Composable
+private fun EnabledTlsOptionsViewPreview() {
+ val connectionInfo = ConnectionInfo(isAuthenticationEnabled = true)
+
+ Surface {
+ AuthenticationOptionsView(connectionInfo)
+ }
+}
diff --git a/app/src/main/kotlin/org/eclipse/kuksa/testapp/databroker/view/DataBrokerConnectionView.kt b/app/src/main/kotlin/org/eclipse/kuksa/testapp/databroker/view/connection/DataBrokerConnectionView.kt
similarity index 70%
rename from app/src/main/kotlin/org/eclipse/kuksa/testapp/databroker/view/DataBrokerConnectionView.kt
rename to app/src/main/kotlin/org/eclipse/kuksa/testapp/databroker/view/connection/DataBrokerConnectionView.kt
index 04955eb6..fcdc428d 100644
--- a/app/src/main/kotlin/org/eclipse/kuksa/testapp/databroker/view/DataBrokerConnectionView.kt
+++ b/app/src/main/kotlin/org/eclipse/kuksa/testapp/databroker/view/connection/DataBrokerConnectionView.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2023 Contributors to the Eclipse Foundation
+ * 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.
@@ -17,7 +17,7 @@
*
*/
-package org.eclipse.kuksa.testapp.databroker.view
+package org.eclipse.kuksa.testapp.databroker.view.connection
import androidx.compose.animation.AnimatedContent
import androidx.compose.animation.AnimatedVisibility
@@ -32,7 +32,6 @@ import androidx.compose.foundation.layout.width
import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material3.Button
-import androidx.compose.material3.Checkbox
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.material3.TextField
@@ -40,7 +39,6 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
-import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
@@ -52,25 +50,27 @@ import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import org.eclipse.kuksa.testapp.databroker.model.ConnectionInfo
+import org.eclipse.kuksa.testapp.databroker.view.DefaultEdgePadding
+import org.eclipse.kuksa.testapp.databroker.view.DefaultElementPadding
+import org.eclipse.kuksa.testapp.databroker.view.MinimumButtonWidth
import org.eclipse.kuksa.testapp.databroker.viewmodel.ConnectionViewModel
import org.eclipse.kuksa.testapp.databroker.viewmodel.ConnectionViewModel.ConnectionViewState
import org.eclipse.kuksa.testapp.extension.compose.Headline
import org.eclipse.kuksa.testapp.extension.compose.RememberCountdown
-import org.eclipse.kuksa.testapp.extension.fetchFileName
import org.eclipse.kuksa.testapp.preferences.ConnectionInfoRepository
@OptIn(ExperimentalComposeUiApi::class)
@Composable
-fun DataBrokerConnection(viewModel: ConnectionViewModel) {
- val context = LocalContext.current
-
+fun DataBrokerConnectionView(viewModel: ConnectionViewModel) {
val keyboardController = LocalSoftwareKeyboardController.current
- val connectionInfoState = viewModel.connectionInfoFlow.collectAsStateWithLifecycle(initialValue = ConnectionInfo())
+ val repositoryConnectionInfoState =
+ viewModel.connectionInfoFlow.collectAsStateWithLifecycle(initialValue = ConnectionInfo())
- var connectionInfo by remember(connectionInfoState.value) {
- mutableStateOf(connectionInfoState.value)
+ val connectionInfoState = remember(repositoryConnectionInfoState.value) {
+ mutableStateOf(repositoryConnectionInfoState.value)
}
+ val connectionInfo = connectionInfoState.value
Column {
Headline("Connection")
@@ -86,8 +86,8 @@ fun DataBrokerConnection(viewModel: ConnectionViewModel) {
TextField(
value = connectionInfo.host,
onValueChange = {
- val newConnectionInfo = connectionInfoState.value.copy(host = it)
- connectionInfo = newConnectionInfo
+ val newConnectionInfo = repositoryConnectionInfoState.value.copy(host = it)
+ connectionInfoState.value = newConnectionInfo
},
keyboardActions = KeyboardActions(
onDone = {
@@ -114,7 +114,7 @@ fun DataBrokerConnection(viewModel: ConnectionViewModel) {
try {
val port = value.toInt()
val newConnectionInfo = connectionInfo.copy(port = port)
- connectionInfo = newConnectionInfo
+ connectionInfoState.value = newConnectionInfo
} catch (e: NumberFormatException) {
// ignore gracefully
}
@@ -135,60 +135,44 @@ fun DataBrokerConnection(viewModel: ConnectionViewModel) {
)
}
Spacer(modifier = Modifier.padding(top = DefaultElementPadding))
- Row(
- verticalAlignment = Alignment.CenterVertically,
- modifier = Modifier.padding(start = DefaultEdgePadding, end = DefaultEdgePadding),
- ) {
- Text(text = "TLS:")
- Checkbox(checked = connectionInfo.isTlsEnabled, onCheckedChange = { isChecked ->
- val newConnectionInfo = connectionInfo.copy(isTlsEnabled = isChecked)
+ TlsOptionsView(
+ connectionInfo = connectionInfo,
+ onTlsStateChanged = { isTlsEnabled ->
+ val newConnectionInfo = connectionInfo.copy(isTlsEnabled = isTlsEnabled)
viewModel.updateConnectionInfo(newConnectionInfo)
- })
- }
-
- if (connectionInfo.isTlsEnabled) {
- val uri = connectionInfo.certificate.uri
- val fileName = uri.fetchFileName(context) ?: "Select certificate..."
-
- FileSelectorSettingView(
- label = "Certificate",
- value = fileName,
- modifier = Modifier.padding(start = DefaultEdgePadding, end = DefaultEdgePadding),
- ) {
- val newCertificate = connectionInfo.certificate.copy(uriPath = it.toString())
+ },
+ onCertificateSelected = { certificateUri ->
+ val newCertificate = connectionInfo.certificate.copy(uriPath = certificateUri.toString())
val newConnectionInfo = connectionInfo.copy(certificate = newCertificate)
viewModel.updateConnectionInfo(newConnectionInfo)
- }
+ },
+ onAuthorityOverrideChanged = { overrideAuthorityValue ->
+ val certificate = connectionInfo.certificate.copy(overrideAuthority = overrideAuthorityValue)
+ val newConnectionInfo = connectionInfo.copy(certificate = certificate)
+ connectionInfoState.value = newConnectionInfo
+ },
+ onKeyboardDone = {
+ viewModel.updateConnectionInfo(connectionInfo)
+ keyboardController?.hide()
+ },
+ )
- Spacer(modifier = Modifier.padding(top = DefaultElementPadding))
+ Spacer(modifier = Modifier.padding(top = DefaultElementPadding))
- Column(
- modifier = Modifier.padding(start = DefaultEdgePadding, end = DefaultEdgePadding),
- ) {
- TextField(
- value = connectionInfo.certificate.overrideAuthority,
- onValueChange = {
- val certificate = connectionInfo.certificate.copy(overrideAuthority = it)
- val newConnectionInfo = connectionInfo.copy(certificate = certificate)
- connectionInfo = newConnectionInfo
- },
- keyboardActions = KeyboardActions(
- onDone = {
- viewModel.updateConnectionInfo(connectionInfo)
- keyboardController?.hide()
- },
- ),
- modifier = Modifier.fillMaxWidth(),
- singleLine = true,
- enabled = connectionInfo.isTlsEnabled,
- label = {
- Text(text = "Authority override")
- },
- )
- }
+ AuthenticationOptionsView(
+ connectionInfo = connectionInfo,
+ onAuthenticationStateChanged = { isAuthenticationEnabled ->
+ val newConnectionInfo =
+ connectionInfo.copy(isAuthenticationEnabled = isAuthenticationEnabled)
+ viewModel.updateConnectionInfo(newConnectionInfo)
+ },
+ onJwtSelected = { jwtUri ->
+ val newConnectionInfo = connectionInfo.copy(jwtUriPath = jwtUri.toString())
+ viewModel.updateConnectionInfo(newConnectionInfo)
+ },
+ )
- Spacer(modifier = Modifier.padding(top = DefaultElementPadding))
- }
+ Spacer(modifier = Modifier.padding(top = DefaultElementPadding))
}
}
Row(
@@ -240,7 +224,7 @@ private fun ConnectedPreview() {
val connectionInfoRepository = ConnectionInfoRepository(LocalContext.current)
val viewModel = ConnectionViewModel(connectionInfoRepository)
Surface {
- DataBrokerConnection(viewModel = viewModel)
+ DataBrokerConnectionView(viewModel = viewModel)
}
}
@@ -251,6 +235,6 @@ private fun DisconnectedPreview() {
val viewModel = ConnectionViewModel(connectionInfoRepository)
viewModel.updateConnectionState(ConnectionViewState.CONNECTING)
Surface {
- DataBrokerConnection(viewModel = viewModel)
+ DataBrokerConnectionView(viewModel = viewModel)
}
}
diff --git a/app/src/main/kotlin/org/eclipse/kuksa/testapp/databroker/view/connection/TlsOptionsView.kt b/app/src/main/kotlin/org/eclipse/kuksa/testapp/databroker/view/connection/TlsOptionsView.kt
new file mode 100644
index 00000000..b258ed6f
--- /dev/null
+++ b/app/src/main/kotlin/org/eclipse/kuksa/testapp/databroker/view/connection/TlsOptionsView.kt
@@ -0,0 +1,134 @@
+/*
+ * 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.testapp.databroker.view.connection
+
+import android.net.Uri
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.text.KeyboardActionScope
+import androidx.compose.foundation.text.KeyboardActions
+import androidx.compose.material3.Checkbox
+import androidx.compose.material3.Surface
+import androidx.compose.material3.Text
+import androidx.compose.material3.TextField
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import androidx.constraintlayout.compose.ConstraintLayout
+import org.eclipse.kuksa.testapp.databroker.model.ConnectionInfo
+import org.eclipse.kuksa.testapp.databroker.view.DefaultEdgePadding
+import org.eclipse.kuksa.testapp.databroker.view.DefaultElementPadding
+import org.eclipse.kuksa.testapp.databroker.view.FileSelectorSettingView
+import org.eclipse.kuksa.testapp.extension.fetchFileName
+
+@Composable
+fun TlsOptionsView(
+ connectionInfo: ConnectionInfo,
+ modifier: Modifier = Modifier,
+ onTlsStateChanged: (Boolean) -> Unit = {},
+ onCertificateSelected: (Uri) -> Unit = {},
+ onAuthorityOverrideChanged: (String) -> Unit = {},
+ onKeyboardDone: (KeyboardActionScope) -> Unit = {},
+) {
+ val context = LocalContext.current
+
+ Column(modifier = modifier.padding(start = DefaultEdgePadding, end = DefaultEdgePadding)) {
+ ConstraintLayout(
+ modifier = Modifier
+ .fillMaxWidth()
+ .clickable {
+ onTlsStateChanged(!connectionInfo.isTlsEnabled)
+ },
+ ) {
+ val (text, checkbox) = createRefs()
+ Text(
+ text = "TLS",
+ modifier = Modifier.constrainAs(text) {
+ top.linkTo(parent.top)
+ bottom.linkTo(parent.bottom)
+ },
+ )
+ Checkbox(
+ checked = connectionInfo.isTlsEnabled,
+ onCheckedChange = onTlsStateChanged,
+ modifier = Modifier
+ .constrainAs(checkbox) {
+ top.linkTo(parent.top)
+ bottom.linkTo(parent.bottom)
+
+ end.linkTo(parent.end)
+ },
+ )
+ }
+
+ if (connectionInfo.isTlsEnabled) {
+ val uri = connectionInfo.certificate.uri
+ val fileName = uri.fetchFileName(context) ?: "Select certificate..."
+
+ FileSelectorSettingView(
+ label = "Certificate",
+ value = fileName,
+ onResult = onCertificateSelected,
+ modifier = Modifier.padding(end = 10.dp),
+ )
+
+ Spacer(modifier = Modifier.padding(top = DefaultElementPadding))
+
+ TextField(
+ value = connectionInfo.certificate.overrideAuthority,
+ onValueChange = onAuthorityOverrideChanged,
+ keyboardActions = KeyboardActions(
+ onDone = onKeyboardDone,
+ ),
+ modifier = Modifier
+ .fillMaxWidth(),
+ singleLine = true,
+ label = {
+ Text(text = "Authority override")
+ },
+ )
+ }
+ }
+}
+
+@Preview
+@Composable
+private fun DisabledTlsOptionsViewPreview() {
+ val connectionInfo = ConnectionInfo(isTlsEnabled = false)
+
+ Surface {
+ TlsOptionsView(connectionInfo)
+ }
+}
+
+@Preview
+@Composable
+private fun EnabledTlsOptionsViewPreview() {
+ val connectionInfo = ConnectionInfo(isTlsEnabled = true)
+
+ Surface {
+ TlsOptionsView(connectionInfo)
+ }
+}
diff --git a/app/src/main/kotlin/org/eclipse/kuksa/testapp/extension/UriExtension.kt b/app/src/main/kotlin/org/eclipse/kuksa/testapp/extension/UriExtension.kt
index f38b0ef4..15a37c68 100644
--- a/app/src/main/kotlin/org/eclipse/kuksa/testapp/extension/UriExtension.kt
+++ b/app/src/main/kotlin/org/eclipse/kuksa/testapp/extension/UriExtension.kt
@@ -22,6 +22,7 @@ package org.eclipse.kuksa.testapp.extension
import android.content.Context
import android.net.Uri
import android.provider.OpenableColumns
+import java.io.FileNotFoundException
fun Uri.fetchFileName(context: Context): String? {
var fileName: String? = null
@@ -35,3 +36,11 @@ fun Uri.fetchFileName(context: Context): String? {
}
return fileName
}
+
+@Throws(FileNotFoundException::class)
+fun Uri.readAsText(context: Context): String {
+ val contentResolver = context.contentResolver
+ return contentResolver.openInputStream(this)?.use {
+ it.reader().readText()
+ } ?: error("Could not read file from uri")
+}
diff --git a/docs/QUICKSTART.md b/docs/QUICKSTART.md
index ffbb0571..0082ddb2 100644
--- a/docs/QUICKSTART.md
+++ b/docs/QUICKSTART.md
@@ -25,7 +25,9 @@ fun connectInsecure(host: String, port: Int) {
.usePlaintext()
.build()
- val connector = DataBrokerConnector(managedChannel)
+ // or jsonWebToken = null when authentication is disabled
+ val jsonWebToken = JsonWebToken("someValidJwt")
+ val connector = DataBrokerConnector(managedChannel, jsonWebToken)
try {
dataBrokerConnection = connector.connect()
// Connection to the Databroker successfully established
@@ -42,7 +44,10 @@ void connectInsecure(String host, int port) {
.usePlaintext()
.build();
- DataBrokerConnector connector = new DataBrokerConnector(managedChannel);
+ // or jsonWebToken = null when authentication is disabled
+ JsonWebToken jsonWebToken = new JsonWebToken("someValidJwt");
+
+ DataBrokerConnector connector = new DataBrokerConnector(managedChannel, jsonWebToken);
connector.connect(new CoroutineCallback() {
@Override
public void onSuccess(DataBrokerConnection result) {
@@ -82,11 +87,15 @@ fun update() {
fun subscribe() {
val property = Property("Vehicle.Speed", listOf(Field.FIELD_VALUE))
val propertyListener = object : PropertyListener {
- override fun onPropertyChanged(vssPath: String, field: Types.Field, updatedValue: Types.DataEntry) {
- when (vssPath) {
- "Vehicle.Speed" -> {
- val speed = updatedValue.value.float
- }
+ override fun onPropertyChanged(entryUpdates: List) {
+ entryUpdates.forEach { entryUpdate ->
+ val updatedValue = entryUpdate.entry
+
+ // handle property change
+ when (updatedValue.path) {
+ "Vehicle.Speed" -> {
+ val speed = updatedValue.value.float
+ }
}
}
}
@@ -124,12 +133,12 @@ void subscribe() {
Property property = new Property("Vehicle.Speed", Collections.singleton(Types.Field.FIELD_VALUE));
dataBrokerConnection.subscribe(property, new PropertyListener() {
@Override
- public void onPropertyChanged(
- @NonNull String vssPath,
- @NonNull Types.Field field,
- @NonNull Types.DataEntry updatedValue) {
-
- switch (vssPath) {
+ public void onPropertyChanged(@NonNull List entryUpdates) {
+ for (KuksaValV1.EntryUpdate entryUpdate : entryUpdates) {
+ Types.DataEntry updatedValue = entryUpdate.getEntry();
+
+ // handle property change
+ switch (updatedValue.getPath()) {
case "Vehicle.Speed":
float speed = updatedValue.getValue().getFloat();
}
@@ -188,30 +197,31 @@ Vehicle.Speed:
```
*Example model*
+
```kotlin
data class VssSpeed @JvmOverloads constructor(
override val `value`: Float = 0f,
- ) : VssProperty {
+) : VssProperty {
override val comment: String
- get() = ""
+ get() = ""
override val description: String
- get() = "Vehicle speed."
+ get() = "Vehicle speed."
override val type: String
- get() = "sensor"
+ get() = "sensor"
override val uuid: String
- get() = "efe50798638d55fab18ab7d43cc490e9"
+ get() = "efe50798638d55fab18ab7d43cc490e9"
override val vssPath: String
- get() = "Vehicle.Speed"
+ get() = "Vehicle.Speed"
override val children: Set
- get() = setOf()
+ get() = setOf()
- override val parentClass: KClass<*>?
- get() = VssVehicle::class
+ override val parentClass: KClass<*>
+ get() = VssVehicle::class
}
```
diff --git a/docs/kuksa-sdk_class-diagram.puml b/docs/kuksa-sdk_class-diagram.puml
index d0741b2a..2d7eece8 100644
--- a/docs/kuksa-sdk_class-diagram.puml
+++ b/docs/kuksa-sdk_class-diagram.puml
@@ -19,6 +19,7 @@ package kuksa {
class SetResponse
class GetResponse
class DataEntryError
+ class EntryUpdate
}
DataBrokerConnector -down-> DataBrokerException
@@ -32,6 +33,7 @@ package kuksa {
DataBrokerSubscriber -up-> DataBrokerTransporter
MultiListener -right-> DisconnectListener
TimeoutConfig -left-* DataBrokerConnector
+ JsonWebToken -right-* DataBrokerConnector
class DataBrokerConnector {
+ connect(): DataBrokerConnection
@@ -43,6 +45,8 @@ package kuksa {
}
class DataBrokerTransporter {
+ + jsonWebToken: JsonWebToken
+
+ fetch(vssPath: String, Collection): GetResponse
+ update(vssPath: String, Collection, Types.Datapoint): SetResponse
+ subscribe(vssPath: String, Field): Subscription
@@ -57,6 +61,7 @@ package kuksa {
class DataBrokerConnection {
+ disconnectListeners: MultiListener
+ + jsonWebToken: JsonWebToken
+ subscribe(Property, PropertyListener)
+ subscribe(T, Collection, VssSpecificationListener)
@@ -70,7 +75,7 @@ package kuksa {
}
interface PropertyListener {
- + onPropertyChanged(vssPath: String, Types.DataEntry)
+ + onPropertyChanged(List)
+ onError(Throwable)
}
@@ -89,6 +94,11 @@ package kuksa {
interface DisconnectListener {
+ onDisconnect()
}
+
+ class JsonWebToken {
+ + authScheme: String
+ + authParameters: String
+ }
}
DataBrokerConnector -up-> ManagedChannel
diff --git a/kuksa-sdk/src/main/kotlin/org/eclipse/kuksa/DataBrokerConnection.kt b/kuksa-sdk/src/main/kotlin/org/eclipse/kuksa/DataBrokerConnection.kt
index 8c4aacf9..ee7db7f9 100644
--- a/kuksa-sdk/src/main/kotlin/org/eclipse/kuksa/DataBrokerConnection.kt
+++ b/kuksa-sdk/src/main/kotlin/org/eclipse/kuksa/DataBrokerConnection.kt
@@ -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
@@ -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
@@ -54,8 +56,18 @@ class DataBrokerConnection internal constructor(
),
private val dataBrokerSubscriber: DataBrokerSubscriber = DataBrokerSubscriber(dataBrokerTransporter),
) {
+ /**
+ * Used to register and unregister multiple [DisconnectListener].
+ */
val disconnectListeners = MultiListener()
+ /**
+ * 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) {
diff --git a/kuksa-sdk/src/main/kotlin/org/eclipse/kuksa/DataBrokerConnector.kt b/kuksa-sdk/src/main/kotlin/org/eclipse/kuksa/DataBrokerConnector.kt
index 565f84ac..9d885395 100644
--- a/kuksa-sdk/src/main/kotlin/org/eclipse/kuksa/DataBrokerConnector.kt
+++ b/kuksa-sdk/src/main/kotlin/org/eclipse/kuksa/DataBrokerConnector.kt
@@ -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
/**
@@ -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,
) {
@@ -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")
diff --git a/kuksa-sdk/src/main/kotlin/org/eclipse/kuksa/DataBrokerTransporter.kt b/kuksa-sdk/src/main/kotlin/org/eclipse/kuksa/DataBrokerTransporter.kt
index 554ca496..f6d3482b 100644
--- a/kuksa-sdk/src/main/kotlin/org/eclipse/kuksa/DataBrokerTransporter.kt
+++ b/kuksa-sdk/src/main/kotlin/org/eclipse/kuksa/DataBrokerTransporter.kt
@@ -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
@@ -49,6 +51,7 @@ internal class DataBrokerTransporter(
private val managedChannel: ManagedChannel,
private val defaultDispatcher: CoroutineDispatcher = Dispatchers.Default,
) {
+
init {
val state = managedChannel.getState(false)
check(state == ConnectivityState.READY) {
@@ -56,6 +59,11 @@ internal class DataBrokerTransporter(
}
}
+ /**
+ * 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.
*
@@ -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)
}
@@ -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)
}
@@ -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)
}
diff --git a/kuksa-sdk/src/main/kotlin/org/eclipse/kuksa/authentication/JsonWebToken.kt b/kuksa-sdk/src/main/kotlin/org/eclipse/kuksa/authentication/JsonWebToken.kt
new file mode 100644
index 00000000..a4c37603
--- /dev/null
+++ b/kuksa-sdk/src/main/kotlin/org/eclipse/kuksa/authentication/JsonWebToken.kt
@@ -0,0 +1,45 @@
+/*
+ * 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"
+ }
+}
diff --git a/kuksa-sdk/src/main/kotlin/org/eclipse/kuksa/authentication/VALStubExtension.kt b/kuksa-sdk/src/main/kotlin/org/eclipse/kuksa/authentication/VALStubExtension.kt
new file mode 100644
index 00000000..7150f76b
--- /dev/null
+++ b/kuksa-sdk/src/main/kotlin/org/eclipse/kuksa/authentication/VALStubExtension.kt
@@ -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)
+}
diff --git a/kuksa-sdk/src/test/kotlin/org/eclipse/kuksa/DataBrokerConnectionTest.kt b/kuksa-sdk/src/test/kotlin/org/eclipse/kuksa/DataBrokerConnectionTest.kt
index 46f572d3..e26af91c 100644
--- a/kuksa-sdk/src/test/kotlin/org/eclipse/kuksa/DataBrokerConnectionTest.kt
+++ b/kuksa-sdk/src/test/kotlin/org/eclipse/kuksa/DataBrokerConnectionTest.kt
@@ -37,6 +37,7 @@ import org.eclipse.kuksa.model.Property
import org.eclipse.kuksa.proto.v1.KuksaValV1
import org.eclipse.kuksa.proto.v1.Types
import org.eclipse.kuksa.proto.v1.Types.Datapoint
+import org.eclipse.kuksa.test.kotest.DefaultDatabroker
import org.eclipse.kuksa.test.kotest.Integration
import org.eclipse.kuksa.vssSpecification.VssDriver
import org.junit.jupiter.api.Assertions.assertEquals
@@ -46,7 +47,7 @@ import kotlin.random.Random
import kotlin.time.Duration.Companion.seconds
class DataBrokerConnectionTest : BehaviorSpec({
- tags(Integration)
+ tags(Integration, DefaultDatabroker)
given("A successfully established connection to the DataBroker") {
val dataBrokerConnection = connectToDataBrokerBlocking()
diff --git a/kuksa-sdk/src/test/kotlin/org/eclipse/kuksa/DataBrokerConnectorAuthenticationTest.kt b/kuksa-sdk/src/test/kotlin/org/eclipse/kuksa/DataBrokerConnectorAuthenticationTest.kt
new file mode 100644
index 00000000..74455579
--- /dev/null
+++ b/kuksa-sdk/src/test/kotlin/org/eclipse/kuksa/DataBrokerConnectorAuthenticationTest.kt
@@ -0,0 +1,175 @@
+/*
+ * Copyright (c) 2023 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
+
+import io.kotest.core.spec.style.BehaviorSpec
+import io.kotest.matchers.shouldBe
+import org.eclipse.kuksa.databroker.DataBrokerConnectorProvider
+import org.eclipse.kuksa.model.Property
+import org.eclipse.kuksa.proto.v1.Types
+import org.eclipse.kuksa.test.kotest.Authentication
+import org.eclipse.kuksa.test.kotest.CustomDatabroker
+import org.eclipse.kuksa.test.kotest.Insecure
+import org.eclipse.kuksa.test.kotest.Integration
+import java.io.InputStream
+import kotlin.random.Random
+import kotlin.random.nextInt
+
+// DataBroker must be started with Authentication enabled:
+// databroker --jwt-public-key /certs/jwt/jwt.key.pub
+
+// ./gradlew clean test -Dkotest.tags="Authentication"
+class DataBrokerConnectorAuthenticationTest : BehaviorSpec({
+ tags(Integration, Authentication, Insecure, CustomDatabroker)
+
+ val random = Random(System.nanoTime())
+
+ given("A DataBrokerConnectorProvider") {
+ val dataBrokerConnectorProvider = DataBrokerConnectorProvider()
+
+ and("an insecure DataBrokerConnector with a READ_WRITE_ALL JWT") {
+ val jwtFileStream = JwtType.READ_WRITE_ALL.asInputStream()
+ val dataBrokerConnector = dataBrokerConnectorProvider.createInsecure(jwtFileStream = jwtFileStream)
+
+ and("a successfully established connection") {
+ val connection = dataBrokerConnector.connect()
+ val property = Property("Vehicle.Speed")
+
+ `when`("Reading Vehicle.Speed") {
+ val response = connection.fetch(property)
+
+ then("No error should occur") {
+ response.errorsList.size shouldBe 0
+ }
+ }
+
+ `when`("Writing the VALUE of Vehicle.Speed") {
+ val nextFloat = random.nextFloat() * 100F
+ val datapoint = Types.Datapoint.newBuilder().setFloat(nextFloat).build()
+
+ val response = connection.update(property, datapoint)
+
+ then("No error should occur") {
+ response.errorsList.size shouldBe 0
+ }
+ }
+ }
+ }
+
+ and("an insecure DataBrokerConnector with a READ_ALL JWT") {
+ val jwtFileStream = JwtType.READ_ALL.asInputStream()
+ val dataBrokerConnector = dataBrokerConnectorProvider.createInsecure(jwtFileStream = jwtFileStream)
+
+ and("a successfully established connection") {
+ val connection = dataBrokerConnector.connect()
+ val property = Property("Vehicle.Speed")
+
+ `when`("Reading Vehicle.Speed") {
+ val response = connection.fetch(property)
+
+ then("No error should appear") {
+ response.errorsList.size shouldBe 0
+ }
+ }
+
+ `when`("Writing the VALUE of Vehicle.Speed") {
+ val nextFloat = random.nextFloat() * 100F
+ val datapoint = Types.Datapoint.newBuilder().setFloat(nextFloat).build()
+
+ val response = connection.update(property, datapoint)
+
+ then("An error should occur") {
+ response.errorsList.size shouldBe 1
+ }
+ }
+ }
+ }
+
+ and("an insecure DataBrokerConnector with a READ_WRITE_ALL_VALUES_ONLY JWT") {
+ val jwtFileStream = JwtType.READ_WRITE_ALL_VALUES_ONLY.asInputStream()
+ val dataBrokerConnector = dataBrokerConnectorProvider.createInsecure(jwtFileStream = jwtFileStream)
+
+ and("a successfully established connection") {
+ val connection = dataBrokerConnector.connect()
+ val actuatorTargetProperty = Property(
+ "Vehicle.Body.Mirrors.DriverSide.Pan",
+ listOf(Types.Field.FIELD_ACTUATOR_TARGET),
+ )
+
+ `when`("Reading the ACTUATOR_TARGET of Vehicle.Body.Mirrors.DriverSide.Pan") {
+ val response = connection.fetch(actuatorTargetProperty)
+
+ then("No error should occur") {
+ response.errorsList.size shouldBe 0
+ }
+ }
+
+ `when`("Writing to the ACTUATOR_TARGET of Vehicle.Body.Mirrors.DriverSide.Pan") {
+ val nextInt = random.nextInt(-100..100)
+ val datapoint = Types.Datapoint.newBuilder().setInt32(nextInt).build()
+
+ val response = connection.update(actuatorTargetProperty, datapoint)
+
+ then("An error should occur") {
+ response.errorsList.size shouldBe 1
+ }
+ }
+
+ val valueProperty = Property(
+ "Vehicle.Speed",
+ listOf(Types.Field.FIELD_VALUE),
+ )
+
+ `when`("Reading the VALUE of Vehicle.Speed") {
+ val response = connection.fetch(valueProperty)
+
+ then("No error should occur") {
+ response.errorsList.size shouldBe 0
+ }
+ }
+
+ `when`("Writing the VALUE of Vehicle.Speed") {
+ val nextFloat = random.nextFloat() * 100F
+ val datapoint = Types.Datapoint.newBuilder().setFloat(nextFloat).build()
+
+ val response = connection.update(valueProperty, datapoint)
+
+ then("No error should occur") {
+ response.errorsList.size shouldBe 0
+ }
+ }
+ }
+ }
+ }
+})
+
+// The tokens provided here might need to be updated irregularly
+// see: https://github.com/eclipse/kuksa.val/tree/master/jwt
+// The tokens only work when the Databroker is started using the correct public key: jwt.key.pub
+enum class JwtType(private val fileName: String) {
+ READ_WRITE_ALL("actuate-provide-all.token"), // ACTUATOR_TARGET and VALUE
+ READ_WRITE_ALL_VALUES_ONLY("provide-all.token"), // VALUE
+ READ_ALL("read-all.token"),
+ ;
+
+ fun asInputStream(): InputStream {
+ return DataBrokerConnectionTest::class.java.classLoader?.getResourceAsStream(fileName)!!
+ }
+}
diff --git a/kuksa-sdk/src/test/kotlin/org/eclipse/kuksa/DataBrokerConnectorSecureTest.kt b/kuksa-sdk/src/test/kotlin/org/eclipse/kuksa/DataBrokerConnectorSecureTest.kt
index 7fdb566b..db185bca 100644
--- a/kuksa-sdk/src/test/kotlin/org/eclipse/kuksa/DataBrokerConnectorSecureTest.kt
+++ b/kuksa-sdk/src/test/kotlin/org/eclipse/kuksa/DataBrokerConnectorSecureTest.kt
@@ -21,12 +21,17 @@ package org.eclipse.kuksa
import io.kotest.core.spec.style.BehaviorSpec
import org.eclipse.kuksa.databroker.DataBrokerConnectorProvider
+import org.eclipse.kuksa.test.kotest.CustomDatabroker
import org.eclipse.kuksa.test.kotest.Integration
import org.eclipse.kuksa.test.kotest.Secure
import org.junit.jupiter.api.Assertions
+// DataBroker must be started with TLS enabled:
+// databroker --tls-cert /certs/Server.pem --tls-private-key /certs/Server.key"
+
+// run command: ./gradlew clean test -Dkotest.tags="Secure"
class DataBrokerConnectorSecureTest : BehaviorSpec({
- tags(Integration, Secure)
+ tags(Integration, Secure, CustomDatabroker)
given("A DataBrokerConnectorProvider") {
val dataBrokerConnectorProvider = DataBrokerConnectorProvider()
diff --git a/kuksa-sdk/src/test/kotlin/org/eclipse/kuksa/DataBrokerConnectorTest.kt b/kuksa-sdk/src/test/kotlin/org/eclipse/kuksa/DataBrokerConnectorTest.kt
index c09edafe..b825a5d4 100644
--- a/kuksa-sdk/src/test/kotlin/org/eclipse/kuksa/DataBrokerConnectorTest.kt
+++ b/kuksa-sdk/src/test/kotlin/org/eclipse/kuksa/DataBrokerConnectorTest.kt
@@ -23,11 +23,12 @@ import io.kotest.assertions.throwables.shouldThrow
import io.kotest.core.spec.style.BehaviorSpec
import io.kotest.matchers.shouldNotBe
import org.eclipse.kuksa.databroker.DataBrokerConnectorProvider
+import org.eclipse.kuksa.test.kotest.DefaultDatabroker
import org.eclipse.kuksa.test.kotest.Insecure
import org.eclipse.kuksa.test.kotest.Integration
class DataBrokerConnectorTest : BehaviorSpec({
- tags(Integration, Insecure)
+ tags(Integration, Insecure, DefaultDatabroker)
given("A DataBrokerConnectorProvider") {
val dataBrokerConnectorProvider = DataBrokerConnectorProvider()
diff --git a/kuksa-sdk/src/test/kotlin/org/eclipse/kuksa/DataBrokerTransporterTest.kt b/kuksa-sdk/src/test/kotlin/org/eclipse/kuksa/DataBrokerTransporterTest.kt
index ee2d9191..fc3200f7 100644
--- a/kuksa-sdk/src/test/kotlin/org/eclipse/kuksa/DataBrokerTransporterTest.kt
+++ b/kuksa-sdk/src/test/kotlin/org/eclipse/kuksa/DataBrokerTransporterTest.kt
@@ -32,12 +32,13 @@ import org.eclipse.kuksa.proto.v1.KuksaValV1
import org.eclipse.kuksa.proto.v1.KuksaValV1.SetResponse
import org.eclipse.kuksa.proto.v1.Types
import org.eclipse.kuksa.proto.v1.Types.Datapoint
+import org.eclipse.kuksa.test.kotest.DefaultDatabroker
import org.eclipse.kuksa.test.kotest.Insecure
import org.eclipse.kuksa.test.kotest.Integration
import kotlin.random.Random
class DataBrokerTransporterTest : BehaviorSpec({
- tags(Integration, Insecure)
+ tags(Integration, Insecure, DefaultDatabroker)
given("An active Connection to the DataBroker") {
val dataBrokerConnectorProvider = DataBrokerConnectorProvider()
diff --git a/kuksa-sdk/src/test/kotlin/org/eclipse/kuksa/databroker/DataBrokerConnectorProvider.kt b/kuksa-sdk/src/test/kotlin/org/eclipse/kuksa/databroker/DataBrokerConnectorProvider.kt
index 24aae5f6..327833bb 100644
--- a/kuksa-sdk/src/test/kotlin/org/eclipse/kuksa/databroker/DataBrokerConnectorProvider.kt
+++ b/kuksa-sdk/src/test/kotlin/org/eclipse/kuksa/databroker/DataBrokerConnectorProvider.kt
@@ -26,6 +26,7 @@ import io.grpc.ManagedChannelBuilder
import io.grpc.TlsChannelCredentials
import org.eclipse.kuksa.DataBrokerConnector
import org.eclipse.kuksa.TimeoutConfig
+import org.eclipse.kuksa.authentication.JsonWebToken
import java.io.IOException
import java.io.InputStream
@@ -34,10 +35,19 @@ class DataBrokerConnectorProvider {
fun createInsecure(
host: String = DataBrokerConfig.HOST,
port: Int = DataBrokerConfig.PORT,
+ jwtFileStream: InputStream? = null,
): DataBrokerConnector {
managedChannel = ManagedChannelBuilder.forAddress(host, port).usePlaintext().build()
- return DataBrokerConnector(managedChannel).apply {
+ val jsonWebToken = jwtFileStream?.let {
+ val token = it.reader().readText()
+ JsonWebToken(token)
+ }
+
+ return DataBrokerConnector(
+ managedChannel,
+ jsonWebToken,
+ ).apply {
timeoutConfig = TimeoutConfig(DataBrokerConfig.TIMEOUT_SECONDS, DataBrokerConfig.TIMEOUT_UNIT)
}
}
@@ -47,6 +57,7 @@ class DataBrokerConnectorProvider {
port: Int = DataBrokerConfig.PORT,
rootCertFileStream: InputStream,
overrideAuthority: String = "",
+ jwtFileStream: InputStream? = null,
): DataBrokerConnector {
val tlsCredentials: ChannelCredentials
try {
@@ -67,7 +78,16 @@ class DataBrokerConnectorProvider {
}
managedChannel = channelBuilder.build()
- return DataBrokerConnector(managedChannel).apply {
+
+ val jsonWebToken = jwtFileStream?.let {
+ val token = it.reader().readText()
+ JsonWebToken(token)
+ }
+
+ return DataBrokerConnector(
+ managedChannel,
+ jsonWebToken,
+ ).apply {
timeoutConfig = TimeoutConfig(DataBrokerConfig.TIMEOUT_SECONDS, DataBrokerConfig.TIMEOUT_UNIT)
}
}
diff --git a/kuksa-sdk/src/test/kotlin/org/eclipse/kuksa/subscription/DataBrokerSubscriberTest.kt b/kuksa-sdk/src/test/kotlin/org/eclipse/kuksa/subscription/DataBrokerSubscriberTest.kt
index 3e2f976a..52f2c58b 100644
--- a/kuksa-sdk/src/test/kotlin/org/eclipse/kuksa/subscription/DataBrokerSubscriberTest.kt
+++ b/kuksa-sdk/src/test/kotlin/org/eclipse/kuksa/subscription/DataBrokerSubscriberTest.kt
@@ -39,6 +39,7 @@ import org.eclipse.kuksa.mocking.FriendlyVssSpecificationListener
import org.eclipse.kuksa.pattern.listener.MultiListener
import org.eclipse.kuksa.pattern.listener.count
import org.eclipse.kuksa.proto.v1.Types
+import org.eclipse.kuksa.test.kotest.DefaultDatabroker
import org.eclipse.kuksa.test.kotest.Insecure
import org.eclipse.kuksa.test.kotest.Integration
import org.eclipse.kuksa.vssSpecification.VssDriver
@@ -46,7 +47,7 @@ import kotlin.time.Duration.Companion.milliseconds
import kotlin.time.Duration.Companion.seconds
class DataBrokerSubscriberTest : BehaviorSpec({
- tags(Integration, Insecure)
+ tags(Integration, Insecure, DefaultDatabroker)
given("An active Connection to the DataBroker") {
val dataBrokerConnectorProvider = DataBrokerConnectorProvider()
diff --git a/kuksa-sdk/src/test/resources/actuate-provide-all.token b/kuksa-sdk/src/test/resources/actuate-provide-all.token
new file mode 100644
index 00000000..110d3c41
--- /dev/null
+++ b/kuksa-sdk/src/test/resources/actuate-provide-all.token
@@ -0,0 +1 @@
+eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJsb2NhbCBkZXYiLCJpc3MiOiJjcmVhdGVUb2tlbi5weSIsImF1ZCI6WyJrdWtzYS52YWwiXSwiaWF0IjoxNTE2MjM5MDIyLCJleHAiOjE3NjcyMjU1OTksInNjb3BlIjoiYWN0dWF0ZSBwcm92aWRlIn0.x-bUZwDCC663wGYrWCYjQZwQWhN1CMuKgxuIN5dUF_izwMutiqF6Xc-tnXgZa93BbT3I74WOMk4awKHBUSTWekGs3-qF6gajorbat6n5180TOqvNu4CXuIPZN5zpngf4id3smMkKOT699tPnSEbmlkj4vk-mIjeOAU-FcYA-VbkKBTsjvfFgKa2OdB5h9uZARBg5Rx7uBN3JsH1I6j9zoLid184Ewa6bhU2qniFt5iPsGJniNsKsRrrndN1KzthO13My44s56yvwSHIOrgDGbXdja_eLuOVOq9pHCjCtorPScgEuUUE4aldIuML-_j397taNP9Y3VZYVvofEK7AuiePTbzwxrZ1RAjK74h1-4ued3A2gUTjr5BsRlc9b7eLZzxLJkrqdfGAzBh_rtrB7p32TbvpjeFP30NW6bB9JS43XACUUm_S_RcyI7BLuUdnFyQDQr6l6sRz9XayYXceilHdCxbAVN0HVnBeui5Bb0mUZYIRZeY8k6zcssmokANTD8ZviDMpKlOU3t5AlXJ0nLkgyMhV9IUTwPUv6F8BTPc-CquJCUNbTyo4ywTSoODWbm3PmQ3Y46gWF06xqnB4wehLscBdVk3iAihQp3tckGhMnx5PI_Oy7utIncr4pRCMos63TnBkfrl7d43cHQTuK0kO76EWtv4ODEHgLvEAv4HA
\ No newline at end of file
diff --git a/kuksa-sdk/src/test/resources/provide-all.token b/kuksa-sdk/src/test/resources/provide-all.token
new file mode 100644
index 00000000..8ce854f3
--- /dev/null
+++ b/kuksa-sdk/src/test/resources/provide-all.token
@@ -0,0 +1 @@
+eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJsb2NhbCBkZXYiLCJpc3MiOiJjcmVhdGVUb2tlbi5weSIsImF1ZCI6WyJrdWtzYS52YWwiXSwiaWF0IjoxNTE2MjM5MDIyLCJleHAiOjE3NjcyMjU1OTksInNjb3BlIjoicHJvdmlkZSJ9.OJWzTvDjcmeWyg3vmBR5TEtqYaHq8HrpFLlTKZAfDBAQBUHpyUEboJ97jfWuWgBnTpnfboyfAbwvLqo6bEVZ6tXzF8n9LtW6HmPbIWoDqXuobM2grUCVaGKuOcnCpMCQYChziqHbYwRJYP9nkYgbQU1kE4dN7880Io4xzq0GEbWksB2CVpOoExQUmCZpCohPs-XEkdmXhcUKnWnOeiSsRGKusx987vpY_WOXh6WE7DfJgzAgpPDo33qI7zQuTzUILORQsiHmsrQO0-zcvokNjaQUzlt5ETZ7MQLCtiUQaN0NMbDMCWkmSfNvZ5hKCNbfr2FaiMzrGBOQdvQiFo-DqZKGNweaGpufYXuaKfn3SXKoDr8u1xDE5oKgWMjxDR9pQYGzIF5bDXITSywCm4kN5DIn7e2_Ga28h3rBl0t0ZT0cwlszftQRueDTFcMns1u9PEDOqf7fRrhjq3zqpxuMAoRANVd2z237eBsS0AvdSIxL52N4xO8P_h93NN8Vaum28fTPxzm8p9WlQh4mgUelggtT415hLcxizx15ARIRG0RiW91Pglzt4WRtXHnsg93Ixd3yXXzZ2i4Y0hqhj_L12SsXunK2VxKup2sFCQz6wM-t_7ADmNYcs80idzsadY8rYKDV8N1WqOOd4ANG_nzWa86Tyu6wAwhDVag5nbFmLZQ
\ No newline at end of file
diff --git a/kuksa-sdk/src/test/resources/read-all.token b/kuksa-sdk/src/test/resources/read-all.token
new file mode 100644
index 00000000..10fafb53
--- /dev/null
+++ b/kuksa-sdk/src/test/resources/read-all.token
@@ -0,0 +1 @@
+eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJsb2NhbCBkZXYiLCJpc3MiOiJjcmVhdGVUb2tlbi5weSIsImF1ZCI6WyJrdWtzYS52YWwiXSwiaWF0IjoxNTE2MjM5MDIyLCJleHAiOjE3NjcyMjU1OTksInNjb3BlIjoicmVhZCJ9.P6tJPRSJWB51UOFDFs8qQ-lGqb1NoWgCekHUKyMiYcs8sR3FGVKSRjSkcqv1tXOlILvqhUwyuTKui25_kFKkTPv47GI0xAqcXtaTmDwHAWZHFC6HWGWGXohu7XvURrim5kMRVHy_VGlzasGgVap0JFk3wmaY-nyFYL_PLDjvGjIQuOwFiUtKK1PfiKviZKyc5EzPUEAoHxFL_BSOsTdDDcaydFe9rSKJzpYrj7qXY0hMJCje2BUGlSUIttR95aSjOZflSxiGystWHME8fKMmDERAx749Jpt37M3taCxBsUzER5olPz65MGzFSikfC-jH_KGmJ4zNYS65_OM1a-CPfW7Ts__pyAXxFULNMHRMIfh8Wiig4UcooMy_ZJO_DN2rq95XdaBbzRua5mxvO2wM6iu5kv4lhNxhjVNGuWFRLLJ_icBUZlvAuC3eqp66B-Y3jJNI0cSnIvsVX8YFVS3ebW8tf40OdeVou8fWZPcQsFAAafBhIxNOW8FbLZ9sRvQ-FGwZy-GyF52IJ5ZKeGfAkeEh9ZLIcyJ2YlGp4q0EOKIdwIBsWfCFtZbAvi2ornO3XvJm94NBqprpvQYN_IB7yyRxDduLjNKqqcFqnrlWYI-ZhvghWH2rEblplgHZdyVD1G9Mbv0_zdNTKFs6J7IP96aV6-4hBOt3kROlS1G7ObA
\ No newline at end of file
diff --git a/samples/src/main/java/com/example/sample/JavaActivity.java b/samples/src/main/java/com/example/sample/JavaActivity.java
index 01acaf54..48b91028 100644
--- a/samples/src/main/java/com/example/sample/JavaActivity.java
+++ b/samples/src/main/java/com/example/sample/JavaActivity.java
@@ -28,6 +28,7 @@
import org.eclipse.kuksa.DisconnectListener;
import org.eclipse.kuksa.PropertyListener;
import org.eclipse.kuksa.VssSpecificationListener;
+import org.eclipse.kuksa.authentication.JsonWebToken;
import org.eclipse.kuksa.model.Property;
import org.eclipse.kuksa.proto.v1.KuksaValV1;
import org.eclipse.kuksa.proto.v1.KuksaValV1.GetResponse;
@@ -67,7 +68,9 @@ public void connectInsecure(String host, int port) {
.usePlaintext()
.build();
- DataBrokerConnector connector = new DataBrokerConnector(managedChannel);
+ // or jsonWebToken = null when authentication is disabled
+ JsonWebToken jsonWebToken = new JsonWebToken("someValidToken");
+ DataBrokerConnector connector = new DataBrokerConnector(managedChannel, jsonWebToken);
connector.connect(new CoroutineCallback() {
@Override
public void onSuccess(DataBrokerConnection result) {
@@ -105,7 +108,10 @@ public void connectSecure(String host, int port, String overrideAuthority) {
}
ManagedChannel managedChannel = channelBuilder.build();
- DataBrokerConnector connector = new DataBrokerConnector(managedChannel);
+
+ // or jsonWebToken = null when authentication is disabled
+ JsonWebToken jsonWebToken = new JsonWebToken("someValidToken");
+ DataBrokerConnector connector = new DataBrokerConnector(managedChannel, jsonWebToken);
connector.connect(new CoroutineCallback() {
@Override
public void onSuccess(DataBrokerConnection result) {
diff --git a/samples/src/main/kotlin/com/example/sample/KotlinActivity.kt b/samples/src/main/kotlin/com/example/sample/KotlinActivity.kt
index 297e08cf..0d550a22 100644
--- a/samples/src/main/kotlin/com/example/sample/KotlinActivity.kt
+++ b/samples/src/main/kotlin/com/example/sample/KotlinActivity.kt
@@ -32,6 +32,7 @@ import org.eclipse.kuksa.DataBrokerException
import org.eclipse.kuksa.DisconnectListener
import org.eclipse.kuksa.PropertyListener
import org.eclipse.kuksa.VssSpecificationListener
+import org.eclipse.kuksa.authentication.JsonWebToken
import org.eclipse.kuksa.model.Property
import org.eclipse.kuksa.proto.v1.KuksaValV1
import org.eclipse.kuksa.proto.v1.Types
@@ -56,7 +57,9 @@ class KotlinActivity : AppCompatActivity() {
.usePlaintext()
.build()
- val connector = DataBrokerConnector(managedChannel)
+ // or jsonWebToken = null when authentication is disabled
+ val jsonWebToken = JsonWebToken("someValidToken")
+ val connector = DataBrokerConnector(managedChannel, jsonWebToken)
try {
dataBrokerConnection = connector.connect()
dataBrokerConnection?.disconnectListeners?.register(disconnectListener)
@@ -89,7 +92,10 @@ class KotlinActivity : AppCompatActivity() {
lifecycleScope.launch {
val managedChannel = channelBuilder.build()
- val connector = DataBrokerConnector(managedChannel)
+
+ // or jsonWebToken = null when authentication is disabled
+ val jsonWebToken = JsonWebToken("someValidToken")
+ val connector = DataBrokerConnector(managedChannel, jsonWebToken)
try {
dataBrokerConnection = connector.connect()
.apply {
diff --git a/test/src/main/java/org/eclipse/kuksa/test/kotest/Tag.kt b/test/src/main/java/org/eclipse/kuksa/test/kotest/Tag.kt
index ca7cc805..01ec66ad 100644
--- a/test/src/main/java/org/eclipse/kuksa/test/kotest/Tag.kt
+++ b/test/src/main/java/org/eclipse/kuksa/test/kotest/Tag.kt
@@ -22,7 +22,10 @@ package org.eclipse.kuksa.test.kotest
import io.kotest.core.NamedTag
val Integration = NamedTag("Integration")
+val DefaultDatabroker = NamedTag("DefaultDatabroker") // unsecure => no tls, no authentication
+val CustomDatabroker = NamedTag("CustomDatabroker")
+val Secure = NamedTag("Secure") // tls enabled, no authentication
+val Authentication = NamedTag("Authentication") // no tls, authentication enabled
val Unit = NamedTag("Unit")
-val Secure = NamedTag("Secure")
val Insecure = NamedTag("Insecure")