From 6110b89fc4c3f88824620166289f75cabc0a367b Mon Sep 17 00:00:00 2001 From: Anders Abel Date: Fri, 10 Nov 2023 11:17:58 +0100 Subject: [PATCH 1/6] Metrics for UI --- hosts/main/HostingExtensions.cs | 3 +- .../main/Pages/Account/Login/Index.cshtml.cs | 5 +- .../main/Pages/Account/Logout/Index.cshtml.cs | 7 +- hosts/main/Pages/Ciba/Consent.cshtml.cs | 8 +- hosts/main/Pages/Consent/Index.cshtml.cs | 4 + hosts/main/Pages/Device/Index.cshtml.cs | 4 + .../Pages/ExternalLogin/Callback.cshtml.cs | 1 + hosts/main/Pages/Grants/Index.cshtml.cs | 3 +- hosts/main/Pages/Telemetry.cs | 170 ++++++++++++++++++ src/Telemetry/Telemetry.cs | 2 +- 10 files changed, 198 insertions(+), 9 deletions(-) create mode 100644 hosts/main/Pages/Telemetry.cs diff --git a/hosts/main/HostingExtensions.cs b/hosts/main/HostingExtensions.cs index 9e8887330..c3fb57327 100644 --- a/hosts/main/HostingExtensions.cs +++ b/hosts/main/HostingExtensions.cs @@ -48,7 +48,8 @@ internal static WebApplication ConfigureServices(this WebApplicationBuilder buil openTelemetry.WithMetrics(m => m .AddMeter(Telemetry.ServiceName) - .AddPrometheusExporter()); + .AddMeter(Pages.Telemetry.ServiceName) + .AddPrometheusExporter()) ; return builder.Build(); } diff --git a/hosts/main/Pages/Account/Login/Index.cshtml.cs b/hosts/main/Pages/Account/Login/Index.cshtml.cs index b50b49d53..eec8c4a07 100644 --- a/hosts/main/Pages/Account/Login/Index.cshtml.cs +++ b/hosts/main/Pages/Account/Login/Index.cshtml.cs @@ -97,6 +97,7 @@ public async Task OnPost() { var user = _users.FindByUsername(Input.Username); await _events.RaiseAsync(new UserLoginSuccessEvent(user.Username, user.SubjectId, user.Username, clientId: context?.Client.ClientId)); + Telemetry.Metrics.UserLogin(context?.Client.ClientId, IdentityServerConstants.LocalIdentityProvider); // only set explicit expiration here if user chooses "remember me". // otherwise we rely upon expiration configured in cookie middleware. @@ -144,7 +145,9 @@ public async Task OnPost() } } - await _events.RaiseAsync(new UserLoginFailureEvent(Input.Username, "invalid credentials", clientId:context?.Client.ClientId)); + const string error = "invalid credentials"; + await _events.RaiseAsync(new UserLoginFailureEvent(Input.Username, error, clientId:context?.Client.ClientId)); + Telemetry.Metrics.UserLoginFailure(context?.Client.ClientId, IdentityServerConstants.LocalIdentityProvider, error); ModelState.AddModelError(string.Empty, LoginOptions.InvalidCredentialsErrorMessage); } diff --git a/hosts/main/Pages/Account/Logout/Index.cshtml.cs b/hosts/main/Pages/Account/Logout/Index.cshtml.cs index 9f9c4dc1e..aef1de3d8 100644 --- a/hosts/main/Pages/Account/Logout/Index.cshtml.cs +++ b/hosts/main/Pages/Account/Logout/Index.cshtml.cs @@ -71,12 +71,13 @@ public async Task OnPost() // delete local authentication cookie await HttpContext.SignOutAsync(); - // raise the logout event - await _events.RaiseAsync(new UserLogoutSuccessEvent(User.GetSubjectId(), User.GetDisplayName())); - // see if we need to trigger federated logout var idp = User.FindFirst(JwtClaimTypes.IdentityProvider)?.Value; + // raise the logout event + await _events.RaiseAsync(new UserLogoutSuccessEvent(User.GetSubjectId(), User.GetDisplayName())); + Telemetry.Metrics.UserLogout(idp); + // if it's a local login we can ignore this workflow if (idp != null && idp != Duende.IdentityServer.IdentityServerConstants.LocalIdentityProvider) { diff --git a/hosts/main/Pages/Ciba/Consent.cshtml.cs b/hosts/main/Pages/Ciba/Consent.cshtml.cs index 4ebf8cd53..17c4c00e6 100644 --- a/hosts/main/Pages/Ciba/Consent.cshtml.cs +++ b/hosts/main/Pages/Ciba/Consent.cshtml.cs @@ -31,7 +31,7 @@ public class Consent : PageModel } public ViewModel View { get; set; } = default!; - + [BindProperty] public InputModel Input { get; set; } = default!; @@ -50,7 +50,7 @@ public async Task OnGet(string? id) return Page(); } - public async Task OnPost() + public async Task OnPost() { // validate return url is still valid var request = await _interaction.GetLoginRequestByInternalIdAsync(Input.Id ?? throw new ArgumentNullException(nameof(Input.Id))); @@ -69,6 +69,7 @@ public async Task OnPost() // emit event await _events.RaiseAsync(new ConsentDeniedEvent(User.GetSubjectId(), request.Client.ClientId, request.ValidatedResources.RawScopeValues)); + Telemetry.Metrics.ConsentDeniedEvent(request.Client.ClientId, request.ValidatedResources.RawScopeValues); } // user clicked 'yes' - validate the data else if (Input.Button == "yes") @@ -90,6 +91,9 @@ public async Task OnPost() // emit event await _events.RaiseAsync(new ConsentGrantedEvent(User.GetSubjectId(), request.Client.ClientId, request.ValidatedResources.RawScopeValues, result.ScopesValuesConsented, false)); + Telemetry.Metrics.ConsentGrantedEvent(request.Client.ClientId, result.ScopesValuesConsented, false); + var denied = request.ValidatedResources.RawScopeValues.Except(result.ScopesValuesConsented); + Telemetry.Metrics.ConsentDeniedEvent(request.Client.ClientId, denied); } else { diff --git a/hosts/main/Pages/Consent/Index.cshtml.cs b/hosts/main/Pages/Consent/Index.cshtml.cs index 2abdee20a..9fe9fcf0d 100644 --- a/hosts/main/Pages/Consent/Index.cshtml.cs +++ b/hosts/main/Pages/Consent/Index.cshtml.cs @@ -66,6 +66,7 @@ public async Task OnPost() // emit event await _events.RaiseAsync(new ConsentDeniedEvent(User.GetSubjectId(), request.Client.ClientId, request.ValidatedResources.RawScopeValues)); + Telemetry.Metrics.ConsentDeniedEvent(request.Client.ClientId, request.ValidatedResources.RawScopeValues); } // user clicked 'yes' - validate the data else if (Input.Button == "yes") @@ -88,6 +89,9 @@ public async Task OnPost() // emit event await _events.RaiseAsync(new ConsentGrantedEvent(User.GetSubjectId(), request.Client.ClientId, request.ValidatedResources.RawScopeValues, grantedConsent.ScopesValuesConsented, grantedConsent.RememberConsent)); + Telemetry.Metrics.ConsentGrantedEvent(request.Client.ClientId, grantedConsent.ScopesValuesConsented, grantedConsent.RememberConsent); + var denied = request.ValidatedResources.RawScopeValues.Except(grantedConsent.ScopesValuesConsented); + Telemetry.Metrics.ConsentDeniedEvent(request.Client.ClientId, denied); } else { diff --git a/hosts/main/Pages/Device/Index.cshtml.cs b/hosts/main/Pages/Device/Index.cshtml.cs index f84bbb159..0d1bb4a9d 100644 --- a/hosts/main/Pages/Device/Index.cshtml.cs +++ b/hosts/main/Pages/Device/Index.cshtml.cs @@ -78,6 +78,7 @@ public async Task OnPost() // emit event await _events.RaiseAsync(new ConsentDeniedEvent(User.GetSubjectId(), request.Client.ClientId, request.ValidatedResources.RawScopeValues)); + Telemetry.Metrics.ConsentDeniedEvent(request.Client.ClientId, request.ValidatedResources.RawScopeValues); } // user clicked 'yes' - validate the data else if (Input.Button == "yes") @@ -100,6 +101,9 @@ public async Task OnPost() // emit event await _events.RaiseAsync(new ConsentGrantedEvent(User.GetSubjectId(), request.Client.ClientId, request.ValidatedResources.RawScopeValues, grantedConsent.ScopesValuesConsented, grantedConsent.RememberConsent)); + Telemetry.Metrics.ConsentGrantedEvent(request.Client.ClientId, grantedConsent.ScopesValuesConsented, grantedConsent.RememberConsent); + var denied = request.ValidatedResources.RawScopeValues.Except(grantedConsent.ScopesValuesConsented); + Telemetry.Metrics.ConsentDeniedEvent(request.Client.ClientId, denied); } else { diff --git a/hosts/main/Pages/ExternalLogin/Callback.cshtml.cs b/hosts/main/Pages/ExternalLogin/Callback.cshtml.cs index 937ced8e9..281131bc2 100644 --- a/hosts/main/Pages/ExternalLogin/Callback.cshtml.cs +++ b/hosts/main/Pages/ExternalLogin/Callback.cshtml.cs @@ -106,6 +106,7 @@ public async Task OnGet() // check if external login is in the context of an OIDC request var context = await _interaction.GetAuthorizationContextAsync(returnUrl); await _events.RaiseAsync(new UserLoginSuccessEvent(provider, providerUserId, user.SubjectId, user.Username, true, context?.Client.ClientId)); + Telemetry.Metrics.UserLogin(context?.Client.ClientId, provider!); if (context != null) { diff --git a/hosts/main/Pages/Grants/Index.cshtml.cs b/hosts/main/Pages/Grants/Index.cshtml.cs index dceca1034..051f00d57 100644 --- a/hosts/main/Pages/Grants/Index.cshtml.cs +++ b/hosts/main/Pages/Grants/Index.cshtml.cs @@ -52,7 +52,7 @@ public async Task OnGet() ClientLogoUrl = client.LogoUri, ClientUrl = client.ClientUri, Description = grant.Description, - Created = grant.CreationTime, + Created = grant.CreationTime, Expires = grant.Expiration, IdentityGrantNames = resources.IdentityResources.Select(x => x.DisplayName ?? x.Name).ToArray(), ApiGrantNames = resources.ApiScopes.Select(x => x.DisplayName ?? x.Name).ToArray() @@ -75,6 +75,7 @@ public async Task OnPost() { await _interaction.RevokeUserConsentAsync(ClientId); await _events.RaiseAsync(new GrantsRevokedEvent(User.GetSubjectId(), ClientId)); + Telemetry.Metrics.GrantsRevoked(ClientId); return RedirectToPage("/Grants/Index"); } diff --git a/hosts/main/Pages/Telemetry.cs b/hosts/main/Pages/Telemetry.cs new file mode 100644 index 000000000..15a993c6d --- /dev/null +++ b/hosts/main/Pages/Telemetry.cs @@ -0,0 +1,170 @@ +using Duende.IdentityServer.Events; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using System.Diagnostics.Metrics; + +namespace IdentityServerHost.Pages; + +#pragma warning disable CA1034 // Nested types should not be visible +#pragma warning disable CA1724 // Type names should not match namespaces + +/// +/// Telemetry helpers for the UI +/// +public static class Telemetry +{ + private static readonly string ServiceVersion = typeof(Telemetry).Assembly.GetName().Version!.ToString(); + + /// + /// Service name for telemetry. + /// + public static readonly string ServiceName = typeof(Telemetry).Assembly.GetName().Name!; + + /// + /// Metrics configuration + /// + public static class Metrics + { + /// + /// Name of Counters + /// + public static class Counters + { + /// + /// consent_granted + /// + public const string ConsentGranted = "consent_granted"; + + /// + /// consent_denied + /// + public const string ConsentDenied = "consent_denied"; + + /// + /// grants_revoked + /// + public const string GrantsRevoked = "grants_revoked"; + + /// + /// user_login + /// + public const string UserLogin = "user_login"; + + /// + /// user_login_failure + /// + public const string UserLoginFailure = "user_login_failure"; + + /// + /// user_logout + /// + public const string UserLogout = "user_logout"; + } + + /// + /// Name of tags + /// + public static class Tags + { + /// + /// client + /// + public const string Client = "client"; + + /// + /// error + /// + public const string Error = "error"; + + /// + /// idp + /// + public const string Idp = "idp"; + + /// + /// remember + /// + public const string Remember = "remember"; + + /// + /// scope + /// + public const string Scope = "scope"; + } + + /// + /// Meter for the IdentityServer host project + /// + private static readonly Meter Meter = new Meter(ServiceName, ServiceVersion); + + private static Counter ConsentGrantedCounter = Meter.CreateCounter(Counters.ConsentGranted); + + /// + /// Helper method to increase counter. The scopes + /// are expanded and called one by one to not cause a combinatory explosion of scopes. + /// + /// Client id + /// Scope names. Each element is added on it's own to the counter + public static void ConsentGrantedEvent(string clientId, IEnumerable scopes, bool remember) + { + ArgumentNullException.ThrowIfNull(scopes); + foreach(var scope in scopes) + { + ConsentGrantedCounter.Add(1, new(Tags.Client, clientId), new(Tags.Scope, scope), new(Tags.Remember, remember)); + } + } + + private static Counter ConsentDeniedCounter = Meter.CreateCounter(Counters.ConsentDenied); + + /// + /// Helper method to increase counter. The scopes + /// are expanded and called one by one to not cause a combinatory explosion of scopes. + /// + /// Client id + /// Scope names. Each element is added on it's own to the counter + public static void ConsentDeniedEvent(string clientId, IEnumerable scopes) + { + ArgumentNullException.ThrowIfNull(scopes); + foreach (var scope in scopes) + { + ConsentDeniedCounter.Add(1, new(Tags.Client, clientId), new(Tags.Scope, scope)); + } + } + + private static Counter GrantsRevokedCounter = Meter.CreateCounter(Counters.GrantsRevoked); + + /// + /// Helper method to increase the counter. + /// + /// Client id to revoke for, or null for all. + public static void GrantsRevoked(string? clientId) + => GrantsRevokedCounter.Add(1, tag: new(Tags.Client, clientId)); + + private static Counter UserLoginCounter = Meter.CreateCounter(Counters.UserLogin); + + /// + /// Helper method to increase counter. + /// + /// Client Id, if available + public static void UserLogin(string? clientId, string idp) + => UserLoginCounter.Add(1, new(Tags.Client, clientId), new(Tags.Idp, idp)); + + private static Counter UserLoginFailureCounter = Meter.CreateCounter(Counters.UserLoginFailure); + + /// + /// Helper method to increase + /// Client Id, if available + /// Error message + public static void UserLoginFailure(string? clientId, string idp, string error) + => UserLoginFailureCounter.Add(1, new(Tags.Client, clientId), new(Tags.Idp, idp), new(Tags.Error, error)); + + private static Counter UserLogoutCounter = Meter.CreateCounter(Counters.UserLogout); + + /// + /// Helper method to increase the counter. + /// + /// Idp/authentication scheme for external authentication, or "local" for built in. + public static void UserLogout(string? idp) + => UserLogoutCounter.Add(1, tag: new(Tags.Idp, idp)); + } +} diff --git a/src/Telemetry/Telemetry.cs b/src/Telemetry/Telemetry.cs index f5937b217..c045483ba 100644 --- a/src/Telemetry/Telemetry.cs +++ b/src/Telemetry/Telemetry.cs @@ -9,7 +9,7 @@ namespace Duende.IdentityServer; /// -/// Constants for Telemetry +/// Telemetry helpers /// public static class Telemetry { From 3000a6d2cd8fb274518a74fed76d3c98dd9e5b70 Mon Sep 17 00:00:00 2001 From: Anders Abel Date: Fri, 10 Nov 2023 16:52:58 +0100 Subject: [PATCH 2/6] Add metrics to EF and AspNetIdentity Hosts --- .../Pages/Account/Login/Index.cshtml.cs | 6 +- .../Pages/Account/Logout/Index.cshtml.cs | 7 +- .../Pages/Consent/Index.cshtml.cs | 4 + .../Pages/Device/Index.cshtml.cs | 4 + .../Pages/ExternalLogin/Callback.cshtml.cs | 1 + .../Pages/Grants/Index.cshtml.cs | 1 + hosts/AspNetIdentity/Pages/Telemetry.cs | 170 ++++++++++++++++++ .../Pages/Account/Login/Index.cshtml.cs | 6 +- .../Pages/Account/Logout/Index.cshtml.cs | 7 +- .../Pages/Ciba/Consent.cshtml.cs | 4 + .../Pages/Consent/Index.cshtml.cs | 4 + .../Pages/Device/Index.cshtml.cs | 4 + .../Pages/ExternalLogin/Callback.cshtml.cs | 1 + .../Pages/Grants/Index.cshtml.cs | 1 + hosts/EntityFramework/Pages/Telemetry.cs | 170 ++++++++++++++++++ hosts/main/Pages/Ciba/Consent.cshtml.cs | 4 +- hosts/main/Pages/Consent/Index.cshtml.cs | 4 +- hosts/main/Pages/Device/Index.cshtml.cs | 4 +- 18 files changed, 388 insertions(+), 14 deletions(-) create mode 100644 hosts/AspNetIdentity/Pages/Telemetry.cs create mode 100644 hosts/EntityFramework/Pages/Telemetry.cs diff --git a/hosts/AspNetIdentity/Pages/Account/Login/Index.cshtml.cs b/hosts/AspNetIdentity/Pages/Account/Login/Index.cshtml.cs index 1b6734137..c1664c79d 100644 --- a/hosts/AspNetIdentity/Pages/Account/Login/Index.cshtml.cs +++ b/hosts/AspNetIdentity/Pages/Account/Login/Index.cshtml.cs @@ -12,6 +12,7 @@ using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.RazorPages; +using static System.Runtime.InteropServices.JavaScript.JSType; namespace IdentityServerHost.Pages.Login; @@ -102,6 +103,7 @@ public async Task OnPost() { var user = await _userManager.FindByNameAsync(Input.Username!); await _events.RaiseAsync(new UserLoginSuccessEvent(user!.UserName, user.Id, user.UserName, clientId: context?.Client.ClientId)); + Telemetry.Metrics.UserLogin(context?.Client.ClientId, IdentityServerConstants.LocalIdentityProvider); if (context != null) { @@ -135,7 +137,9 @@ public async Task OnPost() } } - await _events.RaiseAsync(new UserLoginFailureEvent(Input.Username, "invalid credentials", clientId:context?.Client.ClientId)); + const string error = "invalid credentials"; + await _events.RaiseAsync(new UserLoginFailureEvent(Input.Username, error, clientId:context?.Client.ClientId)); + Telemetry.Metrics.UserLoginFailure(context?.Client.ClientId, IdentityServerConstants.LocalIdentityProvider, error); ModelState.AddModelError(string.Empty, LoginOptions.InvalidCredentialsErrorMessage); } diff --git a/hosts/AspNetIdentity/Pages/Account/Logout/Index.cshtml.cs b/hosts/AspNetIdentity/Pages/Account/Logout/Index.cshtml.cs index 77d79256f..d9a5231d2 100644 --- a/hosts/AspNetIdentity/Pages/Account/Logout/Index.cshtml.cs +++ b/hosts/AspNetIdentity/Pages/Account/Logout/Index.cshtml.cs @@ -75,12 +75,13 @@ public async Task OnPost() // delete local authentication cookie await _signInManager.SignOutAsync(); - // raise the logout event - await _events.RaiseAsync(new UserLogoutSuccessEvent(User.GetSubjectId(), User.GetDisplayName())); - // see if we need to trigger federated logout var idp = User.FindFirst(JwtClaimTypes.IdentityProvider)?.Value; + // raise the logout event + await _events.RaiseAsync(new UserLogoutSuccessEvent(User.GetSubjectId(), User.GetDisplayName())); + Telemetry.Metrics.UserLogout(idp); + // if it's a local login we can ignore this workflow if (idp != null && idp != Duende.IdentityServer.IdentityServerConstants.LocalIdentityProvider) { diff --git a/hosts/AspNetIdentity/Pages/Consent/Index.cshtml.cs b/hosts/AspNetIdentity/Pages/Consent/Index.cshtml.cs index 2abdee20a..357691a39 100644 --- a/hosts/AspNetIdentity/Pages/Consent/Index.cshtml.cs +++ b/hosts/AspNetIdentity/Pages/Consent/Index.cshtml.cs @@ -66,6 +66,7 @@ public async Task OnPost() // emit event await _events.RaiseAsync(new ConsentDeniedEvent(User.GetSubjectId(), request.Client.ClientId, request.ValidatedResources.RawScopeValues)); + Telemetry.Metrics.ConsentDeniedEvent(request.Client.ClientId, request.ValidatedResources.ParsedScopes.Select(s => s.ParsedName)); } // user clicked 'yes' - validate the data else if (Input.Button == "yes") @@ -88,6 +89,9 @@ public async Task OnPost() // emit event await _events.RaiseAsync(new ConsentGrantedEvent(User.GetSubjectId(), request.Client.ClientId, request.ValidatedResources.RawScopeValues, grantedConsent.ScopesValuesConsented, grantedConsent.RememberConsent)); + Telemetry.Metrics.ConsentGrantedEvent(request.Client.ClientId, grantedConsent.ScopesValuesConsented, grantedConsent.RememberConsent); + var denied = request.ValidatedResources.ParsedScopes.Select(s => s.ParsedName).Except(grantedConsent.ScopesValuesConsented); + Telemetry.Metrics.ConsentDeniedEvent(request.Client.ClientId, denied); } else { diff --git a/hosts/AspNetIdentity/Pages/Device/Index.cshtml.cs b/hosts/AspNetIdentity/Pages/Device/Index.cshtml.cs index f84bbb159..0d1721e44 100644 --- a/hosts/AspNetIdentity/Pages/Device/Index.cshtml.cs +++ b/hosts/AspNetIdentity/Pages/Device/Index.cshtml.cs @@ -78,6 +78,7 @@ public async Task OnPost() // emit event await _events.RaiseAsync(new ConsentDeniedEvent(User.GetSubjectId(), request.Client.ClientId, request.ValidatedResources.RawScopeValues)); + Telemetry.Metrics.ConsentDeniedEvent(request.Client.ClientId, request.ValidatedResources.ParsedScopes.Select(s => s.ParsedName)); } // user clicked 'yes' - validate the data else if (Input.Button == "yes") @@ -100,6 +101,9 @@ public async Task OnPost() // emit event await _events.RaiseAsync(new ConsentGrantedEvent(User.GetSubjectId(), request.Client.ClientId, request.ValidatedResources.RawScopeValues, grantedConsent.ScopesValuesConsented, grantedConsent.RememberConsent)); + Telemetry.Metrics.ConsentGrantedEvent(request.Client.ClientId, grantedConsent.ScopesValuesConsented, grantedConsent.RememberConsent); + var denied = request.ValidatedResources.ParsedScopes.Select(s => s.ParsedName).Except(grantedConsent.ScopesValuesConsented); + Telemetry.Metrics.ConsentDeniedEvent(request.Client.ClientId, denied); } else { diff --git a/hosts/AspNetIdentity/Pages/ExternalLogin/Callback.cshtml.cs b/hosts/AspNetIdentity/Pages/ExternalLogin/Callback.cshtml.cs index 2f1163188..695137710 100644 --- a/hosts/AspNetIdentity/Pages/ExternalLogin/Callback.cshtml.cs +++ b/hosts/AspNetIdentity/Pages/ExternalLogin/Callback.cshtml.cs @@ -97,6 +97,7 @@ public async Task OnGet() // check if external login is in the context of an OIDC request var context = await _interaction.GetAuthorizationContextAsync(returnUrl); await _events.RaiseAsync(new UserLoginSuccessEvent(provider, providerUserId, user.Id, user.UserName, true, context?.Client.ClientId)); + Telemetry.Metrics.UserLogin(context?.Client.ClientId, provider!); if (context != null) { diff --git a/hosts/AspNetIdentity/Pages/Grants/Index.cshtml.cs b/hosts/AspNetIdentity/Pages/Grants/Index.cshtml.cs index dceca1034..a38545744 100644 --- a/hosts/AspNetIdentity/Pages/Grants/Index.cshtml.cs +++ b/hosts/AspNetIdentity/Pages/Grants/Index.cshtml.cs @@ -75,6 +75,7 @@ public async Task OnPost() { await _interaction.RevokeUserConsentAsync(ClientId); await _events.RaiseAsync(new GrantsRevokedEvent(User.GetSubjectId(), ClientId)); + Telemetry.Metrics.GrantsRevoked(ClientId); return RedirectToPage("/Grants/Index"); } diff --git a/hosts/AspNetIdentity/Pages/Telemetry.cs b/hosts/AspNetIdentity/Pages/Telemetry.cs new file mode 100644 index 000000000..15a993c6d --- /dev/null +++ b/hosts/AspNetIdentity/Pages/Telemetry.cs @@ -0,0 +1,170 @@ +using Duende.IdentityServer.Events; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using System.Diagnostics.Metrics; + +namespace IdentityServerHost.Pages; + +#pragma warning disable CA1034 // Nested types should not be visible +#pragma warning disable CA1724 // Type names should not match namespaces + +/// +/// Telemetry helpers for the UI +/// +public static class Telemetry +{ + private static readonly string ServiceVersion = typeof(Telemetry).Assembly.GetName().Version!.ToString(); + + /// + /// Service name for telemetry. + /// + public static readonly string ServiceName = typeof(Telemetry).Assembly.GetName().Name!; + + /// + /// Metrics configuration + /// + public static class Metrics + { + /// + /// Name of Counters + /// + public static class Counters + { + /// + /// consent_granted + /// + public const string ConsentGranted = "consent_granted"; + + /// + /// consent_denied + /// + public const string ConsentDenied = "consent_denied"; + + /// + /// grants_revoked + /// + public const string GrantsRevoked = "grants_revoked"; + + /// + /// user_login + /// + public const string UserLogin = "user_login"; + + /// + /// user_login_failure + /// + public const string UserLoginFailure = "user_login_failure"; + + /// + /// user_logout + /// + public const string UserLogout = "user_logout"; + } + + /// + /// Name of tags + /// + public static class Tags + { + /// + /// client + /// + public const string Client = "client"; + + /// + /// error + /// + public const string Error = "error"; + + /// + /// idp + /// + public const string Idp = "idp"; + + /// + /// remember + /// + public const string Remember = "remember"; + + /// + /// scope + /// + public const string Scope = "scope"; + } + + /// + /// Meter for the IdentityServer host project + /// + private static readonly Meter Meter = new Meter(ServiceName, ServiceVersion); + + private static Counter ConsentGrantedCounter = Meter.CreateCounter(Counters.ConsentGranted); + + /// + /// Helper method to increase counter. The scopes + /// are expanded and called one by one to not cause a combinatory explosion of scopes. + /// + /// Client id + /// Scope names. Each element is added on it's own to the counter + public static void ConsentGrantedEvent(string clientId, IEnumerable scopes, bool remember) + { + ArgumentNullException.ThrowIfNull(scopes); + foreach(var scope in scopes) + { + ConsentGrantedCounter.Add(1, new(Tags.Client, clientId), new(Tags.Scope, scope), new(Tags.Remember, remember)); + } + } + + private static Counter ConsentDeniedCounter = Meter.CreateCounter(Counters.ConsentDenied); + + /// + /// Helper method to increase counter. The scopes + /// are expanded and called one by one to not cause a combinatory explosion of scopes. + /// + /// Client id + /// Scope names. Each element is added on it's own to the counter + public static void ConsentDeniedEvent(string clientId, IEnumerable scopes) + { + ArgumentNullException.ThrowIfNull(scopes); + foreach (var scope in scopes) + { + ConsentDeniedCounter.Add(1, new(Tags.Client, clientId), new(Tags.Scope, scope)); + } + } + + private static Counter GrantsRevokedCounter = Meter.CreateCounter(Counters.GrantsRevoked); + + /// + /// Helper method to increase the counter. + /// + /// Client id to revoke for, or null for all. + public static void GrantsRevoked(string? clientId) + => GrantsRevokedCounter.Add(1, tag: new(Tags.Client, clientId)); + + private static Counter UserLoginCounter = Meter.CreateCounter(Counters.UserLogin); + + /// + /// Helper method to increase counter. + /// + /// Client Id, if available + public static void UserLogin(string? clientId, string idp) + => UserLoginCounter.Add(1, new(Tags.Client, clientId), new(Tags.Idp, idp)); + + private static Counter UserLoginFailureCounter = Meter.CreateCounter(Counters.UserLoginFailure); + + /// + /// Helper method to increase + /// Client Id, if available + /// Error message + public static void UserLoginFailure(string? clientId, string idp, string error) + => UserLoginFailureCounter.Add(1, new(Tags.Client, clientId), new(Tags.Idp, idp), new(Tags.Error, error)); + + private static Counter UserLogoutCounter = Meter.CreateCounter(Counters.UserLogout); + + /// + /// Helper method to increase the counter. + /// + /// Idp/authentication scheme for external authentication, or "local" for built in. + public static void UserLogout(string? idp) + => UserLogoutCounter.Add(1, tag: new(Tags.Idp, idp)); + } +} diff --git a/hosts/EntityFramework/Pages/Account/Login/Index.cshtml.cs b/hosts/EntityFramework/Pages/Account/Login/Index.cshtml.cs index b50b49d53..16262bc3e 100644 --- a/hosts/EntityFramework/Pages/Account/Login/Index.cshtml.cs +++ b/hosts/EntityFramework/Pages/Account/Login/Index.cshtml.cs @@ -11,6 +11,7 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.RazorPages; +using static System.Runtime.InteropServices.JavaScript.JSType; namespace IdentityServerHost.Pages.Login; @@ -97,6 +98,7 @@ public async Task OnPost() { var user = _users.FindByUsername(Input.Username); await _events.RaiseAsync(new UserLoginSuccessEvent(user.Username, user.SubjectId, user.Username, clientId: context?.Client.ClientId)); + Telemetry.Metrics.UserLogin(context?.Client.ClientId, IdentityServerConstants.LocalIdentityProvider); // only set explicit expiration here if user chooses "remember me". // otherwise we rely upon expiration configured in cookie middleware. @@ -144,7 +146,9 @@ public async Task OnPost() } } - await _events.RaiseAsync(new UserLoginFailureEvent(Input.Username, "invalid credentials", clientId:context?.Client.ClientId)); + const string error = "invalid credentials"; + await _events.RaiseAsync(new UserLoginFailureEvent(Input.Username, error, clientId:context?.Client.ClientId)); + Telemetry.Metrics.UserLoginFailure(context?.Client.ClientId, IdentityServerConstants.LocalIdentityProvider, error); ModelState.AddModelError(string.Empty, LoginOptions.InvalidCredentialsErrorMessage); } diff --git a/hosts/EntityFramework/Pages/Account/Logout/Index.cshtml.cs b/hosts/EntityFramework/Pages/Account/Logout/Index.cshtml.cs index 9f9c4dc1e..aef1de3d8 100644 --- a/hosts/EntityFramework/Pages/Account/Logout/Index.cshtml.cs +++ b/hosts/EntityFramework/Pages/Account/Logout/Index.cshtml.cs @@ -71,12 +71,13 @@ public async Task OnPost() // delete local authentication cookie await HttpContext.SignOutAsync(); - // raise the logout event - await _events.RaiseAsync(new UserLogoutSuccessEvent(User.GetSubjectId(), User.GetDisplayName())); - // see if we need to trigger federated logout var idp = User.FindFirst(JwtClaimTypes.IdentityProvider)?.Value; + // raise the logout event + await _events.RaiseAsync(new UserLogoutSuccessEvent(User.GetSubjectId(), User.GetDisplayName())); + Telemetry.Metrics.UserLogout(idp); + // if it's a local login we can ignore this workflow if (idp != null && idp != Duende.IdentityServer.IdentityServerConstants.LocalIdentityProvider) { diff --git a/hosts/EntityFramework/Pages/Ciba/Consent.cshtml.cs b/hosts/EntityFramework/Pages/Ciba/Consent.cshtml.cs index 4ebf8cd53..82d0dcc6b 100644 --- a/hosts/EntityFramework/Pages/Ciba/Consent.cshtml.cs +++ b/hosts/EntityFramework/Pages/Ciba/Consent.cshtml.cs @@ -69,6 +69,7 @@ public async Task OnPost() // emit event await _events.RaiseAsync(new ConsentDeniedEvent(User.GetSubjectId(), request.Client.ClientId, request.ValidatedResources.RawScopeValues)); + Telemetry.Metrics.ConsentDeniedEvent(request.Client.ClientId, request.ValidatedResources.ParsedScopes.Select(s => s.ParsedName); } // user clicked 'yes' - validate the data else if (Input.Button == "yes") @@ -90,6 +91,9 @@ public async Task OnPost() // emit event await _events.RaiseAsync(new ConsentGrantedEvent(User.GetSubjectId(), request.Client.ClientId, request.ValidatedResources.RawScopeValues, result.ScopesValuesConsented, false)); + Telemetry.Metrics.ConsentGrantedEvent(request.Client.ClientId, result.ScopesValuesConsented, false); + var denied = request.ValidatedResources.ParsedScopes.Select(s => s.ParsedName).Except(result.ScopesValuesConsented); + Telemetry.Metrics.ConsentDeniedEvent(request.Client.ClientId, denied); } else { diff --git a/hosts/EntityFramework/Pages/Consent/Index.cshtml.cs b/hosts/EntityFramework/Pages/Consent/Index.cshtml.cs index 2abdee20a..357691a39 100644 --- a/hosts/EntityFramework/Pages/Consent/Index.cshtml.cs +++ b/hosts/EntityFramework/Pages/Consent/Index.cshtml.cs @@ -66,6 +66,7 @@ public async Task OnPost() // emit event await _events.RaiseAsync(new ConsentDeniedEvent(User.GetSubjectId(), request.Client.ClientId, request.ValidatedResources.RawScopeValues)); + Telemetry.Metrics.ConsentDeniedEvent(request.Client.ClientId, request.ValidatedResources.ParsedScopes.Select(s => s.ParsedName)); } // user clicked 'yes' - validate the data else if (Input.Button == "yes") @@ -88,6 +89,9 @@ public async Task OnPost() // emit event await _events.RaiseAsync(new ConsentGrantedEvent(User.GetSubjectId(), request.Client.ClientId, request.ValidatedResources.RawScopeValues, grantedConsent.ScopesValuesConsented, grantedConsent.RememberConsent)); + Telemetry.Metrics.ConsentGrantedEvent(request.Client.ClientId, grantedConsent.ScopesValuesConsented, grantedConsent.RememberConsent); + var denied = request.ValidatedResources.ParsedScopes.Select(s => s.ParsedName).Except(grantedConsent.ScopesValuesConsented); + Telemetry.Metrics.ConsentDeniedEvent(request.Client.ClientId, denied); } else { diff --git a/hosts/EntityFramework/Pages/Device/Index.cshtml.cs b/hosts/EntityFramework/Pages/Device/Index.cshtml.cs index f84bbb159..0d1721e44 100644 --- a/hosts/EntityFramework/Pages/Device/Index.cshtml.cs +++ b/hosts/EntityFramework/Pages/Device/Index.cshtml.cs @@ -78,6 +78,7 @@ public async Task OnPost() // emit event await _events.RaiseAsync(new ConsentDeniedEvent(User.GetSubjectId(), request.Client.ClientId, request.ValidatedResources.RawScopeValues)); + Telemetry.Metrics.ConsentDeniedEvent(request.Client.ClientId, request.ValidatedResources.ParsedScopes.Select(s => s.ParsedName)); } // user clicked 'yes' - validate the data else if (Input.Button == "yes") @@ -100,6 +101,9 @@ public async Task OnPost() // emit event await _events.RaiseAsync(new ConsentGrantedEvent(User.GetSubjectId(), request.Client.ClientId, request.ValidatedResources.RawScopeValues, grantedConsent.ScopesValuesConsented, grantedConsent.RememberConsent)); + Telemetry.Metrics.ConsentGrantedEvent(request.Client.ClientId, grantedConsent.ScopesValuesConsented, grantedConsent.RememberConsent); + var denied = request.ValidatedResources.ParsedScopes.Select(s => s.ParsedName).Except(grantedConsent.ScopesValuesConsented); + Telemetry.Metrics.ConsentDeniedEvent(request.Client.ClientId, denied); } else { diff --git a/hosts/EntityFramework/Pages/ExternalLogin/Callback.cshtml.cs b/hosts/EntityFramework/Pages/ExternalLogin/Callback.cshtml.cs index 937ced8e9..281131bc2 100644 --- a/hosts/EntityFramework/Pages/ExternalLogin/Callback.cshtml.cs +++ b/hosts/EntityFramework/Pages/ExternalLogin/Callback.cshtml.cs @@ -106,6 +106,7 @@ public async Task OnGet() // check if external login is in the context of an OIDC request var context = await _interaction.GetAuthorizationContextAsync(returnUrl); await _events.RaiseAsync(new UserLoginSuccessEvent(provider, providerUserId, user.SubjectId, user.Username, true, context?.Client.ClientId)); + Telemetry.Metrics.UserLogin(context?.Client.ClientId, provider!); if (context != null) { diff --git a/hosts/EntityFramework/Pages/Grants/Index.cshtml.cs b/hosts/EntityFramework/Pages/Grants/Index.cshtml.cs index dceca1034..a38545744 100644 --- a/hosts/EntityFramework/Pages/Grants/Index.cshtml.cs +++ b/hosts/EntityFramework/Pages/Grants/Index.cshtml.cs @@ -75,6 +75,7 @@ public async Task OnPost() { await _interaction.RevokeUserConsentAsync(ClientId); await _events.RaiseAsync(new GrantsRevokedEvent(User.GetSubjectId(), ClientId)); + Telemetry.Metrics.GrantsRevoked(ClientId); return RedirectToPage("/Grants/Index"); } diff --git a/hosts/EntityFramework/Pages/Telemetry.cs b/hosts/EntityFramework/Pages/Telemetry.cs new file mode 100644 index 000000000..15a993c6d --- /dev/null +++ b/hosts/EntityFramework/Pages/Telemetry.cs @@ -0,0 +1,170 @@ +using Duende.IdentityServer.Events; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using System.Diagnostics.Metrics; + +namespace IdentityServerHost.Pages; + +#pragma warning disable CA1034 // Nested types should not be visible +#pragma warning disable CA1724 // Type names should not match namespaces + +/// +/// Telemetry helpers for the UI +/// +public static class Telemetry +{ + private static readonly string ServiceVersion = typeof(Telemetry).Assembly.GetName().Version!.ToString(); + + /// + /// Service name for telemetry. + /// + public static readonly string ServiceName = typeof(Telemetry).Assembly.GetName().Name!; + + /// + /// Metrics configuration + /// + public static class Metrics + { + /// + /// Name of Counters + /// + public static class Counters + { + /// + /// consent_granted + /// + public const string ConsentGranted = "consent_granted"; + + /// + /// consent_denied + /// + public const string ConsentDenied = "consent_denied"; + + /// + /// grants_revoked + /// + public const string GrantsRevoked = "grants_revoked"; + + /// + /// user_login + /// + public const string UserLogin = "user_login"; + + /// + /// user_login_failure + /// + public const string UserLoginFailure = "user_login_failure"; + + /// + /// user_logout + /// + public const string UserLogout = "user_logout"; + } + + /// + /// Name of tags + /// + public static class Tags + { + /// + /// client + /// + public const string Client = "client"; + + /// + /// error + /// + public const string Error = "error"; + + /// + /// idp + /// + public const string Idp = "idp"; + + /// + /// remember + /// + public const string Remember = "remember"; + + /// + /// scope + /// + public const string Scope = "scope"; + } + + /// + /// Meter for the IdentityServer host project + /// + private static readonly Meter Meter = new Meter(ServiceName, ServiceVersion); + + private static Counter ConsentGrantedCounter = Meter.CreateCounter(Counters.ConsentGranted); + + /// + /// Helper method to increase counter. The scopes + /// are expanded and called one by one to not cause a combinatory explosion of scopes. + /// + /// Client id + /// Scope names. Each element is added on it's own to the counter + public static void ConsentGrantedEvent(string clientId, IEnumerable scopes, bool remember) + { + ArgumentNullException.ThrowIfNull(scopes); + foreach(var scope in scopes) + { + ConsentGrantedCounter.Add(1, new(Tags.Client, clientId), new(Tags.Scope, scope), new(Tags.Remember, remember)); + } + } + + private static Counter ConsentDeniedCounter = Meter.CreateCounter(Counters.ConsentDenied); + + /// + /// Helper method to increase counter. The scopes + /// are expanded and called one by one to not cause a combinatory explosion of scopes. + /// + /// Client id + /// Scope names. Each element is added on it's own to the counter + public static void ConsentDeniedEvent(string clientId, IEnumerable scopes) + { + ArgumentNullException.ThrowIfNull(scopes); + foreach (var scope in scopes) + { + ConsentDeniedCounter.Add(1, new(Tags.Client, clientId), new(Tags.Scope, scope)); + } + } + + private static Counter GrantsRevokedCounter = Meter.CreateCounter(Counters.GrantsRevoked); + + /// + /// Helper method to increase the counter. + /// + /// Client id to revoke for, or null for all. + public static void GrantsRevoked(string? clientId) + => GrantsRevokedCounter.Add(1, tag: new(Tags.Client, clientId)); + + private static Counter UserLoginCounter = Meter.CreateCounter(Counters.UserLogin); + + /// + /// Helper method to increase counter. + /// + /// Client Id, if available + public static void UserLogin(string? clientId, string idp) + => UserLoginCounter.Add(1, new(Tags.Client, clientId), new(Tags.Idp, idp)); + + private static Counter UserLoginFailureCounter = Meter.CreateCounter(Counters.UserLoginFailure); + + /// + /// Helper method to increase + /// Client Id, if available + /// Error message + public static void UserLoginFailure(string? clientId, string idp, string error) + => UserLoginFailureCounter.Add(1, new(Tags.Client, clientId), new(Tags.Idp, idp), new(Tags.Error, error)); + + private static Counter UserLogoutCounter = Meter.CreateCounter(Counters.UserLogout); + + /// + /// Helper method to increase the counter. + /// + /// Idp/authentication scheme for external authentication, or "local" for built in. + public static void UserLogout(string? idp) + => UserLogoutCounter.Add(1, tag: new(Tags.Idp, idp)); + } +} diff --git a/hosts/main/Pages/Ciba/Consent.cshtml.cs b/hosts/main/Pages/Ciba/Consent.cshtml.cs index 17c4c00e6..f704c65dc 100644 --- a/hosts/main/Pages/Ciba/Consent.cshtml.cs +++ b/hosts/main/Pages/Ciba/Consent.cshtml.cs @@ -69,7 +69,7 @@ public async Task OnPost() // emit event await _events.RaiseAsync(new ConsentDeniedEvent(User.GetSubjectId(), request.Client.ClientId, request.ValidatedResources.RawScopeValues)); - Telemetry.Metrics.ConsentDeniedEvent(request.Client.ClientId, request.ValidatedResources.RawScopeValues); + Telemetry.Metrics.ConsentDeniedEvent(request.Client.ClientId, request.ValidatedResources.ParsedScopes.Select(s => s.ParsedName); } // user clicked 'yes' - validate the data else if (Input.Button == "yes") @@ -92,7 +92,7 @@ public async Task OnPost() // emit event await _events.RaiseAsync(new ConsentGrantedEvent(User.GetSubjectId(), request.Client.ClientId, request.ValidatedResources.RawScopeValues, result.ScopesValuesConsented, false)); Telemetry.Metrics.ConsentGrantedEvent(request.Client.ClientId, result.ScopesValuesConsented, false); - var denied = request.ValidatedResources.RawScopeValues.Except(result.ScopesValuesConsented); + var denied = request.ValidatedResources.ParsedScopes.Select(s => s.ParsedName).Except(result.ScopesValuesConsented); Telemetry.Metrics.ConsentDeniedEvent(request.Client.ClientId, denied); } else diff --git a/hosts/main/Pages/Consent/Index.cshtml.cs b/hosts/main/Pages/Consent/Index.cshtml.cs index 9fe9fcf0d..357691a39 100644 --- a/hosts/main/Pages/Consent/Index.cshtml.cs +++ b/hosts/main/Pages/Consent/Index.cshtml.cs @@ -66,7 +66,7 @@ public async Task OnPost() // emit event await _events.RaiseAsync(new ConsentDeniedEvent(User.GetSubjectId(), request.Client.ClientId, request.ValidatedResources.RawScopeValues)); - Telemetry.Metrics.ConsentDeniedEvent(request.Client.ClientId, request.ValidatedResources.RawScopeValues); + Telemetry.Metrics.ConsentDeniedEvent(request.Client.ClientId, request.ValidatedResources.ParsedScopes.Select(s => s.ParsedName)); } // user clicked 'yes' - validate the data else if (Input.Button == "yes") @@ -90,7 +90,7 @@ public async Task OnPost() // emit event await _events.RaiseAsync(new ConsentGrantedEvent(User.GetSubjectId(), request.Client.ClientId, request.ValidatedResources.RawScopeValues, grantedConsent.ScopesValuesConsented, grantedConsent.RememberConsent)); Telemetry.Metrics.ConsentGrantedEvent(request.Client.ClientId, grantedConsent.ScopesValuesConsented, grantedConsent.RememberConsent); - var denied = request.ValidatedResources.RawScopeValues.Except(grantedConsent.ScopesValuesConsented); + var denied = request.ValidatedResources.ParsedScopes.Select(s => s.ParsedName).Except(grantedConsent.ScopesValuesConsented); Telemetry.Metrics.ConsentDeniedEvent(request.Client.ClientId, denied); } else diff --git a/hosts/main/Pages/Device/Index.cshtml.cs b/hosts/main/Pages/Device/Index.cshtml.cs index 0d1bb4a9d..0d1721e44 100644 --- a/hosts/main/Pages/Device/Index.cshtml.cs +++ b/hosts/main/Pages/Device/Index.cshtml.cs @@ -78,7 +78,7 @@ public async Task OnPost() // emit event await _events.RaiseAsync(new ConsentDeniedEvent(User.GetSubjectId(), request.Client.ClientId, request.ValidatedResources.RawScopeValues)); - Telemetry.Metrics.ConsentDeniedEvent(request.Client.ClientId, request.ValidatedResources.RawScopeValues); + Telemetry.Metrics.ConsentDeniedEvent(request.Client.ClientId, request.ValidatedResources.ParsedScopes.Select(s => s.ParsedName)); } // user clicked 'yes' - validate the data else if (Input.Button == "yes") @@ -102,7 +102,7 @@ public async Task OnPost() // emit event await _events.RaiseAsync(new ConsentGrantedEvent(User.GetSubjectId(), request.Client.ClientId, request.ValidatedResources.RawScopeValues, grantedConsent.ScopesValuesConsented, grantedConsent.RememberConsent)); Telemetry.Metrics.ConsentGrantedEvent(request.Client.ClientId, grantedConsent.ScopesValuesConsented, grantedConsent.RememberConsent); - var denied = request.ValidatedResources.RawScopeValues.Except(grantedConsent.ScopesValuesConsented); + var denied = request.ValidatedResources.ParsedScopes.Select(s => s.ParsedName).Except(grantedConsent.ScopesValuesConsented); Telemetry.Metrics.ConsentDeniedEvent(request.Client.ClientId, denied); } else From 90eaec3eba23aef8663ebadf2a4ee00f7be591da Mon Sep 17 00:00:00 2001 From: Anders Abel Date: Mon, 13 Nov 2023 23:05:06 +0100 Subject: [PATCH 3/6] Code compiles better if parantheses are balanced... --- hosts/EntityFramework/Pages/Ciba/Consent.cshtml.cs | 2 +- hosts/main/Pages/Ciba/Consent.cshtml.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/hosts/EntityFramework/Pages/Ciba/Consent.cshtml.cs b/hosts/EntityFramework/Pages/Ciba/Consent.cshtml.cs index 82d0dcc6b..f615fdf05 100644 --- a/hosts/EntityFramework/Pages/Ciba/Consent.cshtml.cs +++ b/hosts/EntityFramework/Pages/Ciba/Consent.cshtml.cs @@ -69,7 +69,7 @@ public async Task OnPost() // emit event await _events.RaiseAsync(new ConsentDeniedEvent(User.GetSubjectId(), request.Client.ClientId, request.ValidatedResources.RawScopeValues)); - Telemetry.Metrics.ConsentDeniedEvent(request.Client.ClientId, request.ValidatedResources.ParsedScopes.Select(s => s.ParsedName); + Telemetry.Metrics.ConsentDeniedEvent(request.Client.ClientId, request.ValidatedResources.ParsedScopes.Select(s => s.ParsedName)); } // user clicked 'yes' - validate the data else if (Input.Button == "yes") diff --git a/hosts/main/Pages/Ciba/Consent.cshtml.cs b/hosts/main/Pages/Ciba/Consent.cshtml.cs index f704c65dc..2c1aa4abd 100644 --- a/hosts/main/Pages/Ciba/Consent.cshtml.cs +++ b/hosts/main/Pages/Ciba/Consent.cshtml.cs @@ -69,7 +69,7 @@ public async Task OnPost() // emit event await _events.RaiseAsync(new ConsentDeniedEvent(User.GetSubjectId(), request.Client.ClientId, request.ValidatedResources.RawScopeValues)); - Telemetry.Metrics.ConsentDeniedEvent(request.Client.ClientId, request.ValidatedResources.ParsedScopes.Select(s => s.ParsedName); + Telemetry.Metrics.ConsentDeniedEvent(request.Client.ClientId, request.ValidatedResources.ParsedScopes.Select(s => s.ParsedName)); } // user clicked 'yes' - validate the data else if (Input.Button == "yes") From 76079374ba6bbbf56da0f9034b90942ec2415e07 Mon Sep 17 00:00:00 2001 From: Anders Abel Date: Tue, 14 Nov 2023 12:15:32 +0100 Subject: [PATCH 4/6] Add events to configuration UI --- .../Pages/Account/Login/Index.cshtml.cs | 5 +- .../Pages/Account/Logout/Index.cshtml.cs | 7 +- .../Pages/Ciba/Consent.cshtml.cs | 4 + .../Pages/Consent/Index.cshtml.cs | 4 + .../Pages/Device/Index.cshtml.cs | 4 + .../Pages/ExternalLogin/Callback.cshtml.cs | 1 + .../Pages/Grants/Index.cshtml.cs | 1 + hosts/Configuration/Pages/Telemetry.cs | 170 ++++++++++++++++++ 8 files changed, 192 insertions(+), 4 deletions(-) create mode 100644 hosts/Configuration/Pages/Telemetry.cs diff --git a/hosts/Configuration/Pages/Account/Login/Index.cshtml.cs b/hosts/Configuration/Pages/Account/Login/Index.cshtml.cs index 9e07c5b65..249f75b05 100644 --- a/hosts/Configuration/Pages/Account/Login/Index.cshtml.cs +++ b/hosts/Configuration/Pages/Account/Login/Index.cshtml.cs @@ -97,6 +97,7 @@ public async Task OnPost() { var user = _users.FindByUsername(Input.Username); await _events.RaiseAsync(new UserLoginSuccessEvent(user.Username, user.SubjectId, user.Username, clientId: context?.Client.ClientId)); + Telemetry.Metrics.UserLogin(context?.Client.ClientId, IdentityServerConstants.LocalIdentityProvider); // only set explicit expiration here if user chooses "remember me". // otherwise we rely upon expiration configured in cookie middleware. @@ -144,7 +145,9 @@ public async Task OnPost() } } - await _events.RaiseAsync(new UserLoginFailureEvent(Input.Username, "invalid credentials", clientId:context?.Client.ClientId)); + const string error = "invalid credentials"; + await _events.RaiseAsync(new UserLoginFailureEvent(Input.Username, error, clientId:context?.Client.ClientId)); + Telemetry.Metrics.UserLoginFailure(context?.Client.ClientId, IdentityServerConstants.LocalIdentityProvider, error); ModelState.AddModelError(string.Empty, LoginOptions.InvalidCredentialsErrorMessage); } diff --git a/hosts/Configuration/Pages/Account/Logout/Index.cshtml.cs b/hosts/Configuration/Pages/Account/Logout/Index.cshtml.cs index 9f9c4dc1e..aef1de3d8 100644 --- a/hosts/Configuration/Pages/Account/Logout/Index.cshtml.cs +++ b/hosts/Configuration/Pages/Account/Logout/Index.cshtml.cs @@ -71,12 +71,13 @@ public async Task OnPost() // delete local authentication cookie await HttpContext.SignOutAsync(); - // raise the logout event - await _events.RaiseAsync(new UserLogoutSuccessEvent(User.GetSubjectId(), User.GetDisplayName())); - // see if we need to trigger federated logout var idp = User.FindFirst(JwtClaimTypes.IdentityProvider)?.Value; + // raise the logout event + await _events.RaiseAsync(new UserLogoutSuccessEvent(User.GetSubjectId(), User.GetDisplayName())); + Telemetry.Metrics.UserLogout(idp); + // if it's a local login we can ignore this workflow if (idp != null && idp != Duende.IdentityServer.IdentityServerConstants.LocalIdentityProvider) { diff --git a/hosts/Configuration/Pages/Ciba/Consent.cshtml.cs b/hosts/Configuration/Pages/Ciba/Consent.cshtml.cs index 4ebf8cd53..f615fdf05 100644 --- a/hosts/Configuration/Pages/Ciba/Consent.cshtml.cs +++ b/hosts/Configuration/Pages/Ciba/Consent.cshtml.cs @@ -69,6 +69,7 @@ public async Task OnPost() // emit event await _events.RaiseAsync(new ConsentDeniedEvent(User.GetSubjectId(), request.Client.ClientId, request.ValidatedResources.RawScopeValues)); + Telemetry.Metrics.ConsentDeniedEvent(request.Client.ClientId, request.ValidatedResources.ParsedScopes.Select(s => s.ParsedName)); } // user clicked 'yes' - validate the data else if (Input.Button == "yes") @@ -90,6 +91,9 @@ public async Task OnPost() // emit event await _events.RaiseAsync(new ConsentGrantedEvent(User.GetSubjectId(), request.Client.ClientId, request.ValidatedResources.RawScopeValues, result.ScopesValuesConsented, false)); + Telemetry.Metrics.ConsentGrantedEvent(request.Client.ClientId, result.ScopesValuesConsented, false); + var denied = request.ValidatedResources.ParsedScopes.Select(s => s.ParsedName).Except(result.ScopesValuesConsented); + Telemetry.Metrics.ConsentDeniedEvent(request.Client.ClientId, denied); } else { diff --git a/hosts/Configuration/Pages/Consent/Index.cshtml.cs b/hosts/Configuration/Pages/Consent/Index.cshtml.cs index 2abdee20a..357691a39 100644 --- a/hosts/Configuration/Pages/Consent/Index.cshtml.cs +++ b/hosts/Configuration/Pages/Consent/Index.cshtml.cs @@ -66,6 +66,7 @@ public async Task OnPost() // emit event await _events.RaiseAsync(new ConsentDeniedEvent(User.GetSubjectId(), request.Client.ClientId, request.ValidatedResources.RawScopeValues)); + Telemetry.Metrics.ConsentDeniedEvent(request.Client.ClientId, request.ValidatedResources.ParsedScopes.Select(s => s.ParsedName)); } // user clicked 'yes' - validate the data else if (Input.Button == "yes") @@ -88,6 +89,9 @@ public async Task OnPost() // emit event await _events.RaiseAsync(new ConsentGrantedEvent(User.GetSubjectId(), request.Client.ClientId, request.ValidatedResources.RawScopeValues, grantedConsent.ScopesValuesConsented, grantedConsent.RememberConsent)); + Telemetry.Metrics.ConsentGrantedEvent(request.Client.ClientId, grantedConsent.ScopesValuesConsented, grantedConsent.RememberConsent); + var denied = request.ValidatedResources.ParsedScopes.Select(s => s.ParsedName).Except(grantedConsent.ScopesValuesConsented); + Telemetry.Metrics.ConsentDeniedEvent(request.Client.ClientId, denied); } else { diff --git a/hosts/Configuration/Pages/Device/Index.cshtml.cs b/hosts/Configuration/Pages/Device/Index.cshtml.cs index f84bbb159..0d1721e44 100644 --- a/hosts/Configuration/Pages/Device/Index.cshtml.cs +++ b/hosts/Configuration/Pages/Device/Index.cshtml.cs @@ -78,6 +78,7 @@ public async Task OnPost() // emit event await _events.RaiseAsync(new ConsentDeniedEvent(User.GetSubjectId(), request.Client.ClientId, request.ValidatedResources.RawScopeValues)); + Telemetry.Metrics.ConsentDeniedEvent(request.Client.ClientId, request.ValidatedResources.ParsedScopes.Select(s => s.ParsedName)); } // user clicked 'yes' - validate the data else if (Input.Button == "yes") @@ -100,6 +101,9 @@ public async Task OnPost() // emit event await _events.RaiseAsync(new ConsentGrantedEvent(User.GetSubjectId(), request.Client.ClientId, request.ValidatedResources.RawScopeValues, grantedConsent.ScopesValuesConsented, grantedConsent.RememberConsent)); + Telemetry.Metrics.ConsentGrantedEvent(request.Client.ClientId, grantedConsent.ScopesValuesConsented, grantedConsent.RememberConsent); + var denied = request.ValidatedResources.ParsedScopes.Select(s => s.ParsedName).Except(grantedConsent.ScopesValuesConsented); + Telemetry.Metrics.ConsentDeniedEvent(request.Client.ClientId, denied); } else { diff --git a/hosts/Configuration/Pages/ExternalLogin/Callback.cshtml.cs b/hosts/Configuration/Pages/ExternalLogin/Callback.cshtml.cs index 937ced8e9..281131bc2 100644 --- a/hosts/Configuration/Pages/ExternalLogin/Callback.cshtml.cs +++ b/hosts/Configuration/Pages/ExternalLogin/Callback.cshtml.cs @@ -106,6 +106,7 @@ public async Task OnGet() // check if external login is in the context of an OIDC request var context = await _interaction.GetAuthorizationContextAsync(returnUrl); await _events.RaiseAsync(new UserLoginSuccessEvent(provider, providerUserId, user.SubjectId, user.Username, true, context?.Client.ClientId)); + Telemetry.Metrics.UserLogin(context?.Client.ClientId, provider!); if (context != null) { diff --git a/hosts/Configuration/Pages/Grants/Index.cshtml.cs b/hosts/Configuration/Pages/Grants/Index.cshtml.cs index dceca1034..a38545744 100644 --- a/hosts/Configuration/Pages/Grants/Index.cshtml.cs +++ b/hosts/Configuration/Pages/Grants/Index.cshtml.cs @@ -75,6 +75,7 @@ public async Task OnPost() { await _interaction.RevokeUserConsentAsync(ClientId); await _events.RaiseAsync(new GrantsRevokedEvent(User.GetSubjectId(), ClientId)); + Telemetry.Metrics.GrantsRevoked(ClientId); return RedirectToPage("/Grants/Index"); } diff --git a/hosts/Configuration/Pages/Telemetry.cs b/hosts/Configuration/Pages/Telemetry.cs new file mode 100644 index 000000000..15a993c6d --- /dev/null +++ b/hosts/Configuration/Pages/Telemetry.cs @@ -0,0 +1,170 @@ +using Duende.IdentityServer.Events; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using System.Diagnostics.Metrics; + +namespace IdentityServerHost.Pages; + +#pragma warning disable CA1034 // Nested types should not be visible +#pragma warning disable CA1724 // Type names should not match namespaces + +/// +/// Telemetry helpers for the UI +/// +public static class Telemetry +{ + private static readonly string ServiceVersion = typeof(Telemetry).Assembly.GetName().Version!.ToString(); + + /// + /// Service name for telemetry. + /// + public static readonly string ServiceName = typeof(Telemetry).Assembly.GetName().Name!; + + /// + /// Metrics configuration + /// + public static class Metrics + { + /// + /// Name of Counters + /// + public static class Counters + { + /// + /// consent_granted + /// + public const string ConsentGranted = "consent_granted"; + + /// + /// consent_denied + /// + public const string ConsentDenied = "consent_denied"; + + /// + /// grants_revoked + /// + public const string GrantsRevoked = "grants_revoked"; + + /// + /// user_login + /// + public const string UserLogin = "user_login"; + + /// + /// user_login_failure + /// + public const string UserLoginFailure = "user_login_failure"; + + /// + /// user_logout + /// + public const string UserLogout = "user_logout"; + } + + /// + /// Name of tags + /// + public static class Tags + { + /// + /// client + /// + public const string Client = "client"; + + /// + /// error + /// + public const string Error = "error"; + + /// + /// idp + /// + public const string Idp = "idp"; + + /// + /// remember + /// + public const string Remember = "remember"; + + /// + /// scope + /// + public const string Scope = "scope"; + } + + /// + /// Meter for the IdentityServer host project + /// + private static readonly Meter Meter = new Meter(ServiceName, ServiceVersion); + + private static Counter ConsentGrantedCounter = Meter.CreateCounter(Counters.ConsentGranted); + + /// + /// Helper method to increase counter. The scopes + /// are expanded and called one by one to not cause a combinatory explosion of scopes. + /// + /// Client id + /// Scope names. Each element is added on it's own to the counter + public static void ConsentGrantedEvent(string clientId, IEnumerable scopes, bool remember) + { + ArgumentNullException.ThrowIfNull(scopes); + foreach(var scope in scopes) + { + ConsentGrantedCounter.Add(1, new(Tags.Client, clientId), new(Tags.Scope, scope), new(Tags.Remember, remember)); + } + } + + private static Counter ConsentDeniedCounter = Meter.CreateCounter(Counters.ConsentDenied); + + /// + /// Helper method to increase counter. The scopes + /// are expanded and called one by one to not cause a combinatory explosion of scopes. + /// + /// Client id + /// Scope names. Each element is added on it's own to the counter + public static void ConsentDeniedEvent(string clientId, IEnumerable scopes) + { + ArgumentNullException.ThrowIfNull(scopes); + foreach (var scope in scopes) + { + ConsentDeniedCounter.Add(1, new(Tags.Client, clientId), new(Tags.Scope, scope)); + } + } + + private static Counter GrantsRevokedCounter = Meter.CreateCounter(Counters.GrantsRevoked); + + /// + /// Helper method to increase the counter. + /// + /// Client id to revoke for, or null for all. + public static void GrantsRevoked(string? clientId) + => GrantsRevokedCounter.Add(1, tag: new(Tags.Client, clientId)); + + private static Counter UserLoginCounter = Meter.CreateCounter(Counters.UserLogin); + + /// + /// Helper method to increase counter. + /// + /// Client Id, if available + public static void UserLogin(string? clientId, string idp) + => UserLoginCounter.Add(1, new(Tags.Client, clientId), new(Tags.Idp, idp)); + + private static Counter UserLoginFailureCounter = Meter.CreateCounter(Counters.UserLoginFailure); + + /// + /// Helper method to increase + /// Client Id, if available + /// Error message + public static void UserLoginFailure(string? clientId, string idp, string error) + => UserLoginFailureCounter.Add(1, new(Tags.Client, clientId), new(Tags.Idp, idp), new(Tags.Error, error)); + + private static Counter UserLogoutCounter = Meter.CreateCounter(Counters.UserLogout); + + /// + /// Helper method to increase the counter. + /// + /// Idp/authentication scheme for external authentication, or "local" for built in. + public static void UserLogout(string? idp) + => UserLogoutCounter.Add(1, tag: new(Tags.Idp, idp)); + } +} From 078d3cae0e4dbf24687575bef9cb54aacafe9792 Mon Sep 17 00:00:00 2001 From: Anders Abel Date: Tue, 28 Nov 2023 14:45:42 +0100 Subject: [PATCH 5/6] Naming fix --- hosts/AspNetIdentity/Pages/Consent/Index.cshtml.cs | 6 +++--- hosts/AspNetIdentity/Pages/Device/Index.cshtml.cs | 6 +++--- hosts/AspNetIdentity/Pages/Telemetry.cs | 4 ++-- hosts/Configuration/Pages/Ciba/Consent.cshtml.cs | 6 +++--- hosts/Configuration/Pages/Consent/Index.cshtml.cs | 6 +++--- hosts/Configuration/Pages/Device/Index.cshtml.cs | 6 +++--- hosts/Configuration/Pages/Telemetry.cs | 4 ++-- hosts/EntityFramework/Pages/Ciba/Consent.cshtml.cs | 6 +++--- hosts/EntityFramework/Pages/Consent/Index.cshtml.cs | 6 +++--- hosts/EntityFramework/Pages/Device/Index.cshtml.cs | 6 +++--- hosts/EntityFramework/Pages/Telemetry.cs | 4 ++-- hosts/main/Pages/Ciba/Consent.cshtml.cs | 6 +++--- hosts/main/Pages/Consent/Index.cshtml.cs | 6 +++--- hosts/main/Pages/Device/Index.cshtml.cs | 6 +++--- hosts/main/Pages/Telemetry.cs | 4 ++-- 15 files changed, 41 insertions(+), 41 deletions(-) diff --git a/hosts/AspNetIdentity/Pages/Consent/Index.cshtml.cs b/hosts/AspNetIdentity/Pages/Consent/Index.cshtml.cs index 357691a39..e6daedcd7 100644 --- a/hosts/AspNetIdentity/Pages/Consent/Index.cshtml.cs +++ b/hosts/AspNetIdentity/Pages/Consent/Index.cshtml.cs @@ -66,7 +66,7 @@ public async Task OnPost() // emit event await _events.RaiseAsync(new ConsentDeniedEvent(User.GetSubjectId(), request.Client.ClientId, request.ValidatedResources.RawScopeValues)); - Telemetry.Metrics.ConsentDeniedEvent(request.Client.ClientId, request.ValidatedResources.ParsedScopes.Select(s => s.ParsedName)); + Telemetry.Metrics.ConsentDenied(request.Client.ClientId, request.ValidatedResources.ParsedScopes.Select(s => s.ParsedName)); } // user clicked 'yes' - validate the data else if (Input.Button == "yes") @@ -89,9 +89,9 @@ public async Task OnPost() // emit event await _events.RaiseAsync(new ConsentGrantedEvent(User.GetSubjectId(), request.Client.ClientId, request.ValidatedResources.RawScopeValues, grantedConsent.ScopesValuesConsented, grantedConsent.RememberConsent)); - Telemetry.Metrics.ConsentGrantedEvent(request.Client.ClientId, grantedConsent.ScopesValuesConsented, grantedConsent.RememberConsent); + Telemetry.Metrics.ConsentGranted(request.Client.ClientId, grantedConsent.ScopesValuesConsented, grantedConsent.RememberConsent); var denied = request.ValidatedResources.ParsedScopes.Select(s => s.ParsedName).Except(grantedConsent.ScopesValuesConsented); - Telemetry.Metrics.ConsentDeniedEvent(request.Client.ClientId, denied); + Telemetry.Metrics.ConsentDenied(request.Client.ClientId, denied); } else { diff --git a/hosts/AspNetIdentity/Pages/Device/Index.cshtml.cs b/hosts/AspNetIdentity/Pages/Device/Index.cshtml.cs index 0d1721e44..29cf3af0c 100644 --- a/hosts/AspNetIdentity/Pages/Device/Index.cshtml.cs +++ b/hosts/AspNetIdentity/Pages/Device/Index.cshtml.cs @@ -78,7 +78,7 @@ public async Task OnPost() // emit event await _events.RaiseAsync(new ConsentDeniedEvent(User.GetSubjectId(), request.Client.ClientId, request.ValidatedResources.RawScopeValues)); - Telemetry.Metrics.ConsentDeniedEvent(request.Client.ClientId, request.ValidatedResources.ParsedScopes.Select(s => s.ParsedName)); + Telemetry.Metrics.ConsentDenied(request.Client.ClientId, request.ValidatedResources.ParsedScopes.Select(s => s.ParsedName)); } // user clicked 'yes' - validate the data else if (Input.Button == "yes") @@ -101,9 +101,9 @@ public async Task OnPost() // emit event await _events.RaiseAsync(new ConsentGrantedEvent(User.GetSubjectId(), request.Client.ClientId, request.ValidatedResources.RawScopeValues, grantedConsent.ScopesValuesConsented, grantedConsent.RememberConsent)); - Telemetry.Metrics.ConsentGrantedEvent(request.Client.ClientId, grantedConsent.ScopesValuesConsented, grantedConsent.RememberConsent); + Telemetry.Metrics.ConsentGranted(request.Client.ClientId, grantedConsent.ScopesValuesConsented, grantedConsent.RememberConsent); var denied = request.ValidatedResources.ParsedScopes.Select(s => s.ParsedName).Except(grantedConsent.ScopesValuesConsented); - Telemetry.Metrics.ConsentDeniedEvent(request.Client.ClientId, denied); + Telemetry.Metrics.ConsentDenied(request.Client.ClientId, denied); } else { diff --git a/hosts/AspNetIdentity/Pages/Telemetry.cs b/hosts/AspNetIdentity/Pages/Telemetry.cs index 15a993c6d..621d71e48 100644 --- a/hosts/AspNetIdentity/Pages/Telemetry.cs +++ b/hosts/AspNetIdentity/Pages/Telemetry.cs @@ -104,7 +104,7 @@ public static class Tags /// /// Client id /// Scope names. Each element is added on it's own to the counter - public static void ConsentGrantedEvent(string clientId, IEnumerable scopes, bool remember) + public static void ConsentGranted(string clientId, IEnumerable scopes, bool remember) { ArgumentNullException.ThrowIfNull(scopes); foreach(var scope in scopes) @@ -121,7 +121,7 @@ public static void ConsentGrantedEvent(string clientId, IEnumerable scop /// /// Client id /// Scope names. Each element is added on it's own to the counter - public static void ConsentDeniedEvent(string clientId, IEnumerable scopes) + public static void ConsentDenied(string clientId, IEnumerable scopes) { ArgumentNullException.ThrowIfNull(scopes); foreach (var scope in scopes) diff --git a/hosts/Configuration/Pages/Ciba/Consent.cshtml.cs b/hosts/Configuration/Pages/Ciba/Consent.cshtml.cs index f615fdf05..38dd76f82 100644 --- a/hosts/Configuration/Pages/Ciba/Consent.cshtml.cs +++ b/hosts/Configuration/Pages/Ciba/Consent.cshtml.cs @@ -69,7 +69,7 @@ public async Task OnPost() // emit event await _events.RaiseAsync(new ConsentDeniedEvent(User.GetSubjectId(), request.Client.ClientId, request.ValidatedResources.RawScopeValues)); - Telemetry.Metrics.ConsentDeniedEvent(request.Client.ClientId, request.ValidatedResources.ParsedScopes.Select(s => s.ParsedName)); + Telemetry.Metrics.ConsentDenied(request.Client.ClientId, request.ValidatedResources.ParsedScopes.Select(s => s.ParsedName)); } // user clicked 'yes' - validate the data else if (Input.Button == "yes") @@ -91,9 +91,9 @@ public async Task OnPost() // emit event await _events.RaiseAsync(new ConsentGrantedEvent(User.GetSubjectId(), request.Client.ClientId, request.ValidatedResources.RawScopeValues, result.ScopesValuesConsented, false)); - Telemetry.Metrics.ConsentGrantedEvent(request.Client.ClientId, result.ScopesValuesConsented, false); + Telemetry.Metrics.ConsentGranted(request.Client.ClientId, result.ScopesValuesConsented, false); var denied = request.ValidatedResources.ParsedScopes.Select(s => s.ParsedName).Except(result.ScopesValuesConsented); - Telemetry.Metrics.ConsentDeniedEvent(request.Client.ClientId, denied); + Telemetry.Metrics.ConsentDenied(request.Client.ClientId, denied); } else { diff --git a/hosts/Configuration/Pages/Consent/Index.cshtml.cs b/hosts/Configuration/Pages/Consent/Index.cshtml.cs index 357691a39..e6daedcd7 100644 --- a/hosts/Configuration/Pages/Consent/Index.cshtml.cs +++ b/hosts/Configuration/Pages/Consent/Index.cshtml.cs @@ -66,7 +66,7 @@ public async Task OnPost() // emit event await _events.RaiseAsync(new ConsentDeniedEvent(User.GetSubjectId(), request.Client.ClientId, request.ValidatedResources.RawScopeValues)); - Telemetry.Metrics.ConsentDeniedEvent(request.Client.ClientId, request.ValidatedResources.ParsedScopes.Select(s => s.ParsedName)); + Telemetry.Metrics.ConsentDenied(request.Client.ClientId, request.ValidatedResources.ParsedScopes.Select(s => s.ParsedName)); } // user clicked 'yes' - validate the data else if (Input.Button == "yes") @@ -89,9 +89,9 @@ public async Task OnPost() // emit event await _events.RaiseAsync(new ConsentGrantedEvent(User.GetSubjectId(), request.Client.ClientId, request.ValidatedResources.RawScopeValues, grantedConsent.ScopesValuesConsented, grantedConsent.RememberConsent)); - Telemetry.Metrics.ConsentGrantedEvent(request.Client.ClientId, grantedConsent.ScopesValuesConsented, grantedConsent.RememberConsent); + Telemetry.Metrics.ConsentGranted(request.Client.ClientId, grantedConsent.ScopesValuesConsented, grantedConsent.RememberConsent); var denied = request.ValidatedResources.ParsedScopes.Select(s => s.ParsedName).Except(grantedConsent.ScopesValuesConsented); - Telemetry.Metrics.ConsentDeniedEvent(request.Client.ClientId, denied); + Telemetry.Metrics.ConsentDenied(request.Client.ClientId, denied); } else { diff --git a/hosts/Configuration/Pages/Device/Index.cshtml.cs b/hosts/Configuration/Pages/Device/Index.cshtml.cs index 0d1721e44..29cf3af0c 100644 --- a/hosts/Configuration/Pages/Device/Index.cshtml.cs +++ b/hosts/Configuration/Pages/Device/Index.cshtml.cs @@ -78,7 +78,7 @@ public async Task OnPost() // emit event await _events.RaiseAsync(new ConsentDeniedEvent(User.GetSubjectId(), request.Client.ClientId, request.ValidatedResources.RawScopeValues)); - Telemetry.Metrics.ConsentDeniedEvent(request.Client.ClientId, request.ValidatedResources.ParsedScopes.Select(s => s.ParsedName)); + Telemetry.Metrics.ConsentDenied(request.Client.ClientId, request.ValidatedResources.ParsedScopes.Select(s => s.ParsedName)); } // user clicked 'yes' - validate the data else if (Input.Button == "yes") @@ -101,9 +101,9 @@ public async Task OnPost() // emit event await _events.RaiseAsync(new ConsentGrantedEvent(User.GetSubjectId(), request.Client.ClientId, request.ValidatedResources.RawScopeValues, grantedConsent.ScopesValuesConsented, grantedConsent.RememberConsent)); - Telemetry.Metrics.ConsentGrantedEvent(request.Client.ClientId, grantedConsent.ScopesValuesConsented, grantedConsent.RememberConsent); + Telemetry.Metrics.ConsentGranted(request.Client.ClientId, grantedConsent.ScopesValuesConsented, grantedConsent.RememberConsent); var denied = request.ValidatedResources.ParsedScopes.Select(s => s.ParsedName).Except(grantedConsent.ScopesValuesConsented); - Telemetry.Metrics.ConsentDeniedEvent(request.Client.ClientId, denied); + Telemetry.Metrics.ConsentDenied(request.Client.ClientId, denied); } else { diff --git a/hosts/Configuration/Pages/Telemetry.cs b/hosts/Configuration/Pages/Telemetry.cs index 15a993c6d..621d71e48 100644 --- a/hosts/Configuration/Pages/Telemetry.cs +++ b/hosts/Configuration/Pages/Telemetry.cs @@ -104,7 +104,7 @@ public static class Tags /// /// Client id /// Scope names. Each element is added on it's own to the counter - public static void ConsentGrantedEvent(string clientId, IEnumerable scopes, bool remember) + public static void ConsentGranted(string clientId, IEnumerable scopes, bool remember) { ArgumentNullException.ThrowIfNull(scopes); foreach(var scope in scopes) @@ -121,7 +121,7 @@ public static void ConsentGrantedEvent(string clientId, IEnumerable scop /// /// Client id /// Scope names. Each element is added on it's own to the counter - public static void ConsentDeniedEvent(string clientId, IEnumerable scopes) + public static void ConsentDenied(string clientId, IEnumerable scopes) { ArgumentNullException.ThrowIfNull(scopes); foreach (var scope in scopes) diff --git a/hosts/EntityFramework/Pages/Ciba/Consent.cshtml.cs b/hosts/EntityFramework/Pages/Ciba/Consent.cshtml.cs index f615fdf05..38dd76f82 100644 --- a/hosts/EntityFramework/Pages/Ciba/Consent.cshtml.cs +++ b/hosts/EntityFramework/Pages/Ciba/Consent.cshtml.cs @@ -69,7 +69,7 @@ public async Task OnPost() // emit event await _events.RaiseAsync(new ConsentDeniedEvent(User.GetSubjectId(), request.Client.ClientId, request.ValidatedResources.RawScopeValues)); - Telemetry.Metrics.ConsentDeniedEvent(request.Client.ClientId, request.ValidatedResources.ParsedScopes.Select(s => s.ParsedName)); + Telemetry.Metrics.ConsentDenied(request.Client.ClientId, request.ValidatedResources.ParsedScopes.Select(s => s.ParsedName)); } // user clicked 'yes' - validate the data else if (Input.Button == "yes") @@ -91,9 +91,9 @@ public async Task OnPost() // emit event await _events.RaiseAsync(new ConsentGrantedEvent(User.GetSubjectId(), request.Client.ClientId, request.ValidatedResources.RawScopeValues, result.ScopesValuesConsented, false)); - Telemetry.Metrics.ConsentGrantedEvent(request.Client.ClientId, result.ScopesValuesConsented, false); + Telemetry.Metrics.ConsentGranted(request.Client.ClientId, result.ScopesValuesConsented, false); var denied = request.ValidatedResources.ParsedScopes.Select(s => s.ParsedName).Except(result.ScopesValuesConsented); - Telemetry.Metrics.ConsentDeniedEvent(request.Client.ClientId, denied); + Telemetry.Metrics.ConsentDenied(request.Client.ClientId, denied); } else { diff --git a/hosts/EntityFramework/Pages/Consent/Index.cshtml.cs b/hosts/EntityFramework/Pages/Consent/Index.cshtml.cs index 357691a39..e6daedcd7 100644 --- a/hosts/EntityFramework/Pages/Consent/Index.cshtml.cs +++ b/hosts/EntityFramework/Pages/Consent/Index.cshtml.cs @@ -66,7 +66,7 @@ public async Task OnPost() // emit event await _events.RaiseAsync(new ConsentDeniedEvent(User.GetSubjectId(), request.Client.ClientId, request.ValidatedResources.RawScopeValues)); - Telemetry.Metrics.ConsentDeniedEvent(request.Client.ClientId, request.ValidatedResources.ParsedScopes.Select(s => s.ParsedName)); + Telemetry.Metrics.ConsentDenied(request.Client.ClientId, request.ValidatedResources.ParsedScopes.Select(s => s.ParsedName)); } // user clicked 'yes' - validate the data else if (Input.Button == "yes") @@ -89,9 +89,9 @@ public async Task OnPost() // emit event await _events.RaiseAsync(new ConsentGrantedEvent(User.GetSubjectId(), request.Client.ClientId, request.ValidatedResources.RawScopeValues, grantedConsent.ScopesValuesConsented, grantedConsent.RememberConsent)); - Telemetry.Metrics.ConsentGrantedEvent(request.Client.ClientId, grantedConsent.ScopesValuesConsented, grantedConsent.RememberConsent); + Telemetry.Metrics.ConsentGranted(request.Client.ClientId, grantedConsent.ScopesValuesConsented, grantedConsent.RememberConsent); var denied = request.ValidatedResources.ParsedScopes.Select(s => s.ParsedName).Except(grantedConsent.ScopesValuesConsented); - Telemetry.Metrics.ConsentDeniedEvent(request.Client.ClientId, denied); + Telemetry.Metrics.ConsentDenied(request.Client.ClientId, denied); } else { diff --git a/hosts/EntityFramework/Pages/Device/Index.cshtml.cs b/hosts/EntityFramework/Pages/Device/Index.cshtml.cs index 0d1721e44..29cf3af0c 100644 --- a/hosts/EntityFramework/Pages/Device/Index.cshtml.cs +++ b/hosts/EntityFramework/Pages/Device/Index.cshtml.cs @@ -78,7 +78,7 @@ public async Task OnPost() // emit event await _events.RaiseAsync(new ConsentDeniedEvent(User.GetSubjectId(), request.Client.ClientId, request.ValidatedResources.RawScopeValues)); - Telemetry.Metrics.ConsentDeniedEvent(request.Client.ClientId, request.ValidatedResources.ParsedScopes.Select(s => s.ParsedName)); + Telemetry.Metrics.ConsentDenied(request.Client.ClientId, request.ValidatedResources.ParsedScopes.Select(s => s.ParsedName)); } // user clicked 'yes' - validate the data else if (Input.Button == "yes") @@ -101,9 +101,9 @@ public async Task OnPost() // emit event await _events.RaiseAsync(new ConsentGrantedEvent(User.GetSubjectId(), request.Client.ClientId, request.ValidatedResources.RawScopeValues, grantedConsent.ScopesValuesConsented, grantedConsent.RememberConsent)); - Telemetry.Metrics.ConsentGrantedEvent(request.Client.ClientId, grantedConsent.ScopesValuesConsented, grantedConsent.RememberConsent); + Telemetry.Metrics.ConsentGranted(request.Client.ClientId, grantedConsent.ScopesValuesConsented, grantedConsent.RememberConsent); var denied = request.ValidatedResources.ParsedScopes.Select(s => s.ParsedName).Except(grantedConsent.ScopesValuesConsented); - Telemetry.Metrics.ConsentDeniedEvent(request.Client.ClientId, denied); + Telemetry.Metrics.ConsentDenied(request.Client.ClientId, denied); } else { diff --git a/hosts/EntityFramework/Pages/Telemetry.cs b/hosts/EntityFramework/Pages/Telemetry.cs index 15a993c6d..621d71e48 100644 --- a/hosts/EntityFramework/Pages/Telemetry.cs +++ b/hosts/EntityFramework/Pages/Telemetry.cs @@ -104,7 +104,7 @@ public static class Tags /// /// Client id /// Scope names. Each element is added on it's own to the counter - public static void ConsentGrantedEvent(string clientId, IEnumerable scopes, bool remember) + public static void ConsentGranted(string clientId, IEnumerable scopes, bool remember) { ArgumentNullException.ThrowIfNull(scopes); foreach(var scope in scopes) @@ -121,7 +121,7 @@ public static void ConsentGrantedEvent(string clientId, IEnumerable scop /// /// Client id /// Scope names. Each element is added on it's own to the counter - public static void ConsentDeniedEvent(string clientId, IEnumerable scopes) + public static void ConsentDenied(string clientId, IEnumerable scopes) { ArgumentNullException.ThrowIfNull(scopes); foreach (var scope in scopes) diff --git a/hosts/main/Pages/Ciba/Consent.cshtml.cs b/hosts/main/Pages/Ciba/Consent.cshtml.cs index 2c1aa4abd..226135641 100644 --- a/hosts/main/Pages/Ciba/Consent.cshtml.cs +++ b/hosts/main/Pages/Ciba/Consent.cshtml.cs @@ -69,7 +69,7 @@ public async Task OnPost() // emit event await _events.RaiseAsync(new ConsentDeniedEvent(User.GetSubjectId(), request.Client.ClientId, request.ValidatedResources.RawScopeValues)); - Telemetry.Metrics.ConsentDeniedEvent(request.Client.ClientId, request.ValidatedResources.ParsedScopes.Select(s => s.ParsedName)); + Telemetry.Metrics.ConsentDenied(request.Client.ClientId, request.ValidatedResources.ParsedScopes.Select(s => s.ParsedName)); } // user clicked 'yes' - validate the data else if (Input.Button == "yes") @@ -91,9 +91,9 @@ public async Task OnPost() // emit event await _events.RaiseAsync(new ConsentGrantedEvent(User.GetSubjectId(), request.Client.ClientId, request.ValidatedResources.RawScopeValues, result.ScopesValuesConsented, false)); - Telemetry.Metrics.ConsentGrantedEvent(request.Client.ClientId, result.ScopesValuesConsented, false); + Telemetry.Metrics.ConsentGranted(request.Client.ClientId, result.ScopesValuesConsented, false); var denied = request.ValidatedResources.ParsedScopes.Select(s => s.ParsedName).Except(result.ScopesValuesConsented); - Telemetry.Metrics.ConsentDeniedEvent(request.Client.ClientId, denied); + Telemetry.Metrics.ConsentDenied(request.Client.ClientId, denied); } else { diff --git a/hosts/main/Pages/Consent/Index.cshtml.cs b/hosts/main/Pages/Consent/Index.cshtml.cs index 357691a39..e6daedcd7 100644 --- a/hosts/main/Pages/Consent/Index.cshtml.cs +++ b/hosts/main/Pages/Consent/Index.cshtml.cs @@ -66,7 +66,7 @@ public async Task OnPost() // emit event await _events.RaiseAsync(new ConsentDeniedEvent(User.GetSubjectId(), request.Client.ClientId, request.ValidatedResources.RawScopeValues)); - Telemetry.Metrics.ConsentDeniedEvent(request.Client.ClientId, request.ValidatedResources.ParsedScopes.Select(s => s.ParsedName)); + Telemetry.Metrics.ConsentDenied(request.Client.ClientId, request.ValidatedResources.ParsedScopes.Select(s => s.ParsedName)); } // user clicked 'yes' - validate the data else if (Input.Button == "yes") @@ -89,9 +89,9 @@ public async Task OnPost() // emit event await _events.RaiseAsync(new ConsentGrantedEvent(User.GetSubjectId(), request.Client.ClientId, request.ValidatedResources.RawScopeValues, grantedConsent.ScopesValuesConsented, grantedConsent.RememberConsent)); - Telemetry.Metrics.ConsentGrantedEvent(request.Client.ClientId, grantedConsent.ScopesValuesConsented, grantedConsent.RememberConsent); + Telemetry.Metrics.ConsentGranted(request.Client.ClientId, grantedConsent.ScopesValuesConsented, grantedConsent.RememberConsent); var denied = request.ValidatedResources.ParsedScopes.Select(s => s.ParsedName).Except(grantedConsent.ScopesValuesConsented); - Telemetry.Metrics.ConsentDeniedEvent(request.Client.ClientId, denied); + Telemetry.Metrics.ConsentDenied(request.Client.ClientId, denied); } else { diff --git a/hosts/main/Pages/Device/Index.cshtml.cs b/hosts/main/Pages/Device/Index.cshtml.cs index 0d1721e44..29cf3af0c 100644 --- a/hosts/main/Pages/Device/Index.cshtml.cs +++ b/hosts/main/Pages/Device/Index.cshtml.cs @@ -78,7 +78,7 @@ public async Task OnPost() // emit event await _events.RaiseAsync(new ConsentDeniedEvent(User.GetSubjectId(), request.Client.ClientId, request.ValidatedResources.RawScopeValues)); - Telemetry.Metrics.ConsentDeniedEvent(request.Client.ClientId, request.ValidatedResources.ParsedScopes.Select(s => s.ParsedName)); + Telemetry.Metrics.ConsentDenied(request.Client.ClientId, request.ValidatedResources.ParsedScopes.Select(s => s.ParsedName)); } // user clicked 'yes' - validate the data else if (Input.Button == "yes") @@ -101,9 +101,9 @@ public async Task OnPost() // emit event await _events.RaiseAsync(new ConsentGrantedEvent(User.GetSubjectId(), request.Client.ClientId, request.ValidatedResources.RawScopeValues, grantedConsent.ScopesValuesConsented, grantedConsent.RememberConsent)); - Telemetry.Metrics.ConsentGrantedEvent(request.Client.ClientId, grantedConsent.ScopesValuesConsented, grantedConsent.RememberConsent); + Telemetry.Metrics.ConsentGranted(request.Client.ClientId, grantedConsent.ScopesValuesConsented, grantedConsent.RememberConsent); var denied = request.ValidatedResources.ParsedScopes.Select(s => s.ParsedName).Except(grantedConsent.ScopesValuesConsented); - Telemetry.Metrics.ConsentDeniedEvent(request.Client.ClientId, denied); + Telemetry.Metrics.ConsentDenied(request.Client.ClientId, denied); } else { diff --git a/hosts/main/Pages/Telemetry.cs b/hosts/main/Pages/Telemetry.cs index 15a993c6d..621d71e48 100644 --- a/hosts/main/Pages/Telemetry.cs +++ b/hosts/main/Pages/Telemetry.cs @@ -104,7 +104,7 @@ public static class Tags /// /// Client id /// Scope names. Each element is added on it's own to the counter - public static void ConsentGrantedEvent(string clientId, IEnumerable scopes, bool remember) + public static void ConsentGranted(string clientId, IEnumerable scopes, bool remember) { ArgumentNullException.ThrowIfNull(scopes); foreach(var scope in scopes) @@ -121,7 +121,7 @@ public static void ConsentGrantedEvent(string clientId, IEnumerable scop /// /// Client id /// Scope names. Each element is added on it's own to the counter - public static void ConsentDeniedEvent(string clientId, IEnumerable scopes) + public static void ConsentDenied(string clientId, IEnumerable scopes) { ArgumentNullException.ThrowIfNull(scopes); foreach (var scope in scopes) From dee60a9ae23f7f0bf9048a6f9e5473279f155cbc Mon Sep 17 00:00:00 2001 From: Anders Abel Date: Tue, 28 Nov 2023 15:04:42 +0100 Subject: [PATCH 6/6] Code cleanup --- hosts/AspNetIdentity/Pages/Account/Login/Index.cshtml.cs | 1 - hosts/AspNetIdentity/Pages/Telemetry.cs | 2 -- hosts/Configuration/Pages/Telemetry.cs | 2 -- hosts/EntityFramework/Pages/Telemetry.cs | 2 -- hosts/main/Pages/Telemetry.cs | 2 -- 5 files changed, 9 deletions(-) diff --git a/hosts/AspNetIdentity/Pages/Account/Login/Index.cshtml.cs b/hosts/AspNetIdentity/Pages/Account/Login/Index.cshtml.cs index c1664c79d..edc95a2d5 100644 --- a/hosts/AspNetIdentity/Pages/Account/Login/Index.cshtml.cs +++ b/hosts/AspNetIdentity/Pages/Account/Login/Index.cshtml.cs @@ -12,7 +12,6 @@ using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.RazorPages; -using static System.Runtime.InteropServices.JavaScript.JSType; namespace IdentityServerHost.Pages.Login; diff --git a/hosts/AspNetIdentity/Pages/Telemetry.cs b/hosts/AspNetIdentity/Pages/Telemetry.cs index 621d71e48..cdc142833 100644 --- a/hosts/AspNetIdentity/Pages/Telemetry.cs +++ b/hosts/AspNetIdentity/Pages/Telemetry.cs @@ -1,5 +1,3 @@ -using Duende.IdentityServer.Events; -using Microsoft.CodeAnalysis.CSharp.Syntax; using System.Diagnostics.Metrics; namespace IdentityServerHost.Pages; diff --git a/hosts/Configuration/Pages/Telemetry.cs b/hosts/Configuration/Pages/Telemetry.cs index 621d71e48..cdc142833 100644 --- a/hosts/Configuration/Pages/Telemetry.cs +++ b/hosts/Configuration/Pages/Telemetry.cs @@ -1,5 +1,3 @@ -using Duende.IdentityServer.Events; -using Microsoft.CodeAnalysis.CSharp.Syntax; using System.Diagnostics.Metrics; namespace IdentityServerHost.Pages; diff --git a/hosts/EntityFramework/Pages/Telemetry.cs b/hosts/EntityFramework/Pages/Telemetry.cs index 621d71e48..cdc142833 100644 --- a/hosts/EntityFramework/Pages/Telemetry.cs +++ b/hosts/EntityFramework/Pages/Telemetry.cs @@ -1,5 +1,3 @@ -using Duende.IdentityServer.Events; -using Microsoft.CodeAnalysis.CSharp.Syntax; using System.Diagnostics.Metrics; namespace IdentityServerHost.Pages; diff --git a/hosts/main/Pages/Telemetry.cs b/hosts/main/Pages/Telemetry.cs index 621d71e48..cdc142833 100644 --- a/hosts/main/Pages/Telemetry.cs +++ b/hosts/main/Pages/Telemetry.cs @@ -1,5 +1,3 @@ -using Duende.IdentityServer.Events; -using Microsoft.CodeAnalysis.CSharp.Syntax; using System.Diagnostics.Metrics; namespace IdentityServerHost.Pages;