https://play.openpolicyagent.org/ この辺を参考にSWI-Prologで書いてみました。
dict を使うとこんな感じで書けます。出力だけJSONにしてます。
conf.pl
data(_{
user_roles: _{
alice: [ admin ],
bob: [ employee, billing ],
eve: [ customer ]
},
role_grants: _{
customer: [
_{ action: read, type: dog },
_{ action: read, type: cat },
_{ action: adopt, type: dog },
_{ action: adopt, type: cat }
],
employee: [
_{ action: read, type: dog },
_{ action: read, type: cat },
_{ action: update, type: dog },
_{ action: update, type: cat }
],
billing: [
_{ action: read, type: finance },
_{ action: update, type: finance }
]
}
}).
allow(Input):- user_is_admin(Input).
allow(Input):-
user_is_granted(Input,Grant),
Input.action = Grant.action,
Input.type = Grant.type.
user_is_admin(Input):-
data(Data),
member(admin, Data.user_roles.get(Input.user)).
user_is_granted(Input,Grant) :-
data(Data),
(member(admin, Data.user_roles.get(Input.user));
member(Role,Data.user_roles.get(Input.user))),
member(Grant,Data.role_grants.get(Role)).
getall(Input,Rs):-
findall(Grant,user_is_granted(Input,Grant),Rs),!.
query(Input,R,R2):- (allow(Input)->R=true;R=false),getall(Input,R2).
:- use_module(library(http/json)).
:-query(_{user: alice, action: read, object: id123, type: finance},R,R2),writeln(R),json_write(current_output,R2),nl.
:-query(_{user: bob, action: read, object: id123, type: finance},R,R2),writeln(R),json_write(current_output,R2),nl.
:-query(_{user: eve, action: read, object: id123, type: finance},R,R2),writeln(R),json_write(current_output,R2),nl.
:-halt.
output
$ swipl conf.pl
true
[
{"action":"read", "type":"dog"},
{"action":"read", "type":"cat"},
{"action":"update", "type":"dog"},
{"action":"update", "type":"cat"},
{"action":"read", "type":"finance"},
{"action":"update", "type":"finance"},
{"action":"read", "type":"dog"},
{"action":"read", "type":"cat"},
{"action":"adopt", "type":"dog"},
{"action":"adopt", "type":"cat"}
]
true
[
{"action":"read", "type":"dog"},
{"action":"read", "type":"cat"},
{"action":"update", "type":"dog"},
{"action":"update", "type":"cat"},
{"action":"read", "type":"finance"},
{"action":"update", "type":"finance"}
]
false
[
{"action":"read", "type":"dog"},
{"action":"read", "type":"cat"},
{"action":"adopt", "type":"dog"},
{"action":"adopt", "type":"cat"}
]
dictを使わない場合はRDB的に以下のように書くと扱いが楽です:
opa.pl
user_role(alice,admin).
user_role(bob,employee).
user_role(bob,billing).
user_role(eve,customer).
role_grants(customer,read,dog).
role_grants(customer,read,cat).
role_grants(customer,adopt,dog).
role_grants(customer,adopt,cat).
role_grants(employee,read,dog).
role_grants(employee,read,cat).
role_grants(employee,update,dog).
role_grants(employee,update,cat).
role_grants(billing,read,finance).
role_grants(billing,update,finance).
allow(Input) :- user_is_granted(Input,Action,Type),
member(action:Action,Input),member(type:Type,Input).
user_is_granted(Input,Grant,Type) :-
member(user:User,Input),
(user_role(User,admin);user_role(User,Role)),
role_grants(Role,Grant,Type).
read_grants(Input,Grants):-
findall(Grant:Type,user_is_granted(Input,Grant,Type),Grants).
query(Input,R,R2):- (allow(Input),R=true,!;R=false,!),read_grants(Input,R2).
:- query([ user: bob, action: read, object: id123, type: finance],R,R2),writeln(bob:R),writeln(R2).
:- query([ user: eve, action: read, object: id123, type: finance],R,R2),writeln(eve:R),writeln(R2).
:- query([ user: alice, action: read, object: id123, type: finance],R,R2),writeln(alice:R),writeln(R2).
:- halt.
output
$ swipl opa.pl
bob:true
[read:dog,read:cat,update:dog,update:cat,read:finance,update:finance]
eve:false
[read:dog,read:cat,adopt:dog,adopt:cat]
alice:true
[read:dog,read:cat,adopt:dog,adopt:cat,read:dog,read:cat,update:dog,update:cat,read:finance,update:finance]