Filed under: Best Practices
Comments: None
In NexJ Studio, it is possible to provide record-level view security on any class. This means it’s possible to handle scenarios where we want only non-deleted employee records to be visible to all non-admin users, while admin users should be able to see all the employee records.
Record-level view security is set by specifying the access attribute on the read event of a class. In NexJ Studio, by convention, the access attribute is set to an attribute called “readable”. Scheme code can be written in the readable attribute and is then interpreted by the framework and eventually converted to an SQL query. The following are examples that illustrate how to use the readable attribute.
Example:No Users Should Have Access
Assume there are records of type Entity in the database. Some of these records are “soft deleted” and should not be visible to the end user. Access to these records can be controlled by first overriding the read event of Entity and setting its access property to the “readable” attribute:
1 |
<Event access="readable" args="attributes where orderBy count offset xlock" name="read" static="true" visibility="public"> |
And then setting the readable attribute of the Entity class to the following:
1 |
<Attribute name="readable" type="boolean" value="(not (@ deleted))"/> |
Now running (Entity'read '() '() '() '() '() #f)
executes the underlying query (select A.id from NJEntity A where A.deletedFlag = 0)
. The query shows that no Entity record with a deletedFlag value of true will be returned to the end user.
Example:Subset of Users Should Have Access
Assume the same situation as above. However this time we have an administrative user (userA) with a privilege “EntityViewAllDeleted” and we want users with that privilege to be able to view all Entity records. This can be accomplished by changing the readable attribute as follows:
1 2 3 4 5 6 7 |
<Attribute name="readable" type="boolean" value="(if (in-privilege? “EntityViewAllDeleted”) #t ;else (not (@ deleted) )" /> |
Now executing (Entity'read '() '() '() '() '() #f)
from userA’s point of view generates the underlying query (select A.id from NJEntity A)
which would return all records as expected. However, executing it from any other user’s point of view would generate (select A.id from NJEntity A where A.deletedFlag = 0)
which would once again only return Entity records that have a false deletedFlag value.
Readable Attribute Restrictions
Framework evaluates Scheme code written in the readable attribute to SQL statements. As such there are certain restrictions on what can be and cannot appear in the readable attribute:
- Advanced Scheme expressions such as cond cannot be used.
- Functions cannot be used to build the WHERE clause.
- The keyword “this” is not recognized but the equivalent macro “@” is.
Record-Level View Security Best Practices
Due to the way the readable attribute is handled, a slight change can have a great effect on overall application performance. As such, the following are a few guidelines to follow when making changes to the readable attribute:
- Avoid using “or” statements. Use “if” statements instead as framework can evaluate the if condition at run time if possible before compiling the SQL statement and in turn reduce the amount of joins/attributes requested. Also, attempt to make the ”if” statement something that can be evaluated before reading from the database. Using conditions such as
(in-privilege? “Admin”)
which can be evaluated before hitting the database instead of(= (@ attr) ,value)
can help maintain performance. However, it is perfectly understandable that this cannot always be done. - Always attempt to define the readable attribute on the first concrete class that is persisted. For example, if we have the Entity class, which is persisted, and we have two more classes, Person and Company, which are children of Entity, then the readable attribute should be defined on Entity.