public static TokenSet ToTokenSet(this ActionstepCredential actionstepCredential)
        {
            if (actionstepCredential is null)
            {
                throw new ArgumentNullException(nameof(actionstepCredential));
            }

            if (actionstepCredential.Owner is null)
            {
                throw new ArgumentException(
                          $"The property '{nameof(ActionstepCredential.Owner)}' must be set to create a valid '{nameof(TokenSet)}'.");
            }

            if (actionstepCredential.ActionstepOrg is null)
            {
                throw new ArgumentException(
                          $"The property '{nameof(ActionstepCredential.ActionstepOrg)}' must be set to create a valid '{nameof(TokenSet)}'.");
            }

            var tokenSet = new TokenSet(
                actionstepCredential.AccessToken,
                actionstepCredential.TokenType,
                actionstepCredential.ExpiresIn,
                actionstepCredential.ApiEndpoint,
                actionstepCredential.ActionstepOrg.Key,
                actionstepCredential.RefreshToken,
                Instant.FromDateTimeUtc(actionstepCredential.ReceivedAtUtc),
                actionstepCredential.Owner.Id.ToString(CultureInfo.InvariantCulture),
                actionstepCredential.Id.ToString(CultureInfo.InvariantCulture),
                actionstepCredential.IdToken,
                actionstepCredential.RevokedAtUtc.HasValue ? Instant.FromDateTimeUtc(actionstepCredential.RevokedAtUtc.Value) : (Instant?)null);

            if (actionstepCredential.LockId != Guid.Empty)
            {
                tokenSet.RefreshLockInfo = new TokenSetRefreshLockInfo(
                    Instant.FromDateTimeUtc(actionstepCredential.LockExpiresAtUtc),
                    actionstepCredential.LockId);
            }

            return(tokenSet);
        }
        private void PrepareTestData(IServiceProvider serviceProvider)
        {
            var dbContext     = serviceProvider.GetService <WCADbContext>();
            var testUser      = dbContext.GetTestUser();
            var actionstepOrg = new ActionstepOrg
            {
                Key            = "testOrg",
                Title          = "testOrg",
                CreatedBy      = testUser,
                DateCreatedUtc = DateTime.UtcNow
            };

            dbContext.ActionstepOrgs.Add(actionstepOrg);
            dbContext.SaveChanges();

            var startTime = DateTime.UtcNow;

            var actionstepCredential = new ActionstepCredential
            {
                AccessToken          = "testToken",
                AccessTokenExpiryUtc = startTime.AddMinutes(20),
                ActionstepOrg        = actionstepOrg,
                ApiEndpoint          = new Uri("http://test-endpoint/api"),
                CreatedBy            = testUser,
                DateCreatedUtc       = startTime,
                ExpiresIn            = 60 * 20,
                ReceivedAtUtc        = startTime,
                Owner                 = testUser,
                RefreshToken          = "testRefreshToken",
                RefreshTokenExpiryUtc = startTime.AddDays(20),
                TokenType             = "bearer"
            };

            dbContext.ActionstepCredentials.Add(actionstepCredential);
            dbContext.SaveChanges();
        }
        public static void UpdateFromTokenSet(this ActionstepCredential actionstepCredential, TokenSet tokenSet)
        {
            if (actionstepCredential is null)
            {
                throw new ArgumentNullException(nameof(actionstepCredential));
            }
            if (tokenSet is null)
            {
                throw new ArgumentNullException(nameof(tokenSet));
            }

            if (!string.IsNullOrEmpty(tokenSet.Id))
            {
                if (tokenSet.Id != actionstepCredential.Id.ToString(CultureInfo.InvariantCulture))
                {
                    throw new TokenSetIdDoesntMatchActionstepCredentialIdException(actionstepCredential.Id, tokenSet.Id);
                }
            }

            if (tokenSet.OrgKey != actionstepCredential.ActionstepOrg?.Key)
            {
                throw new TokenSetOrgDoesntMatchActionstepCredentialOrgException(actionstepCredential.ActionstepOrg?.Key, tokenSet.OrgKey, tokenSet.Id);
            }

            if (!string.IsNullOrEmpty(tokenSet.UserId))
            {
                if (tokenSet.UserId != actionstepCredential.Owner?.Id)
                {
                    throw new TokenSetUserDoesntMatchActionstepCredentialUserException(actionstepCredential.Owner?.Id, tokenSet.UserId, tokenSet.Id);
                }
            }

            actionstepCredential.AccessToken           = tokenSet.AccessToken;
            actionstepCredential.AccessTokenExpiryUtc  = tokenSet.AccessTokenExpiresAt.ToDateTimeUtc();
            actionstepCredential.TokenType             = tokenSet.TokenType;
            actionstepCredential.ExpiresIn             = tokenSet.ExpiresIn;
            actionstepCredential.ApiEndpoint           = tokenSet.ApiEndpoint;
            actionstepCredential.RefreshToken          = tokenSet.RefreshToken;
            actionstepCredential.RefreshTokenExpiryUtc = tokenSet.RefreshTokenExpiresAt.ToDateTimeUtc();
            actionstepCredential.ReceivedAtUtc         = tokenSet.ReceivedAt.ToDateTimeUtc();
            actionstepCredential.IdToken = tokenSet.IdToken;

            if (tokenSet.RevokedAt.HasValue)
            {
                actionstepCredential.RevokedAtUtc = tokenSet.RevokedAt.Value.ToDateTimeUtc();
            }
            else
            {
                actionstepCredential.RevokedAtUtc = null;
            }

            if (tokenSet.RefreshLockInfo is null)
            {
                actionstepCredential.LockExpiresAtUtc = DateTime.MinValue;
                actionstepCredential.LockId           = Guid.Empty;
            }
            else
            {
                actionstepCredential.LockExpiresAtUtc = tokenSet.RefreshLockInfo.LockExpiresAt.ToDateTimeUtc();
                actionstepCredential.LockId           = tokenSet.RefreshLockInfo.LockId;
            }
        }
        /// <summary>
        ///
        /// </summary>
        /// <param name="tokenSet"></param>
        /// <returns></returns>
        /// <exception cref="KeyNotFoundException">Thrown if a tokenSet could not be found with the specified ID.</exception>
        public async Task <TokenSet> AddOrUpdateTokenSet(TokenSet tokenSet)
        {
            // Use a separate scope for each method call to prevent DBContext caching
            using var scope     = _serviceScopeFactory.CreateScope();
            using var dbContext = scope.ServiceProvider.GetService <WCADbContext>();

            if (tokenSet is null)
            {
                throw new ArgumentNullException(nameof(tokenSet));
            }

            _telemetry.TrackEvent(nameof(AddOrUpdateTokenSet), new Dictionary <string, string>()
            {
                { "TokenSetId", tokenSet.Id },
                { "RefreshLockInfo ExpiresAt", tokenSet.RefreshLockInfo?.LockExpiresAt.ToString() },
                { "RefreshLockInfo LockId", tokenSet.RefreshLockInfo?.LockId.ToString() },
            });

            ActionstepCredential actionstepCredential = null;

            var tokenSetUser = await dbContext.Users.FindAsync(tokenSet.UserId);

            if (tokenSetUser is null)
            {
                throw new UserNotFoundException("Couldn't find user attempting to add or update TokenSet.", tokenSet.UserId);
            }

            if (!string.IsNullOrEmpty(tokenSet.Id))
            {
                var tokenSetId = int.Parse(tokenSet.Id, CultureInfo.InvariantCulture);
                actionstepCredential = await dbContext.ActionstepCredentials
                                       .Include(c => c.Owner)
                                       .Include(c => c.ActionstepOrg)
                                       .SingleOrDefaultAsync(c => c.Id == tokenSetId);
            }

            // If not found by id, attempt to find by user and org
            if (actionstepCredential is null)
            {
                actionstepCredential = await dbContext.ActionstepCredentials
                                       .Include(c => c.Owner)
                                       .Include(c => c.ActionstepOrg)
                                       .ForOwnerAndOrg(tokenSetUser, tokenSet.OrgKey)
                                       .SingleOrDefaultAsync();
            }

            var now = _clock.GetCurrentInstant().ToDateTimeUtc();

            // If still null, then we need to create it
            if (actionstepCredential is null)
            {
                actionstepCredential                = new ActionstepCredential();
                actionstepCredential.Owner          = tokenSetUser;
                actionstepCredential.CreatedBy      = tokenSetUser;
                actionstepCredential.DateCreatedUtc = now;
                dbContext.ActionstepCredentials.Add(actionstepCredential);
            }

            if (actionstepCredential.ActionstepOrg == null || actionstepCredential.ActionstepOrg.Key == null)
            {
                var orgToAssociate = dbContext.ActionstepOrgs.Where(o => o.Key == tokenSet.OrgKey).SingleOrDefault();

                if (orgToAssociate == null)
                {
                    var newOrg = new ActionstepOrg()
                    {
                        Key            = tokenSet.OrgKey,
                        Title          = tokenSet.OrgKey,
                        CreatedBy      = actionstepCredential.Owner,
                        DateCreatedUtc = now,
                        UpdatedBy      = actionstepCredential.Owner,
                        LastUpdatedUtc = now,
                    };

                    EntityEntry <ActionstepOrg> addedOrgEntity = dbContext.ActionstepOrgs.Add(newOrg);
                    orgToAssociate = addedOrgEntity.Entity;
                }

                actionstepCredential.ActionstepOrg = orgToAssociate;
            }
            else
            {
                if (!actionstepCredential.ActionstepOrg.Key.Equals(tokenSet.OrgKey, StringComparison.InvariantCulture))
                {
                    throw new TokenSetOrgKeyMismatchException(
                              "An attempt was made to update a TokenSet, however the Actionstep org key supplied in the TokenSet doesn't match the saved org key stored.",
                              actionstepCredential.ActionstepOrg.Key,
                              tokenSet);
                }
            }

            /// Copies simple field mappings, except things like org because it's a complex relationship in <see cref="ActionstepCredential"/>.
            actionstepCredential.UpdateFromTokenSet(tokenSet);

            actionstepCredential.UpdatedBy        = tokenSetUser;
            actionstepCredential.LastUpdatedUtc   = now;
            actionstepCredential.ConcurrencyStamp = Guid.NewGuid();

            try
            {
                await dbContext.SaveChangesAsync();
            }
            catch (DbUpdateConcurrencyException dbConcurrencyException)
            {
                throw new TokenSetConcurrencyException(tokenSet, dbConcurrencyException);
            }

            return(actionstepCredential.ToTokenSet());
        }