public async Task DeleteTokenAsync() { // we cannot delete a code which has not yet been created if (_id == null) { return; } if (_redisClient == null) { _redisClient = await Singletons.GetRedisClientAsync(); } int RESULT_KEY_CONFLICT = -1; // generate Lua script (which we will use to commit all changes--or the new record--in an atomic transaction) StringBuilder luaBuilder = new StringBuilder(); List <string> arguments = new List <string>(); //int iArgument = 1; // if the code has already been deleted, return success luaBuilder.Append( "if redis.call(\"EXISTS\", KEYS[1]) == 0 then\n" + " return 1\n" + "end\n"); // luaBuilder.Append( "redis.call(\"DEL\", KEYS[1])\n"); // luaBuilder.Append("return 1\n"); long luaResult = 0; List <string> keys = new List <string>(); keys.Add(REDIS_PREFIX_OAUTH2CODE + REDIS_PREFIX_SEPARATOR + _id); luaResult = await _redisClient.EvalAsync <string, string, long>(luaBuilder.ToString(), keys.ToArray(), arguments.ToArray()).ConfigureAwait(false); // NOTE: the result will contain a negative integer (error) or positive one (success) if (luaResult == 1) { // reset our server-assigned values _id = null; } else if (luaResult == RESULT_KEY_CONFLICT) { // key name conflict; abort return; } else { // unknown error throw new Exception("Critical Redis error!"); } if (luaResult < 0) { throw new Exception("Critical Redis error!"); } }
public static async Task <OAuth2AuthorizationCode> LoadAuthCodeAsync(string authCodeId, LoadAuthCodeOptions options) { if ((options & LoadAuthCodeOptions.SearchLocal) == LoadAuthCodeOptions.SearchLocal) { if (_redisClient == null) { _redisClient = await Singletons.GetRedisClientAsync(); } string fullyQualifiedAuthCodeKey = REDIS_PREFIX_OAUTH2CODE + REDIS_PREFIX_SEPARATOR + authCodeId; bool localAuthCodeExists = (await _redisClient.ExistsAsync(new string[] { fullyQualifiedAuthCodeKey }) > 0); if (localAuthCodeExists) { Dictionary <string, string> authCodeDictionary = await _redisClient.HashGetAllASync <string, string, string>(fullyQualifiedAuthCodeKey); long expiresInMilliseconds = await _redisClient.PttlAsync(fullyQualifiedAuthCodeKey); string clientId = authCodeDictionary.ContainsKey("client-id") ? authCodeDictionary["client-id"] : null; string accountId = authCodeDictionary.ContainsKey("account-id") ? authCodeDictionary["account-id"] : null; if (accountId == null) { return(null); } string userId = authCodeDictionary.ContainsKey("user-id") ? authCodeDictionary["user-id"] : null; string redirectUri = authCodeDictionary.ContainsKey("redirect-uri") ? authCodeDictionary["redirect-uri"] : null; // get "is-used" value (which, when present, indicates that the authorization code has already been submitted to the token endpoint). bool isUsed = authCodeDictionary.ContainsKey("is-used"); // if the code is used, it may also have already been assigned a token-id; we store this in case the code is compromised before it expires (i.e. and we need to revoke the token). string tokenId = authCodeDictionary.ContainsKey("token-id") ? authCodeDictionary["token-id"] : null; OAuth2AuthorizationCode result = new OAuth2AuthorizationCode(); result._id = authCodeId; result._clientId = clientId; result._accountId = accountId; result._userId = userId; result._redirectUri = redirectUri; result._isUsed = isUsed; result._tokenId = tokenId; if (expiresInMilliseconds >= 0) { result._expirationTime = DateTimeOffset.UtcNow.AddMilliseconds(expiresInMilliseconds); } return(result); } } // valid auth code could not be found return(null); }
public static async Task <OAuth2InitialAccessToken> LoadInitialAccessTokenAsync(string tokenId, LoadTokenOptions options) { if ((options & LoadTokenOptions.LocalTokens) == LoadTokenOptions.LocalTokens) { if (_redisClient == null) { _redisClient = await Singletons.GetRedisClientAsync(); } string fullyQualifiedTokenKey = REDIS_PREFIX_OAUTH2_TOKEN + REDIS_PREFIX_SEPARATOR + tokenId; bool localTokenExists = (await _redisClient.ExistsAsync(new string[] { fullyQualifiedTokenKey }) > 0); if (localTokenExists) { Dictionary <string, string> tokenDictionary = await _redisClient.HashGetAllASync <string, string, string>(fullyQualifiedTokenKey); string tokenType = tokenDictionary.ContainsKey("type") ? tokenDictionary["type"] : null; if (tokenType == null || tokenType != TOKEN_TYPE_INITIAL_ACCESS_TOKEN) { return(null); } string tokenIsCachedAsString = tokenDictionary.ContainsKey("cached") ? tokenDictionary["cached"] : null; bool tokenIsCached = (tokenIsCachedAsString != null && tokenIsCachedAsString != "0"); string timeCreatedAsString = tokenDictionary.ContainsKey("time-created") ? tokenDictionary["time-created"] : null; Int64? timeCreatedInUnixMicroseconds = null; Int64 timeCreatedAsInt64; if (timeCreatedAsString != null && Int64.TryParse(timeCreatedAsString, out timeCreatedAsInt64)) { timeCreatedInUnixMicroseconds = timeCreatedAsInt64; } string timeUpdatedAsString = tokenDictionary.ContainsKey("time-updated") ? tokenDictionary["time-updated"] : null; Int64? timeUpdatedInUnixMicroseconds = null; Int64 timeUpdatedAsInt64; if (timeUpdatedAsString != null && Int64.TryParse(timeUpdatedAsString, out timeUpdatedAsInt64)) { timeUpdatedInUnixMicroseconds = timeUpdatedAsInt64; } OAuth2InitialAccessToken resultToken = new OAuth2InitialAccessToken(); resultToken._softwareId = tokenDictionary.ContainsKey("software-id") ? tokenDictionary["software-id"] : null; if (resultToken._softwareId == null) { return(null); } resultToken._accountId = tokenDictionary.ContainsKey("account-id") ? tokenDictionary["account-id"] : null; // if our result token could be loaded, populate the default fields common to all OAuth2Tokens. resultToken._id = tokenId; ParsingHelper.ServerDetails?loginServerDetails = ParsingHelper.ExtractServerDetailsFromAccountServerIdIdentifier(tokenId); if (loginServerDetails == null) { throw new Exception(); } resultToken._loginServerDetails = loginServerDetails.Value; resultToken._isCached = tokenIsCached; resultToken._timeCreatedInUnixMicroseconds = timeCreatedInUnixMicroseconds; resultToken._timeUpdatedInUnixMicroseconds = timeUpdatedInUnixMicroseconds; return(resultToken); } } // valid token could not be found return(null); }
public async Task DeleteTokenAsync() { // we only support saving a local token (i.e. not updating a remote token) if (_isCached) { throw new InvalidOperationException(); } // we cannot delete a token which has not yet been created if (_timeCreatedInUnixMicroseconds == null) { return; } if (_redisClient == null) { _redisClient = await Singletons.GetRedisClientAsync(); } int RESULT_KEY_CONFLICT = -1; // generate Lua script (which we will use to commit all changes--or the new record--in an atomic transaction) StringBuilder luaBuilder = new StringBuilder(); List <string> arguments = new List <string>(); int iArgument = 1; // if the token has already been deleted, return success luaBuilder.Append( "if redis.call(\"EXISTS\", KEYS[1]) == 0 then\n" + " return 1\n" + "end\n"); // for deletions: make sure that the "time-created" timestamp has no changed (i.e. that a new key has not replaced the old key) luaBuilder.Append("local time_created = redis.call(\"HGET\", KEYS[1], \"time-created\")\n"); luaBuilder.Append("if time_created ~= ARGV[" + iArgument.ToString() + "] then\n" + " return " + RESULT_KEY_CONFLICT.ToString() + "\n" + "end\n"); arguments.Add(_timeCreatedInUnixMicroseconds.ToString()); iArgument++; // luaBuilder.Append( "redis.call(\"DEL\", KEYS[1])\n"); // // remove the token from corresponding sets (filtered and non-filtered indexes) luaBuilder.Append("redis.call(\"SREM\", KEYS[2], ARGV[" + iArgument.ToString() + "])\n"); luaBuilder.Append("redis.call(\"SREM\", KEYS[3], ARGV[" + iArgument.ToString() + "])\n"); arguments.Add(_id); iArgument++; // luaBuilder.Append("return 1\n"); long luaResult = 0; List <string> keys = new List <string>(); keys.Add(REDIS_PREFIX_OAUTH2_TOKEN + REDIS_PREFIX_SEPARATOR + _id); if (_accountId != null) { // index of all oauth2tokens for this account keys.Add(REDIS_PREFIX_LOGIN_SERVICE + REDIS_PREFIX_SEPARATOR + _accountId + REDIS_SLASH + _loginServerDetails.ServerId + REDIS_SUFFIX_SEPARATOR + REDIS_SUFFIX_OAUTH2TOKENS); // index of initial access oauth2tokens for this account keys.Add(REDIS_PREFIX_LOGIN_SERVICE + REDIS_PREFIX_SEPARATOR + _accountId + REDIS_SLASH + _loginServerDetails.ServerId + REDIS_SUFFIX_SEPARATOR + REDIS_SUFFIX_OAUTH2TOKENS + REDIS_BRACKET_LEFT + "type" + REDIS_ATSIGN + "initial" + REDIS_BRACKET_RIGHT); } else { // index of all oauth2tokens for the root keys.Add(REDIS_PREFIX_LOGIN_SERVICE + REDIS_PREFIX_SEPARATOR + REDIS_ASTERISK + REDIS_SLASH + _loginServerDetails.ServerId + REDIS_SUFFIX_SEPARATOR + REDIS_SUFFIX_OAUTH2TOKENS); // index of initial access oauth2tokens for the root keys.Add(REDIS_PREFIX_LOGIN_SERVICE + REDIS_PREFIX_SEPARATOR + REDIS_ASTERISK + REDIS_SLASH + _loginServerDetails.ServerId + REDIS_SUFFIX_SEPARATOR + REDIS_SUFFIX_OAUTH2TOKENS + REDIS_BRACKET_LEFT + "type" + REDIS_ATSIGN + "initial" + REDIS_BRACKET_RIGHT); } luaResult = await _redisClient.EvalAsync <string, string, long>(luaBuilder.ToString(), keys.ToArray(), arguments.ToArray()).ConfigureAwait(false); // NOTE: the result will contain a negative integer (error) or positive one (success) if (luaResult == 1) { // reset our server-assigned values _timeCreatedInUnixMicroseconds = null; _timeUpdatedInUnixMicroseconds = null; _id = null; _isCached = false; } else if (luaResult == RESULT_KEY_CONFLICT) { // key name conflict; abort return; } else { // unknown error throw new Exception("Critical Redis error!"); } if (luaResult < 0) { throw new Exception("Critical Redis error!"); } }
public async Task SaveTokenAsync() { // we only support saving a local token (i.e. not updating a remote token) if (_isCached) { throw new InvalidOperationException(); } if (_redisClient == null) { _redisClient = await Singletons.GetRedisClientAsync(); } bool objectIsNew = (_timeCreatedInUnixMicroseconds == null); int RESULT_KEY_CONFLICT = -1; int RESULT_DATA_CORRUPTION = -2; int RESULT_UPDATED_SINCE_LOAD = -3; // get current server time long newTimeUpdatedInUnixMicroseconds = await _redisClient.TimeAsync(); if (newTimeUpdatedInUnixMicroseconds < 0) { throw new Exception("Critical Redis error!"); } if (newTimeUpdatedInUnixMicroseconds < _timeUpdatedInUnixMicroseconds) { throw new Exception("Critical Redis error!"); } // generate Lua script (which we will use to commit all changes--or the new record--in an atomic transaction) StringBuilder luaBuilder = new StringBuilder(); List <string> arguments = new List <string>(); int iArgument = 1; if (objectIsNew) { // for new tokens: if a token with this token-id already exists, return 0. luaBuilder.Append( "if redis.call(\"EXISTS\", KEYS[1]) == 1 then\n" + " return " + RESULT_KEY_CONFLICT.ToString() + "\n" + "end\n"); } else { // for updated: make sure that the "time-created" timestamp has no changed (i.e. that a new key has not replaced the old key) luaBuilder.Append("local time_created = redis.call(\"HGET\", KEYS[1], \"time-created\")\n"); luaBuilder.Append("if time_created ~= ARGV[" + iArgument.ToString() + "] then\n" + " return " + RESULT_KEY_CONFLICT.ToString() + "\n" + "end\n"); arguments.Add(_timeCreatedInUnixMicroseconds.ToString()); iArgument++; // for updates: make sure that our old "time-updated" timestamp has not changed luaBuilder.Append("local old_time_updated = redis.call(\"HGET\", KEYS[1], \"time-updated\")\n"); luaBuilder.Append("if old_time_updated ~= ARGV[" + iArgument.ToString() + "] then\n" + " return " + RESULT_UPDATED_SINCE_LOAD.ToString() + "\n" + "end\n"); arguments.Add(_timeUpdatedInUnixMicroseconds.ToString()); iArgument++; } // if (objectIsNew) { luaBuilder.Append( "if redis.call(\"HSET\", KEYS[1], \"type\", ARGV[" + iArgument.ToString() + "]) == 0 then\n" + (objectIsNew ? " redis.call(\"DEL\", KEYS[1])\n" : "") + " return " + RESULT_DATA_CORRUPTION.ToString() + "\n" + "end\n"); arguments.Add(TOKEN_TYPE_INITIAL_ACCESS_TOKEN); iArgument++; } // if (objectIsNew) { luaBuilder.Append( "if redis.call(\"HSET\", KEYS[1], \"time-created\", ARGV[" + iArgument.ToString() + "]) == 0 then\n" + " return " + RESULT_DATA_CORRUPTION.ToString() + "\n" + "end\n"); arguments.Add(newTimeUpdatedInUnixMicroseconds.ToString()); iArgument++; } // luaBuilder.Append( "if redis.call(\"HSET\", KEYS[1], \"time-updated\", ARGV[" + iArgument.ToString() + "]) == 0 then\n" + " return " + RESULT_DATA_CORRUPTION.ToString() + "\n" + "end\n"); arguments.Add(newTimeUpdatedInUnixMicroseconds.ToString()); iArgument++; // if (_softwareId_IsDirty) { if (_softwareId != null) { // if there is a software-id assigned to this token, save it. luaBuilder.Append( "if redis.call(\"HSET\", KEYS[1], \"software-id\", ARGV[" + iArgument.ToString() + "]) == 0 then\n" + (objectIsNew ? " redis.call(\"DEL\", KEYS[1])\n" : "") + " return " + RESULT_DATA_CORRUPTION.ToString() + "\n" + "end\n"); arguments.Add(_softwareId); iArgument++; } else { // if the software-id has been removed, delete it. luaBuilder.Append("redis.call(\"HDEL\", KEYS[1], \"software-id\")\n"); } // clear the dirty flag _softwareId_IsDirty = false; } // if (_accountId_IsDirty) { if (_accountId != null) { // if there is an account-id assigned to this token, save it. luaBuilder.Append( "if redis.call(\"HSET\", KEYS[1], \"account-id\", ARGV[" + iArgument.ToString() + "]) == 0 then\n" + (objectIsNew ? " redis.call(\"DEL\", KEYS[1])\n" : "") + " return " + RESULT_DATA_CORRUPTION.ToString() + "\n" + "end\n"); arguments.Add(_accountId); iArgument++; } else { // if the account-id has been removed, delete it. luaBuilder.Append("redis.call(\"HDEL\", KEYS[1], \"account-id\")\n"); } // clear the dirty flag _accountId_IsDirty = false; } // luaBuilder.Append("return 1\n"); long luaResult = 0; for (int iRetry = 0; iRetry < 1000; iRetry++) { if (objectIsNew) { // generate a new 32-byte (192-bit) token_id _id = _loginServerDetails.ToAccountIdServerIdIdentifierString() + "-" + (new string(RandomHelper.CreateRandomCharacterSequence_Readable6bit_ForIdentifiers(32))); } List <string> keys = new List <string>(); keys.Add(REDIS_PREFIX_OAUTH2_TOKEN + REDIS_PREFIX_SEPARATOR + _id); luaResult = await _redisClient.EvalAsync <string, string, long>(luaBuilder.ToString(), keys.ToArray(), arguments.ToArray()).ConfigureAwait(false); // NOTE: the result will contain a negative integer (error) or one (success) //if we were able to create a key, break out of this loop; otherwise, try generating new keys up to 1000 times. if (luaResult == 1) { // save our "time-updated" timestamp _timeUpdatedInUnixMicroseconds = newTimeUpdatedInUnixMicroseconds; if (objectIsNew) { // save our "time-created" timestamp _timeCreatedInUnixMicroseconds = newTimeUpdatedInUnixMicroseconds; if (_accountId == null) { // if the token belongs to the entire system (and not to an account), add it to the root token collection. await _redisClient.SetAddAsync <string, string>(REDIS_PREFIX_LOGIN_SERVICE + REDIS_PREFIX_SEPARATOR + REDIS_ASTERISK + REDIS_SLASH + _loginServerDetails.ServerId + REDIS_SUFFIX_SEPARATOR + REDIS_SUFFIX_OAUTH2TOKENS, new string[] { _id }); await _redisClient.SetAddAsync <string, string>(REDIS_PREFIX_LOGIN_SERVICE + REDIS_PREFIX_SEPARATOR + REDIS_ASTERISK + REDIS_SLASH + _loginServerDetails.ServerId + REDIS_SUFFIX_SEPARATOR + REDIS_SUFFIX_OAUTH2TOKENS + REDIS_BRACKET_LEFT + "type" + REDIS_ATSIGN + "initial" + REDIS_BRACKET_RIGHT, new string[] { _id }); } else { // if the token belongs to the account (and not to the user), add it to the account's token collection. await _redisClient.SetAddAsync <string, string>(REDIS_PREFIX_LOGIN_SERVICE + REDIS_PREFIX_SEPARATOR + _accountId + REDIS_SLASH + _loginServerDetails.ServerId + REDIS_SUFFIX_SEPARATOR + REDIS_SUFFIX_OAUTH2TOKENS, new string[] { _id }); await _redisClient.SetAddAsync <string, string>(REDIS_PREFIX_LOGIN_SERVICE + REDIS_PREFIX_SEPARATOR + _accountId + REDIS_SLASH + _loginServerDetails.ServerId + REDIS_SUFFIX_SEPARATOR + REDIS_SUFFIX_OAUTH2TOKENS + REDIS_BRACKET_LEFT + "type" + REDIS_ATSIGN + "initial" + REDIS_BRACKET_RIGHT, new string[] { _id }); } } break; } else if (luaResult == RESULT_KEY_CONFLICT) { // key name conflict; try again } else if (luaResult == RESULT_DATA_CORRUPTION) { // data corruption throw new Exception("Critical Redis error!"); } else if (luaResult == RESULT_UPDATED_SINCE_LOAD) { // token was updated since we loaded it; we need to reload the token, make the changes again, and then attempt to save it again throw new Exception("Critical Redis error!"); } else { // unknown error throw new Exception("Critical Redis error!"); } } if (luaResult < 0) { throw new Exception("Critical Redis error!"); } }
public async Task <bool> MarkAsUsedAsync() { if (_redisClient == null) { _redisClient = await Singletons.GetRedisClientAsync(); } bool objectIsNew = (_id == null); int RESULT_DATA_CORRUPTION = -2; int RESULT_DOES_NOT_EXIST = -4; int RESULT_ALREADY_USED = -5; // generate Lua script (which we will use to commit all changes--or the new record--in an atomic transaction) StringBuilder luaBuilder = new StringBuilder(); List <string> arguments = new List <string>(); int iArgument = 1; if (objectIsNew) { // for new authorization codes: if a token with this authcode-id does not already exist, return 0 and we will simply abort. luaBuilder.Append( "if redis.call(\"EXISTS\", KEYS[1]) == 0 then\n" + " return " + RESULT_DOES_NOT_EXIST.ToString() + "\n" + "end\n"); } // make sure that this authorization code is not already "used"; if it is, abort. luaBuilder.Append( "if redis.call(\"HEXISTS\", KEYS[1], \"is-used\") == 1 then\n" + " return " + RESULT_ALREADY_USED.ToString() + "\n" + "end\n"); arguments.Add("1"); iArgument++; // mark this authorization code as "used" (which indicates that it has been submitted to the token endpoint) luaBuilder.Append( "if redis.call(\"HSET\", KEYS[1], \"is-used\", ARGV[" + iArgument.ToString() + "]) == 0 then\n" + " return " + RESULT_DATA_CORRUPTION.ToString() + "\n" + "end\n"); arguments.Add("1"); iArgument++; // if the authorization code expired while we were updating the flag (and therefore we RE-CREATED the key), delete the accidentally-just-recreated key luaBuilder.Append( "if redis.call(\"PTTL\", KEYS[1]) == -1 then\n" + " redis.call(\"DEL\", KEYS[1])\n" + " return " + RESULT_DOES_NOT_EXIST.ToString() + "\n" + "end\n"); // luaBuilder.Append("return 1\n"); List <string> keys = new List <string>(); keys.Add(REDIS_PREFIX_OAUTH2CODE + REDIS_PREFIX_SEPARATOR + _id); long luaResult = await _redisClient.EvalAsync <string, string, long>(luaBuilder.ToString(), keys.ToArray(), arguments.ToArray()).ConfigureAwait(false); if (luaResult == 1) { _isUsed = true; return(true); } else { return(false); } }
public async Task SaveAuthCodeAsync() { if (_redisClient == null) { _redisClient = await Singletons.GetRedisClientAsync(); } bool objectIsNew = (_id == null); int RESULT_KEY_CONFLICT = -1; int RESULT_DATA_CORRUPTION = -2; // generate Lua script (which we will use to commit all changes--or the new record--in an atomic transaction) StringBuilder luaBuilder = new StringBuilder(); List <string> arguments = new List <string>(); int iArgument = 1; if (objectIsNew) { // for new authorization codes: if a token with this authcode-id already exists, return 0...and we will try again. luaBuilder.Append( "if redis.call(\"EXISTS\", KEYS[1]) == 1 then\n" + " return " + RESULT_KEY_CONFLICT.ToString() + "\n" + "end\n"); } if (_clientId_IsDirty) { if (_clientId != null) { // if there is a client assigned to this authorization code, save it. luaBuilder.Append( "if redis.call(\"HSET\", KEYS[1], \"client-id\", ARGV[" + iArgument.ToString() + "]) == 0 then\n" + (objectIsNew ? " redis.call(\"DEL\", KEYS[1])\n" : "") + " return " + RESULT_DATA_CORRUPTION.ToString() + "\n" + "end\n"); arguments.Add(_clientId); iArgument++; } else { // if the client-id has been removed, delete it. luaBuilder.Append("redis.call(\"HDEL\", KEYS[1], \"client-id\")\n"); } // clear the dirty flag _clientId_IsDirty = false; } if (_accountId_IsDirty) { if (_accountId != null) { // if there is an account assigned to this authorization code, save it. luaBuilder.Append( "if redis.call(\"HSET\", KEYS[1], \"account-id\", ARGV[" + iArgument.ToString() + "]) == 0 then\n" + (objectIsNew ? " redis.call(\"DEL\", KEYS[1])\n" : "") + " return " + RESULT_DATA_CORRUPTION.ToString() + "\n" + "end\n"); arguments.Add(_accountId); iArgument++; } else { // if the account-id has been removed, delete it. luaBuilder.Append("redis.call(\"HDEL\", KEYS[1], \"account-id\")\n"); } // clear the dirty flag _accountId_IsDirty = false; } if (_userId_IsDirty) { if (_userId != null) { // if there is a user assigned to this authorization code, save it. luaBuilder.Append( "if redis.call(\"HSET\", KEYS[1], \"user-id\", ARGV[" + iArgument.ToString() + "]) == 0 then\n" + (objectIsNew ? " redis.call(\"DEL\", KEYS[1])\n" : "") + " return " + RESULT_DATA_CORRUPTION.ToString() + "\n" + "end\n"); arguments.Add(_userId); iArgument++; } else { // if the user-id has been removed, delete it. luaBuilder.Append("redis.call(\"HDEL\", KEYS[1], \"user-id\")\n"); } // clear the dirty flag _userId_IsDirty = false; } if (_redirectUri_IsDirty) { if (_redirectUri != null) { // if there is a redirect-uri assigned to this authorization code, save it. luaBuilder.Append( "if redis.call(\"HSET\", KEYS[1], \"redirect-uri\", ARGV[" + iArgument.ToString() + "]) == 0 then\n" + (objectIsNew ? " redis.call(\"DEL\", KEYS[1])\n" : "") + " return " + RESULT_DATA_CORRUPTION.ToString() + "\n" + "end\n"); arguments.Add(_redirectUri); iArgument++; } else { // if the redirect-uri has been removed, delete it. luaBuilder.Append("redis.call(\"HDEL\", KEYS[1], \"redirect-uri\")\n"); } // clear the dirty flag _redirectUri_IsDirty = false; } // NOTE: when a token is assigned to an authorization code, we re-save the code with the token (in case the same token is reused...in which case we can revoke the already-allocated token) if (_tokenId_IsDirty) { if (_tokenId != null) { // if there is a token assigned to this authorization code, save it. luaBuilder.Append( "if redis.call(\"HSET\", KEYS[1], \"token-id\", ARGV[" + iArgument.ToString() + "]) == 0 then\n" + (objectIsNew ? " redis.call(\"DEL\", KEYS[1])\n" : "") + " return 0\n" + "end\n"); arguments.Add(_tokenId); iArgument++; } else { // if the token-id has been removed, delete it. luaBuilder.Append("redis.call(\"HDEL\", KEYS[1], \"token-id\")\n"); } // clear the dirty flag _tokenId_IsDirty = false; } if (_expirationTime_IsDirty) { if (_expirationTime != null) { double expirationMilliseconds = _expirationTime.Value.Subtract(DateTimeOffset.UtcNow).TotalMilliseconds; if (expirationMilliseconds >= 0) { long expirationMillisecondsAsWholeNumber = (long)expirationMilliseconds; // if there is an expiration time assigned to this authorization code, set it. luaBuilder.Append( "if redis.call(\"PEXPIRE\", KEYS[1], ARGV[" + iArgument.ToString() + "]) == 0 then\n" + (objectIsNew ? " redis.call(\"DEL\", KEYS[1])\n" : "") + " return 0\n" + "end\n"); arguments.Add(expirationMillisecondsAsWholeNumber.ToString()); iArgument++; } } else { // if the expiration has been removed, delete it. luaBuilder.Append( "if redis.call(\"PERSIST\", KEYS[1]) == 0 then\n" + (objectIsNew ? " redis.call(\"DEL\", KEYS[1])\n" : "") + " return 0\n" + "end\n"); } // clear the dirty flag _expirationTime_IsDirty = false; } // luaBuilder.Append("return 1\n"); long luaResult = 0; for (int iRetry = 0; iRetry < (objectIsNew ? 1000 : 1); iRetry++) { if (objectIsNew) { // generate a 24-byte (144-bit) token_id _id = _authServerId + "-" + (new string(RandomHelper.CreateRandomCharacterSequence_Readable6bit_ForIdentifiers(24))); } List <string> keys = new List <string>(); keys.Add(REDIS_PREFIX_OAUTH2CODE + REDIS_PREFIX_SEPARATOR + _id); luaResult = await _redisClient.EvalAsync <string, string, long>(luaBuilder.ToString(), keys.ToArray(), arguments.ToArray()).ConfigureAwait(false); //if we were able to create a key, break out of this loop; otherwise, try generating new keys up to ten times. if (luaResult == 1) { break; } else if (luaResult == RESULT_KEY_CONFLICT) { // key name conflict; try again } else if (luaResult == RESULT_DATA_CORRUPTION) { // data corruption throw new Exception("Critical Redis error!"); } else { // unknown error throw new Exception("Critical Redis error!"); } } if (luaResult < 0) { throw new Exception("Critical Redis error!"); } }
public static async Task <OAuth2Client> LoadClientAsync(string clientId, LoadClientOptions options) { if ((options & LoadClientOptions.LocalClients) == LoadClientOptions.LocalClients) { if (_redisClient == null) { _redisClient = await Singletons.GetRedisClientAsync(); } string fullyQualifiedClientKey = REDIS_PREFIX_CLIENT + REDIS_PREFIX_SEPARATOR + clientId; bool localClientExists = (await _redisClient.ExistsAsync(new string[] { fullyQualifiedClientKey }) > 0); if (localClientExists) { Dictionary <string, string> clientDictionary = await _redisClient.HashGetAllASync <string, string, string>(fullyQualifiedClientKey); string clientIsCachedAsString = clientDictionary.ContainsKey("cached") ? clientDictionary["cached"] : null; bool clientIsCached = (clientIsCachedAsString != null && clientIsCachedAsString != "0"); string timeCreatedAsString = clientDictionary.ContainsKey("time-created") ? clientDictionary["time-created"] : null; Int64? timeCreatedInUnixMicroseconds = null; Int64 timeCreatedAsInt64; if (timeCreatedAsString != null && Int64.TryParse(timeCreatedAsString, out timeCreatedAsInt64)) { timeCreatedInUnixMicroseconds = timeCreatedAsInt64; } string timeUpdatedAsString = clientDictionary.ContainsKey("time-updated") ? clientDictionary["time-updated"] : null; Int64? timeUpdatedInUnixMicroseconds = null; Int64 timeUpdatedAsInt64; if (timeUpdatedAsString != null && Int64.TryParse(timeUpdatedAsString, out timeUpdatedAsInt64)) { timeUpdatedInUnixMicroseconds = timeUpdatedAsInt64; } OAuth2Client resultClient = new OAuth2Client(); resultClient._id = clientId; ParsingHelper.ServerDetails?loginServerDetails = ParsingHelper.ExtractServerDetailsFromAccountServerIdIdentifier(clientId); if (loginServerDetails == null) { throw new Exception(); } resultClient._loginServerDetails = loginServerDetails.Value; // resultClient._accountId = clientDictionary.ContainsKey("account-id") ? clientDictionary["account-id"] : null; // if (clientDictionary.ContainsKey("issued-at")) { long issuedAtAsLong; if (long.TryParse(clientDictionary["issued-at"], out issuedAtAsLong)) { resultClient._issuedAt = DateTimeOffset.FromUnixTimeSeconds(issuedAtAsLong); } } // if (clientDictionary.ContainsKey("secret-hash")) { // load the base64-encoded binary hash of the client secret resultClient._secret = clientDictionary["secret-hash"]; resultClient._secretIsHashed = true; } else { resultClient._secret = null; resultClient._secretIsHashed = false; } // if (resultClient._secret != null) { if (clientDictionary.ContainsKey("expires-at")) { long expiresAtAsLong; if (long.TryParse(clientDictionary["expires-at"], out expiresAtAsLong)) { resultClient._expiresAt = DateTimeOffset.FromUnixTimeSeconds(expiresAtAsLong); } } } // resultClient._softwareId = clientDictionary.ContainsKey("software-id") ? clientDictionary["software-id"] : null; // resultClient._softwareVersion = clientDictionary.ContainsKey("software-version") ? clientDictionary["software-version"] : null; // if (!clientDictionary.ContainsKey("token-endpoint-auth-method")) { // this field is required; return null if it is not present. return(null); } resultClient._tokenEndpointAuthMethod = OAuth2Convert.ConvertStringToTokenEndpointAuthMethod(clientDictionary["token-endpoint-auth-method"]).Value; // resultClient._redirectUris = await _redisClient.SetMembersAsync <string, string>(fullyQualifiedClientKey + REDIS_SUFFIX_SEPARATOR + REDIS_SUFFIX_REDIRECT_URIS).ConfigureAwait(false); // List <string> grantTypesAsStrings = await _redisClient.SetMembersAsync <string, string>(fullyQualifiedClientKey + REDIS_SUFFIX_SEPARATOR + REDIS_SUFFIX_GRANT_TYPES).ConfigureAwait(false); if (grantTypesAsStrings != null) { resultClient._grantTypes = new ListWithDirtyFlag <OAuth2GrantType>(); foreach (string grantTypeAsString in grantTypesAsStrings) { resultClient._grantTypes.Add(OAuth2Convert.ConvertStringToGrantType(grantTypeAsString).Value); } } // List <string> responseTypesAsStrings = await _redisClient.SetMembersAsync <string, string>(fullyQualifiedClientKey + REDIS_SUFFIX_SEPARATOR + REDIS_SUFFIX_RESPONSE_TYPES).ConfigureAwait(false); if (responseTypesAsStrings != null) { resultClient._responseTypes = new ListWithDirtyFlag <OAuth2ResponseType>(); foreach (string responseTypeAsString in responseTypesAsStrings) { resultClient._responseTypes.Add(OAuth2Convert.ConvertStringToResponseType(responseTypeAsString).Value); } } resultClient._scopes = await _redisClient.SetMembersAsync <string, string>(fullyQualifiedClientKey + REDIS_SUFFIX_SEPARATOR + REDIS_SUFFIX_SCOPES).ConfigureAwait(false); // if (clientDictionary.ContainsKey("registration-token-hash")) { // load the base64-encoded binary hash of the client registration token resultClient._registrationToken = clientDictionary["registration-token-hash"]; resultClient._registrationTokenIsHashed = true; } else { resultClient._registrationToken = null; resultClient._registrationTokenIsHashed = false; } resultClient._isCached = clientIsCached; resultClient._timeCreatedInUnixMicroseconds = timeCreatedInUnixMicroseconds; resultClient._timeUpdatedInUnixMicroseconds = timeUpdatedInUnixMicroseconds; return(resultClient); } } // client could not be found return(null); }
public async Task SaveClientAsync() { // we only support saving a local client (i.e. not updating a remote client) if (_isCached) { throw new InvalidOperationException(); } if (_redisClient == null) { _redisClient = await Singletons.GetRedisClientAsync(); } bool objectIsNew = (_timeCreatedInUnixMicroseconds == null); int RESULT_KEY_CONFLICT = -1; int RESULT_DATA_CORRUPTION = -2; int RESULT_UPDATED_SINCE_LOAD = -3; // get current server time long newTimeUpdatedInUnixMicroseconds = await _redisClient.TimeAsync(); if (newTimeUpdatedInUnixMicroseconds < 0) { throw new Exception("Critical Redis error!"); } if (newTimeUpdatedInUnixMicroseconds < _timeUpdatedInUnixMicroseconds) { throw new Exception("Critical Redis error!"); } if (objectIsNew) { // assign clientId, clientSecret, issuedAt time and clientRefreshToken // for non-implicit grant types: generate a clientSecret if ((_grantTypes.Contains(OAuth2GrantType.AuthorizationCode) && _tokenEndpointAuthMethod != OAuth2TokenEndpointAuthMethod.None) || _grantTypes.Contains(OAuth2GrantType.ClientCredentials) || _grantTypes.Contains(OAuth2GrantType.Password)) { _secret = new string(RandomHelper.CreateRandomCharacterSequence_Readable6bit_ForIdentifiers(32)); _secret_IsDirty = true; _expiresAt = null; _expiresAt_IsDirty = true; } _issuedAt = DateTimeOffset.UtcNow; // create client registration token (32-byte == 192-bit) /* NOTE: if we ever want to look up the registration token in the #oauth2tokens collections, we will need to start making sure the token is unique-for-server here */ _registrationToken = _loginServerDetails.ToAccountIdServerIdIdentifierString() + "-" + (new string(RandomHelper.CreateRandomCharacterSequence_Readable6bit_ForIdentifiers(32))); _registrationToken_IsDirty = true; } // generate Lua script (which we will use to commit all changes--or the new record--in an atomic transaction) StringBuilder luaBuilder = new StringBuilder(); List <string> arguments = new List <string>(); int iArgument = 1; if (objectIsNew) { // for new clients: if a client with this client-id already exists, return 0...and we will try again. luaBuilder.Append( "if redis.call(\"EXISTS\", KEYS[1]) == 1 then\n" + " return " + RESULT_KEY_CONFLICT.ToString() + "\n" + "end\n"); } else { // for updated: make sure that the "time-created" timestamp has not changed (i.e. that a new key has not replaced the old key) luaBuilder.Append("local time_created = redis.call(\"HGET\", KEYS[1], \"time-created\")\n"); luaBuilder.Append("if time_created ~= ARGV[" + iArgument.ToString() + "] then\n" + " return " + RESULT_KEY_CONFLICT.ToString() + "\n" + "end\n"); arguments.Add(_timeCreatedInUnixMicroseconds.ToString()); iArgument++; // for updates: make sure that our old "time-updated" timestamp has not changed luaBuilder.Append("local old_time_updated = redis.call(\"HGET\", KEYS[1], \"time-updated\")\n"); luaBuilder.Append("if old_time_updated ~= ARGV[" + iArgument.ToString() + "] then\n" + " return " + RESULT_UPDATED_SINCE_LOAD.ToString() + "\n" + "end\n"); arguments.Add(_timeUpdatedInUnixMicroseconds.ToString()); iArgument++; } // if (objectIsNew) { luaBuilder.Append( "if redis.call(\"HSET\", KEYS[1], \"time-created\", ARGV[" + iArgument.ToString() + "]) == 0 then\n" + " return " + RESULT_DATA_CORRUPTION.ToString() + "\n" + "end\n"); arguments.Add(newTimeUpdatedInUnixMicroseconds.ToString()); iArgument++; } // luaBuilder.Append( "if redis.call(\"HSET\", KEYS[1], \"time-updated\", ARGV[" + iArgument.ToString() + "]) == 0 then\n" + " return " + RESULT_DATA_CORRUPTION.ToString() + "\n" + "end\n"); arguments.Add(newTimeUpdatedInUnixMicroseconds.ToString()); iArgument++; // if (_accountId_IsDirty) { if (_accountId != null) { // if there is an account assigned to this token, save it. luaBuilder.Append( "if redis.call(\"HSET\", KEYS[1], \"account-id\", ARGV[" + iArgument.ToString() + "]) == 0 then\n" + (objectIsNew ? " redis.call(\"DEL\", KEYS[1])\n" : "") + " return " + RESULT_DATA_CORRUPTION.ToString() + "\n" + "end\n"); arguments.Add(_accountId); iArgument++; } else { // if the account-id has been removed, delete it. luaBuilder.Append("redis.call(\"HDEL\", KEYS[1], \"account-id\")\n"); } // clear the dirty flag _accountId_IsDirty = false; } if (_secret_IsDirty) { if (_secret != null) { // if there is a secret assigned to this client, save it. luaBuilder.Append( "if redis.call(\"HSET\", KEYS[1], \"secret-hash\", ARGV[" + iArgument.ToString() + "]) == 0 then\n" + (objectIsNew ? " redis.call(\"DEL\", KEYS[1])\n" : "") + " return " + RESULT_DATA_CORRUPTION.ToString() + "\n" + "end\n"); arguments.Add(Convert.ToBase64String(HashClientSecret(Encoding.UTF8.GetBytes(_secret)))); iArgument++; } else { // if the secret has been removed, delete it. luaBuilder.Append("redis.call(\"HDEL\", KEYS[1], \"secret-hash\")\n"); } // clear the dirty flag _secret_IsDirty = false; } if (objectIsNew) { // set the issued-at time luaBuilder.Append( "if redis.call(\"HSET\", KEYS[1], \"issued-at\", ARGV[" + iArgument.ToString() + "]) == 0 then\n" + (objectIsNew ? " redis.call(\"DEL\", KEYS[1])\n" : "") + " return " + RESULT_DATA_CORRUPTION.ToString() + "\n" + "end\n"); arguments.Add(_issuedAt.Value.ToUnixTimeSeconds().ToString()); iArgument++; } // set the expires-at time if (_expiresAt_IsDirty) { if (_expiresAt != null) { long expiresAtAsLong = _expiresAt.Value.ToUnixTimeSeconds(); luaBuilder.Append( "if redis.call(\"HSET\", KEYS[1], \"expires-at\", ARGV[" + iArgument.ToString() + "]) == 0 then\n" + (objectIsNew ? " redis.call(\"DEL\", KEYS[1])\n" : "") + " return " + RESULT_DATA_CORRUPTION.ToString() + "\n" + "end\n"); arguments.Add(expiresAtAsLong.ToString()); iArgument++; } else { // if the expiration has been removed, delete it. luaBuilder.Append("redis.call(\"HDEL\", KEYS[1], \"expires-at\")\n"); } // clear the dirty flag _expiresAt_IsDirty = false; } if (_softwareId_IsDirty) { luaBuilder.Append( "if redis.call(\"HSET\", KEYS[1], \"software-id\", ARGV[" + iArgument.ToString() + "]) == 0 then\n" + (objectIsNew ? " redis.call(\"DEL\", KEYS[1])\n" : "") + " return " + RESULT_DATA_CORRUPTION.ToString() + "\n" + "end\n"); arguments.Add(_softwareId); iArgument++; // clear the dirty flag _softwareId_IsDirty = false; } if (_softwareVersion_IsDirty) { if (_softwareVersion != null) { // set the softwareVersion luaBuilder.Append( "if redis.call(\"HSET\", KEYS[1], \"software-version\", ARGV[" + iArgument.ToString() + "]) == 0 then\n" + (objectIsNew ? " redis.call(\"DEL\", KEYS[1])\n" : "") + " return " + RESULT_DATA_CORRUPTION.ToString() + "\n" + "end\n"); arguments.Add(_softwareVersion); iArgument++; } else { // if the software-version has been removed, delete it. luaBuilder.Append("redis.call(\"HDEL\", KEYS[1], \"software-version\")\n"); } // clear the dirty flag _softwareVersion_IsDirty = false; } // set the tokenEndpointAuthMethod if (_tokenEndpointAuthMethod_IsDirty) { luaBuilder.Append( "if redis.call(\"HSET\", KEYS[1], \"token-endpoint-auth-method\", ARGV[" + iArgument.ToString() + "]) == 0 then\n" + (objectIsNew ? " redis.call(\"DEL\", KEYS[1])\n" : "") + " return " + RESULT_DATA_CORRUPTION.ToString() + "\n" + "end\n"); arguments.Add(OAuth2Convert.ConvertTokenEndpointAuthMethodToString(_tokenEndpointAuthMethod)); iArgument++; // clear the dirty flag _tokenEndpointAuthMethod_IsDirty = false; } //populate the set of redirect-uris if (_redirectUris.IsDirty) { luaBuilder.Append(objectIsNew ? "" : "redis.call(\"DEL\", KEYS[2])\n"); foreach (string redirectUri in _redirectUris) { luaBuilder.Append( "if redis.call(\"SADD\", KEYS[2], ARGV[" + iArgument.ToString() + "]) == 0 then\n" + (objectIsNew ? " redis.call(\"DEL\", KEYS[1])\n" : "") + (objectIsNew ? " redis.call(\"DEL\", KEYS[2])\n" : "") + " return " + RESULT_DATA_CORRUPTION.ToString() + "\n" + "end\n"); arguments.Add(redirectUri); iArgument++; } // clear the dirty flag _redirectUris.IsDirty = false; } // populate the set of grant-types if (_grantTypes.IsDirty) { luaBuilder.Append(objectIsNew ? "" : "redis.call(\"DEL\", KEYS[3])\n"); foreach (OAuth2GrantType grantType in _grantTypes) { luaBuilder.Append( "if redis.call(\"SADD\", KEYS[3], ARGV[" + iArgument.ToString() + "]) == 0 then\n" + (objectIsNew ? " redis.call(\"DEL\", KEYS[1])\n" : "") + (objectIsNew ? " redis.call(\"DEL\", KEYS[2])\n" : "") + (objectIsNew ? " redis.call(\"DEL\", KEYS[3])\n" : "") + " return " + RESULT_DATA_CORRUPTION.ToString() + "\n" + "end\n"); arguments.Add(OAuth2Convert.ConvertGrantTypeToString(grantType)); iArgument++; } // clear the dirty flag _grantTypes.IsDirty = false; } // populate the set of response-types if (_responseTypes.IsDirty) { luaBuilder.Append(objectIsNew ? "" : "redis.call(\"DEL\", KEYS[4])\n"); foreach (OAuth2ResponseType responseType in _responseTypes) { luaBuilder.Append( "if redis.call(\"SADD\", KEYS[4], ARGV[" + iArgument.ToString() + "]) == 0 then\n" + (objectIsNew ? " redis.call(\"DEL\", KEYS[1])\n" : "") + (objectIsNew ? " redis.call(\"DEL\", KEYS[2])\n" : "") + (objectIsNew ? " redis.call(\"DEL\", KEYS[3])\n" : "") + (objectIsNew ? " redis.call(\"DEL\", KEYS[4])\n" : "") + " return " + RESULT_DATA_CORRUPTION.ToString() + "\n" + "end\n"); arguments.Add(OAuth2Convert.ConvertResponseTypeToString(responseType)); iArgument++; } // clear the dirty flag _responseTypes.IsDirty = false; } // populate the set of scopes if (_scopes.IsDirty) { luaBuilder.Append(objectIsNew ? "" : "redis.call(\"DEL\", KEYS[5])\n"); foreach (string scope in _scopes) { luaBuilder.Append( "if redis.call(\"SADD\", KEYS[5], ARGV[" + iArgument.ToString() + "]) == 0 then\n" + (objectIsNew ? " redis.call(\"DEL\", KEYS[1])\n" : "") + (objectIsNew ? " redis.call(\"DEL\", KEYS[2])\n" : "") + (objectIsNew ? " redis.call(\"DEL\", KEYS[3])\n" : "") + (objectIsNew ? " redis.call(\"DEL\", KEYS[4])\n" : "") + (objectIsNew ? " redis.call(\"DEL\", KEYS[5])\n" : "") + " return " + RESULT_DATA_CORRUPTION.ToString() + "\n" + "end\n"); arguments.Add(scope); iArgument++; } // clear the dirty flag _scopes.IsDirty = false; } // if (_registrationToken_IsDirty) { if (_registrationToken != null) { // if there is a registration token assigned to this client, save it. luaBuilder.Append( "if redis.call(\"HSET\", KEYS[1], \"registration-token-hash\", ARGV[" + iArgument.ToString() + "]) == 0 then\n" + (objectIsNew ? " redis.call(\"DEL\", KEYS[1])\n" : "") + " return " + RESULT_DATA_CORRUPTION.ToString() + "\n" + "end\n"); arguments.Add(Convert.ToBase64String(HashClientRegistrationToken(Encoding.UTF8.GetBytes(_registrationToken)))); iArgument++; } else { // if the registration token has been removed, delete it. // NOTE: this operation is technically supported by spec--but removing the ability for a client to manage a token (usually because of third-party meddling) can have some unfortunate consequences as well luaBuilder.Append("redis.call(\"HDEL\", KEYS[1], \"registration-token-hash\")\n"); } // clear the dirty flag _registrationToken_IsDirty = false; } // luaBuilder.Append("return 1\n"); long luaResult = 0; for (int iRetry = 0; iRetry < 1000; iRetry++) { if (objectIsNew) { // generate a 32-byte (192-bit) client_id _id = _loginServerDetails.ToAccountIdServerIdIdentifierString() + "-" + (new string(RandomHelper.CreateRandomCharacterSequence_Readable6bit_ForIdentifiers(32))); } List <string> keys = new List <string>(); keys.Add(REDIS_PREFIX_CLIENT + REDIS_PREFIX_SEPARATOR + _id); keys.Add(REDIS_PREFIX_CLIENT + REDIS_PREFIX_SEPARATOR + _id + REDIS_SUFFIX_SEPARATOR + REDIS_SUFFIX_REDIRECT_URIS); keys.Add(REDIS_PREFIX_CLIENT + REDIS_PREFIX_SEPARATOR + _id + REDIS_SUFFIX_SEPARATOR + REDIS_SUFFIX_GRANT_TYPES); keys.Add(REDIS_PREFIX_CLIENT + REDIS_PREFIX_SEPARATOR + _id + REDIS_SUFFIX_SEPARATOR + REDIS_SUFFIX_RESPONSE_TYPES); keys.Add(REDIS_PREFIX_CLIENT + REDIS_PREFIX_SEPARATOR + _id + REDIS_SUFFIX_SEPARATOR + REDIS_SUFFIX_SCOPES); luaResult = await _redisClient.EvalAsync <string, string, long>(luaBuilder.ToString(), keys.ToArray(), arguments.ToArray()).ConfigureAwait(false); // NOTE: the result will contain a negative integer (error) or one (success) // if we were able to create a key, break out of this loop; otherwise, try generating new keys up to ten times. if (luaResult == 1) { // save our "time-updated" timestamp _timeUpdatedInUnixMicroseconds = newTimeUpdatedInUnixMicroseconds; if (objectIsNew) { // save our "time-created" timestamp _timeCreatedInUnixMicroseconds = newTimeUpdatedInUnixMicroseconds; if (_accountId == null) { // if the client belongs to the entire system (and not to an account), add it to the root client collection. await _redisClient.SetAddAsync <string, string>(REDIS_PREFIX_LOGIN_SERVICE + REDIS_PREFIX_SEPARATOR + REDIS_ASTERISK + REDIS_SLASH + _loginServerDetails.ServerId + REDIS_SUFFIX_SEPARATOR + REDIS_SUFFIX_OAUTH2CLIENTS, new string[] { _id }); } else { // if the client belongs to the account (and not to the user), add it to the account's client collection. await _redisClient.SetAddAsync <string, string>(REDIS_PREFIX_LOGIN_SERVICE + REDIS_PREFIX_SEPARATOR + _accountId + REDIS_SLASH + _loginServerDetails.ServerId + REDIS_SUFFIX_SEPARATOR + REDIS_SUFFIX_OAUTH2CLIENTS, new string[] { _id }); } } break; } else if (luaResult == RESULT_KEY_CONFLICT) { // key name conflict; try again } else if (luaResult == RESULT_DATA_CORRUPTION) { // data corruption throw new Exception("Critical Redis error!"); } else if (luaResult == RESULT_UPDATED_SINCE_LOAD) { // token was updated since we loaded it; we need to reload the token, make the changes again, and then attempt to save it again throw new Exception("Critical Redis error!"); } else { // unknown error throw new Exception("Critical Redis error!"); } } if (luaResult < 0) { throw new Exception("Critical Redis error!"); } }