Skip to content

Commit a159e7e

Browse files
committed
fix(plc4j): Adjust endpoint selection logic.
Amended selection logic made use of streams with side effects. When server had more than one endpoint and these endpoints had same policy (i.e. anonymous) with different identifiers (i.e. anonymous_1, anonymous_2), it lead to wrong activate session requests which mixed endpoint and user token policy. Updated logic removes side effects and makes sure that only policies of matched server endpoint are tested against client configuration. Closes #1705. Signed-off-by: Łukasz Dywicki <[email protected]>
1 parent cb8153a commit a159e7e

File tree

1 file changed

+36
-38
lines changed

1 file changed

+36
-38
lines changed

Diff for: plc4j/drivers/opcua/src/main/java/org/apache/plc4x/java/opcua/context/SecureChannel.java

+36-38
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
*/
1919
package org.apache.plc4x.java.opcua.context;
2020

21+
import static java.util.Map.entry;
2122
import static java.util.concurrent.Executors.newSingleThreadScheduledExecutor;
2223
import static org.apache.plc4x.java.opcua.readwrite.ChunkType.*;
2324

@@ -27,6 +28,7 @@
2728
import java.security.cert.CertificateEncodingException;
2829
import java.security.cert.CertificateFactory;
2930
import java.security.cert.X509Certificate;
31+
import java.util.Map.Entry;
3032
import java.util.concurrent.ScheduledExecutorService;
3133
import java.util.concurrent.ScheduledFuture;
3234
import java.util.concurrent.TimeUnit;
@@ -56,7 +58,6 @@
5658
import java.util.concurrent.CompletableFuture;
5759
import java.util.regex.Matcher;
5860
import java.util.regex.Pattern;
59-
import java.util.stream.Stream;
6061

6162
import static java.util.concurrent.Executors.newSingleThreadExecutor;
6263

@@ -82,8 +83,6 @@ public class SecureChannel {
8283
private final String sessionName = "UaSession:" + APPLICATION_TEXT.getStringValue() + ":" + RandomStringUtils.random(20, true, true);
8384
private final PascalByteString localCertificateString;
8485
private final PascalByteString remoteCertificateThumbprint;
85-
private PascalString policyId;
86-
private UserTokenType tokenType;
8786
private final PascalString endpoint;
8887
private final String username;
8988
private final String password;
@@ -324,13 +323,14 @@ private CompletableFuture<ActivateSessionResponse> onConnectActivateSessionReque
324323
LOGGER.debug("error getting host", e);
325324
}
326325

327-
selectEndpoint(sessionResponse);
328-
329-
if (this.policyId == null) {
326+
Entry<EndpointDescription, UserTokenPolicy> endpointAndAuthPolicy = selectEndpoint(sessionResponse);
327+
if (endpointAndAuthPolicy == null) {
330328
throw new PlcRuntimeException("Unable to find endpoint - " + endpoints[1]);
331329
}
332330

333-
ExtensionObject userIdentityToken = getIdentityToken(this.tokenType, policyId.getStringValue());
331+
PascalString policyId = endpointAndAuthPolicy.getValue().getPolicyId();
332+
UserTokenType tokenType = endpointAndAuthPolicy.getValue().getTokenType();
333+
ExtensionObject userIdentityToken = getIdentityToken(tokenType, policyId.getStringValue());
334334
RequestHeader requestHeader = conversation.createRequestHeader();
335335
SignatureData clientSignature = new SignatureData(NULL_STRING, NULL_BYTE_STRING);
336336
if (conversation.getSecurityPolicy() != SecurityPolicy.NONE) {
@@ -501,31 +501,34 @@ private static ReadBufferByteBased toBuffer(Supplier<Payload> supplier) {
501501
}
502502

503503
/**
504-
* Selects the endpoint to use based on the connection string provided.
505-
* If Discovery is disabled it will use the host address return from the server
504+
* Selects the endpoint and authentication policy based on client settings.
506505
*
507506
* @param sessionResponse - The CreateSessionResponse message returned by the server
508-
* @throws PlcRuntimeException - If no endpoint with a compatible policy is found raise and error.
507+
* @return Entry representing desired server endpoint and user token policy to access it.
509508
*/
510-
private void selectEndpoint(CreateSessionResponse sessionResponse) throws PlcRuntimeException {
509+
private Entry<EndpointDescription, UserTokenPolicy> selectEndpoint(CreateSessionResponse sessionResponse) {
511510
// Get a list of the endpoints which match ours.
512-
Stream<EndpointDescription> filteredEndpoints = sessionResponse.getServerEndpoints().stream()
513-
.map(e -> (EndpointDescription) e)
514-
.filter(this::isEndpoint);
515-
516-
//Determine if the requested security policy is included in the endpoint
517-
filteredEndpoints.forEach(endpoint -> hasIdentity(
518-
endpoint.getUserIdentityTokens().stream()
519-
.map(p -> (UserTokenPolicy) p)
520-
.toArray(UserTokenPolicy[]::new)
521-
));
522-
523-
if (this.policyId == null) {
524-
throw new PlcRuntimeException("Unable to find endpoint - " + this.endpoints.get(0));
511+
EndpointDescription selectedEndpoint = null;
512+
for (ExtensionObjectDefinition endpoint : sessionResponse.getServerEndpoints()) {
513+
if (!(endpoint instanceof EndpointDescription)) {
514+
continue;
515+
}
516+
if (isEndpoint((EndpointDescription) endpoint)) {
517+
selectedEndpoint = (EndpointDescription) endpoint;
518+
break;
519+
}
525520
}
526-
if (this.tokenType == null) {
527-
throw new PlcRuntimeException("Unable to find Security Policy for endpoint - " + this.endpoints.get(0));
521+
522+
for (ExtensionObjectDefinition tokenPolicy : selectedEndpoint.getUserIdentityTokens()) {
523+
if (!(tokenPolicy instanceof UserTokenPolicy)) {
524+
continue;
525+
}
526+
if (hasIdentity((UserTokenPolicy) tokenPolicy)) {
527+
return entry(selectedEndpoint, (UserTokenPolicy) tokenPolicy);
528+
}
528529
}
530+
531+
return null;
529532
}
530533

531534
/**
@@ -569,21 +572,16 @@ private boolean isEndpoint(EndpointDescription endpoint) throws PlcRuntimeExcept
569572
}
570573

571574
/**
572-
* Confirms that a policy that matches the connection string is available from
573-
* the returned endpoints. It sets the policyId and tokenType for the policy to use.
575+
* Confirms that given policy matches the connection string used by client.
574576
*
575-
* @param policies - A list of policies returned with the endpoint description.
577+
* @param policy - UserTokenPolicy configured for server endpoint.
578+
* @return True if given token policy matches client configuration.
576579
*/
577-
private void hasIdentity(UserTokenPolicy[] policies) {
578-
for (UserTokenPolicy identityToken : policies) {
579-
if ((identityToken.getTokenType() == UserTokenType.userTokenTypeAnonymous) && (this.username == null)) {
580-
policyId = identityToken.getPolicyId();
581-
tokenType = identityToken.getTokenType();
582-
} else if ((identityToken.getTokenType() == UserTokenType.userTokenTypeUserName) && (this.username != null)) {
583-
policyId = identityToken.getPolicyId();
584-
tokenType = identityToken.getTokenType();
585-
}
580+
private boolean hasIdentity(UserTokenPolicy policy) {
581+
if ((policy.getTokenType() == UserTokenType.userTokenTypeAnonymous) && this.username == null) {
582+
return true;
586583
}
584+
return policy.getTokenType() == UserTokenType.userTokenTypeUserName && this.username != null;
587585
}
588586

589587
/**

0 commit comments

Comments
 (0)