Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
215 changes: 178 additions & 37 deletions src/Opc.Ua.Gds.Server.Common/ApplicationsNodeManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -709,8 +709,17 @@ .. userTokenCertificateGroup.CertificateTypes
}

keyCredNode.StartRequest!.OnCallAsync = OnKeyCredentialStartRequestAsync;
keyCredNode.StartRequest.OnReadRolePermissions = OnAddSelfAdminRolePermissions;
keyCredNode.StartRequest.OnReadUserRolePermissions = OnAddSelfAdminUserRolePermissions;
keyCredNode.FinishRequest!.OnCallAsync = OnKeyCredentialFinishRequestAsync;
keyCredNode.FinishRequest.OnReadRolePermissions = OnAddSelfAdminRolePermissions;
keyCredNode.FinishRequest.OnReadUserRolePermissions = OnAddSelfAdminUserRolePermissions;
keyCredNode.Revoke?.OnCallAsync = OnKeyCredentialRevokeAsync;
if (keyCredNode.Revoke != null)
{
keyCredNode.Revoke.OnReadRolePermissions = OnAddSelfAdminRolePermissions;
keyCredNode.Revoke.OnReadUserRolePermissions = OnAddSelfAdminUserRolePermissions;
}

return keyCredNode;
case ObjectTypes.AuthorizationServiceType:
Expand Down Expand Up @@ -2595,18 +2604,38 @@ private async ValueTask<KeyCredentialStartRequestMethodStateResult> OnKeyCredent
ArrayOf<NodeId> requestedRoles,
CancellationToken cancellationToken)
{
AuthorizationHelper.HasAuthenticatedSecureChannel(context, requireEncryption: true);
ArrayOf<Variant> auditInputs = [applicationUri, publicKey, securityPolicyUri, requestedRoles];
NodeId requestId;
try
{
AuthorizationHelper.HasAuthenticatedSecureChannel(context, requireEncryption: true);
ByteString clientCertificateFingerprint =
AuthorizationHelper.GetClientCertificateFingerprint(context);
NodeId applicationId = ResolveKeyCredentialApplicationId(m_database, applicationUri);
AuthorizationHelper.HasAuthorization(
context,
AuthorizationHelper.KeyCredentialAdminOrSelfAdminOrAppAdmin,
applicationId);
IApplicationOwnedKeyCredentialRequestStore store = GetApplicationOwnedKeyCredentialStore();

m_logger.OnKeyCredentialStartRequest(applicationUri);
m_logger.OnKeyCredentialStartRequest(applicationUri);

NodeId requestId = await KeyCredentialRequestStore.StartRequestAsync(
applicationUri,
publicKey,
securityPolicyUri,
requestedRoles,
cancellationToken).ConfigureAwait(false);
requestId = await store.StartOwnedBoundRequestAsync(
applicationUri,
applicationId,
publicKey,
securityPolicyUri,
requestedRoles,
clientCertificateFingerprint,
cancellationToken).ConfigureAwait(false);
}
catch (Exception ex)
{
Server.ReportKeyCredentialRequestedAuditEvent(
context, objectId, method, auditInputs, m_logger, ex);
throw;
}

ArrayOf<Variant> auditInputs = [applicationUri, publicKey, securityPolicyUri, requestedRoles];
Server.ReportKeyCredentialRequestedAuditEvent(
context, objectId, method, auditInputs, m_logger);

Expand All @@ -2625,36 +2654,52 @@ private async ValueTask<KeyCredentialFinishRequestMethodStateResult> OnKeyCreden
bool cancelRequest,
CancellationToken cancellationToken)
{
AuthorizationHelper.HasAuthenticatedSecureChannel(context, requireEncryption: true);
ArrayOf<Variant> auditInputs = [requestId, cancelRequest];
try
{
AuthorizationHelper.HasAuthenticatedSecureChannel(context, requireEncryption: true);
ByteString clientCertificateFingerprint =
AuthorizationHelper.GetClientCertificateFingerprint(context);
IApplicationOwnedKeyCredentialRequestStore store = GetApplicationOwnedKeyCredentialStore();
NodeId applicationId = await store
.GetRequestApplicationIdAsync(requestId, cancellationToken)
.ConfigureAwait(false);
AuthorizationHelper.HasAuthorization(
context,
AuthorizationHelper.KeyCredentialAdminOrSelfAdminOrAppAdmin,
applicationId);

m_logger.OnKeyCredentialFinishRequest(requestId);
m_logger.OnKeyCredentialFinishRequest(requestId);

FinishKeyCredentialRequestResult finished = await KeyCredentialRequestStore.FinishRequestAsync(
requestId,
cancelRequest,
cancellationToken).ConfigureAwait(false);
FinishKeyCredentialRequestResult finished =
await store.FinishOwnedBoundRequestAsync(
requestId,
cancelRequest,
applicationId,
clientCertificateFingerprint,
cancellationToken).ConfigureAwait(false);
KeyCredentialFinishRequestMethodStateResult result =
CreateKeyCredentialFinishResult(finished);

var result = new KeyCredentialFinishRequestMethodStateResult
{
CredentialId = finished.CredentialId ?? string.Empty,
CredentialSecret = finished.CredentialSecret,
CertificateThumbprint = finished.CertificateThumbprint ?? string.Empty,
SecurityPolicyUri = finished.SecurityPolicyUri ?? string.Empty,
GrantedRoles = finished.GrantedRoles
};
if (finished.State == KeyCredentialRequestState.New)
{
var exception = new ServiceResultException(StatusCodes.BadRequestNotComplete);
Server.ReportKeyCredentialDeliveredAuditEvent(
context, objectId, method, auditInputs, m_logger, exception);
return result;
}

Server.ReportKeyCredentialDeliveredAuditEvent(
context, objectId, method, auditInputs, m_logger);

if (finished.State == KeyCredentialRequestState.New)
{
result.ServiceResult = new ServiceResult(StatusCodes.BadNothingToDo);
return result;
}

ArrayOf<Variant> auditInputs = [requestId, cancelRequest];
Server.ReportKeyCredentialDeliveredAuditEvent(
context, objectId, method, auditInputs, m_logger);

result.ServiceResult = ServiceResult.Good;
return result;
catch (Exception ex)
{
Server.ReportKeyCredentialDeliveredAuditEvent(
context, objectId, method, auditInputs, m_logger, ex);
throw;
}
}

private async ValueTask<KeyCredentialRevokeMethodStateResult> OnKeyCredentialRevokeAsync(
Expand All @@ -2664,19 +2709,115 @@ private async ValueTask<KeyCredentialRevokeMethodStateResult> OnKeyCredentialRev
string credentialId,
CancellationToken cancellationToken)
{
AuthorizationHelper.HasAuthenticatedSecureChannel(context, requireEncryption: true);
ArrayOf<Variant> auditInputs = [credentialId];
try
{
AuthorizationHelper.HasAuthenticatedSecureChannel(context, requireEncryption: true);
_ = AuthorizationHelper.GetClientCertificateFingerprint(context);
IApplicationOwnedKeyCredentialRequestStore store = GetApplicationOwnedKeyCredentialStore();
NodeId applicationId = await store
.GetCredentialApplicationIdAsync(credentialId, cancellationToken)
.ConfigureAwait(false);
AuthorizationHelper.HasAuthorization(
context,
AuthorizationHelper.KeyCredentialAdminOrSelfAdminOrAppAdmin,
applicationId);

m_logger.OnKeyCredentialRevoke(credentialId);
m_logger.OnKeyCredentialRevoke(credentialId);

await KeyCredentialRequestStore.RevokeAsync(credentialId, cancellationToken).ConfigureAwait(false);
await store
.RevokeOwnedAsync(credentialId, applicationId, cancellationToken)
.ConfigureAwait(false);
}
catch (Exception ex)
{
Server.ReportKeyCredentialRevokedAuditEvent(
context, objectId, method, auditInputs, m_logger, ex);
throw;
}

ArrayOf<Variant> auditInputs = [credentialId];
Server.ReportKeyCredentialRevokedAuditEvent(
context, objectId, method, auditInputs, m_logger);

return new KeyCredentialRevokeMethodStateResult { ServiceResult = ServiceResult.Good };
}

internal static NodeId ResolveKeyCredentialApplicationId(
IApplicationsDatabase database,
string applicationUri)
{
if (database == null)
{
throw new ArgumentNullException(nameof(database));
}
if (string.IsNullOrWhiteSpace(applicationUri))
{
throw new ServiceResultException(
StatusCodes.BadNotFound,
"The ApplicationUri is not known to the GDS.");
}

ApplicationRecordDataType[] matches = database
.FindApplications(applicationUri)?
.Where(application => string.Equals(
application.ApplicationUri,
applicationUri,
StringComparison.Ordinal))
.ToArray() ?? [];
if (matches.Length == 0)
{
throw new ServiceResultException(
StatusCodes.BadNotFound,
"The ApplicationUri is not known to the GDS.");
}
if (matches.Length != 1 || matches[0].ApplicationId.IsNull)
{
throw new ServiceResultException(
StatusCodes.BadConfigurationError,
"The ApplicationUri does not uniquely identify a registered application.");
}

return matches[0].ApplicationId;
}

internal static ServiceResult GetKeyCredentialFinishServiceResult(
KeyCredentialRequestState state)
{
return state == KeyCredentialRequestState.New
? new ServiceResult(StatusCodes.BadRequestNotComplete)
: ServiceResult.Good;
}

internal static KeyCredentialFinishRequestMethodStateResult CreateKeyCredentialFinishResult(
FinishKeyCredentialRequestResult finished)
{
if (finished.State == KeyCredentialRequestState.New)
{
return new KeyCredentialFinishRequestMethodStateResult
{
ServiceResult = GetKeyCredentialFinishServiceResult(finished.State)
};
}

return new KeyCredentialFinishRequestMethodStateResult
{
ServiceResult = ServiceResult.Good,
CredentialId = finished.CredentialId ?? string.Empty,
CredentialSecret = finished.CredentialSecret,
CertificateThumbprint = finished.CertificateThumbprint ?? string.Empty,
SecurityPolicyUri = finished.SecurityPolicyUri ?? string.Empty,
GrantedRoles = finished.GrantedRoles
};
}

private IApplicationOwnedKeyCredentialRequestStore GetApplicationOwnedKeyCredentialStore()
{
return KeyCredentialRequestStore as IApplicationOwnedKeyCredentialRequestStore ??
throw new ServiceResultException(
StatusCodes.BadSecurityChecksFailed,
"The key-credential store does not support application ownership checks.");
}

/// <inheritdoc/>
protected override void Dispose(bool disposing)
{
Expand Down
24 changes: 14 additions & 10 deletions src/Opc.Ua.Gds.Server.Common/Diagnostics/AuditEvents.cs
Original file line number Diff line number Diff line change
Expand Up @@ -350,7 +350,7 @@ internal static void ReportKeyCredentialRequestedAuditEvent(
server, systemContext, objectId, method, inputArguments,
"KeyCredentialRequestedAuditEvent",
static () => new KeyCredentialRequestedAuditEventState(null),
logger, exception);
logger, exception, redactExceptionMessage: true);
}

/// <summary>
Expand All @@ -362,13 +362,14 @@ internal static void ReportKeyCredentialDeliveredAuditEvent(
NodeId objectId,
MethodState method,
ArrayOf<Variant> inputArguments,
ILogger logger)
ILogger logger,
Exception? exception = null)
{
ReportSimpleAuditEvent(
server, systemContext, objectId, method, inputArguments,
"KeyCredentialDeliveredAuditEvent",
static () => new KeyCredentialDeliveredAuditEventState(null),
logger, null);
logger, exception, redactExceptionMessage: true);
}

/// <summary>
Expand All @@ -387,7 +388,7 @@ internal static void ReportKeyCredentialRevokedAuditEvent(
server, systemContext, objectId, method, inputArguments,
"KeyCredentialRevokedAuditEvent",
static () => new KeyCredentialRevokedAuditEventState(null),
logger, exception);
logger, exception, redactExceptionMessage: true);
}

/// <summary>
Expand All @@ -406,7 +407,7 @@ internal static void ReportAccessTokenIssuedAuditEvent(
server, systemContext, objectId, method, inputArguments,
"AccessTokenIssuedAuditEvent",
static () => new AccessTokenIssuedAuditEventState(null),
logger, exception);
logger, exception, redactExceptionMessage: false);
}

/// <summary>
Expand All @@ -422,16 +423,19 @@ private static void ReportSimpleAuditEvent(
string eventName,
Func<AuditUpdateMethodEventState> factory,
ILogger logger,
Exception? exception)
Exception? exception,
bool redactExceptionMessage)
{
try
{
AuditUpdateMethodEventState e = factory();

TranslationInfo message = exception == null
? new TranslationInfo(eventName, "en-US", $"{eventName}.")
: new TranslationInfo(eventName, "en-US",
$"{eventName} - Exception: {exception.Message}.");
string messageText = exception == null
? $"{eventName}."
: redactExceptionMessage
? $"{eventName} failed."
: $"{eventName} - Exception: {exception.Message}.";
var message = new TranslationInfo(eventName, "en-US", messageText);

e.Initialize(
systemContext, null, EventSeverity.Min,
Expand Down
Loading
Loading