4.3 Secure business services
Following best security practices, you'd always want to secure business services first, to make sure that the user cannot call any operations that they are not allowed to call, and that they don't have access to any data that they are not allowed to see.
In our example application, access to sales orders should be allowed only to internal employees, and to individual or store customers, but not to vendors or other types of users. Moreover, external customers should be able to see only their own sales orders. Let's see how we can implement these security requirements within the business services generated by Xomega.
Restricting access to operation
We'll start by restricting access to the read list
operation for sales orders to external users that are not customers.
First, let's open the Resources.resx
file under the AdventureWorks.Services.Entities
project, and add a message for restricted operations, as follows.
Name | Value | Comment |
---|---|---|
OperationNotAllowed | Operation not allowed. |
Don't forget to run the custom tool on the nested Messages.tt
file to regenerate message constants.
Now let's open the ReadListAsync
method of our SalesOrderService
implementation class, and add the following custom code for security checks at the top of the method.
// CUSTOM_CODE_START: add namespaces for custom code below
using AdventureWorks.Services.Common.Enumerations;
using Xomega.Framework;
// CUSTOM_CODE_END
...
public partial class SalesOrderService : BaseService, ISalesOrderService
{
...
public virtual async Task<Output<ICollection<SalesOrder_ReadListOutput>>> ReadListAsync(
SalesOrder_ReadListInput_Criteria _criteria, CancellationToken token = default)
{
...
// CUSTOM_CODE_START: add custom security checks for Read operation below
if (!CurrentPrincipal.IsEmployee() && !CurrentPrincipal.IsIndividualCustomer() &&
!CurrentPrincipal.IsStoreContact())
{
currentErrors.CriticalError(ErrorType.Security, Messages.OperationNotAllowed);
}
// CUSTOM_CODE_END
...
}
}
Notice how we are using the CurrentPrincipal
member of the service to determine the permissions of the current user, and leverage the convenient methods that we added to the SecurityManager
class earlier.
Since the ErrorType
enum is in the Xomega.Framework
namespace, which was not used or declared in this generated file, we needed to declare it in the custom section for the namespaces at the top of the file to prevent it from being erased during regeneration.
If the security check fails, we report a critical error of type Security
, which will abort the execution and will return the OperationNotAllowed
message to the client.
When the business services are called via REST API, these security errors will be reported using the HTTP status code 403 (Forbidden), following the REST standards.
Restricting access to data
Now, let's restrict the data returned by the ReadListAsync
method for external customers to show only their own sales orders.
We need to find a custom code placeholder for additional filter criteria on the source query and add some custom code that checks if the CurrentPrincipal
is a store or individual customer, and then add their associated storeId
or personId
respectively as additional filter criteria for the sales order's customer, as follows.
public partial class SalesOrderService : BaseService, ISalesOrderService
{
...
public virtual async Task<Output<ICollection<SalesOrder_ReadListOutput>>> ReadListAsync(
SalesOrder_ReadListInput_Criteria _criteria, CancellationToken token = default)
{
...
// CUSTOM_CODE_START: add custom filter criteria to the source query for ReadList operation below
if (CurrentPrincipal.IsStoreContact())
{
int? storeId = CurrentPrincipal.GetStoreId();
src = src.Where(o => o.CustomerObject.StoreObject.BusinessEntityId == storeId);
}
if (CurrentPrincipal.IsIndividualCustomer())
{
int? personId = CurrentPrincipal.GetPersonId();
src = src.Where(o => o.CustomerObject.PersonObject.BusinessEntityId == personId);
}
// CUSTOM_CODE_END
...
}
}
As before, we are using our custom extension methods from SecurityManager
to easily retrieve the storeId
or personId
for the CurrentPrincipal
.
To properly secure all business services you'll need to add similar custom security checks to other CRUD operations of the SalesOrderService
, and report any security errors as appropriate.