Skip to content
23 changes: 23 additions & 0 deletions Libraries/Opc.Ua.Client/Session/ISession.cs
Original file line number Diff line number Diff line change
Expand Up @@ -607,6 +607,17 @@ Task<Dictionary<NodeId, DataDictionary>> LoadDataTypeSystem(
/// <param name="checkDomain">If set to <c>true</c> then the domain in the certificate must match the endpoint used.</param>
void Open(string sessionName, uint sessionTimeout, IUserIdentity identity, IList<string> preferredLocales, bool checkDomain);

/// <summary>
/// Establishes a session with the server.
/// </summary>
/// <param name="sessionName">The name to assign to the session.</param>
/// <param name="sessionTimeout">The session timeout.</param>
/// <param name="identity">The user identity.</param>
/// <param name="preferredLocales">The list of preferred locales.</param>
/// <param name="checkDomain">If set to <c>true</c> then the domain in the certificate must match the endpoint used.</param>
/// <param name="closeChannel">If set to <c>true</c> then the channel is closed when the Open fails.</param>
void Open(string sessionName, uint sessionTimeout, IUserIdentity identity, IList<string> preferredLocales, bool checkDomain, bool closeChannel);

/// <summary>
/// Updates the preferred locales used for the session.
/// </summary>
Expand Down Expand Up @@ -669,6 +680,18 @@ Task<Dictionary<NodeId, DataDictionary>> LoadDataTypeSystem(
/// <param name="ct">The cancellation token.</param>
Task OpenAsync(string sessionName, uint sessionTimeout, IUserIdentity identity, IList<string> preferredLocales, bool checkDomain, CancellationToken ct);

/// <summary>
/// Establishes a session with the server.
/// </summary>
/// <param name="sessionName">The name to assign to the session.</param>
/// <param name="sessionTimeout">The session timeout.</param>
/// <param name="identity">The user identity.</param>
/// <param name="preferredLocales">The list of preferred locales.</param>
/// <param name="checkDomain">If set to <c>true</c> then the domain in the certificate must match the endpoint used.</param>
/// <param name="closeChannel">If set to <c>true</c> then the channel is closed when the Open fails.</param>
/// <param name="ct">The cancellation token.</param>
Task OpenAsync(string sessionName, uint sessionTimeout, IUserIdentity identity, IList<string> preferredLocales, bool checkDomain, bool closeChannel, CancellationToken ct);

/// <summary>
/// Reads the values for the node attributes and returns a node object collection.
/// </summary>
Expand Down
41 changes: 34 additions & 7 deletions Libraries/Opc.Ua.Client/Session/Session.cs
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,7 @@
m_SubscriptionsChanged = template.m_SubscriptionsChanged;
m_SessionClosing = template.m_SessionClosing;
m_SessionConfigurationChanged = template.m_SessionConfigurationChanged;
m_RenewUserIdentity = template.m_RenewUserIdentity;
}

foreach (Subscription subscription in template.Subscriptions)
Expand Down Expand Up @@ -1268,11 +1269,12 @@

try
{
session.RecreateRenewUserIdentity();
// open the session.
session.Open(
template.SessionName,
(uint)template.SessionTimeout,
template.Identity,
session.Identity,
template.PreferredLocales,
template.m_checkDomain);

Expand Down Expand Up @@ -1314,11 +1316,12 @@

try
{
session.RecreateRenewUserIdentity();

Check warning on line 1319 in Libraries/Opc.Ua.Client/Session/Session.cs

View check run for this annotation

Codecov / codecov/patch

Libraries/Opc.Ua.Client/Session/Session.cs#L1319

Added line #L1319 was not covered by tests
// open the session.
session.Open(
template.m_sessionName,
(uint)template.m_sessionTimeout,
template.m_identity,
session.Identity,

Check warning on line 1324 in Libraries/Opc.Ua.Client/Session/Session.cs

View check run for this annotation

Codecov / codecov/patch

Libraries/Opc.Ua.Client/Session/Session.cs#L1324

Added line #L1324 was not covered by tests
template.m_preferredLocales,
template.m_checkDomain);

Expand Down Expand Up @@ -1349,13 +1352,15 @@

try
{
session.RecreateRenewUserIdentity();
// open the session.
session.Open(
template.m_sessionName,
(uint)template.m_sessionTimeout,
template.m_identity,
session.Identity,
template.m_preferredLocales,
template.m_checkDomain);
template.m_checkDomain,
false);

// create the subscriptions.
foreach (Subscription subscription in session.Subscriptions)
Expand Down Expand Up @@ -2286,7 +2291,17 @@
IUserIdentity identity,
IList<string> preferredLocales)
{
Open(sessionName, sessionTimeout, identity, preferredLocales, true);
Open(sessionName, sessionTimeout, identity, preferredLocales, true, true);
}
/// <inheritdoc/>
public void Open(
string sessionName,
uint sessionTimeout,
IUserIdentity identity,
IList<string> preferredLocales,
bool checkDomain)
{
Open(sessionName, sessionTimeout, identity, preferredLocales, true, true);
}

/// <inheritdoc/>
Expand All @@ -2296,7 +2311,8 @@
uint sessionTimeout,
IUserIdentity identity,
IList<string> preferredLocales,
bool checkDomain)
bool checkDomain,
bool closeChannel)
{
OpenValidateIdentity(ref identity, out var identityToken, out var identityPolicy, out string securityPolicyUri, out bool requireEncryption);

Expand Down Expand Up @@ -2555,7 +2571,7 @@
{
try
{
Close(true);
Close(closeChannel);

Check warning on line 2574 in Libraries/Opc.Ua.Client/Session/Session.cs

View check run for this annotation

Codecov / codecov/patch

Libraries/Opc.Ua.Client/Session/Session.cs#L2574

Added line #L2574 was not covered by tests
}
catch (Exception e)
{
Expand Down Expand Up @@ -5346,6 +5362,17 @@

return false;
}

/// <summary>
/// Helper to refresh the identity (reprompt for password, refresh token) in case of a Recreate of the Session.
/// </summary>
public virtual void RecreateRenewUserIdentity()
{
if (m_RenewUserIdentity != null)
{
m_identity = m_RenewUserIdentity(this, m_identity);
}
}
#endregion

#region Private Methods
Expand Down
28 changes: 24 additions & 4 deletions Libraries/Opc.Ua.Client/Session/SessionAsync.cs
Original file line number Diff line number Diff line change
Expand Up @@ -67,13 +67,26 @@
return OpenAsync(sessionName, sessionTimeout, identity, preferredLocales, true, ct);
}

/// <inheritdoc/>
public Task OpenAsync(
string sessionName,
uint sessionTimeout,
IUserIdentity identity,
IList<string> preferredLocales,
bool checkDomain,
CancellationToken ct)
{
return OpenAsync(sessionName, sessionTimeout, identity, preferredLocales, checkDomain, true, ct);
}

/// <inheritdoc/>
public async Task OpenAsync(
string sessionName,
uint sessionTimeout,
IUserIdentity identity,
IList<string> preferredLocales,
bool checkDomain,
bool closeChannel,
CancellationToken ct)
{
OpenValidateIdentity(ref identity, out var identityToken, out var identityPolicy, out string securityPolicyUri, out bool requireEncryption);
Expand Down Expand Up @@ -322,7 +335,10 @@
try
{
await base.CloseSessionAsync(null, false, CancellationToken.None).ConfigureAwait(false);
await CloseChannelAsync(CancellationToken.None).ConfigureAwait(false);
if (closeChannel)
{
await CloseChannelAsync(CancellationToken.None).ConfigureAwait(false);

Check warning on line 340 in Libraries/Opc.Ua.Client/Session/SessionAsync.cs

View check run for this annotation

Codecov / codecov/patch

Libraries/Opc.Ua.Client/Session/SessionAsync.cs#L340

Added line #L340 was not covered by tests
}
}
catch (Exception e)
{
Expand Down Expand Up @@ -1514,11 +1530,12 @@

try
{
session.RecreateRenewUserIdentity();
// open the session.
await session.OpenAsync(
sessionTemplate.SessionName,
(uint)sessionTemplate.SessionTimeout,
sessionTemplate.Identity,
session.Identity,
sessionTemplate.PreferredLocales,
sessionTemplate.m_checkDomain,
ct).ConfigureAwait(false);
Expand Down Expand Up @@ -1562,11 +1579,12 @@

try
{
session.RecreateRenewUserIdentity();

Check warning on line 1582 in Libraries/Opc.Ua.Client/Session/SessionAsync.cs

View check run for this annotation

Codecov / codecov/patch

Libraries/Opc.Ua.Client/Session/SessionAsync.cs#L1582

Added line #L1582 was not covered by tests
// open the session.
await session.OpenAsync(
sessionTemplate.m_sessionName,
(uint)sessionTemplate.m_sessionTimeout,
sessionTemplate.m_identity,
session.Identity,

Check warning on line 1587 in Libraries/Opc.Ua.Client/Session/SessionAsync.cs

View check run for this annotation

Codecov / codecov/patch

Libraries/Opc.Ua.Client/Session/SessionAsync.cs#L1587

Added line #L1587 was not covered by tests
sessionTemplate.m_preferredLocales,
sessionTemplate.m_checkDomain,
ct).ConfigureAwait(false);
Expand Down Expand Up @@ -1604,13 +1622,15 @@

try
{
session.RecreateRenewUserIdentity();
// open the session.
await session.OpenAsync(
sessionTemplate.m_sessionName,
(uint)sessionTemplate.m_sessionTimeout,
sessionTemplate.m_identity,
session.Identity,
sessionTemplate.m_preferredLocales,
sessionTemplate.m_checkDomain,
false,
ct).ConfigureAwait(false);

// create the subscriptions.
Expand Down
18 changes: 18 additions & 0 deletions Libraries/Opc.Ua.Client/Session/TraceableSession.cs
Original file line number Diff line number Diff line change
Expand Up @@ -626,6 +626,15 @@
}
}

/// <inheritdoc/>
public void Open(string sessionName, uint sessionTimeout, IUserIdentity identity, IList<string> preferredLocales, bool checkDomain, bool closeChannel)
{
using (Activity activity = ActivitySource.StartActivity())

Check warning on line 632 in Libraries/Opc.Ua.Client/Session/TraceableSession.cs

View check run for this annotation

Codecov / codecov/patch

Libraries/Opc.Ua.Client/Session/TraceableSession.cs#L632

Added line #L632 was not covered by tests
{
m_session.Open(sessionName, sessionTimeout, identity, preferredLocales, checkDomain, closeChannel);
}
}

Check warning on line 636 in Libraries/Opc.Ua.Client/Session/TraceableSession.cs

View check run for this annotation

Codecov / codecov/patch

Libraries/Opc.Ua.Client/Session/TraceableSession.cs#L634-L636

Added lines #L634 - L636 were not covered by tests

/// <inheritdoc/>
public void ChangePreferredLocales(StringCollection preferredLocales)
{
Expand Down Expand Up @@ -707,6 +716,15 @@
}
}

/// <inheritdoc/>
public async Task OpenAsync(string sessionName, uint sessionTimeout, IUserIdentity identity, IList<string> preferredLocales, bool checkDomain, bool closeChannel, CancellationToken ct)
{
using (Activity activity = ActivitySource.StartActivity())

Check warning on line 722 in Libraries/Opc.Ua.Client/Session/TraceableSession.cs

View check run for this annotation

Codecov / codecov/patch

Libraries/Opc.Ua.Client/Session/TraceableSession.cs#L722

Added line #L722 was not covered by tests
{
await m_session.OpenAsync(sessionName, sessionTimeout, identity, preferredLocales, checkDomain, closeChannel, ct).ConfigureAwait(false);
}
}

Check warning on line 726 in Libraries/Opc.Ua.Client/Session/TraceableSession.cs

View check run for this annotation

Codecov / codecov/patch

Libraries/Opc.Ua.Client/Session/TraceableSession.cs#L724-L726

Added lines #L724 - L726 were not covered by tests


/// <inheritdoc/>
public async Task FetchNamespaceTablesAsync(CancellationToken ct = default)
Expand Down
89 changes: 89 additions & 0 deletions Tests/Opc.Ua.Client.Tests/ClientTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -625,6 +625,12 @@
session2.DetachChannel();
session2.Dispose();


//Recreate session using same channel
var session3 = await ClientFixture.SessionFactory.RecreateAsync(session1, channel);

Check warning on line 630 in Tests/Opc.Ua.Client.Tests/ClientTest.cs

View workflow job for this annotation

GitHub Actions / test-windows-latest-Client.ComplexTypes

Consider calling ConfigureAwait on the awaited task (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2007)

Check warning on line 630 in Tests/Opc.Ua.Client.Tests/ClientTest.cs

View workflow job for this annotation

GitHub Actions / test-windows-latest-Client

Consider calling ConfigureAwait on the awaited task (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2007)

_ = session3.ReadValue(VariableIds.Server_ServerStatus, typeof(ServerStatusDataType));

channel.Dispose();
}

Expand Down Expand Up @@ -851,6 +857,89 @@
Utils.SilentDispose(session2);
}

/// <summary>
/// Open a session on a channel, then recreate using the session as a template, verify the renewUserIdentityHandler is brought to the new session and called before Session.Open
/// /// </summary>
[Test, Order(270)]
[TestCase(false)]
[TestCase(true)]
public async Task RecreateSessionWithRenewUserIdentity(bool async)
{
IUserIdentity userIdentityAnonymous = new UserIdentity() ;
IUserIdentity userIdentityPW = new UserIdentity("user1", "password");

// the first channel determines the endpoint
ConfiguredEndpoint endpoint = await ClientFixture.GetEndpointAsync(ServerUrl, SecurityPolicies.Basic256Sha256, Endpoints).ConfigureAwait(false);
Assert.NotNull(endpoint);

UserTokenPolicy identityPolicy = endpoint.Description.FindUserTokenPolicy(userIdentityAnonymous.TokenType,
userIdentityAnonymous.IssuedTokenType,
endpoint.Description.SecurityPolicyUri);
if (identityPolicy == null)
{
Assert.Ignore($"No UserTokenPolicy found for {userIdentityAnonymous.TokenType} / {userIdentityAnonymous.IssuedTokenType}");
}

// the active channel
ISession session1 = await ClientFixture.ConnectAsync(endpoint, userIdentityAnonymous).ConfigureAwait(false);
Assert.NotNull(session1);

ServerStatusDataType value1 = (ServerStatusDataType)session1.ReadValue(VariableIds.Server_ServerStatus, typeof(ServerStatusDataType));
Assert.NotNull(value1);

// hook callback to renew the user identity
session1.RenewUserIdentity += (session, identity) => {
return userIdentityPW;
};

Client.Session session2;
if (async)
{
session2 = await Client.Session.RecreateAsync((Session)((TraceableSession)session1).Session).ConfigureAwait(false);
}
else
{
session2 = Client.Session.Recreate((Session)((TraceableSession)session1).Session);
}

// create new channel
ITransportChannel channel2 = await ClientFixture.CreateChannelAsync(session1.ConfiguredEndpoint, false).ConfigureAwait(false);
Assert.NotNull(channel2);

Client.Session session3;
if (async)
{
session3 = await Client.Session.RecreateAsync((Session)((TraceableSession)session1).Session, channel2).ConfigureAwait(false);

}
else
{
session3 = Client.Session.Recreate((Session)((TraceableSession)session1).Session, channel2);
}

//validate new Session Ids are used and also UserName PW identity token is injected as renewed token
Assert.AreNotEqual(session1.SessionId, session2.SessionId);
Assert.AreEqual(userIdentityPW.TokenType, session2.Identity.TokenType);
Assert.AreNotEqual(session1.SessionId, session3.SessionId);
Assert.AreEqual(userIdentityPW.TokenType, session3.Identity.TokenType);

ServerStatusDataType value2 = (ServerStatusDataType)session2.ReadValue(VariableIds.Server_ServerStatus, typeof(ServerStatusDataType));
Assert.NotNull(value2);


session1.DeleteSubscriptionsOnClose = true;
session1.Close(1000);
Utils.SilentDispose(session1);

session2.DeleteSubscriptionsOnClose = true;
session2.Close(1000);
Utils.SilentDispose(session2);

session3.DeleteSubscriptionsOnClose = true;
session3.Close(1000);
Utils.SilentDispose(session3);
}

[Test, Order(300)]
public new void GetOperationLimits()
{
Expand Down
Loading