-
-
Notifications
You must be signed in to change notification settings - Fork 10
/
Copy pathsetRights.js
154 lines (133 loc) · 5.91 KB
/
setRights.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
const { MoleculerError } = require('moleculer').Errors;
const { MIME_TYPES } = require('@semapps/mime-types');
const urlJoin = require('url-join');
const {
getAclUriFromResourceUri,
convertBodyToTriples,
filterTriplesForResource,
FULL_AGENTCLASS_URI,
FULL_FOAF_AGENT
} = require('../../../utils');
module.exports = {
api: async function api(ctx) {
const contentType = ctx.meta.headers['content-type'];
let { slugParts } = ctx.params;
if (!contentType || (contentType !== MIME_TYPES.JSON && contentType !== MIME_TYPES.TURTLE))
throw new MoleculerError(`Content type not supported : ${contentType}`, 400, 'BAD_REQUEST');
const newRights = await convertBodyToTriples(ctx.meta.body, contentType);
if (newRights.length === 0) throw new MoleculerError('PUT rights cannot be empty', 400, 'BAD_REQUEST');
// This is the root container
if (!slugParts || slugParts.length === 0) slugParts = ['/'];
await ctx.call('webacl.resource.setRights', {
resourceUri: urlJoin(this.settings.baseUrl, ...slugParts),
newRights
});
ctx.meta.$statusCode = 204;
},
action: {
visibility: 'public',
params: {
resourceUri: { type: 'string' },
webId: { type: 'string', optional: true },
// newRights is an array of objects of the form { auth: 'http://localhost:3000/_acl/container29#Control', p: 'http://www.w3.org/ns/auth/acl#agent', o: 'https://data.virtual-assembly.org/users/sebastien.rosset' }
newRights: { type: 'array', optional: false, min: 1 }
// minimum is one right : We cannot leave a resource without rights.
},
async handler(ctx) {
let { webId, newRights, resourceUri } = ctx.params;
webId = webId || ctx.meta.webId || 'anon';
const isContainer = await this.checkResourceOrContainerExists(ctx, resourceUri);
// check that the user has Control perm.
// TODO: bypass this check if user is 'system' (use system as a super-admin) ?
const { control } = await ctx.call('webacl.resource.hasRights', {
resourceUri,
rights: { control: true },
webId
});
if (!control) throw new MoleculerError('Access denied ! user must have Control permission', 403, 'ACCESS_DENIED');
// filter out all the newRights that are not for the resource
const aclUri = getAclUriFromResourceUri(this.settings.baseUrl, resourceUri);
newRights = newRights.filter(a => filterTriplesForResource(a, aclUri, isContainer));
if (newRights.length === 0)
throw new MoleculerError('The rights cannot be changed because they are incorrect', 400, 'BAD_REQUEST');
const currentPerms = await this.getExistingPerms(
ctx,
resourceUri,
this.settings.baseUrl,
this.settings.graphName,
isContainer
);
// find the difference between newRights and currentPerms. add only what is not existent yet. and remove those that are not needed anymore
const differenceAdd = newRights.filter(
x => !currentPerms.some(y => x.auth === y.auth && x.o === y.o && x.p === y.p)
);
const differenceDelete = currentPerms.filter(
x => !newRights.some(y => x.auth === y.auth && x.o === y.o && x.p === y.p)
);
if (differenceAdd.length === 0 && differenceDelete.length === 0) return;
// compile a list of Authorization already present. because if some of them don't exist, we need to create them
const currentAuths = this.compileAuthorizationNodesMap(currentPerms);
let addRequest = '';
for (const add of differenceAdd) {
if (!currentAuths[add.auth]) {
addRequest += this.generateNewAuthNode(add.auth);
currentAuths[add.auth] = 1;
} else {
currentAuths[add.auth] += 1;
}
addRequest += `<${add.auth}> <${add.p}> <${add.o}>.\n`;
}
let deleteRequest = '';
for (const del of differenceDelete) {
deleteRequest += `<${del.auth}> <${del.p}> <${del.o}>.\n`;
currentAuths[del.auth] -= 1;
}
for (const [auth, count] of Object.entries(currentAuths)) {
if (count < 1) {
deleteRequest += this.generateNewAuthNode(auth);
}
}
// we do the 2 calls in one, so it is in the same transaction, and will rollback in case of failure.
await ctx.call('triplestore.update', {
query: `INSERT DATA { GRAPH <${this.settings.graphName}> { ${addRequest} } }; DELETE DATA { GRAPH <${this.settings.graphName}> { ${deleteRequest} } }`,
webId: 'system'
});
const defaultRightsUpdated =
isContainer &&
(differenceAdd.some(triple => triple.auth.includes('#Default')) ||
differenceDelete.some(triple => triple.auth.includes('#Default')));
const addPublicRead = differenceAdd.some(
triple => triple.auth.includes('#Read') && triple.p === FULL_AGENTCLASS_URI && triple.o === FULL_FOAF_AGENT
);
const removePublicRead = differenceDelete.some(
triple => triple.auth.includes('#Read') && triple.p === FULL_AGENTCLASS_URI && triple.o === FULL_FOAF_AGENT
);
const addDefaultPublicRead =
isContainer &&
differenceAdd.some(
triple =>
triple.auth.includes('#DefaultRead') && triple.p === FULL_AGENTCLASS_URI && triple.o === FULL_FOAF_AGENT
);
const removeDefaultPublicRead =
isContainer &&
differenceDelete.some(
triple =>
triple.auth.includes('#DefaultRead') && triple.p === FULL_AGENTCLASS_URI && triple.o === FULL_FOAF_AGENT
);
const returnValues = {
uri: resourceUri,
webId,
dataset: ctx.meta.dataset,
created: false,
isContainer,
defaultRightsUpdated,
addPublicRead,
removePublicRead,
addDefaultPublicRead,
removeDefaultPublicRead
};
ctx.emit('webacl.resource.updated', returnValues, { meta: { webId: null, dataset: null } });
return returnValues;
}
}
};