/// <summary>
    /// Overridden to assign the IsSensitive property values
    /// </summary>
    /// <param name="content"></param>
    /// <param name="properties"></param>
    /// <param name="context"></param>
    /// <returns></returns>
    protected override List <ContentPropertyDisplay> MapProperties(IContentBase content, List <IProperty> properties, MapperContext context)
    {
        List <ContentPropertyDisplay> result = base.MapProperties(content, properties, context);
        var         member     = (IMember)content;
        IMemberType?memberType = _memberTypeService.Get(member.ContentTypeId);

        // now update the IsSensitive value
        foreach (ContentPropertyDisplay prop in result)
        {
            // check if this property is flagged as sensitive
            var isSensitiveProperty = memberType?.IsSensitiveProperty(prop.Alias) ?? false;
            // check permissions for viewing sensitive data
            if (isSensitiveProperty && _backofficeSecurityAccessor.BackOfficeSecurity?.CurrentUser?.HasAccessToSensitiveData() == false)
            {
                // mark this property as sensitive
                prop.IsSensitive = true;
                // mark this property as readonly so that it does not post any data
                prop.Readonly = true;
                // replace this editor with a sensitive value
                prop.View = "sensitivevalue";
                // clear the value
                prop.Value = null;
            }
        }
        return(result);
    }
Exemplo n.º 2
0
        // no MapAll - take care
        private void Map(IMemberType source, MemberTypeDisplay target, MapperContext context)
        {
            MapTypeToDisplayBase <MemberTypeDisplay, MemberPropertyTypeDisplay>(source, target);

            //map the MemberCanEditProperty,MemberCanViewProperty,IsSensitiveData
            foreach (var propertyType in source.PropertyTypes)
            {
                var localCopy   = propertyType;
                var displayProp = target.Groups.SelectMany(dest => dest.Properties).SingleOrDefault(dest => dest.Alias.InvariantEquals(localCopy.Alias));
                if (displayProp == null)
                {
                    continue;
                }
                displayProp.MemberCanEditProperty = source.MemberCanEditProperty(localCopy.Alias);
                displayProp.MemberCanViewProperty = source.MemberCanViewProperty(localCopy.Alias);
                displayProp.IsSensitiveData       = source.IsSensitiveProperty(localCopy.Alias);
            }
        }
Exemplo n.º 3
0
        /// <summary>
        /// Update existing member data
        /// </summary>
        /// <param name="contentItem">The member to save</param>
        /// <remarks>
        /// We need to use both IMemberService and ASP.NET Identity to do our updates because Identity is responsible for passwords/security.
        /// When this method is called, the IMember will already have updated/mapped values from the http POST.
        /// So then we do this in order:
        /// 1. Deal with sensitive property values on IMember
        /// 2. Use IMemberService to persist all changes
        /// 3. Use ASP.NET and MemberUserManager to deal with lockouts
        /// 4. Use ASP.NET, MemberUserManager and password changer to deal with passwords
        /// 5. Deal with groups/roles
        /// </remarks>
        private async Task <ActionResult <bool> > UpdateMemberAsync(MemberSave contentItem)
        {
            contentItem.PersistedContent.WriterId = _backOfficeSecurityAccessor.BackOfficeSecurity.CurrentUser.Id;

            // If the user doesn't have access to sensitive values, then we need to check if any of the built in member property types
            // have been marked as sensitive. If that is the case we cannot change these persisted values no matter what value has been posted.
            // There's only 3 special ones we need to deal with that are part of the MemberSave instance: Comments, IsApproved, IsLockedOut
            // but we will take care of this in a generic way below so that it works for all props.
            if (!_backOfficeSecurityAccessor.BackOfficeSecurity.CurrentUser.HasAccessToSensitiveData())
            {
                IMemberType memberType          = _memberTypeService.Get(contentItem.PersistedContent.ContentTypeId);
                var         sensitiveProperties = memberType
                                                  .PropertyTypes.Where(x => memberType.IsSensitiveProperty(x.Alias))
                                                  .ToList();

                foreach (IPropertyType sensitiveProperty in sensitiveProperties)
                {
                    // TODO: This logic seems to deviate from the logic that is in v8 where we are explitly checking
                    // against 3 properties: Comments, IsApproved, IsLockedOut, is the v8 version incorrect?

                    ContentPropertyBasic destProp = contentItem.Properties.FirstOrDefault(x => x.Alias == sensitiveProperty.Alias);
                    if (destProp != null)
                    {
                        // if found, change the value of the contentItem model to the persisted value so it remains unchanged
                        object origValue = contentItem.PersistedContent.GetValue(sensitiveProperty.Alias);
                        destProp.Value = origValue;
                    }
                }
            }

            // First save the IMember with mapped values before we start updating data with aspnet identity
            _memberService.Save(contentItem.PersistedContent);

            bool needsResync = false;

            MemberIdentityUser identityMember = await _memberManager.FindByIdAsync(contentItem.Id.ToString());

            if (identityMember == null)
            {
                return(ValidationProblem("Member was not found"));
            }

            // Handle unlocking with the member manager (takes care of other nuances)
            if (identityMember.IsLockedOut && contentItem.IsLockedOut == false)
            {
                IdentityResult unlockResult = await _memberManager.SetLockoutEndDateAsync(identityMember, DateTimeOffset.Now.AddMinutes(-1));

                if (unlockResult.Succeeded == false)
                {
                    return(ValidationProblem(
                               $"Could not unlock for member {contentItem.Id} - error {unlockResult.Errors.ToErrorMessage()}"));
                }
                needsResync = true;
            }
            else if (identityMember.IsLockedOut == false && contentItem.IsLockedOut)
            {
                // NOTE: This should not ever happen unless someone is mucking around with the request data.
                // An admin cannot simply lock a user, they get locked out by password attempts, but an admin can unlock them
                return(ValidationProblem("An admin cannot lock a member"));
            }

            // If we're changing the password...
            // Handle changing with the member manager & password changer (takes care of other nuances)
            if (contentItem.Password != null)
            {
                IdentityResult validatePassword = await _memberManager.ValidatePasswordAsync(contentItem.Password.NewPassword);

                if (validatePassword.Succeeded == false)
                {
                    return(ValidationProblem(validatePassword.Errors.ToErrorMessage()));
                }

                if (!int.TryParse(identityMember.Id, NumberStyles.Integer, CultureInfo.InvariantCulture, out var intId))
                {
                    return(ValidationProblem("Member ID was not valid"));
                }

                var changingPasswordModel = new ChangingPasswordModel
                {
                    Id          = intId,
                    OldPassword = contentItem.Password.OldPassword,
                    NewPassword = contentItem.Password.NewPassword,
                };

                // Change and persist the password
                Attempt <PasswordChangedModel> passwordChangeResult = await _passwordChanger.ChangePasswordWithIdentityAsync(changingPasswordModel, _memberManager);

                if (!passwordChangeResult.Success)
                {
                    foreach (string memberName in passwordChangeResult.Result?.ChangeError?.MemberNames ?? Enumerable.Empty <string>())
                    {
                        ModelState.AddModelError(memberName, passwordChangeResult.Result.ChangeError?.ErrorMessage ?? string.Empty);
                    }
                    return(ValidationProblem(ModelState));
                }

                needsResync = true;
            }

            // Update the roles and check for changes
            ActionResult <bool> rolesChanged = await AddOrUpdateRoles(contentItem.Groups, identityMember);

            if (!rolesChanged.Value && rolesChanged.Result != null)
            {
                return(rolesChanged.Result);
            }
            else
            {
                needsResync = true;
            }

            // If there have been underlying changes made by ASP.NET Identity, then we need to resync the
            // IMember on the PersistedContent with what is stored since it will be mapped to display.
            if (needsResync)
            {
                contentItem.PersistedContent = _memberService.GetById(contentItem.PersistedContent.Id);
            }

            return(true);
        }