careful-doctor-42516
02/07/2024, 7:01 PMimport { Namespace, SubjectSet, Context } from "@ory/permission-namespace-types"
class LivingPerson implements Namespace {
}
class PUser implements Namespace {
related: {
livingPersons: LivingPerson[]
}
}
class P implements Namespace {
related: {
members: PUser[]
owners: PUser[]
admins: PUser[]
}
}
class UUR implements Namespace {
related: {
owningPs: P[]
}
permits = {
view: (ctx: Context): boolean =>
this.related.owningPs.traverse( (p): boolean =>
p.related.admins.traverse((u): boolean =>
u.related.livingPersons.traverse((l): boolean => (l == ctx.subject))))
}
}
The syntax error as reported by the "permissions" tab, Permission Rules subtab, on the ory console is that on the line that has
this.related.owningPs.traverse( (p): boolean =>
there is a syntax error at the "p". It says it expected => but got :
The thing I wanted to express was that
• UUR has exactly 1 owning P
• PUser has exactly 1 LivingPerson
I did both of this as arrays, because I didn't see any examples of doing them as singletons.
Related: Can one express something like "traverse the relation R of BLAH only for BLAHs that have property X set?" If so, I guess I don't see how to do that when inputing the 'facts" about some particular BLAH.
Any help would be grand.witty-holiday-65473
02/07/2024, 7:08 PMwitty-holiday-65473
02/07/2024, 7:09 PMcareful-doctor-42516
02/07/2024, 7:09 PMcareful-doctor-42516
02/07/2024, 7:09 PMwitty-holiday-65473
02/07/2024, 7:09 PMwitty-holiday-65473
02/07/2024, 7:09 PMcareful-doctor-42516
02/07/2024, 7:10 PMwitty-holiday-65473
02/07/2024, 7:10 PMcareful-doctor-42516
02/07/2024, 7:11 PMwitty-holiday-65473
02/07/2024, 7:17 PM(p): boolean
is invalid.witty-holiday-65473
02/07/2024, 7:17 PMwitty-holiday-65473
02/07/2024, 7:18 PMcareful-doctor-42516
02/07/2024, 7:35 PMcareful-doctor-42516
02/08/2024, 1:19 PMcareful-doctor-42516
02/08/2024, 1:25 PMwitty-holiday-65473
02/15/2024, 9:46 AMwitty-holiday-65473
02/15/2024, 9:52 AMadd Project:administrators#access@alice
add Project:managers#access@bob
add Project:users#access@carol
add Role:admin#principal@Project:administrators
add Role:manager#principal@Project:managers
add Role:user#principal@Project:users
add Policy:createPolicy#attach@Role:admin
add Policy:editPolicy#attach@Role:manager
add Policy:readPolicy#attach@Role:user
add Resource:repository#create@Policy:createPolicy
add Resource:repository#edit@Policy:editPolicy
add Resource:repository#read@Policy:readPolicy
add ResourcePool:resourcePool#resources@Resource:repository
check Allowed ResourcePool:resourcePool#can_create@alice
check Allowed ResourcePool:resourcePool#can_edit@alice
check Allowed ResourcePool:resourcePool#can_read@alice
check Denied ResourcePool:resourcePool#can_create@bob
check Allowed ResourcePool:resourcePool#can_edit@bob
check Allowed ResourcePool:resourcePool#can_read@bob
check Denied ResourcePool:resourcePool#can_create@carol
check Denied ResourcePool:resourcePool#can_edit@carol
check Allowed ResourcePool:resourcePool#can_read@carol
check Denied ResourcePool:resourcePool#can_create@repository
check Denied ResourcePool:resourcePool#can_edit@repository
check Denied ResourcePool:resourcePool#can_read@repository
check Allowed ResourcePool:resourcePool#can_edit@resourcePool
definition file:
// Copyright © 2023 Ory Corp
// SPDX-License-Identifier: Apache-2.0
import { Context, Namespace } from "@ory/keto-namespace-types"
class User implements Namespace {}
class Project implements Namespace {
related: {
access: User[]
}
}
class Role implements Namespace {
related: {
principal: Project[]
}
permits = {
can_assume: (ctx: Context): boolean =>
this.related.principal.traverse((p) =>
p.related.access.includes(ctx.subject),
),
}
}
class Policy implements Namespace {
related: {
attach: Role[]
}
}
class Resource implements Namespace {
related: {
create: Policy[]
edit: Policy[]
read: Policy[]
}
permits = {
can_create: (ctx: Context): boolean =>
this.related.create.traverse((p) =>
p.related.attach.traverse((r) => r.permits.can_assume(ctx)),
),
can_edit: (ctx: Context): boolean =>
this.related.edit.traverse((p) =>
p.related.attach.traverse((r) => r.permits.can_assume(ctx)),
),
can_read: (ctx: Context): boolean =>
this.related.read.traverse((p) =>
p.related.attach.traverse((r) => r.permits.can_assume(ctx)),
),
}
}
class ResourcePool implements Namespace {
related: {
resources: Resource[]
}
permits = {
can_create: (ctx: Context): boolean =>
this.related.resources.traverse((r) => r.permits.can_create(ctx)),
can_edit: (ctx: Context): boolean =>
this.equals(ctx.subject) ||
this.permits.can_create(ctx) ||
this.related.resources.traverse((r) => r.permits.can_edit(ctx)),
can_read: (ctx: Context): boolean =>
this.permits.can_edit(ctx) ||
this.related.resources.traverse((r) => r.permits.can_read(ctx)),
}
}
keep in mind this will not work on master. only on my fork(s). kitchen-sink
is the one that has all the things.
give that one a go and let me know if you have questions.
the "cmd" script i use to "add" "check" and "del" tuples from the first "tuple file" is here:
#!/usr/bin/env bash
ansi() {
if ! [[ $2 =~ ^[0-9]+ ]] || [[ -z "$3" ]]; then
echo -e "\e[${1}m${*:2}\e[0m"
else
echo -e "\e[${1}m$(printf "%*s" ${2} ${*:3})\e[0m"
fi
}
red() {
ansi 31 "$@"
}
green() {
ansi 32 "$@"
}
yellow() {
ansi 33 "$@"
}
blue() {
ansi 34 "$@"
}
gray() {
ansi 90 "$@"
}
file="$1"
cmd="$2"
if [[ "$file" == "del-all" ]]; then
keto relation-tuple delete-all --force $is
exit 0
fi
if [[ "$file" == "parse" ]]; then
echo 'flowchart LR
%%{init: {"flowchart": {"defaultRenderer": "elk"}} }%%'
keto relation-tuple get --format json-pretty $is | jq -r '.|.relation_tuples|.[]|
if(.subject_set != null) then
" ".subject_set.object+" --> |"+.relation+"| "+.object
else
" "+.subject_id+"(("+.subject_id+"))"+" --> |"+.relation+"| "+.object
end' | sort
exit 0
fi
if [[ -z "$file" ]]; then
printf "$0 <file> <cmd>\n\tfile: the file to process\n\tcmd: the cmd to filter\n\nAt least <file> is required\n\n"
exit 1
fi
if [[ "$cmd" == "parse" ]]; then
data=$(cat $file | jq -r '.[]|.relation_tuple|if(.subject_set != null) then "add "+.namespace+":"+.object+"#"+.relation+"@"+.subject_set.namespace+":"+.subject_set.object else "add "+.namespace+":"+.object+"#"+.relation+"@"+.subject_id end')
if [[ -z "$data" ]]; then
data=$(cat $file | jq -r '.relation_tuples|.[]|if(.subject_set != null) then "add "+.namespace+":"+.object+"#"+.relation+"@"+.subject_set.namespace+":"+.subject_set.object else "add "+.namespace+":"+.object+"#"+.relation+"@"+.subject_id end' 2>/dev/null)
fi
echo "$data" > $file
exit 0
fi
readarray -t commands < "$file"
if [[ -z "$cmd" ]]; then
hascmd=""
else
hascmd="1"
fi
for line in "${commands[@]}"
do
if [[ "$line" =~ ^[[:space:]]*#.*$ ]]; then
gray "$line"
continue
fi
read -a params <<< "$line"
if [[ -z "$hascmd" ]]; then
cmd="${params[0]}"
fi
if [[ "$cmd" =~ add|del|check|checkAll ]]; then
if [[ "${params[0]}" == "add" ]]; then
if [[ "$cmd" == "del" ]]; then
echo "Removing: $(echo "${params[1]}")"
echo "${params[1]}" | keto relation-tuple parse - --format json | keto relation-tuple delete - >/dev/null $is
elif [[ "$cmd" == "add" ]]; then
exists="$(echo "${params[1]}" | keto relation-tuple parse - --format json | $(jq -r 'if(.subject_set != null) then "keto relation-tuple get -q --namespace "+.namespace+" --relation "+.relation+" --object "+.object+" --subject-set "+.subject_set.namespace+":"+.subject_set.object+"#"+.subject_set.relation+" '$is'" else "keto relation-tuple get -q --namespace "+.namespace+" --relation "+.relation+" --object "+" "+.object+" --subject-id "+.subject_id+" '$is'" end'))"
if [[ -z "$exists" ]]; then
echo "Inserting: ${params[1]}"
echo "${params[1]}" | keto relation-tuple parse - --format json | keto relation-tuple create - >/dev/null $is
else
gray "Relation exists: $exists"
fi
fi
elif [[ "${params[0]}" == "check" && "$cmd" == "check" ]]; then
expect="${params[1]}"
tuple="${params[2]}"
result="$(keto check `echo "$tuple" | keto relation-tuple parse - --format json | jq -r 'if(.subject_set != null) then .subject_set.namespace+":"+.subject_set.object+"#"+.subject_set.relation+" "+.relation+" "+.namespace+" "+.object else .subject_id+" "+.relation+" "+.namespace+" "+.object end'` $is 2>/dev/null)"
if [[ "$expect" == "$result" ]]; then
printf "%s %s %s\n" "Check:" "$(green 7 "$expect")" "$tuple"
else
printf "%s %s %s\n" "Check:" "$(red 7 "$expect")" "$tuple"
fi
elif [[ "${params[0]}" == "checkAll" && "$cmd" == "checkAll" ]]; then
IFS=', ' read -r -a obj_list <<< "${params[1]}"
IFS=', ' read -r -a rel_list <<< "${params[2]}"
IFS=', ' read -r -a sub_list <<< "${params[3]}"
let count=$((${#obj_list[@]}*${#rel_list[@]}*${#sub_list[@]}))
let i=0
for sub in "${sub_list[@]}"
do
for rel in "${rel_list[@]}"
do
for obj in "${obj_list[@]}"
do
i=$((i+1))
tuple="$obj#$rel@$sub"
ptuple="$(ansi 96 "$obj")#$(ansi 95 "$rel")@$(ansi 92 "$sub")"
result="$(keto check `echo "$tuple" | keto relation-tuple parse - --format json | jq -r 'if(.subject_set != null) then .subject_set.namespace+":"+.subject_set.object+"#"+.subject_set.relation+" "+.relation+" "+.namespace+" "+.object else .subject_id+" "+.relation+" "+.namespace+" "+.object end'` $is 2>/dev/null)"
result="$(yellow 7 $result)"
printf "[%3d of %3d] %s %s %s\n" $i $count "Check:" "$result" "$ptuple"
done
done
done
fi
fi
done
don't judge my bash script. i realize i could have just used getopt(s) and it would have been cleaner but a "perfect test script" was not my goal.witty-holiday-65473
02/15/2024, 9:57 AMcmd parse
careful-doctor-42516
02/22/2024, 3:17 PM