Skip to content

Commit

Permalink
feat(plc4j/opcua): Add support for PlcUsernamePasswordAuthentication (#…
Browse files Browse the repository at this point in the history
…1107)

* plc4j-driver-opcua: Add support for PlcUsernamePasswordAuthentication

Attempts to resolve issue #1104

* chore(plc4j/opcua): fix dependency issues

---------

Co-authored-by: Sebastian Rühl <[email protected]>
  • Loading branch information
takraj and sruehl authored Sep 25, 2023
1 parent bd064a5 commit 41d82d8
Show file tree
Hide file tree
Showing 4 changed files with 97 additions and 6 deletions.
4 changes: 4 additions & 0 deletions plc4j/drivers/opcua/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,10 @@
<groupId>io.netty</groupId>
<artifactId>netty-buffer</artifactId>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-transport</artifactId>
</dependency>

<dependency>
<groupId>org.apache.commons</groupId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -121,13 +123,22 @@ public class SecureChannel {
private final List<String> 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();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
package org.apache.plc4x.java.opcua.protocol;

import java.nio.ByteBuffer;
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;
Expand Down Expand Up @@ -91,6 +93,9 @@ public void close(ConversationContext<OpcuaAPU> context) {

@Override
public void onDisconnect(ConversationContext<OpcuaAPU> context) {
if (channel == null) {
return;
}
for (Map.Entry<Long, OpcuaSubscriptionHandle> subscriber : subscriptions.entrySet()) {
subscriber.getValue().stopSubscriber();
}
Expand All @@ -100,15 +105,19 @@ public void onDisconnect(ConversationContext<OpcuaAPU> context) {
@Override
public void setDriverContext(DriverContext driverContext) {
super.setDriverContext(driverContext);
this.channel = new SecureChannel((OpcuaDriverContext) driverContext, this.configuration);
}

@Override
public void onConnect(ConversationContext<OpcuaAPU> 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);
}
Expand All @@ -118,11 +127,20 @@ public void onDiscover(ConversationContext<OpcuaAPU> 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<PlcReadResponse> read(PlcReadRequest readRequest) {
LOGGER.trace("Reading Value");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -170,6 +175,59 @@ Stream<DynamicNode> 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<PlcConnection> 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<PlcConnection> 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<PlcConnection> 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
Expand Down

0 comments on commit 41d82d8

Please sign in to comment.