Custom C#Bot ACL Security Filtering
Scenario
This is a staff management app. Users have the ability to apply for leave by submitting a leave application
. From the perspective of the user:
- I want to be able to make any updates and have full CRUD control over my application
- I also want to be able to nominate another user to be a
reviewer
for my application - A reviewer cannot update the application’s
Justification
field.
Entity Model
The entity model consists of two entities:
- An
application
entity with aJustification
property and aworkflow
extension - A
user
entity with auser
extension. - The
application
entity has both anapplicant
andreviewer
reference to theuser
entity.

Security Model
User
has been given full CRUD permissions over the target application, as well as admin backend access. This is the first step of us implementing custom security, we will give full permissions in the model, and then limit permissions in special circumstances. Visitors have no permissions.

Backend
UserApplicationEntity.cs
The class UserApplicationEntity
is an ACL (Access Control List) that contains the security rules for CRUD access of Application
entities by Users
.
C#Bot will write out our security rules to reflect what we have specified in the security diagram. A typical security rule will look something like this:
public bool GetUpdate(User user, IEnumerable<IAbstractModel> models, SecurityContext context)
{
// % protected region % [Override update rule contents here here] off begin
return true;
// % protected region % [Override update rule contents here here] end
}
The function name GetUpdate
is called when the server-side is performing an update on an Application
, and returns a boolean indicating if the user should be allowed to perform the action. In the case above, it is returning true, since we specified that Users
can update Applications
in the security model.
Each ACL
will contain methods for create, update, read and delete that look like the example above, since they all implement the interface IAcl
which describes what an ACL
should contain.
Each of these CRUD ACL
methods are given a copy of the current user
, a list of the models
/entities (in this case applications) and a SecurityContext
which contains other services like the DbContext
(which we can use to access the database).
The ACLs
are checked after the entities/updates have been added the the DbContext
change tracker, so the changes are queued, but have not yet been executed.
We can perform custom security logic in the Bot-Written ACLs
to return true or false for particular cases we want to check. In our example, we want to make sure that a user who has created an application or is the applicant
(through the applicant reference) of the application can make updates to their applications
.
We can do that by turning the protected region on, and updating the GetUpdate
method to look something like this:
public bool GetUpdate(User user, IEnumerable<IAbstractModel> models, SecurityContext context)
{
// % protected region % [Override update rule contents here here] on begin
if (context.DbContext.ChangeTracker.Entries().Any(x => x.Entity is ApplicationEntity))
{
var applicationEntityEntries = context.DbContext.ChangeTracker.Entries<ApplicationEntity>().ToList();
if (applicationEntityEntries.All(x => x.Entity.Owner == user.Id || x.Entity.ApplicantId == user.Id))
{
return true;
}
}
return false;
// % protected region % [Override update rule contents here here] end
}
So now the new ACL
update logic works like this:
- Check that there are some
ApplicationEntity
entries in the DbContext change tracker.- Copy all the
ApplicationEntity
entries from the change tracker to a local variable. - Check that the user who is attempting to perform these changes on
ApplicationEntity
is either theOwner
(created them) or is theApplicant
(through the applicant reference).- If they are, return
true
and allow them to perform the updates.
- If they are, return
- Copy all the
- Return
false
and prevent the updates from going through.
If I wanted to include the conditions to prevent a reviewer
from updating certain fields on the application, I would use the following code:
if (applicationEntityEntries.All(x => x.Entity.ReviewerId == user.Id))
{
var changedEntities = applicationEntityEntries.Select(x => x.Entity).ToList();
var entityIds = changedEntities.Select(x => x.Id);
var originalEntities = context.DbContext.ApplicationEntity
.AsNoTracking()
.Where(x => entityIds.Contains(x.Id))
.ToList();
var allJustificationsUnchanged = changedEntities
.All(x => x.Justification == originalEntities.First(o => o.Id == x.Id).Justification);
if (allJustificationsUnchanged)
{
return true;
}
}
The logic is as follows:
Check that the user who is attempting the update is the nominated
reviewer
of theapplications
(by comparing the reviewer id on the application to the user id).
- Pull out a copy of the
ApplicationEntity
s. - Get a list of IDs of all the
ApplicationEntity
s that are being changed. - Retrieve the original versions of there entities from the database.
- Check that the
Justification
attributes have not been changed. - If
Justification
property has not been changed, allow the changes to go through.
With all the changes together, the code looks like this:
public bool GetUpdate(User user, IEnumerable<IAbstractModel> models, SecurityContext context)
{
// % protected region % [Override update rule contents here here] off begin
if(context.DbContext.ChangeTracker.Entries().Any(x => x.Entity is ApplicationEntity))
{
var applicationEntityEntries = context.DbContext.ChangeTracker.Entries<ApplicationEntity>().ToList();
if (applicationEntityEntries.All(x => x.Entity.Owner == user.Id || x.Entity.ApplicantId == user.Id))
{
return true;
}
if (applicationEntityEntries.All(x => x.Entity.ReviewerId == user.Id))
{
var changedEntities = applicationEntityEntries.Select(x => x.Entity).ToList();
var entityIds = changedEntities.Select(x => x.Id);
var originalEntities = context.DbContext.ApplicationEntity
.AsNoTracking()
.Where(x => entityIds.Contains(x.Id))
.ToList();
var allJustificationsUnchanged = changedEntities
.All(x => x.Justification == originalEntities.First(o => o.Id == x.Id).Justification);
if (allJustificationsUnchanged)
{
return true;
}
}
}
return false;
// % protected region % [Override update rule contents here here] end
}
After all our changes have been made, we can log in to the target application and test them out. The image below shows, through a network request, a review getting an error when trying to update an application they have changed the Justification
property on.

Was this article helpful?