Ticket Q359904
Visible to All Users

SecurityStrategyComplex: How to modify objects/properties in code when the user does not have the permission?

created 13 years ago

How can this be done? Is there some kind of impersonating available, whereby the program can access the data on two levels: user and programcode? That could solve my issue http://www.devexpress.com/Support/Center/Issues/ViewIssue.aspx?issueid=B209005.
Willem

Answers approved by DevExpress Support

created 9 months ago

With XAF v24.1.2+, it is now possible to call the new SetPropertyValueWithSecurityBypass method from within your BaseObject descendants to bypass security checks for certain protected properties in your internal application logic (custom base classes are also supported).
This capability is especially valuable to XAF developers when you wish to set service properties like CreatedBy, ModifiedBy, CreatedOn, UpdatedOn, etc. - our Audit Trail and Model Difference modules operate using a similar mechanism. The `SetPropertyValueWithBypassSecurity` method can freely set protected properties on behalf of a restricted/regular user in code much like an application administrator - without having to know admin credentials (like impersonation for service tasks).

C#
ApplicationUser GetCurrentUser() { return ObjectSpace.GetObjectByKey<ApplicationUser>( ObjectSpace.ServiceProvider.GetRequiredService<ISecurityStrategyBase>().UserId); } public override void OnSaving() { base.OnSaving(); if (ObjectSpace.IsNewObject(this)) { SetPropertyValueWithSecurityBypass(nameof(CreatedBy), GetCurrentUser()); } else { SetPropertyValueWithSecurityBypass(nameof(UpdatedBy), GetCurrentUser()); SetPropertyValueWithSecurityBypass(nameof(UpdatedOn), DateTime.Now); } }

As you probably know, this is a long-requested option for many XAFers - an option that prevented full migration from Integrated Mode to Middle Tier Security (because CreateNonsecuredObjectSpace and other workarounds were inconvenient).

The `SetPropertyValueWithBypassSecurity` also includes certain limitations like calling this method is only supported from inside `BaseObject.OnSaving` (for the best security) or the bypassed property cannot update other protected properties internally. We are of the opinion that this new option will cover 80% of popular use-cases.

Please test this new functionality in XAF v24.1, and let us know how this works for you. Thanks.

See Also: Breaking Change

    created 12 years ago (modified 3 years ago)

    1. If you need to filter objects from the database by security permissions and at the same time have direct access to them bypassing the security system, you need to implement two object spaces:
    - A secured object space;
    - A non-secured object space connected directly to the database.

    To implement the second type of the object space, you can use the code given below:

    1.1. In Client-Side Security, call CreateNonsecuredObjectSpace (XPO/EF Core) to create an unsecured IObjectSpace instance:

    C#
    using DevExpress.ExpressApp; using DevExpress.ExpressApp.Security.ClientServer; using System.Linq; namespace YourSolutionName { public class CustomViewController : ViewController { //... private void SomeMethod() { SecuredObjectSpaceProvider securedObjectSpaceProvider = (SecuredObjectSpaceProvider)Application.ObjectSpaceProviders.First(p => p is SecuredObjectSpaceProvider); IObjectSpace unsecuredObjectSpace = securedObjectSpaceProvider.CreateNonsecuredObjectSpace(); } } }

    1.2. In Middle Tier Security (XPO Only), create an unsecured ObjectSpaceProvider instance on the client side.

    C#
    XPObjectSpaceProvider provider = new XPObjectSpaceProvider(<connectiostring>, null); IObjectSpace os = provider.CreateObjectSpace();

    2. The first approach doesn't help when the code that requires direct access to secured data is executed in the context of a BO model. Modifications a user made during the transaction are not yet saved to the database. In some scenarios, this makes it difficult to use a separate object space connected to the database directly. The second part of this article describes approaches that can be considered when the solution described in the first part is not applicable.

    Business logic implemented at the BO level can be categorized as follows:
    2.1. Maintaining the object state consistency. For example, updating the calculated persistent property value.
    2.2. Adding records to system tables that are not used by the current user, such as audit, logs, etc.

    The business logic of the first category is an inherent part of the current user's activity, and it is better to grant user permissions for corresponding actions.

    In the second case, consider the following possible approaches:
    2.2.1. Actions for which a user has no permission (read system data, create objects, change property values) should be suppressed at the UI level, but allowed at the Security System level. This can be used with the business logic that affects objects not participating in the direct relationship with a business object the user works with (audit, logs). It may be sufficient just to deny the Navigation permission for Views displaying data from system tables.
    2.2.2. In scenarios where the consistency of the object state is not critical, you can modify it separately from the user's transaction using a non-secured object space connected directly to the database as described in point 1. Note that in this case, both secured and non-secured transactions can modify the same data. So to prevent ambiguity, consider performing updates from the non-secured object space with a delay. To do this, use background threads in a Win application or a service in a Web application.
    2.2.3. There is no simple solution that can be used to implement the business logic that should be executed as a part of the current user's transaction if the business logic requires executing operations denied for the current user. Below, we describe one approach that allows you to accomplish this task. Note that this solution is provided as is and may have limitations. For instance, the business logic is executed only if all changes made by the user in the current transactions passed validation. We tested the provided solution only in specific scenarios, so you can use it at your own risk.

    The attached example (dxSample.zip) demonstrates how to implement a popular master-detail scenario described in the How to: Calculate a Property Value Based on Values from a Detail Collection topic using this approach. Refer to the Calculate from SecuredObjectSpaceProvider's objects ticket to see a real-life customer scenario.

    To modify protected data in code within the same transaction, you can use the following SecuredObjectSpaceProvider descendant:

    C#
    using CL1316.Module.BusinessObjects; using DevExpress.ExpressApp.DC; using DevExpress.ExpressApp.DC.Xpo; using DevExpress.ExpressApp.Security; using DevExpress.ExpressApp.Security.ClientServer; using DevExpress.ExpressApp.Utils; using DevExpress.ExpressApp.Xpo; using DevExpress.Xpo; using System; using System.Collections.Generic; using System.Data; using System.Linq; using System.Text; using System.Threading.Tasks; namespace CL1316.Module { public class MySecuredObjectSpaceProvider : SecuredObjectSpaceProvider { public MySecuredObjectSpaceProvider(ISelectDataSecurityProvider selectDataSecurityProvider, string databaseConnectionString, IDbConnection connection) : base(selectDataSecurityProvider, databaseConnectionString, connection) { } protected override UnitOfWork CreateDirectUnitOfWork(IDataLayer dataLayer) { UnitOfWork directBaseUow = base.CreateDirectUnitOfWork(dataLayer); directBaseUow.Disposed += new EventHandler(directBaseUow_Disposed); directBaseUow.BeforeCommitTransaction += new SessionManipulationEventHandler(directBaseUow_BeforeCommitTransaction); return directBaseUow; } void directBaseUow_Disposed(object sender, EventArgs e) { UnitOfWork directBaseUow = (UnitOfWork)sender; directBaseUow.Disposed -= new EventHandler(directBaseUow_Disposed); directBaseUow.BeforeCommitTransaction -= new SessionManipulationEventHandler(directBaseUow_BeforeCommitTransaction); } void directBaseUow_BeforeCommitTransaction(object sender, SessionManipulationEventArgs e) { List<PayCard> cardsToUpdate = new List<PayCard>(); foreach (System.Object item in e.Session.GetObjectsToSave()) { if (item is PayCard) { if (!cardsToUpdate.Contains((PayCard)item)){ cardsToUpdate.Add((PayCard)item); } } else if (item is PayCardUsage) { PayCardUsage payCardUsage = (PayCardUsage)item; if (payCardUsage.PayCard != null && !cardsToUpdate.Contains(payCardUsage.PayCard)) { cardsToUpdate.Add(payCardUsage.PayCard); } } } foreach (PayCard payCard in cardsToUpdate) { payCard.CalculatePayMoneyAmount(true); } } } }

    Take special note of the directBaseUow_BeforeCommitTransaction method where you can execute your custom logic. This will still require modifying your business class code to extract the logic from the property setter. We admit that this may be inconvenient if you have a lot of such logic, but this is the best solution we can suggest at the moment.

    See also:
    How to customize the Object Space behavior in XPO-based XAF applications
    How to customize the UnitOfWork behavior in XPO-based XAF applications

      Show previous comments (6)

        I created a sample project to demonstrate my setup.

        Dennis Garavsky (DevExpress) 11 years ago

          @Bernd: I have extracted your comments into a separate ticket (Help with a solution from Q359904), and we will be glad to help you on this.

            Hi Dennis and all,
            It seems that the provided approach do not works, I provided a description of the problem in this ticket and waiting for a feedback.
            Thanks in advance!
            D

            created 12 years ago (modified 12 years ago)

            Security issues can occur when manipulating data without necessary permissions. To deny such scenarios, we designed a SecuredObjectSpaceProvider.
            I suggest you use the Client-Side Security - UI-Level Mode. You can find samples of how to use it in our SecurityDemo demo application. This security model allows you to manipulate any data programmatically, but not from the UI.
            Feel free to contact me if I misunderstand your requirements.

              Show previous comments (5)

                Also, that solution doesn't cover this scenario that I've copied and pasted from 4 different tickets now:
                I have a custom function criteria operator called NewObjectId, which evaluates to Guid.Empty. I have a lot of instances where I lock down roles to objects that have already been saved using the Criteria Oid != NewObjectId(). An exception gets thrown on the save.

                Dennis Garavsky (DevExpress) 12 years ago

                  Nate, this means that their functionality is mutually exclusive and it does not make any sense to combine them together. Moreover, if you combine them, then we will be back where we started: you will not be able to modify protected members in code. Technical details aside (we will determine how to best address this requirement), we see the problem you are talking about and we will think about a proper solution for it as part of the Security - Provide an easy way to create additional objects or modify protected properties in code (preferably within the same transaction) request. In the meantime, we have no other suggestions except for moving this particular logic from the business object to a ViewController, where it seems it belongs from the very beginning, because non-persistent properties are not logically a part of the data model (it is designed only for the ViewModel or binding), but rather a part of the UI-related logic.
                  I admit however, that this may be an awkward solution, but we have no better solution at the moment.
                  Nate, please let me know if you experience any difficulties implementing the suggested solution.
                  As for the custom criteria function, we would be more than happy to find an alternative solution for this requirement. Please elaborate more on it in a separate ticket for it not to mix everything within a single thread.

                    Thanks Dennis,
                    This isn't exclusive to non-persistent properties. I do the same with persistent… that one just happened to be non-persistent with a persistent alias, because of how the data is stored in the DB. So use this as the example, because I do both. ;)
                    public class DomainObject1 : BaseObject
                           {
                                Employee fEmployee;
                                [ImmediatePostData]
                                public Employee Employee
                                {
                                     get { return fEmployee; }
                                     set
                                     {
                                          SetPropertyValue<Employee>("Employee", ref fEmployee, value);
                                          if (!IsSaving && !IsLoading)
                                          {
                                               EmployeeCommissionDollar = EmployeeCommissionDollar ;
                                          }
                                     }
                                }

                    decimal fEmployeeCommissionDollar;
                              public decimal EmployeeCommissionDollar
                              {
                                   get { return fEmployeeCommissionDollar; }
                                   set
                                   {
                                        SetPropertyValue<decimal>("EmployeeCommissionDollar", ref fEmployeeCommissionDollar, value);
                                   }
                              }
                           }

                    created 13 years ago (modified 12 years ago)

                    Hello Willem,
                    I am afraid that this functionality is not available, when the middle-tier Application Server is used for security. We have especially introduced this feature to increase the data protection level, and it is impossible to work around the security in this case. If you do not use the middle-tier server, most likely setting the SecurityModule.StrictSecurityStrategyBehavior static property to false will allow you to achieve the behavior you require. You can also modify objects outside the ObjectSpace, in a separate UnitOfWork, in order not to check permissions on the client side.
                    Thanks,
                    Anatol

                      Show previous comments (3)

                        Or enable SecurityModule.StrictSecurityStrategyBehavior to work correctly in SecurityStrategyComplex.

                          Here's another one I just ran into. I have a custom function criteria operator called NewObjectId, which evaluates to Guid.Empty. I have some instances where I lock down roles to objects that have already been saved using the Criteria Oid != NewObjectId(). Works great, but an exception gets thrown on the save because the Oid has been generated before the rest of the members are set.

                          DevExpress Support Team 12 years ago

                            Please accept our apologies for the delay in responding. We couldn't find an immediate answer or resolution. Please bear with us. We will inform you as soon as an answer is found.

                            Disclaimer: The information provided on DevExpress.com and affiliated web properties (including the DevExpress Support Center) is provided "as is" without warranty of any kind. Developer Express Inc disclaims all warranties, either express or implied, including the warranties of merchantability and fitness for a particular purpose. Please refer to the DevExpress.com Website Terms of Use for more information in this regard.

                            Confidential Information: Developer Express Inc does not wish to receive, will not act to procure, nor will it solicit, confidential or proprietary materials and information from you through the DevExpress Support Center or its web properties. Any and all materials or information divulged during chats, email communications, online discussions, Support Center tickets, or made available to Developer Express Inc in any manner will be deemed NOT to be confidential by Developer Express Inc. Please refer to the DevExpress.com Website Terms of Use for more information in this regard.