forked from couchbaselabs/mobile-training-todo
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathsync-gateway-config.json
245 lines (219 loc) · 7.77 KB
/
sync-gateway-config.json
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
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
{
"interface":":4984",
"log": ["HTTP", "Auth"],
"databases": {
"todo": {
"server": "walrus:",
"bucket": "todo",
"users": {
"user1": {"password": "pass", "admin_channels": ["user1"]},
"user2": {"password": "pass", "admin_channels": ["user2"]},
"mod": {"password": "pass", "admin_roles": ["moderator"]},
"admin": {"password": "pass", "admin_roles": ["admin"]}
},
"roles": {
"moderator": {},
"admin": {}
},
"sync": `
function(doc, oldDoc){
/* Type validation */
if (isCreate()) {
// Don't allow creating a document without a type.
validateNotEmpty("type", doc.type);
} else if (isUpdate()) {
// Don't allow changing the type of any document.
validateReadOnly("type", doc.type, oldDoc.type);
}
if (getType() == "moderator") {
/* Control Write Access */
// Only allow admins to add/remove moderators.
requireRole("admin");
/* Validate */
if (!isDelete()) {
// Validate required fields.
validateNotEmpty("username", doc.username);
if (isCreate()) {
// We use a key pattern to ensure unique moderators within the system,
// so we need to ensure that doc._id matches the pattern
// moderator.{username}.
if (doc._id != "moderator." + doc.username) {
throw({forbidden: "_id must match the pattern moderator.{username}."});
}
} else {
// doc._id is tied to username, validated during create, and must remain this
// way to ensure unique moderators within the system.
validateReadOnly("username", doc.username, oldDoc.username);
}
}
/* Route */
if (!isDelete()) {
// Add user to moderator role.
role(doc.username, "role:moderator");
}
// Add doc to the user's channel.
var username = doc._deleted ? oldDoc.username : doc.username;
channel(username);
/* Grant Read Access */
if (!isDelete()) {
// Grant user access to moderators channel.
access(doc.username, "moderators");
}
// Grant user access to their channel.
access(doc.username, doc.username);
} else if (getType() == "task-list") { // Task List access control
/* Write Access */
var owner = doc._deleted ? oldDoc.owner : doc.owner;
try {
// Moderators can create/update lists for other users.
requireRole("moderator");
} catch (e) {
// Users can create/update lists for themselves.
requireUser(owner);
}
/* Validation */
if (!isDelete()) {
// Validate required fields.
validateNotEmpty("name", doc.name);
validateNotEmpty("owner", doc.owner);
if (isCreate()) {
// Validate that the _id is prefixed by owner.
if (!hasPrefix(doc._id, doc.owner + ".")) {
throw({forbidden: "task-list id must be prefixed by list owner"});
}
} else {
// Don’t allow task-list ownership to be changed.
validateReadOnly("owner", doc.owner, oldDoc.owner);
}
}
/* Routing */
// Add doc to task-list's channel.
channel("task-list." + doc._id);
channel("moderators");
/* Read Access */
// Grant task-list owner access to the task-list, its tasks, and its users.
access(owner, "task-list." + doc._id);
access(owner, "task-list." + doc._id + ".users");
access("role:moderator", "task-list." + doc._id);
} else if (getType() == "task") {
/* Write Access */
if (!isDelete()) {
validateNotEmpty("taskList", doc.taskList);
}
var owner = doc._deleted ? oldDoc.taskList.owner : doc.taskList.owner;
var listId = doc._deleted ? oldDoc.taskList.id : doc.taskList.id;
try {
requireAccess("task-list." + listId);
} catch (e) {
requireUser(owner);
}
/* Validate */
if (!isDelete()) {
// Validate required fields.
validateNotEmpty("taskList.id", doc.taskList.id);
validateNotEmpty("taskList.owner", doc.taskList.owner);
validateNotEmpty("task", doc.task);
if (isCreate()) {
// Validate that the taskList.id is prefixed by taskList.owner. We only need to
// validate this during create because these fields are read-only after create.
if (!hasPrefix(doc.taskList.id, doc.taskList.owner + ".")) {
throw({forbidden: "task-list id must be prefixed by task-list owner"});
}
} else {
// Don’t allow tasks to be moved to another task-list.
validateReadOnly("taskList.id", doc.taskList.id, oldDoc.taskList.id);
validateReadOnly("taskList.owner", doc.taskList.owner, oldDoc.taskList.owner);
}
}
/* Route */
// Add doc to task-list and moderators channel.
channel("task-list." + listId);
channel("moderators");
} else if (getType() == "task-list.user") {
/* Control Write Access */
if (!isDelete()) {
validateNotEmpty("taskList", doc.taskList);
}
var owner = doc._deleted ? oldDoc.taskList.owner : doc.taskList.owner;
var username = doc._deleted ? oldDoc.username : doc.username;
try {
requireUser(owner);
} catch (e) {
requireRole("moderator");
}
/* Validate */
if (!isDelete()) {
// Validate required fields.
validateNotEmpty("taskList.id", doc.taskList.id);
validateNotEmpty("taskList.owner", doc.taskList.owner);
validateNotEmpty("username", doc.username);
if (isCreate()) {
// We use a key pattern to ensure unique users w/in a list, so we need to
// ensure that doc._id matches the pattern {taskList.id}.{username}.
if (doc._id != doc.taskList.id + "." + doc.username) {
throw({forbidden: "_id must match the pattern {taskList.id}.{username}."});
}
// Validate that the taskList.id is prefixed by taskList.owner.
if (!hasPrefix(doc.taskList.id, doc.taskList.owner + ".")) {
throw({forbidden: "task-list id must be prefixed by task-list owner"});
}
} else {
// Don’t allow users to be moved to another task-list. Also, doc._id is tied to
// these values, validated during create, and must remain this way to ensure
// uniqueness within a list.
validateReadOnly("taskList.id", doc.taskList.id, oldDoc.taskList.id);
validateReadOnly("taskList.owner", doc.taskList.owner, oldDoc.taskList.owner);
}
}
/* Route */
// Add doc to task-list users and moderators channel.
var listId = doc._deleted ? oldDoc.taskList.id : doc.taskList.id;
channel("task-list." + listId + ".users");
channel("moderators");
/* Grant Read Access */
// Grant the user access to the task-list and its tasks.
if (!isDelete()) {
access(doc.username, "task-list." + listId);
}
channel(username);
} else {
// Log invalid document type error.
log("Invalid document type: " + doc.type);
throw({forbidden: "Invalid document type: " + doc.type});
}
function getType() {
return (isDelete() ? oldDoc.type : doc.type);
}
function isCreate() {
// Checking false for the Admin UI to work
return ((oldDoc == false) || (oldDoc == null || oldDoc._deleted) && !isDelete());
}
function isUpdate() {
return (!isCreate() && !isDelete());
}
function isDelete() {
return (doc._deleted == true);
}
function validateNotEmpty(key, value) {
if (!value) {
throw({forbidden: key + " is not provided."});
}
}
function validateReadOnly(name, value, oldValue) {
if (value != oldValue) {
throw({forbidden: name + " is read-only."});
}
}
// Checks whether the provided value starts with the specified prefix
function hasPrefix(value, prefix) {
if (value && prefix) {
return value.substring(0, prefix.length) == prefix
} else {
return false
}
}
}
`
}
}
}