/// <summary> /// Deletes the access tokens that expires before the specified expire date. Called when creating /// an access token to cleanup. /// </summary> /// <param name="expires">The expire date.</param> /// <returns>The number of deleted tokens.</returns> public async Task <int> DeleteAccessTokens(DateTimeOffset expires) { var db = this.GetDatabase(); var tran = db.CreateTransaction(); var keysToDelete = await db.SortedSetRangeByScoreAsync($"{this.Configuration.AccessTokenPrefix}:_index:expires", 0, expires.ToUnixTime()); // Remove items from indexes tran.SortedSetRemoveRangeByScoreAsync($"{this.Configuration.AccessTokenPrefix}:_index:expires", 0, expires.ToUnixTime()); // Remove items foreach (var key in keysToDelete) { var data = await db.HashGetAllAsync(key.ToString()); var token = new RedisAccessToken(data); tran.HashDeleteAsync($"{this.Configuration.AccessTokenPrefix}:_index:client:{token.ClientId}", key); tran.HashDeleteAsync($"{this.Configuration.AccessTokenPrefix}:_index:redirecturi:{token.RedirectUri}", key); tran.HashDeleteAsync($"{this.Configuration.AccessTokenPrefix}:_index:subject:{token.Subject}", key); tran.KeyDeleteAsync(key.ToString()); } var result = await tran.ExecuteAsync(CommandFlags.HighPriority); if (result) { return(keysToDelete.Length); } return(0); }
/// <summary> /// Gets all access tokens for the specified user that expires **after** the specified date. /// Called when authenticating an access token to limit the number of tokens to go through when validating the hash. /// </summary> /// <param name="subject">The subject.</param> /// <param name="expires">The expire date.</param> /// <returns>The access tokens.</returns> public async Task <IEnumerable <IAccessToken> > GetAccessTokens(string subject, DateTimeOffset expires) { var db = this.GetDatabase(); var min = expires.ToUnixTime(); var tokens = new List <IAccessToken>(); var nonExpiredKeys = await db.SortedSetRangeByScoreAsync($"{this.Configuration.AccessTokenPrefix}:_index:expires", min, DateTimeMax); var subjectKeys = await db.HashGetAllAsync($"{this.Configuration.AccessTokenPrefix}:_index:subject:{subject}"); var unionKeys = nonExpiredKeys.Join(subjectKeys, x => x.ToString(), y => y.Name.ToString(), (x, y) => x); foreach (var key in unionKeys) { var hashEntries = await db.HashGetAllAsync(key.ToString()); if (hashEntries.Any()) { var token = new RedisAccessToken(hashEntries); if (token.ValidTo > expires) { tokens.Add(token); } } } return(tokens.Where(x => x.Subject == subject)); }
/// <summary> /// Gets all access tokens that expires **after** the specified date. Called when authenticating /// an access token to limit the number of tokens to go through when validating the hash. /// </summary> /// <param name="expires">The expire date.</param> /// <returns>The access tokens.</returns> public async Task <IEnumerable <IAccessToken> > GetAccessTokens(DateTimeOffset expires) { var db = this.GetDatabase(); var min = expires.ToUnixTime(); var tokens = new List <IAccessToken>(); var nonExpiredKeys = await db.SortedSetRangeByScoreAsync($"{this.Configuration.AccessTokenPrefix}:_index:expires", min, DateTimeMax); foreach (var key in nonExpiredKeys) { var hashEntries = await db.HashGetAllAsync(key.ToString()); if (hashEntries.Any()) { var token = new RedisAccessToken(hashEntries); if (token.ValidTo > expires) { tokens.Add(token); } } } return(tokens); }
/// <summary>Inserts the specified access token. Called when creating an access token.</summary> /// <param name="accessToken">The access token.</param> /// <returns>The inserted access token. <c>null</c> if the insertion was unsuccessful.</returns> public async Task <IAccessToken> InsertAccessToken(IAccessToken accessToken) { var token = new RedisAccessToken(accessToken); // Validate token if (!token.IsValid()) { throw new ArgumentException($"The access token is invalid: {JsonConvert.SerializeObject(token)}", nameof(accessToken)); } var key = this.GenerateKeyPath(token); var db = this.GetDatabase(); var tran = db.CreateTransaction(); try { this.Configuration.Log.DebugFormat("Inserting access token hash in key {0}", key); // Add hash to key tran.HashSetAsync(key, token.ToHashEntries()); var expires = token.ValidTo.ToUnixTime(); this.Configuration.Log.DebugFormat("Inserting key {0} to access token set with score {1}", key, expires); // Add key to sorted set for future reference by expire time. The score is the expire time in seconds since epoch. tran.SortedSetAddAsync($"{this.Configuration.AccessTokenPrefix}:_index:expires", key, expires); // Add key to hashed set for future reference by client id, redirect uri or subject. The value is the expire time in seconds since epoch. tran.HashSetAsync($"{this.Configuration.AccessTokenPrefix}:_index:client:{token.ClientId}", key, expires); tran.HashSetAsync($"{this.Configuration.AccessTokenPrefix}:_index:redirecturi:{token.RedirectUri}", key, expires); tran.HashSetAsync($"{this.Configuration.AccessTokenPrefix}:_index:subject:{token.Subject}", key, expires); this.Configuration.Log.DebugFormat("Making key {0} expire at {1}", key, token.ValidTo); // Make the keys expire when the code times out tran.KeyExpireAsync(key, token.ValidTo.UtcDateTime); await tran.ExecuteAsync(); return(token); } catch (Exception ex) { this.Configuration.Log.Error("Error when inserting access token", ex); } return(null); }
/// <summary>Gets the specified access token.</summary> /// <param name="identifier">The identifier.</param> /// <returns>The access token.</returns> public async Task <IAccessToken> GetAccessToken(string identifier) { if (string.IsNullOrEmpty(identifier)) { throw new ArgumentNullException(nameof(identifier)); } var db = this.GetDatabase(); var key = Convert.ToBase64String(Encoding.UTF8.GetBytes(identifier)); var hashEntries = await db.HashGetAllAsync($"{this.Configuration.AccessTokenPrefix}:{key}"); if (hashEntries.Any()) { var token = new RedisAccessToken(hashEntries); return(token); } return(null); }
/// <summary> /// Deletes the access tokens belonging to the specified client, redirect uri and subject. /// </summary> /// <param name="clientId">Identifier for the client.</param> /// <param name="redirectUri">The redirect uri.</param> /// <param name="subject">The subject.</param> /// <returns>The number of deleted tokens.</returns> public async Task <int> DeleteAccessTokens(string clientId, string redirectUri, string subject) { var db = this.GetDatabase(); var tran = db.CreateTransaction(); var clientKeys = db.HashGetAll($"{this.Configuration.AccessTokenPrefix}:_index:client:{clientId}"); var redirectUriKeys = db.HashGetAll($"{this.Configuration.AccessTokenPrefix}:_index:redirecturi:{redirectUri}"); var subjectKeys = db.HashGetAll($"{this.Configuration.AccessTokenPrefix}:_index:subject:{subject}"); var unionKeys = clientKeys .Join(redirectUriKeys, x => x.Name, y => y.Name, (x, y) => x) .Join(subjectKeys, x => x.Name, y => y.Name, (x, y) => x.Name); // Remove keys foreach (var key in unionKeys) { var data = await db.HashGetAllAsync(key.ToString()); var token = new RedisAccessToken(data); // Remove items from indexes tran.SortedSetRemoveAsync($"{this.Configuration.AccessTokenPrefix}:_index:expires", key.ToString()); tran.HashDeleteAsync($"{this.Configuration.AccessTokenPrefix}:_index:client:{token.ClientId}", key.ToString()); tran.HashDeleteAsync($"{this.Configuration.AccessTokenPrefix}:_index:redirecturi:{token.RedirectUri}", key.ToString()); tran.HashDeleteAsync($"{this.Configuration.AccessTokenPrefix}:_index:subject:{token.Subject}", key.ToString()); // Remove key tran.KeyDeleteAsync(key.ToString()); } var result = await tran.ExecuteAsync(CommandFlags.HighPriority); if (result) { return(unionKeys.Count()); } return(0); }
/// <summary>Generates a key.</summary> /// <param name="accessToken">The access token.</param> /// <returns>The key.</returns> protected string GenerateKeyPath(RedisAccessToken accessToken) { return(this.Configuration.AccessTokenPrefix + ":" + Convert.ToBase64String(Encoding.UTF8.GetBytes(accessToken.GetIdentifier()))); }