From 5cb4e391d0fa3afa0af3936c68d7f26b0eb130c2 Mon Sep 17 00:00:00 2001 From: Rajmund Takacs Date: Sun, 24 Sep 2023 17:33:27 +0200 Subject: [PATCH] plc4j-driver-opcua: Add support for PlcUsernamePasswordAuthentication Attempts to resolve issue #1104 --- .../java/opcua/context/SecureChannel.java | 17 +++++- .../opcua/protocol/OpcuaProtocolLogic.java | 24 +++++++- .../plc4x/java/opcua/OpcuaPlcDriverTest.java | 58 +++++++++++++++++++ 3 files changed, 93 insertions(+), 6 deletions(-) diff --git a/plc4j/drivers/opcua/src/main/java/org/apache/plc4x/java/opcua/context/SecureChannel.java b/plc4j/drivers/opcua/src/main/java/org/apache/plc4x/java/opcua/context/SecureChannel.java index 31f55bfaf92..16e3e366160 100644 --- a/plc4j/drivers/opcua/src/main/java/org/apache/plc4x/java/opcua/context/SecureChannel.java +++ b/plc4j/drivers/opcua/src/main/java/org/apache/plc4x/java/opcua/context/SecureChannel.java @@ -20,6 +20,8 @@ import org.apache.commons.lang3.RandomStringUtils; import org.apache.commons.lang3.RandomUtils; +import org.apache.plc4x.java.api.authentication.PlcAuthentication; +import org.apache.plc4x.java.api.authentication.PlcUsernamePasswordAuthentication; import org.apache.plc4x.java.api.exceptions.PlcConnectionException; import org.apache.plc4x.java.api.exceptions.PlcRuntimeException; import org.apache.plc4x.java.opcua.config.OpcuaConfiguration; @@ -121,13 +123,22 @@ public class SecureChannel { private final List endpoints = new ArrayList<>(); private final AtomicLong senderSequenceNumber = new AtomicLong(); - public SecureChannel(OpcuaDriverContext driverContext, OpcuaConfiguration configuration) { + public SecureChannel(OpcuaDriverContext driverContext, OpcuaConfiguration configuration, PlcAuthentication authentication) { this.configuration = configuration; this.driverContext = driverContext; this.endpoint = new PascalString(driverContext.getEndpoint()); - this.username = configuration.getUsername(); - this.password = configuration.getPassword(); + if (authentication != null) { + if (authentication instanceof PlcUsernamePasswordAuthentication) { + this.username = ((PlcUsernamePasswordAuthentication) authentication).getUsername(); + this.password = ((PlcUsernamePasswordAuthentication) authentication).getPassword(); + } else { + throw new PlcRuntimeException("This type of connection only supports username-password authentication"); + } + } else { + this.username = configuration.getUsername(); + this.password = configuration.getPassword(); + } this.securityPolicy = "http://opcfoundation.org/UA/SecurityPolicy#" + configuration.getSecurityPolicy(); CertificateKeyPair ckp = driverContext.getCertificateKeyPair(); diff --git a/plc4j/drivers/opcua/src/main/java/org/apache/plc4x/java/opcua/protocol/OpcuaProtocolLogic.java b/plc4j/drivers/opcua/src/main/java/org/apache/plc4x/java/opcua/protocol/OpcuaProtocolLogic.java index fb320c26d93..a4b14d29ee5 100644 --- a/plc4j/drivers/opcua/src/main/java/org/apache/plc4x/java/opcua/protocol/OpcuaProtocolLogic.java +++ b/plc4j/drivers/opcua/src/main/java/org/apache/plc4x/java/opcua/protocol/OpcuaProtocolLogic.java @@ -18,6 +18,8 @@ */ package org.apache.plc4x.java.opcua.protocol; +import org.apache.plc4x.java.api.authentication.PlcAuthentication; +import org.apache.plc4x.java.api.exceptions.PlcConnectionException; import org.apache.plc4x.java.api.exceptions.PlcRuntimeException; import org.apache.plc4x.java.api.messages.*; import org.apache.plc4x.java.api.model.PlcConsumerRegistration; @@ -90,6 +92,9 @@ public void close(ConversationContext context) { @Override public void onDisconnect(ConversationContext context) { + if (channel == null) { + return; + } for (Map.Entry subscriber : subscriptions.entrySet()) { subscriber.getValue().stopSubscriber(); } @@ -99,7 +104,6 @@ public void onDisconnect(ConversationContext context) { @Override public void setDriverContext(DriverContext driverContext) { super.setDriverContext(driverContext); - this.channel = new SecureChannel((OpcuaDriverContext) driverContext, this.configuration); } @Override @@ -107,7 +111,12 @@ public void onConnect(ConversationContext context) { LOGGER.debug("Opcua Driver running in ACTIVE mode."); if (this.channel == null) { - this.channel = new SecureChannel((OpcuaDriverContext) driverContext, this.configuration); + try { + this.channel = createSecureChannel(context.getAuthentication()); + } catch (PlcRuntimeException ex) { + context.getChannel().pipeline().fireExceptionCaught(new PlcConnectionException(ex)); + return; + } } this.channel.onConnect(context); } @@ -117,11 +126,20 @@ public void onDiscover(ConversationContext context) { // Only the TCP transport supports login. LOGGER.debug("Opcua Driver running in ACTIVE mode, discovering endpoints"); if (this.channel == null) { - this.channel = new SecureChannel((OpcuaDriverContext) driverContext, this.configuration); + try { + this.channel = createSecureChannel(context.getAuthentication()); + } catch (PlcRuntimeException ex) { + context.getChannel().pipeline().fireExceptionCaught(new PlcConnectionException(ex)); + return; + } } channel.onDiscover(context); } + private SecureChannel createSecureChannel(PlcAuthentication authentication) { + return new SecureChannel((OpcuaDriverContext) driverContext, configuration, authentication); + } + @Override public CompletableFuture read(PlcReadRequest readRequest) { LOGGER.trace("Reading Value"); diff --git a/plc4j/drivers/opcua/src/test/java/org/apache/plc4x/java/opcua/OpcuaPlcDriverTest.java b/plc4j/drivers/opcua/src/test/java/org/apache/plc4x/java/opcua/OpcuaPlcDriverTest.java index d8c5038599f..3c8fa77889b 100644 --- a/plc4j/drivers/opcua/src/test/java/org/apache/plc4x/java/opcua/OpcuaPlcDriverTest.java +++ b/plc4j/drivers/opcua/src/test/java/org/apache/plc4x/java/opcua/OpcuaPlcDriverTest.java @@ -21,6 +21,8 @@ import io.vavr.collection.List; import org.apache.plc4x.java.DefaultPlcDriverManager; import org.apache.plc4x.java.api.PlcConnection; +import org.apache.plc4x.java.api.authentication.PlcUsernamePasswordAuthentication; +import org.apache.plc4x.java.api.exceptions.PlcConnectionException; import org.apache.plc4x.java.api.messages.PlcReadRequest; import org.apache.plc4x.java.api.messages.PlcReadResponse; import org.apache.plc4x.java.api.messages.PlcWriteRequest; @@ -91,6 +93,9 @@ public class OpcuaPlcDriverTest { private static final String UINT64_ARRAY_IDENTIFIER = "ns=2;s=HelloWorld/ArrayTypes/UInt64Array"; private static final String DATE_TIME_ARRAY_IDENTIFIER = "ns=2;s=HelloWorld/ArrayTypes/DateTimeArray"; + //Restricted + public static final String STRING_IDENTIFIER_ONLY_ADMIN_READ_WRITE = "ns=2;s=HelloWorld/OnlyAdminCanRead/String"; + // Address of local milo server private final String miloLocalAddress = "127.0.0.1:12686/milo"; //Tcp pattern of OPC UA @@ -170,6 +175,59 @@ Stream connectionWithDiscoveryParam() throws Exception { .map(DynamicNode.class::cast) .toJavaStream(); } + + @Test + void connectionWithUrlAuthentication() throws Exception { + DefaultPlcDriverManager driverManager = new DefaultPlcDriverManager(); + try (PlcConnection opcuaConnection = driverManager.getConnection(tcpConnectionAddress + "?username=admin&password=password2")) { + Condition is_connected = new Condition<>(PlcConnection::isConnected, "is connected"); + assertThat(opcuaConnection).is(is_connected); + + PlcReadRequest.Builder builder = opcuaConnection.readRequestBuilder() + .addTagAddress("String", STRING_IDENTIFIER_ONLY_ADMIN_READ_WRITE); + + PlcReadRequest request = builder.build(); + PlcReadResponse response = request.execute().get(); + + assertThat(response.getResponseCode("String")).isEqualTo(PlcResponseCode.OK); + } + } + + @Test + void connectionWithPlcAuthentication() throws Exception { + DefaultPlcDriverManager driverManager = new DefaultPlcDriverManager(); + try (PlcConnection opcuaConnection = driverManager.getConnection(tcpConnectionAddress, + new PlcUsernamePasswordAuthentication("admin", "password2"))) { + Condition is_connected = new Condition<>(PlcConnection::isConnected, "is connected"); + assertThat(opcuaConnection).is(is_connected); + + PlcReadRequest.Builder builder = opcuaConnection.readRequestBuilder() + .addTagAddress("String", STRING_IDENTIFIER_ONLY_ADMIN_READ_WRITE); + + PlcReadRequest request = builder.build(); + PlcReadResponse response = request.execute().get(); + + assertThat(response.getResponseCode("String")).isEqualTo(PlcResponseCode.OK); + } + } + + @Test + void connectionWithPlcAuthenticationOverridesUrlParam() throws Exception { + DefaultPlcDriverManager driverManager = new DefaultPlcDriverManager(); + try (PlcConnection opcuaConnection = driverManager.getConnection(tcpConnectionAddress + "?username=user&password=password1", + new PlcUsernamePasswordAuthentication("admin", "password2"))) { + Condition is_connected = new Condition<>(PlcConnection::isConnected, "is connected"); + assertThat(opcuaConnection).is(is_connected); + + PlcReadRequest.Builder builder = opcuaConnection.readRequestBuilder() + .addTagAddress("String", STRING_IDENTIFIER_ONLY_ADMIN_READ_WRITE); + + PlcReadRequest request = builder.build(); + PlcReadResponse response = request.execute().get(); + + assertThat(response.getResponseCode("String")).isEqualTo(PlcResponseCode.OK); + } + } } @Nested