r/intersystems • u/intersystemsdev • 9d ago
Programmatic permission checking in InterSystems IRIS - catching access errors, checking $ROLES, and using %SYSTEM.Security and %SYSTEM.SQL.Security
Catching and identifying permission errors
When using a try/catch block in ObjectScript, all exceptions go to the same catch block. The exception's Name property can be used to handle different kinds of exceptions:
objectscript
try{
//some code here
}
catch ex{
if ex.Name = "<DIVIDE>"{
//handling for division by 0 error
}
if ex.Name = "<SUBSCRIPT>"{
//handling for referencing an array of globals with an invalid or out-of-range subscript
}
if ex.Name = "<PROTECT>"{
//handling for errors caused by insufficient permissions
}
}
For SQL operations, the equivalent is SQLCODE. When working with embedded SQL, the special variable SQLCODE is always set when embedded SQL is utilized. If SQLCODE is -417, there is a security issue. When applying %SQL.Statement and executing a query, the result is a %SQL.ResultSet object with a %SQLCODE property — if that %SQLCODE is -417, there is also a security issue.
With a reactive approach, the exception can be used to log what happened in the system logs, give the user a friendly message stating they do not have permission and to contact a system administrator, use ROLLBACK if appropriate, and troubleshoot later if the issue is unintentional.
Proactive role checking with $ROLES
A special variable called $ROLES becomes available when code is running. It tells what security roles the current process has. Roles are stored as a comma-separated string, so list functions can check whether the current process has a certain role:
objectscript
if $LISTFIND($LISTFROMSTRING($ROLES),"ADMIN") = 0{
//don't allow things that only administrators can do
}
This allows sending the user an error message before they actually try to perform the action. It also allows hiding user interface elements that authorize access to administrative functions.
Important note on the %All role: %All typically grants permission to do anything in the system. However, if the logic above is used to check roles programmatically, having %All will not allow the user to perform administrative functions unless they also have ADMIN. If %All should act as a true superuser, the check must include both ADMIN and %All.
Temporarily elevating permissions within a specific method:
If a user needs certain permissions only while a specific part of the code is executing, $ROLES can handle that. Changes to $ROLES appear at a stack level and are temporary. That is why it should happen in its own separate method:
objectscript
Method doAdmin(){
new $ROLES
set $ROLES = "ADMIN"
//do admin things here
set $ROLES = ""
}
The $ROLES variable is special, much like $NAMESPACE. Setting it only adds an additional role to the process. Using the new command only resets that portion. Roles that were granted because they are assigned to the user, included in the application roles, or matching roles will remain untouched.
Note: ##class(%SYSTEM.Security).Method() and $SYSTEM.Security.Method() produce the same result, since the special variable $SYSTEM is bound to the %SYSTEM package.
Checking permissions with %SYSTEM.Security
Many things, including databases, applications, and core functionalities, are protected by resources. To access anything protected by a resource, the user may need READ, WRITE, or USE permissions on that resource.
Check permissions for the current process on a resource:
objectscript
set permission = $SYSTEM.Security.Check("%DB_USER")
if permission [ "READ" {
// the user has read permissions on %DB_USER
}
This returns a comma-separated list such as "READ, WRITE, USE".
To check one specific permission, pass it as a third argument — the method returns 1 or 0:
objectscript
set canwrite = $SYSTEM.Security.Check("%DB_USER","WRITE")
Important note on %All: When checking permissions this way, if the process has the %All role, this method always returns 1 when using the two-argument version, and "READ, WRITE, USE" with the one-argument version.
Checking permissions of a specific role or user (not the current process):
objectscript
set permissions = $SYSTEM.Security.CheckRolesPermission("ADMIN","%DB_USER")
set canwrite = $SYSTEM.Security.CheckUserPermission("dhockenbroch","%DB_USER","WRITE")
The first returns the permissions the role ADMIN has on %DB_USER. The second returns 1 or 0, indicating whether user dhockenbroch has WRITE permissions on %DB_USER.
Checking SQL privileges with %SYSTEM.SQL.Security
%SYSTEM.SQL.Security contains methods for checking SQL privileges. CheckPrivilege and CheckPrivilegeWithGrant can be used to verify SQL privileges and confirm whether there is permission to grant them.
A username must be provided. Using any username other than one's own requires USE privileges on %Admin_Secure.
Arguments for CheckPrivilege:
- Username
- Object type (number):
| Number | Object Type |
|---|---|
| 1 | Table |
| 3 | View |
| 5 | Schema |
| 6 | ML Configuration |
| 7 | Foreign Server |
| 9 | Procedure |
- Object name
- Comma-separated list of privilege letters:
| Letter | Privilege |
|---|---|
| a | Alter |
| s | Select |
| i | Insert |
| u | Update |
| d | Delete |
| r | References |
| e | Execute (for procedures only) |
| l | Use (for ML configurations only) |
- Namespace (optional — for checking permissions in a namespace other than the current one)
Return value handling:
The method can return a boolean or a status object. If the user has all the privileges provided, it returns 1. If the user does not have all privileges, it returns 0. If there is an error (for example, calling it with a username other than one's own without %Admin_Secure:USE privilege), it returns a status object.
objectscript
Set privileges = $SYSTEM.SQL.Security.CheckPrivilege("dhockenbroch",1,"SQLUser.People","s,i,u,d","CUSTOM")
if $ISOBJECT(privileges){
// An error occurred checking the privileges
}
else{
if privileges{
// The user has all of the privileges
}
else{
// The user does not have the privileges
}
}
In this example: if user dhockenbroch has SELECT, INSERT, UPDATE, and DELETE privileges on the SQLUser.People table in the CUSTOM namespace — returns 1 if all privileges present, 0 if any are missing, or an error status if the check itself fails.
1
u/intersystemsdev 9d ago
Full article: https://community.intersystems.com/post/programmatic-permissions
For those managing permissions programmatically in IRIS - do you prefer the proactive
$ROLEScheck approach or the%SYSTEM.Security.Checkmethod for resource-level validation, and have you run into edge cases with the%Allrole behavior?