┌─────────────────────────────────────────────────────────────────────────────┐
│ PERMISSION CHECK FLOW │
└─────────────────────────────────────────────────────────────────────────────┘
┌──────────┐
│ User │ ─────────────────────────┐
└────┬─────┘ │
│ belongs to │ owns (CRUNCH)
▼ ▼
┌──────────┐ ┌─────────────────┐
│ Group │ │ Capability │
└────┬─────┘ │ (can expire) │
│ has many └────────┬────────┘
▼ │ grants
┌───────────────────┐ │
│ GroupPermission │ │
│ Junction │ │
└────────┬──────────┘ │
│ links to │
▼ ▼
┌─────────────────────────────────────────────────────────────────┐
│ Permission │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ Permission String Patterns: │ │
│ │ │ │
│ │ • * → Global (do anything) │ │
│ │ • service.<name> → Service access │ │
│ │ • <model>.read.<id> → Read object │ │
│ │ • <model>.create → Create object │ │
│ │ • <model>.update.<id> → Update object │ │
│ │ • <model>.remove.<id> → Delete object │ │
│ │ • <model>.ro.<property> → Read property │ │
│ │ • <model>.rw.<property> → Write property │ │
│ │ • <model>.column.<prop> → View table column │ │
│ │ • @<RoleName> → Inherit Role's permissions │ │
│ └─────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────────┐
│ AUTH SERVICE DECORATOR CHAIN │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ Request → PMAuthService → CachingAuthService → CapabilityAuthService │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ (monitoring) (caching) (CRUNCH check) │
│ │ │
│ EnabledCheckAuthService ← PasswordExpiryAuthService │
│ │ │ │
│ ▼ ▼ │
│ (user enabled?) (password expired?) │
│ │
│ TwoFactorAuthService → UserAndGroupAuthService │
│ │ │ │
│ ▼ ▼ │
│ (2FA check) (Group permissions) │
│ │ │
│ ▼ │
│ ✓ GRANTED / ✗ DENIED │
└─────────────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────────┐
│ ROLE INHERITANCE │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌───────────────┐ @OpsRole ┌───────────────┐ │
│ │ AdminGroup │ ──────────────────► │ OpsRole │ (Role Group) │
│ │ │ inherits │ (no users) │ │
│ │ permissions: │ │ permissions: │ │
│ │ - user.* │ │ - report.* │ │
│ │ - @OpsRole │◄────────────────────│ - audit.* │ │
│ └───────────────┘ gets all perms └───────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
Permissions in FOAM represent access to system resources and follow Java’s hierarchical naming convention. 1 The system uses wildcard matching where an asterisk can appear by itself or at the end of a name preceded by a dot to signify wildcard matches.
Valid wildcard patterns:
* - Global permission (ability to do anything)foo.* - Hierarchical wildcardInvalid patterns:
*foo, a*b, foo* - Asterisk not at end or not preceded by dotExamples:
* - Ability to do anything, granted to ‘admin’ groupuser.read.* - Ability to read all Users from the UserDAOtheme.write.acme - Ability to write only the ‘acme’ Theme from the ThemeDAOboolean foam.core.auth.AuthService.check(X x, String permission)
UserAndGroupAuthService serves as the primary implementation, decorated with multiple service layers:
service.<serviceName>service.userDAOservice.run.<serviceName>service.run.Health, service.run.uptime<permissionPrefix>.<operation>.<id>
permissionPrefix = toLowerCase(model.name)read, remove, create, updatemenu.read.settings, email.create, notificationsetting.remove.*foam.core.auth.AuthorizationDAO, foam.core.auth.StandardAuthorizer 7create operations don’t include <id><prefix>.read.<id> patternAuthorizableAuthorizer, GlobalCreateAuthorizer, etc.<modelName>.<ro|rw>.<propertyName>theme.rw.loginimage, user.ro.complianceAbstractPropertyInfo.java, PermissionedPropertyDAO.js 8writePermissionRequired: true
readPermissionRequired: true
updatePermissionRequired: true
availablePermissions: ['command.read.test']availablePermissions: ['foam.core.alarming.Alarm.rw.start']availablePermissions: ['user.action.delete']<model>.section.<section>, both lowercased like the ro/rw/column familiesschedulable.section.history, flow.section.scriptsectionpermissionRequired: true in the section.<model>.column.<property>approvalrequest.column.approvercolumnPermissionRequired: truePermissions of the form @<GroupName> grant all permissions owned by the <GroupName> group.
This is typically used to create “Role” Groups which don’t have any direct users but are just used
to create collections of Permissions. These types of Groups are prefixed by “Role” by convention.
Ex.: "OpsRole", "ReflowRole"
| Pattern | Purpose |
|---|---|
service.<serviceName> |
Service access |
service.run.<serviceName> |
WebAgent execution |
<model>.read.<id> |
Read object |
<model>.update.<id> |
Update object |
<model>.remove.<id> |
Delete object |
<model>.create |
Create object |
<model>.ro.<property> |
Read property |
<model>.rw.<property> |
Write property |
<model>.column.<property> |
View table column |
| No standard pattern | Execute actions |
@<RoleName> |
Grant all Role permissions |
The FOAM permission system implements a comprehensive authorization framework with wildcard support, hierarchical naming, and multiple specialized authorizers. 10 The system integrates with both traditional group-based permissions and the advanced CRUNCH capability system for fine-grained access control.
Wiki pages you might want to explore:
File: src/foam/core/auth/Permission.js (L7-24)
foam.CLASS({
package: 'foam.core.auth',
name: 'Permission',
documentation: 'A permission represents access to system resources.',
properties: [
{
class: 'String',
name: 'id',
tableWidth: 400
},
{
class: 'String',
name: 'description',
documentation: 'Description of the Group.'
}
],
File: src/foam/core/auth/pom.js (L16-16)
{ name: "User", flags: "js|java" },
File: src/foam/core/auth/pom.js (L67-67)
{ name: "Group", flags: "js|java" },
File: src/foam/core/auth/Relationships.js (L7-14)
foam.RELATIONSHIP({
cardinality: '*:*',
sourceModel: 'foam.core.auth.Group',
targetModel: 'foam.core.auth.Permission',
forwardName: 'permissions',
inverseName: 'groups',
junctionDAOKey: 'groupPermissionJunctionDAO'
});
File: src/foam/core/crunch/Capability.js (L7-14)
foam.CLASS({
package: 'foam.core.crunch',
name: 'Capability',
implements: [
// See CapabilityRefinement for additional implements
'foam.core.auth.LifecycleAware'
],
File: src/foam/core/auth/AuthorizableAuthorizer.java (L14-44)
public class AuthorizableAuthorizer implements Authorizer {
protected String permissionPrefix_;
public AuthorizableAuthorizer(String permissionPrefix) {
permissionPrefix_ = permissionPrefix;
}
public void authorizeOnCreate(X x, FObject obj) throws AuthorizationException {
if ( obj instanceof Authorizable ) {
((Authorizable) obj).authorizeOnCreate(x);
}
}
public void authorizeOnRead(X x, FObject obj) throws AuthorizationException {
if ( obj instanceof Authorizable ) {
((Authorizable) obj).authorizeOnRead(x);
}
}
public void authorizeOnUpdate(X x, FObject oldObj, FObject obj) throws AuthorizationException {
if ( obj instanceof Authorizable ) {
((Authorizable) obj).authorizeOnUpdate(x, oldObj);
}
}
public void authorizeOnDelete(X x, FObject obj) throws AuthorizationException {
if ( obj instanceof Authorizable ) {
((Authorizable) obj).authorizeOnDelete(x);
}
}
File: src/foam/lib/PermissionedPropertyPredicate.js (L22-28)
if ( prop.getReadPermissionRequired() ) {
String propName = prop.getName().toLowerCase();
AuthService auth = (AuthService) x.get("auth");
return ( auth != null )
? (auth.check(x, of + ".ro." + propName) || auth.check(x, of + ".rw." + propName))
: false;
File: src/foam/core/auth/PermissionTableView.js (L13-39)
foam.CLASS({
package: 'foam.core.auth',
name: 'PermissionTableView',
extends: 'foam.u2.Controller',
implements: [ 'foam.mlang.Expressions' ],
requires: [
'foam.graphics.ScrollCView',
'foam.core.auth.Group',
'foam.core.auth.GroupPermissionJunction',
'foam.core.auth.Permission'
],
imports: [
'auth',
'groupDAO',
'groupPermissionJunctionDAO',
'permissionDAO',
'user'
],
constants: {
COLS: 26,
ROWS: 17,
ROLE_PREFIX: 'Role'
},
File: src/permissions.jrl (L1-10)
p({"class":"foam.core.auth.Permission","id":"*","description":"Do anything global permission."})
p({"class":"foam.core.auth.Permission","id":"menu.auth","description":"Perform authentication related configuration"})
p({"class":"foam.core.auth.Permission","id":"service.*","description":"Global permission for reading of services"})
p({"class":"foam.core.auth.Permission","id":"service.read.*","description":"Global permission for reading of services from a DAO"})
p({"class":"foam.core.auth.Permission","id":"service.auth.checkUser","description":"Permission to grant access to check user permissions."})
p({"class":"foam.core.auth.Permission","id":"emailmessage.read.*","description":""})
p({"class":"foam.core.auth.Permission","id":"smsmessage.read.*","description":""})
p({"class":"foam.core.auth.Permission","id":"menu.read.admin","description":"Ability to use the 'admin' menu."})
p({"class":"foam.core.auth.Permission","id":"menu.read.admin.send-notification","description":"Ability to use Send Notification UI"})
p({"class":"foam.core.auth.Permission","id":"menu.read.admin.customise-theme","description":"Ability to use Theme Customisation UI"})
TODO: difference between cSpecDAO and AuthenticatedCSpecDAO