resistance is obsolete ™ ;-)
the groupware construction kit

Object Access Control Lists

OGo supports fine grained access control lists on any OGo object (stored in the relational database).

The ACLs are stored in the object_acl table and the primary key of an object is used to find out about the ACL entries. The ACL system allows positive entries ('allowed') as well as negative entries ('forbidden') and supports arbitary permission flags. The ACL entries themselves are sorted, that is, they have an execution order - if an entry matches, processing will be stopped.

Permissions

As mentioned above, a permission is represented by a certain flag - which is stored in the database as a single character. As a result you can choose between something like 100 permissions (well, in theory 256, but you should restrict permissions to 'readable' names).
Examples:

m - manager access (a local root)
r - object information can be read
w - object can be updated
d - existing objects can be deleted
i - new objects can be created
f - user can write "forms" related to the object (usually a project)
l - object can be 'listed', that is, very basic information can be retrieved

SQL Table: object_acl

CREATE TABLE object_acl (
object_acl_id int not null,
sort_key int not null,
action varchar(10) not null,
object_id varchar(255) not null,
auth_id varchar(255),
permissions varchar(50));

Example:

OGo=# select * from object_acl;
object_acl_id | sort_key | action | object_id | auth_id | permissions ---------------+----------+---------+-----------+---------+-------------
263900 | 0 | allowed | 263750 | 10150 | wr

The ACL entry is '263900' and protects object '263750'. The account or team referred to by '10150' is allowed 'r' (read) and 'w' (write) access.

SkyAccessManager

In Objective-C the ACL system is not managed using commands but rather using an object called the "access manager". The access manager is attached to an LSCommandContext and can be retrieved using:

SkyAccessManager *am = [[self commandContext] accessManager];

Selected SkyAccessManager methods (the 'operation' is actually a set of permission flags, eg 'rwd' or just 'r'):

- (BOOL)operation:(NSString *)_op allowedOnObjectID:(EOGlobalID *)_oid;
- (BOOL)operation:(NSString *)_op allowedOnObjectIDs:(NSArray *)_oids;
- (BOOL)setOperations:(NSDictionary *)_op onObjectID:(EOGlobalID *)_objId;

Since each method potentially does an SQL query (unless the information is already fetched), you should always use a bulk method if you want to check a list of objects - eg if you have fetched 100 persons and need to check whether you are allowed to deliver ('read') them to the user.

-(NSDictionary *)allowedOperationsForObjectIds:(NSArray *)_gids

This method returns a structure containing information on the access rights. Example:

Jan 28 21:12:35 skylistacls [23620]: <0x080B8DF4[ListACLs]> global-ids: (
<0x084F5F9C[EOKeyGlobalID]: Person 263750>
)
Jan 28 21:12:35 skylistacls [23620]: <0x080B8DF4[ListACLs]> allowed operations: {
<0x084F5F9C[EOKeyGlobalID]: Person 263750> = {
<0x0855FE5C[EOKeyGlobalID]: Team 10150> = wr;
};
}

Top-level maps the global-ids passed in to another dictionary, then we have a mapping from the principal to the set of operations defined. In this case the team '10150' has read/write access to person '263750'.

Note: this information can be retrieved by anyone is not itself protected. So take care when displaying that information in the UI (might be inappropriate).

TODO: I'm not sure whether this is appropriate? Does it properly process negative (forbid) ACLs entries?

Debugging SkyAccessManager

To enable extensive access manager logging, enable the SkyAccessManagerDebug default.

Tools

There are two new shell tools for playing with the access manager, skylistacls and skycheckperm.

Misc

Its a somewhat weird detail that the access manager is implemented in Logic/LSFoundation yet the access control handlers are implemented in the DocumentAPI! (eg OGoContacts/SkyContactsAccessHandler.m). Maybe this should be fixed in the future.

SkyAccessHandler

Notably actual object permission checks are not performed by the SkyAccessManager itself, but rather based on so called "SkyAccessHandler's" which are objects that perform and access check for a given global-id. The access handlers are located using the NGBundleManager (that is, the bundle-info.plist declaration) and are keyed on the global-id's entity.

That way, we can have different access control semantics for different objects. For example appointments have a certain set of "implicit" ACLs, ie all the appointment participants have automatically read permissions. Another example are contacts which also contain additional permission specifiers, is_private and is_readonly, which do not require entries in the object_acl table.

Notably "builtin" permissions are almost always faster than ACLs - so you should really only specify one if you really need the flexibility!

SkyAccessHandler
SkyContactsAccessHandler - Person, Enterprise, Team, Company

Zidenote: LSTypeManager

If you only have a primary key, eg some ID taken from the command line, and you want to turn it into an EOGlobalID, you can use the LSTypeManager object which is also attached to the command context.
Example:

id<LSTypeManager> tm = [cmdcxt typeManager];
gids = [tm globalIDsForPrimaryKeys:pkeys];

Note: the primary keys being passed in need to be of the proper format! Eg you cannot use NSString objects, but NSNumber's:

NSNumber *pkey = [NSNumber numberWithUnsignedInt:[_arg unsignedIntValue]];

Like the access manager, it is clever to use bulk queries! The type manager potentially does a SQL query per request (but has an aggressive cache and uses the object_info permanent cache).


Some old text from some other file ...

Enterprise Record

Old-Style Access Check

- (BOOL)isEditDisabled {
id myAccount = [[self session] activeAccount];
id accountId = [myAccount valueForKey:@"companyId"];
id obj = [self object]; BOOL isEnabled = NO;
BOOL isPrivate = [[obj valueForKey:@"isPrivate"] boolValue];
BOOL isReadonly = [[obj valueForKey:@"isReadonly"] boolValue];
isEnabled = ((!isPrivate && !isReadonly) || ([accountId isEqual:[[obj owner] valueForKey:@"ownerId"]]) ||
([[self session] activeAccountIsRoot]));
return !isEnabled;
}

New-Style Access Check using SkyAccessManager

- (BOOL)isEditDisabled {
return ![[[self commandContext] accessManager]
operation:@"w" allowedOnObjectID:
[[self object] valueForKey:@"globalID"]];
}
We welcome your feedback!
Trademarks.  
This site is sponsored by
SKYRIX Software AG
ZideOne GmbH
MDlink