public async Task <TokenSet> Handle(RefreshActionstepCredentialsCommand message, CancellationToken token) { if (message is null) { throw new ArgumentNullException(nameof(message)); } ValidationResult result = _validator.Validate(message); if (!result.IsValid) { throw new ValidationException("Invalid input.", result.Errors); } var tokenToRefresh = await _tokenSetRepository.GetTokenSetById(message.ActionstepCredentialIdToRefresh.ToString(CultureInfo.InvariantCulture)); if (tokenToRefresh == null) { throw new InvalidOperationException($"No Actionstep credentials found with the id {message.ActionstepCredentialIdToRefresh}. No credentials to be refreshed."); } return(await _actionstepService.RefreshAccessTokenIfExpired(tokenToRefresh, forceRefresh : true)); }
public async Task Run( #pragma warning disable CA1801 // Remove unused parameter: Required by framework [TimerTrigger("0 0 3 * * *")] TimerInfo myTimer, #pragma warning restore CA1801 // Remove unused parameter ILogger logger) { if (logger is null) { throw new ArgumentNullException(nameof(logger)); } var individualCredentialRefreshExceptions = new List <Exception>(); const int minimumTokenValidityInDays = 5; var latestRefreshTokenExpiryToRefresh = _clock.GetCurrentInstant().Plus(Duration.FromDays(minimumTokenValidityInDays)); logger.LogInformation( "Retrieving tokens expiring within the next {MinimumTokenValidityInDays} days. Checking for tokens that expire between now and before {LatestRefreshTokenExpiryToRefresh}.", minimumTokenValidityInDays, latestRefreshTokenExpiryToRefresh); var tokensNearingExpiry = await _tokenSetRepository.GetTokensByRefreshExpiry(latestRefreshTokenExpiryToRefresh); int refreshedCount = 0; int skippedCount = 0; int errorCount = 0; int totalTokenCount = 0; foreach (var tokenSet in tokensNearingExpiry) { totalTokenCount++; try { if (!tokenSet.RefreshTokenAppearsValid(_clock)) { skippedCount++; var revokedAt = tokenSet.RevokedAt.HasValue ? tokenSet.RevokedAt.Value.ToString() : "Not revoked"; logger.LogInformation( "Skipping invalid TokenSet. TokenSet ID: '{TokenSetId}', User ID: '{UserId}', OrgKey: '{OrgKey}', Revoked at (UTC): '{RevokedAt}', Refresh token expires at (UTC): '{RefreshTokenExpiresAt}'", tokenSet.Id, tokenSet.UserId, tokenSet.OrgKey, revokedAt, tokenSet.RefreshTokenExpiresAt.ToString()); } else { await _actionstepService.RefreshAccessTokenIfExpired(tokenSet, forceRefresh : true); refreshedCount++; logger.LogInformation( "Successfully refreshed TokenSet. TokenSet ID: '{TokenSetId}', User ID: '{UserId}', OrgKey: '{OrgKey}', Refresh token expires at (UTC): '{RefreshTokenExpiresAt}'", tokenSet.Id, tokenSet.UserId, tokenSet.OrgKey, tokenSet.RefreshTokenExpiresAt.ToString()); } } #pragma warning disable CA1031 /// Do not catch general exception types: Exception is logged and captured in AggregatException. We don't want one credential failure to prevent others from being refreshed. catch (Exception ex) #pragma warning restore CA1031 // Do not catch general exception types { individualCredentialRefreshExceptions.Add(ex); errorCount++; var revokedAt = tokenSet.RevokedAt.HasValue ? tokenSet.RevokedAt.Value.ToString() : "Not revoked"; logger.LogError( ex, "Error encountered while refreshingTokenSet. TokenSet ID: '{TokenSetId}', User ID: '{UserId}', OrgKey: '{OrgKey}', Revoked at (UTC): '{RevokedAt}', Refresh token expires at (UTC): '{RefreshTokenExpiresAt}'", tokenSet.Id, tokenSet.UserId, tokenSet.OrgKey, revokedAt, tokenSet.RefreshTokenExpiresAt.ToString()); } } if (totalTokenCount > 0) { logger.LogInformation( "Finished processing {TotalTokenCount} tokens. Refreshed: {RefreshedCount}, skipped: {SkippedCount}, errored: {ErrorCount}.", totalTokenCount, refreshedCount, skippedCount, errorCount); } else { logger.LogInformation("No tokens found that are close to expiry."); } if (individualCredentialRefreshExceptions.Count > 0) { // If there were any failures, throwing ensures that the TimerJob shows up as failed. throw new AggregateException("One or more refresh operations failed.", individualCredentialRefreshExceptions); } }