1
0
mirror of https://github.com/bitwarden/server.git synced 2025-07-01 16:12:49 -05:00

chore: update LastActivityDate on installation token refresh (#5081)

This commit is contained in:
Addison Beck
2025-01-06 16:22:03 -05:00
committed by GitHub
parent cd7c4bf6ce
commit 90f7bfe63d
13 changed files with 229 additions and 7 deletions

View File

@ -1,11 +1,13 @@
using System.Diagnostics;
using System.Security.Claims;
using Bit.Core;
using Bit.Core.AdminConsole.Services;
using Bit.Core.Auth.Models.Api.Response;
using Bit.Core.Auth.Repositories;
using Bit.Core.Context;
using Bit.Core.Entities;
using Bit.Core.IdentityServer;
using Bit.Core.Platform.Installations;
using Bit.Core.Repositories;
using Bit.Core.Services;
using Bit.Core.Settings;
@ -23,6 +25,7 @@ public class CustomTokenRequestValidator : BaseRequestValidator<CustomTokenReque
ICustomTokenRequestValidator
{
private readonly UserManager<User> _userManager;
private readonly IUpdateInstallationCommand _updateInstallationCommand;
public CustomTokenRequestValidator(
UserManager<User> userManager,
@ -39,7 +42,8 @@ public class CustomTokenRequestValidator : BaseRequestValidator<CustomTokenReque
IPolicyService policyService,
IFeatureService featureService,
ISsoConfigRepository ssoConfigRepository,
IUserDecryptionOptionsBuilder userDecryptionOptionsBuilder
IUserDecryptionOptionsBuilder userDecryptionOptionsBuilder,
IUpdateInstallationCommand updateInstallationCommand
)
: base(
userManager,
@ -59,6 +63,7 @@ public class CustomTokenRequestValidator : BaseRequestValidator<CustomTokenReque
userDecryptionOptionsBuilder)
{
_userManager = userManager;
_updateInstallationCommand = updateInstallationCommand;
}
public async Task ValidateAsync(CustomTokenRequestValidationContext context)
@ -76,16 +81,24 @@ public class CustomTokenRequestValidator : BaseRequestValidator<CustomTokenReque
}
string[] allowedGrantTypes = ["authorization_code", "client_credentials"];
string clientId = context.Result.ValidatedRequest.ClientId;
if (!allowedGrantTypes.Contains(context.Result.ValidatedRequest.GrantType)
|| context.Result.ValidatedRequest.ClientId.StartsWith("organization")
|| context.Result.ValidatedRequest.ClientId.StartsWith("installation")
|| context.Result.ValidatedRequest.ClientId.StartsWith("internal")
|| clientId.StartsWith("organization")
|| clientId.StartsWith("installation")
|| clientId.StartsWith("internal")
|| context.Result.ValidatedRequest.Client.AllowedScopes.Contains(ApiScopes.ApiSecrets))
{
if (context.Result.ValidatedRequest.Client.Properties.TryGetValue("encryptedPayload", out var payload) &&
!string.IsNullOrWhiteSpace(payload))
{
context.Result.CustomResponse = new Dictionary<string, object> { { "encrypted_payload", payload } };
}
if (FeatureService.IsEnabled(FeatureFlagKeys.RecordInstallationLastActivityDate)
&& context.Result.ValidatedRequest.ClientId.StartsWith("installation"))
{
var installationIdPart = clientId.Split(".")[1];
await RecordActivityForInstallation(clientId.Split(".")[1]);
}
return;
}
@ -152,6 +165,7 @@ public class CustomTokenRequestValidator : BaseRequestValidator<CustomTokenReque
context.Result.CustomResponse["KeyConnectorUrl"] = userDecryptionOptions.KeyConnectorOption.KeyConnectorUrl;
context.Result.CustomResponse["ResetMasterPassword"] = false;
}
return Task.CompletedTask;
}
@ -202,4 +216,25 @@ public class CustomTokenRequestValidator : BaseRequestValidator<CustomTokenReque
context.Result.ErrorDescription = requestContext.ValidationErrorResult.ErrorDescription;
context.Result.CustomResponse = requestContext.CustomResponse;
}
/// <summary>
/// To help mentally separate organizations that self host from abandoned
/// organizations we hook in to the token refresh event for installations
/// to write a simple `DateTime.Now` to the database.
/// </summary>
/// <remarks>
/// This works well because installations don't phone home very often.
/// Currently self hosted installations only refresh tokens every 24
/// hours or so for the sake of hooking in to cloud's push relay service.
/// If installations ever start refreshing tokens more frequently we may need to
/// adjust this to avoid making a bunch of unnecessary database calls!
/// </remarks>
private async Task RecordActivityForInstallation(string? installationIdString)
{
if (!Guid.TryParse(installationIdString, out var installationId))
{
return;
}
await _updateInstallationCommand.UpdateLastActivityDateAsync(installationId);
}
}

View File

@ -44,8 +44,7 @@ public class WebAuthnGrantValidator : BaseRequestValidator<ExtensionGrantValidat
IDataProtectorTokenFactory<WebAuthnLoginAssertionOptionsTokenable> assertionOptionsDataProtector,
IFeatureService featureService,
IUserDecryptionOptionsBuilder userDecryptionOptionsBuilder,
IAssertWebAuthnLoginCredentialCommand assertWebAuthnLoginCredentialCommand
)
IAssertWebAuthnLoginCredentialCommand assertWebAuthnLoginCredentialCommand)
: base(
userManager,
userService,