I have the following Go code:
package main
import (
"context"
_ "embed"
"fmt"
"strings"
"time"
"github.com/ichiban/prolog"
)
//go:embed permissions.pl
var permissions string
type User struct {
Name string
Roles []string
}
func (u *User) CheckPerm(ctx context.Context, model, op string) bool {
userRules := make([]string, 0, len(u.Roles))
for _, role := range u.Roles {
userRules = append(userRules, fmt.Sprintf("hasRole(user, %s).", role))
}
rules := strings.Join(userRules, "\n")
p := prolog.New(nil, nil)
err := p.ExecContext(ctx, permissions)
if err != nil {
panic(err)
}
err = p.ExecContext(ctx, rules)
if err != nil {
panic(err)
}
// result, err := p.QueryContext(ctx, fmt.Sprintf("checkPerm(user, %s, %s).", model, op))
result, err := p.QueryContext(ctx, "checkPerm(user, ?, ?).", model, op)
if err != nil {
panic(err)
}
return result.Next()
}
func main() {
start := time.Now()
defer func() {
fmt.Printf("DONE: %s\n", time.Since(start))
}()
user := &User{
Name: "Alice",
Roles: []string{"jobadmin"},
}
ctx := context.Background()
fmt.Println(user.CheckPerm(ctx, "jobs", "write"))
}
Which references the following permissions.pl:
:- dynamic(hasRole/2).
% role(RoleName, Model, Operation)
role(jobuser, jobs, pivot).
role(jobadmin, jobs, write).
role(jobadmin, jobs, delete).
% if the role can write, the role can read
role(Role, Model, read) :-
role(Role, Model, write).
role(Role, Model, Operation) :-
inherit(ParentRole, Role),
role(ParentRole, Model, Operation).
inherit(jobuser, jobadmin).
checkPerm(User, Model, Operation) :-
hasRole(User, Role),
role(Role, Model, Operation).
This is all done for the sake of learning. My goal is to specify RBAC rules in permissions.pl, append a few rules that define the user's roles, then query with checkPerm/3
to see true or false if the user has permission.
If I load up permissions.pl in swipl and run assert(hasRole(user, jobadmin)), checkPerm(user, jobs, write).
I get true
as expected based on the rules. However, when I run the Go code which essentially asks the same question, I get back false
. It seems to hinge on how I provide parameters to QueryContext
.
In my code sample, I've commented out a version that uses fmt.Sprintf(...)
and that approach works correctly (running the Go code produces true
). However, QueryContext
supports parameters and I'd like to use them properly. I've tried a few approaches to get ?
parameters to work: wrap model
and op
in single quotes or use engine.NewAtom(...)
on model
and op
. But I always get back false
without any errors. What am I doing wrong?
Your code didn't work because QueryContext(ctx, "checkPerm(user, ?, ?).", "jobs", "write")
is same as QueryContext(ctx, "checkPerm(user, \"jobs\", \"write\").")
. Both "jobs"
and "write"
are double quoted in Prolog which means they're strings (lists of single-character atoms)- [j, o, b, s]
and [w, r, i, t, e]
.
The preferred solution is to use atom_chars/2
which converts atoms into strings (chars) and vice versa:
result, err := p.QueryContext(ctx, "atom_chars(Model, ?), atom_chars(Op, ?), checkPerm(user, Model, Op).", model, op)
https://go.dev/play/p/-rx0-72qhOl
I don't recommend :- set_prolog_flag(double_quotes, atom).
because it entirely changes how your prolog processor works just to pass certain parameters.