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"]];
}