I’m struggling with some ABAC roles as they pertain to GraphQL relationships and I was hoping to get some clarity here. The issue is that when I have a GraphQL query that runs a custom UDF, and that UDF runs as a custom Role, even if the Role has permission to read the relationship data, it’s not returned in the GraphQL response.
Here’s an example of what’s happening.
GraphQL schema
type Relative {
name: String
}
type Parent {
name: String
relative: Relative
}
type ParentWithRelative @embedded {
name: String
relativeName: String
}
type Query {
allParentsSortedByName: [Parent] @resolver(name: "allParentsSortedByName", paginated: true)
allParentsWithRelatives: [ParentWithRelative] @resolver(name: "allParentsWithRelatives", paginated: true)
}
I have an index parentsSortedByName
CreateIndex({
name: 'parentsSortedByName',
source: [Collection('Parent')],
values: [
{
field: ['data', 'name'],
},
{
field: ['ref'],
},
],
})
I have a graphql
role that has permission to call the allParentsSortedByName
and allParentsWithRelatives
functions.
CreateRole({
name: 'graphql',
privileges: [
{
resource: Function('allParentsSortedByName'),
actions: {
call: true,
},
},
{
resource: Function('allParentsWithRelatives'),
actions: {
call: true,
},
},
],
membership: []
})
And an internal
role that has read/call access to everything
CreateRole({
name: 'internal',
privileges: [
{
resource: Collection('Relative'),
actions: {
read: true,
write: false,
create: false,
delete: false,
history_read: false,
history_write: false,
unrestricted_read: false
}
},
{
resource: Collection('Parent'),
actions: {
read: true,
write: false,
create: false,
delete: false,
history_read: false,
history_write: false,
unrestricted_read: false
}
},
{
resource: Index('parentsSortedByName'),
actions: {
unrestricted_read: false,
read: true
}
},
{
resource: Function('allParentsSortedByName'),
actions: {
call: true
}
},
{
resource: Function('allParentsWithRelatives'),
actions: {
call: true
}
}
],
membership: []
})
I have an allParentsSortedByName
function that runs as the internal
role
Update(Function('allParentsSortedByName'), {
name: 'allParentsSortedByName',
role: Role('internal'),
body: Query(
Lambda(
'_',
Let(
{
match: Match(Index('parentsSortedByName')),
page: Paginate(Var('match')),
},
Map(Var('page'), Lambda(['name', 'ref'], Get(Var('ref')))),
),
),
),
})
And an allParentsWithRelatives
function that also runs as the internal role
Update(Function('allParentsWithRelatives'), {
name: 'allParentsWithRelatives',
role: Role('internal'),
body: Query(
Lambda(
'_',
Let(
{
match: Match(Index('parentsSortedByName')),
page: Paginate(Var('match')),
},
Map(Var('page'), Lambda(
['name', 'ref'],
Let({
data: Select(['data'], Get(Var('ref'))),
relative: Get(Select(['relative'], Var('data'))),
relativeName: Select(['data', 'name'], Var('relative'))
},
{
name: Select(['name'], Var('data')),
relativeName: Var('relativeName')
}
)))
),
),
),
})
I add 2 parents, and a relative for each one.
Create(Ref(Collection('Relative'), '1'), { data: { name: 'rel1' }} );
Create(Ref(Collection('Relative'), '2'), { data: { name: 'rel2' }} );
Create(Ref(Collection('Parent'), '1'), { data: { name: 'parent1', relative: Ref(Collection('Relative'), '1') }});
Create(Ref(Collection('Parent'), '2'), { data: { name: 'parent2', relative: Ref(Collection('Relative'), '2') }});
I create a new key with the role internal
and another with the role graphql
.
Using the shell logged in with the secret from the graphql
key I run these and I get the expected results.
Call(Function("allParentsSortedByName"));
Call(Function("allParentsWithRelatives"));
Then I run this and get permission denied, which is what I wanted.
Get(Ref(Collection('Parent'), '1'));
Now in the GraphQL interface, using the Basic authentication, I run these, and they work as expected.
query {
allParentsSortedByName {
data {
name
relative {
name
}
}
}
allParentsWithRelatives {
data {
name
relativeName
}
}
}
Next I do the same with a Bearer token using the internal
key, and that also works as expected.
Now I do it with a Bearer token using the graphql
key. In this case allParentsWithRelatives
works as expected because it’s not using relations. However (finally we get to my issue) the allParentsSortedByName
query returns null
for the relative. So how do I fix the above system so that I can get the relative data in the GraphQL way?
(The reason I’m using a Role to run my UDFs is because I don’t have a backend so all my tokens are public, and I want to prevent anyone from being able to run arbitrary reads.)