Ejemplo n.º 1
0
        /// <summary>
        /// Updates the last login, writes to the person's history log, and saves changes to the database
        /// </summary>
        /// <param name="userName">Name of the user.</param>
        public static void UpdateLastLogin(string userName)
        {
            if (string.IsNullOrWhiteSpace(userName))
            {
                return;
            }

            int? personId     = null;
            bool impersonated = userName.StartsWith("rckipid=");

            using (var rockContext = new RockContext())
            {
                if (!impersonated)
                {
                    var userLogin = new UserLoginService(rockContext).Queryable().Where(a => a.UserName == userName).FirstOrDefault();
                    if (userLogin != null)
                    {
                        userLogin.LastLoginDateTime = RockDateTime.Now;
                        personId = userLogin.PersonId;
                        rockContext.SaveChanges();
                    }
                }
                else
                {
                    var impersonationToken = userName.Substring(8);
                    personId = new PersonService(rockContext).GetByImpersonationToken(impersonationToken, false, null)?.Id;
                }
            }

            if (personId == null)
            {
                return;
            }

            var relatedDataBuilder  = new System.Text.StringBuilder();
            int?relatedEntityTypeId = null;
            int?relatedEntityId     = null;

            if (impersonated)
            {
                var impersonatedByUser = HttpContext.Current?.Session?["ImpersonatedByUser"] as UserLogin;

                relatedEntityTypeId = EntityTypeCache.GetId <Rock.Model.Person>();
                relatedEntityId     = impersonatedByUser?.PersonId;

                if (impersonatedByUser != null)
                {
                    relatedDataBuilder.Append($" impersonated by { impersonatedByUser.Person.FullName }");
                }
            }

            if (HttpContext.Current != null && HttpContext.Current.Request != null)
            {
                string cleanUrl = PersonToken.ObfuscateRockMagicToken(HttpContext.Current.Request.UrlProxySafe().AbsoluteUri);

                // obfuscate the URL specified in the returnurl, just in case it contains any sensitive information (like a rckipid)
                Regex returnurlRegEx = new Regex(@"returnurl=([^&]*)");
                cleanUrl = returnurlRegEx.Replace(cleanUrl, "returnurl=XXXXXXXXXXXXXXXXXXXXXXXXXXXX");

                string clientIPAddress;
                try
                {
                    clientIPAddress = Rock.Web.UI.RockPage.GetClientIpAddress();
                }
                catch
                {
                    // if we get an exception getting the IP Address, just ignore it
                    clientIPAddress = "";
                }

                relatedDataBuilder.AppendFormat(
                    " to <span class='field-value'>{0}</span>, from <span class='field-value'>{1}</span>",
                    cleanUrl,
                    clientIPAddress);
            }

            var historyChangeList = new History.HistoryChangeList();
            var historyChange     = historyChangeList.AddChange(History.HistoryVerb.Login, History.HistoryChangeType.Record, userName);

            if (relatedDataBuilder.Length > 0)
            {
                historyChange.SetRelatedData(relatedDataBuilder.ToString(), null, null);
            }

            var historyList = HistoryService.GetChanges(typeof(Rock.Model.Person), Rock.SystemGuid.Category.HISTORY_PERSON_ACTIVITY.AsGuid(), personId.Value, historyChangeList, null, null, null, null, null);

            if (historyList.Any())
            {
                Task.Run(async() =>
                {
                    // Wait 1 second to allow all post save actions to complete
                    await Task.Delay(1000);
                    try
                    {
                        using (var rockContext = new RockContext())
                        {
                            rockContext.BulkInsert(historyList);
                        }
                    }
                    catch (SystemException ex)
                    {
                        ExceptionLogService.LogException(ex, null);
                    }
                });
            }
        }
Ejemplo n.º 2
0
        /// <summary>
        /// This method is called in the
        /// <see cref="M:Rock.Data.Model`1.PreSaveChanges(Rock.Data.DbContext,System.Data.Entity.Infrastructure.DbEntityEntry,System.Data.Entity.EntityState)" />
        /// method. Use it to populate <see cref="P:Rock.Data.Model`1.HistoryItems" /> if needed.
        /// These history items are queued to be written into the database post save (so that they
        /// are only written if the save actually occurs).
        /// </summary>
        /// <param name="dbContext">The database context.</param>
        /// <param name="entry">The entry.</param>
        /// <param name="state">The state.</param>
        protected override void BuildHistoryItems(Data.DbContext dbContext, DbEntityEntry entry, EntityState state)
        {
            // Sometimes, especially if the model is being deleted, some properties might not be
            // populated, but we can query to try to get their original value. We need to use a new
            // rock context to get the actual value from the DB
            var rockContext   = new RockContext();
            var service       = new FinancialPersonSavedAccountService(rockContext);
            var originalModel = service.Queryable("PersonAlias, FinancialPaymentDetail")
                                .FirstOrDefault(fpsa => fpsa.Id == Id);

            // Use the original value for the person alias or the new value if that is not set
            var personId = (originalModel?.PersonAlias ?? PersonAlias)?.PersonId;

            if (!personId.HasValue)
            {
                // If this model is new, it won't have any virtual properties hydrated or an original
                // record in the database
                if (PersonAliasId.HasValue)
                {
                    var personAliasService = new PersonAliasService(rockContext);
                    var personAlias        = personAliasService.Get(PersonAliasId.Value);
                    personId = personAlias?.PersonId;
                }

                // We can't log history if we don't know who the saved account belongs to
                if (!personId.HasValue)
                {
                    return;
                }
            }

            History.HistoryVerb verb;

            switch (state)
            {
            case EntityState.Added:
                verb = History.HistoryVerb.Add;
                break;

            case EntityState.Deleted:
                verb = History.HistoryVerb.Delete;
                break;

            default:
                // As of now, there is no requirement to log other events
                return;
            }

            var historyChangeList = new History.HistoryChangeList();

            historyChangeList.AddChange(verb, History.HistoryChangeType.Record, "Financial Person Saved Account");

            HistoryItems = HistoryService.GetChanges(
                typeof(Person),
                Rock.SystemGuid.Category.HISTORY_PERSON.AsGuid(),
                personId.Value,
                historyChangeList,
                GetNameForHistory(originalModel?.FinancialPaymentDetail ?? FinancialPaymentDetail),
                typeof(FinancialPersonSavedAccount),
                Id,
                dbContext.GetCurrentPersonAlias()?.Id,
                dbContext.SourceOfChange);
        }
Ejemplo n.º 3
0
        /// <summary>
        /// This method is called in the
        /// <see cref="M:Rock.Data.Model`1.PreSaveChanges(Rock.Data.DbContext,System.Data.Entity.Infrastructure.DbEntityEntry,System.Data.Entity.EntityState)" />
        /// method. Use it to populate <see cref="P:Rock.Data.Model`1.HistoryItems" /> if needed.
        /// These history items are queued to be written into the database post save (so that they
        /// are only written if the save actually occurs).
        /// </summary>
        /// <param name="dbContext">The database context.</param>
        /// <param name="entry">The entry.</param>
        /// <param name="state">The state.</param>
        protected override void BuildHistoryItems(Data.DbContext dbContext, DbEntityEntry entry, EntityState state)
        {
            /*
             * 12/18/2019 BJW
             *
             * We want to log the history of attribute values within a person matrix. Most of this logging occurs from
             * the attribute value model. However, when a matrix item (row in the table) is deleted, the pre-save event
             * of the attribute values is not called. Therefore, the delete event needs to be logged here. Additionally,
             * when the matrix item is added, the history is much cleaner when added here so that all the values of the
             * row are consolidated to one history item.  Modified state is not possible to log here because the
             * matrix item is not actually modified when its attributes change.
             *
             * Task: https://app.asana.com/0/1120115219297347/1136643182208595/f
             */

            if (state != EntityState.Deleted && state != EntityState.Added)
            {
                return;
            }

            var rockContext = new RockContext();
            var matrixId    = AttributeMatrixId != default ?
                              AttributeMatrixId :
                              entry.OriginalValues["AttributeMatrixId"].ToStringSafe().AsIntegerOrNull();

            var matrix = AttributeMatrix;

            if (matrix == null && matrixId.HasValue)
            {
                var matrixService = new AttributeMatrixService(rockContext);
                matrix = matrixService.Queryable().AsNoTracking().FirstOrDefault(am => am.Id == matrixId);
            }

            if (matrix == null)
            {
                return;
            }

            // The root attribute matrix attribute value is linked to the matrix by the guid as the attribute value
            var matrixGuidString      = matrix.Guid.ToString();
            var personEntityTypeId    = EntityTypeCache.Get(typeof(Person)).Id;
            var attributeValueService = new AttributeValueService(rockContext);
            var rootAttributeValue    = attributeValueService.Queryable().AsNoTracking().FirstOrDefault(av =>
                                                                                                        av.Value.Equals(matrixGuidString, System.StringComparison.OrdinalIgnoreCase) &&
                                                                                                        av.Attribute.EntityTypeId == personEntityTypeId
                                                                                                        );

            if (rootAttributeValue?.EntityId == null)
            {
                return;
            }

            var rootAttributeCache = AttributeCache.Get(rootAttributeValue.AttributeId);

            if (rootAttributeCache == null)
            {
                return;
            }

            // Evaluate the history changes
            var historyChangeList = new History.HistoryChangeList();

            if (AttributeValues == null || !AttributeValues.Any())
            {
                this.LoadAttributes();
            }

            var isDelete = state == EntityState.Deleted;

            foreach (var attributeValue in AttributeValues.Values)
            {
                var attributeCache    = AttributeCache.Get(attributeValue.AttributeId);
                var formattedOldValue = isDelete ? GetHistoryFormattedValue(attributeValue.Value, attributeCache) : string.Empty;
                var formattedNewValue = isDelete ? string.Empty : GetHistoryFormattedValue(attributeValue.Value, attributeCache);
                History.EvaluateChange(historyChangeList, attributeCache.Name, formattedOldValue, formattedNewValue, attributeCache.FieldType.Field.IsSensitive());
            }

            if (!historyChangeList.Any())
            {
                historyChangeList.AddChange(
                    isDelete ? History.HistoryVerb.Delete : History.HistoryVerb.Add,
                    History.HistoryChangeType.Record,
                    $"{rootAttributeCache.Name} Item");
            }

            HistoryItems = HistoryService.GetChanges(
                typeof(Person),
                SystemGuid.Category.HISTORY_PERSON_DEMOGRAPHIC_CHANGES.AsGuid(),
                rootAttributeValue.EntityId.Value,
                historyChangeList,
                rootAttributeCache.Name,
                typeof(Attribute),
                rootAttributeCache.Id,
                dbContext.GetCurrentPersonAlias()?.Id,
                dbContext.SourceOfChange);
        }
Ejemplo n.º 4
0
        /// <summary>
        /// This method is called in the
        /// <see cref="M:Rock.Data.Model`1.PreSaveChanges(Rock.Data.DbContext,System.Data.Entity.Infrastructure.DbEntityEntry,System.Data.Entity.EntityState)" />
        /// method. Use it to populate <see cref="P:Rock.Data.Model`1.HistoryItems" /> if needed.
        /// These history items are queued to be written into the database post save (so that they
        /// are only written if the save actually occurs).
        /// </summary>
        /// <param name="dbContext">The database context.</param>
        /// <param name="entry">The entry.</param>
        /// <param name="state">The state.</param>
        protected override void BuildHistoryItems(Data.DbContext dbContext, DbEntityEntry entry, EntityState state)
        {
            // Sometimes, especially if the model is being deleted, some properties might not be
            // populated, but we can query to try to get their original value. We need to use a new
            // rock context to get the actual value from the DB
            var rockContext   = new RockContext();
            var service       = new PersonSearchKeyService(rockContext);
            var originalModel = service.Queryable("PersonAlias")
                                .FirstOrDefault(fpsa => fpsa.Id == Id);

            // Use the original value for the person alias or the new value if that is not set
            var personId = (originalModel?.PersonAlias ?? PersonAlias)?.PersonId;

            if (!personId.HasValue)
            {
                // If this model is new, it won't have any virtual properties hydrated or an original
                // record in the database
                if (PersonAliasId.HasValue)
                {
                    var personAliasService = new PersonAliasService(rockContext);
                    var personAlias        = personAliasService.Get(PersonAliasId.Value);
                    personId = personAlias?.PersonId;
                }

                // We can't log history if we don't know who the saved account belongs to
                if (!personId.HasValue)
                {
                    return;
                }
            }

            History.HistoryVerb verb;

            switch (state)
            {
            case EntityState.Added:
                verb = History.HistoryVerb.Add;
                break;

            case EntityState.Deleted:
                verb = History.HistoryVerb.Delete;
                break;

            case EntityState.Modified:
                verb = History.HistoryVerb.Modify;
                break;

            default:
                // As of now, there is no requirement to log other events
                return;
            }

            var caption = verb == History.HistoryVerb.Modify ?
                          "Person Search Key" :
                          GetCaptionForHistory(originalModel?.SearchValue ?? SearchValue, originalModel?.SearchTypeValueId ?? SearchTypeValueId);

            var historyChangeList = new History.HistoryChangeList();

            if (verb != History.HistoryVerb.Modify)
            {
                historyChangeList.AddChange(verb, History.HistoryChangeType.Record, "Person Search Key");
            }
            else
            {
                History.EvaluateChange(historyChangeList, $"SearchValue", entry.OriginalValues["SearchValue"].ToStringSafe(), SearchValue, false);

                var originalSearchType = DefinedValueCache.Get(entry.OriginalValues["SearchTypeValueId"].ToStringSafe().AsInteger());
                var currentSearchType  = DefinedValueCache.Get(SearchTypeValueId);
                History.EvaluateChange(historyChangeList, $"SearchType", originalSearchType?.Value, currentSearchType?.Value, false);
            }

            HistoryItems = HistoryService.GetChanges(
                typeof(Person),
                Rock.SystemGuid.Category.HISTORY_PERSON.AsGuid(),
                personId.Value,
                historyChangeList,
                caption,
                typeof(PersonSearchKey),
                Id,
                dbContext.GetCurrentPersonAlias()?.Id,
                dbContext.SourceOfChange);
        }
Ejemplo n.º 5
0
        /// <summary>
        /// This method is called in the
        /// <see cref="M:Rock.Data.Model`1.PreSaveChanges(Rock.Data.DbContext,System.Data.Entity.Infrastructure.DbEntityEntry,System.Data.Entity.EntityState)" />
        /// method. Use it to populate <see cref="P:Rock.Data.Model`1.HistoryItems" /> if needed.
        /// These history items are queued to be written into the database post save (so that they
        /// are only written if the save actually occurs).
        /// </summary>
        /// <param name="dbContext">The database context.</param>
        /// <param name="entry">The entry.</param>
        /// <param name="state">The state.</param>
        protected override void BuildHistoryItems(Data.DbContext dbContext, DbEntityEntry entry, EntityState state)
        {
            var attributeCache = AttributeCache.Get(AttributeId);

            if (attributeCache?.EntityTypeId == null)
            {
                return;
            }

            var entityTypeId = attributeCache.EntityTypeId.Value;
            var entityId     = EntityId;

            if (!entityId.HasValue && (entry.State == EntityState.Modified || entry.State == EntityState.Deleted))
            {
                entityId = entry.OriginalValues["EntityId"].ToStringSafe().AsIntegerOrNull();
            }

            var caption = attributeCache.Name;

            // Check to see if this attribute is for a person or group, and if so, save to history table
            var personEntityTypeId = EntityTypeCache.Get(typeof(Person)).Id;

            var entityTypesToSaveToHistoryTable = new List <int> {
                personEntityTypeId,
                EntityTypeCache.Get(typeof(Group)).Id
            };

            var saveToHistoryTable = entityTypesToSaveToHistoryTable.Contains(entityTypeId);

            // If the value is not directly linked to a person or group, it still may be linked through an attribute matrix.
            // Matrix attribute changes are only logged here for modify. Add and delete are handled in the AttributeMatrixItem.
            if (!saveToHistoryTable && state == EntityState.Modified && IsLikelyWithinMatrix())
            {
                var rootMatrixAttributeValue = GetRootMatrixAttributeValue();

                if (rootMatrixAttributeValue == null)
                {
                    return;
                }

                var rootMatrixAttributeCache = AttributeCache.Get(rootMatrixAttributeValue.AttributeId);

                if (rootMatrixAttributeCache == null || !rootMatrixAttributeCache.EntityTypeId.HasValue)
                {
                    return;
                }

                saveToHistoryTable = entityTypesToSaveToHistoryTable.Contains(rootMatrixAttributeCache.EntityTypeId.Value);

                if (saveToHistoryTable)
                {
                    // Use the values from the root matrix attribute since this is the attribute that ties the
                    // values to a person or group and are thus more meaningful
                    entityTypeId = rootMatrixAttributeCache.EntityTypeId.Value;
                    entityId     = rootMatrixAttributeValue.EntityId;
                    caption      = rootMatrixAttributeCache.Name;
                }
            }

            if (!saveToHistoryTable || !entityId.HasValue)
            {
                return;
            }

            // We have determined to write to the History table. Now determine what changed.
            var oldValue = GetHistoryOldValue(entry);
            var newValue = GetHistoryNewValue(state);

            if (oldValue == newValue)
            {
                return;
            }

            // Evaluate the history change
            var formattedOldValue = GetHistoryFormattedValue(oldValue, attributeCache);
            var formattedNewValue = GetHistoryFormattedValue(newValue, attributeCache);
            var historyChangeList = new History.HistoryChangeList();

            History.EvaluateChange(historyChangeList, attributeCache.Name, formattedOldValue, formattedNewValue, attributeCache.FieldType.Field.IsSensitive());

            if (!historyChangeList.Any())
            {
                return;
            }

            var categoryGuid = entityTypeId == personEntityTypeId?
                               SystemGuid.Category.HISTORY_PERSON_DEMOGRAPHIC_CHANGES.AsGuid() :
                                   SystemGuid.Category.HISTORY_GROUP_CHANGES.AsGuid();

            HistoryItems = HistoryService.GetChanges(
                entityTypeId == personEntityTypeId ? typeof(Person) : typeof(Group),
                categoryGuid,
                entityId.Value,
                historyChangeList,
                caption,
                typeof(Attribute),
                AttributeId,
                dbContext.GetCurrentPersonAlias()?.Id,
                dbContext.SourceOfChange);
        }
Ejemplo n.º 6
0
            /// <summary>
            /// Called after the save operation has been executed
            /// </summary>
            /// <remarks>
            /// This method is only called if <see cref="M:Rock.Data.EntitySaveHook`1.PreSave" /> returns
            /// without error.
            /// </remarks>
            protected override void PostSave()
            {
                var rockContext = ( RockContext )this.RockContext;

                if (HistoryChanges != null)
                {
                    foreach (var historyItem in HistoryChanges)
                    {
                        int personId = historyItem.PersonId > 0 ? historyItem.PersonId : Entity.PersonId;

                        // if GroupId is 0, it is probably a Group that wasn't saved yet, so get the GroupId from historyItem.Group.Id instead
                        if (historyItem.GroupId == 0)
                        {
                            historyItem.GroupId = historyItem.Group?.Id;
                        }

                        var changes = HistoryService.GetChanges(
                            typeof(Person),
                            Rock.SystemGuid.Category.HISTORY_PERSON_GROUP_MEMBERSHIP.AsGuid(),
                            personId,
                            historyItem.PersonHistoryChangeList,
                            historyItem.Caption,
                            typeof(Group),
                            historyItem.GroupId,
                            Entity.ModifiedByPersonAliasId,
                            rockContext.SourceOfChange);

                        if (changes.Any())
                        {
                            Task.Run(async() =>
                            {
                                // Wait 1 second to allow all post save actions to complete
                                await Task.Delay(1000);
                                try
                                {
                                    using (var insertRockContext = new RockContext())
                                    {
                                        insertRockContext.BulkInsert(changes);
                                    }
                                }
                                catch (SystemException ex)
                                {
                                    ExceptionLogService.LogException(ex, null);
                                }
                            });
                        }

                        var groupMemberChanges = HistoryService.GetChanges(
                            typeof(GroupMember),
                            Rock.SystemGuid.Category.HISTORY_GROUP_CHANGES.AsGuid(),
                            Entity.Id,
                            historyItem.GroupMemberHistoryChangeList,
                            historyItem.Caption,
                            typeof(Group),
                            historyItem.GroupId,
                            Entity.ModifiedByPersonAliasId,
                            rockContext.SourceOfChange);

                        if (groupMemberChanges.Any())
                        {
                            Task.Run(async() =>
                            {
                                // Wait 1 second to allow all post save actions to complete
                                await Task.Delay(1000);
                                try
                                {
                                    using (var insertRockContext = new RockContext())
                                    {
                                        insertRockContext.BulkInsert(groupMemberChanges);
                                    }
                                }
                                catch (SystemException ex)
                                {
                                    ExceptionLogService.LogException(ex, null);
                                }
                            });
                        }
                    }
                }

                base.PostSave();

                // if this is a GroupMember record on a Family, ensure that AgeClassification, PrimaryFamily,
                // GivingLeadId, and GroupSalution is updated
                // NOTE: This is also done on Person.PostSaveChanges in case Birthdate changes
                var groupTypeFamilyRoleIds = GroupTypeCache.GetFamilyGroupType()?.Roles?.Select(a => a.Id).ToList();

                if (groupTypeFamilyRoleIds?.Any() == true)
                {
                    if (groupTypeFamilyRoleIds.Contains(Entity.GroupRoleId))
                    {
                        PersonService.UpdatePersonAgeClassification(Entity.PersonId, rockContext);
                        PersonService.UpdatePrimaryFamily(Entity.PersonId, rockContext);
                        PersonService.UpdateGivingLeaderId(Entity.PersonId, rockContext);


                        GroupService.UpdateGroupSalutations(Entity.GroupId, rockContext);

                        if (_preSaveChangesOldGroupId.HasValue && _preSaveChangesOldGroupId.Value != Entity.GroupId)
                        {
                            // if person was moved to a different family, the old family will need its GroupSalutations updated
                            GroupService.UpdateGroupSalutations(_preSaveChangesOldGroupId.Value, rockContext);
                        }
                    }
                }

                if (State == EntityContextState.Added || State == EntityContextState.Modified)
                {
                    if (Entity.Group != null && Entity.Person != null)
                    {
                        if (Entity.Group?.IsSecurityRoleOrSecurityGroupType() == true)
                        {
                            /* 09/27/2021 MDP
                             *
                             * If this GroupMember record results in making this Person having a higher AccountProtectionProfile level,
                             * update the Person's AccountProtectionProfile.
                             * Note: If this GroupMember record could result in making this Person having a *lower* AccountProtectionProfile level,
                             * don't lower the AccountProtectionProfile here, because other rules have to be considered before
                             * lowering the AccountProtectionProfile level. So we'll let the RockCleanup job take care of making sure the
                             * AccountProtectionProfile is updated after factoring in all the rules.
                             *
                             */

                            if (Entity.Group.ElevatedSecurityLevel >= Utility.Enums.ElevatedSecurityLevel.Extreme &&
                                Entity.Person.AccountProtectionProfile < Utility.Enums.AccountProtectionProfile.Extreme)
                            {
                                Entity.Person.AccountProtectionProfile = Utility.Enums.AccountProtectionProfile.Extreme;
                                rockContext.SaveChanges();
                            }
                            else if (Entity.Group.ElevatedSecurityLevel >= Utility.Enums.ElevatedSecurityLevel.High &&
                                     Entity.Person.AccountProtectionProfile < Utility.Enums.AccountProtectionProfile.High)
                            {
                                Entity.Person.AccountProtectionProfile = Utility.Enums.AccountProtectionProfile.High;
                                rockContext.SaveChanges();
                            }
                        }
                    }
                }
            }