Skip to content

Commit

Permalink
Fix graphql schema for m2o fields without permissions to related coll…
Browse files Browse the repository at this point in the history
…ection (#13015)

* fix graphql schema for m2o fields

* add e2e tests for graphql m2o & o2m

* remove unused code

* fix mariadb json error

* attempt to fix oracle

* possibly fix graphql m2o queries
  • Loading branch information
azrikahar committed May 3, 2022
1 parent 62d8224 commit ad46bfe
Show file tree
Hide file tree
Showing 7 changed files with 237 additions and 9 deletions.
13 changes: 5 additions & 8 deletions api/src/utils/reduce-schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,17 +53,14 @@ export function reduceSchema(
continue;
}

const relatedCollection: string | undefined =
schema.relations.find((relation) => relation.collection === collectionName && relation.field === fieldName)
?.related_collection ||
schema.relations.find(
(relation) => relation.related_collection === collectionName && relation.meta?.one_field === fieldName
)?.collection;
const o2mRelation = schema.relations.find(
(relation) => relation.related_collection === collectionName && relation.meta?.one_field === fieldName
);

if (
relatedCollection &&
o2mRelation &&
!permissions?.some(
(permission) => permission.collection === relatedCollection && actions.includes(permission.action)
(permission) => permission.collection === o2mRelation.collection && actions.includes(permission.action)
)
) {
continue;
Expand Down
110 changes: 110 additions & 0 deletions tests/api/items/many-to-many.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,116 @@ describe('/items', () => {
});
});
});

describe('/:collection/:id GraphQL Query', () => {
describe('retrieves an artist and an event off the artists_events table', () => {
it.each(vendors)('%s', async (vendor) => {
const artist = createArtist();
const event = createEvent();
await seedTable(databases.get(vendor)!, 1, 'artists', artist);
await seedTable(databases.get(vendor)!, 1, 'events', event);

const relation = await seedTable(
databases.get(vendor)!,
1,
'artists_events',
{
artists_id: artist.id,
events_id: event.id,
},
{
select: ['id'],
where: ['artists_id', artist.id],
}
);

const query = `
{
artists_events_by_id (id: ${relation[relation.length - 1].id}) {
artists_id {
name
}
events_id {
cost
}
}
}`;

const response = await request(getUrl(vendor))
.post('/graphql')
.send({ query })
.set('Authorization', 'Bearer AdminToken')
.expect('Content-Type', /application\/json/)
.expect(200);

const { data } = response.body;

expect(data.artists_events_by_id).toMatchObject({
artists_id: { name: expect.any(String) },
events_id: { cost: expect.any(Number) },
});
});
});

describe('should get users of the directus_roles table with read permissions to directus_users', () => {
it.each(vendors)('%s', async (vendor) => {
const query = `
{
roles {
id
users {
id
}
}
}`;

const response = await request(getUrl(vendor))
.post('/graphql/system')
.send({ query })
.set('Authorization', 'Bearer AdminToken')
.expect('Content-Type', /application\/json/)
.expect(200);

const { data } = response.body;

expect(data.roles[data.roles.length - 1]).toMatchObject({
id: expect.any(String),
users: expect.arrayContaining([
expect.objectContaining({
id: expect.any(String),
}),
]),
});
});
});

describe('should not get users of the directus_roles table without read permissions to directus_users', () => {
it.each(vendors)('%s', async (vendor) => {
const query = `
{
roles {
id
users
}
}`;

const response = await request(getUrl(vendor))
.post('/graphql/system')
.send({ query })
.set('Authorization', 'Bearer UserToken')
.expect('Content-Type', /application\/json/)
.expect(400);

const { errors } = response.body;

expect(errors[0].extensions.code).toBe('GRAPHQL_VALIDATION_EXCEPTION');
expect(errors[0].extensions.graphqlErrors[0].message).toBe(
'Cannot query field "users" on type "directus_roles".'
);
});
});
});

describe('/:collection/:id PATCH', () => {
describe('updates one artists_events to a different artist', () => {
it.each(vendors)('%s', async (vendor) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,109 @@ describe('/items', () => {
});
});

describe('/:collection GraphQL Query', () => {
describe('retrieves all items from guest table with favorite_artist', () => {
it.each(vendors)('%s', async (vendor) => {
const artist = createArtist();
const name = 'test-user';
await seedTable(databases.get(vendor)!, 1, 'artists', artist);
const guests = createMany(createGuest, 10, { name, favorite_artist: artist.id });
await seedTable(databases.get(vendor)!, 1, 'guests', guests);

const query = `
{
guests (filter: { name: { _eq: "${name}" } }) {
birthday
favorite_artist {
name
}
}
}`;

const response = await request(getUrl(vendor))
.post('/graphql')
.send({ query })
.set('Authorization', 'Bearer AdminToken')
.expect('Content-Type', /application\/json/)
.expect(200);

const { data } = response.body;

expect(data.guests.length).toBeGreaterThanOrEqual(10);
expect(data.guests[data.guests.length - 1]).toMatchObject({
birthday: expect.any(String),
favorite_artist: expect.objectContaining({
name: expect.any(String),
}),
});
});
});

describe('Should get "favorite_artist" field with ID string when retrieving all items from guests without read permission to artists', () => {
it.each(vendors)('%s', async (vendor) => {
const artist = createArtist();
const name = 'test-user';
await seedTable(databases.get(vendor)!, 1, 'artists', artist);
const guests = createMany(createGuest, 10, { name, favorite_artist: artist.id });
await seedTable(databases.get(vendor)!, 1, 'guests', guests);

const query = `
{
guests (filter: { name: { _eq: "${name}" } }) {
favorite_artist
}
}`;

const response = await request(getUrl(vendor))
.post('/graphql')
.send({ query })
.set('Authorization', 'Bearer UserToken')
.expect('Content-Type', /application\/json/)
.expect(200);

const { data } = response.body;

expect(data.guests.length).toBeGreaterThanOrEqual(10);
expect(data.guests[data.guests.length - 1]).toMatchObject({
favorite_artist: expect.any(String),
});
});
});

describe('Should not get nested fields in "favourite_artist" when retrieving all items from guests without read permission to artists', () => {
it.each(vendors)('%s', async (vendor) => {
const artist = createArtist();
const name = 'test-user';
await seedTable(databases.get(vendor)!, 1, 'artists', artist);
const guests = createMany(createGuest, 10, { name, favorite_artist: artist.id });
await seedTable(databases.get(vendor)!, 1, 'guests', guests);

const query = `
{
guests (filter: { name: { _eq: "${name}" } }) {
favorite_artist {
name
}
}
}`;

const response = await request(getUrl(vendor))
.post('/graphql')
.send({ query })
.set('Authorization', 'Bearer UserToken')
.expect('Content-Type', /application\/json/)
.expect(400);

const { errors } = response.body;

expect(errors[0].extensions.code).toBe('GRAPHQL_VALIDATION_EXCEPTION');
expect(errors[0].extensions.graphqlErrors[0].message).toBe(
'Field "favorite_artist" must not have a selection since type "String" has no subfields.'
);
});
});
});

describe('/:collection POST', () => {
describe('createOne', () => {
describe('creates one guest with a favorite_artist', () => {
Expand Down
1 change: 0 additions & 1 deletion tests/setup/seeds/02_directus_relations.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ exports.seed = async function (knex) {
many_field: 'favorite_artist',
one_collection: 'artists',
},
{ many_collection: 'artists_events', many_field: 'events_id', one_collection: 'artists' },
{
many_collection: 'artists_events',
many_field: 'events_id',
Expand Down
19 changes: 19 additions & 0 deletions tests/setup/seeds/06_directus_permissions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
exports.seed = async function (knex) {
await knex('directus_permissions').del();

return await knex('directus_permissions').insert([
// TestUser role permissions
{
role: '214faee7-d6a6-4a4c-b1cd-f9e9bd0b6fb7',
collection: 'guests',
action: 'read',
fields: '*',
},
{
role: '214faee7-d6a6-4a4c-b1cd-f9e9bd0b6fb7',
collection: 'directus_roles',
action: 'read',
fields: '*',
},
]);
};
File renamed without changes.
File renamed without changes.

0 comments on commit ad46bfe

Please sign in to comment.