Prolog でポリシーファイルを検索する

https://play.openpolicyagent.org/ この辺を参考にSWI-Prologで書いてみました。

dict を使うとこんな感じで書けます。出力だけJSONにしてます。

```prolog: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

```bash
$ 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的に以下のように書くと扱いが楽です:

```prolog: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

```bash
$ 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]
```