-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathbase_library.sol
346 lines (306 loc) · 14.4 KB
/
base_library.sol
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
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
pragma solidity 0.5.4;
import {Container} from "./container.sol";
import {BaseAccessControlGroup} from "./base_access_control_group.sol";
import {BaseContent} from "./base_content.sol";
import {IFactorySpace, INodeSpace} from "./base_space_interfaces.sol";
//import "./access_indexor.sol";
import "./meta_object.sol";
/* -- Revision history --
BaseLibrary20190221101700ML: First versioned released
BaseLibrary20190318101300ML: Migrated to 0.4.24
BaseLibrary20190506153700ML: Adds access indexing
BaseLibrary20190510151800ML: Modified createContent to use contentspace factory
BaseLibrary20190515103800ML: Overloads canPublish to take into account EDIT privilege granted for update request and commit
BaseLibrary20190522154000SS: Changed hash bytes32 to string
BaseLibrary20190523121700ML: Fixes logic of add/remove of groups to revert to compact arrays
BaseLibrary20190528151200ML: Uses Container abstraction
BaseLibrary20190605150200ML: Splits out canConfirm from canPublish
BaseLibrary20191010140800ML: Content can be deleted by content owner or the library owner
BaseLibrary20200110162700ML: Adds support for visibility, differentiates rights to access and edit library object and content
BaseLibrary20200211164300ML: Modified to conform to authV3 API
BaseLibrary20200316135200ML: Leverages inherited hasAccess
BaseLibrary20200928110000PO: Replace tx.origin with msg.sender in some cases
BaseLibrary20201129223200ML: Bump up version to match update in the factory
*/
contract BaseLibrary is MetaObject, Container {
bytes32 public version = "BaseLibrary20201129223200ML"; //class name (max 16), date YYYYMMDD, time HHMMSS and Developer initials XX
address payable[] public contributorGroups;
address payable[] public reviewerGroups;
address payable[] public accessorGroups;
uint256 public contributorGroupsLength = 0;
uint256 public reviewerGroupsLength = 0;
uint256 public accessorGroupsLength = 0;
address payable[] public approvalRequests;
uint256 public approvalRequestsLength = 0;
mapping (address => uint256) private approvalRequestsMap; //index offset by 1 to avoid confusing 0 for removed
event ContentObjectCreated(address contentAddress, address content_type, address spaceAddress);
event ContentObjectDeleted(address contentAddress, address spaceAddress);
event ContributorGroupAdded(address group);
event ContributorGroupRemoved(address group);
event ReviewerGroupAdded(address group);
event ReviewerGroupRemoved(address group);
event AccessorGroupAdded(address group);
event AccessorGroupRemoved(address group);
event UnauthorizedOperation(uint operationCode, address candidate);
event ApproveContentRequest(address contentAddress, address submitter);
event ApproveContent(address contentAddress, bool approved, string note);
event UpdateKmsAddress(address addressKms);
constructor(address address_KMS, address payable _content_space) public payable {
contentSpace = _content_space;
addressKMS = address_KMS;
visibility = 0;
indexCategory = 3; // AccessIndexor CATEGORY_LIBRARY
}
function canConfirm() public view returns (bool) {
INodeSpace bcs = INodeSpace(contentSpace);
return bcs.canNodePublish(msg.sender);
}
function canPublish() public view returns (bool) {
if ((msg.sender == owner) || canEdit()) {
return true;
}
return false;
}
function canCommit() public view returns (bool) {
return canEdit();
}
function addToGroupList(address payable _addGroup, address payable[] storage _groupList, uint256 _groupListLength) internal returns (uint256) {
for (uint256 i = 0; i < _groupListLength; i++) {
if (_addGroup == _groupList[i]) {
return _groupListLength;
}
}
if (_groupListLength < _groupList.length) {
_groupList[_groupListLength] = _addGroup;
} else {
_groupList.push(_addGroup);
}
return (_groupListLength + 1);
}
function removeFromGroupList(address payable _removeGroup, address payable[] storage _groupList, uint256 _groupListLength) internal returns (uint256) {
for (uint256 i = 0; i < _groupListLength; i++) {
if (_removeGroup == _groupList[i]) {
delete _groupList[i];
if (i != _groupListLength - 1) {
_groupList[i] = _groupList[_groupListLength - 1];
delete _groupList[_groupListLength - 1];
}
return (_groupListLength - 1);
}
}
return _groupListLength;
}
function addContributorGroup(address payable group) public onlyEditor {
uint256 prevLen = contributorGroupsLength;
contributorGroupsLength = addToGroupList(group, contributorGroups, prevLen);
if (contributorGroupsLength > prevLen) {
emit ContributorGroupAdded(group);
/*
AccessIndexor accessIndex = AccessIndexor(group);
accessIndex.setLibraryRights(address(this), accessIndex.TYPE_SEE(), accessIndex.ACCESS_TENTATIVE());
*/
setRights(group, 0 /*AccessIndexor TYPE_SEE*/, 1 /*AccessIndexor ACCESS_TENTATIVE*/);
}
}
function groupIsListed(address payable group, address payable[] memory groups) internal pure returns (bool) {
for (uint256 i = 0; i < groups.length; i++) {
if (group == groups[i]) {
return true;
}
}
return false;
}
function removeContributorGroup(address payable group) public onlyEditor returns (bool) {
uint256 prevLen = contributorGroupsLength;
contributorGroupsLength = removeFromGroupList(group, contributorGroups, prevLen);
if (contributorGroupsLength < prevLen) {
emit ContributorGroupRemoved(group);
/*
AccessIndexor accessIndex = AccessIndexor(group);
accessIndex.setLibraryRights(address(this), accessIndex.TYPE_SEE(), accessIndex.ACCESS_NONE());
*/
setRights(group, 0 /*AccessIndexor TYPE_SEE*/, 0 /*AccessIndexor ACCESS_TENTATIVE*/);
return true;
}
return false;
}
function addReviewerGroup(address payable group) public onlyEditor {
uint256 prevLen = reviewerGroupsLength;
reviewerGroupsLength = addToGroupList(group, reviewerGroups, prevLen);
if (reviewerGroupsLength > prevLen) {
emit ReviewerGroupAdded(group);
/*
AccessIndexor accessIndex = AccessIndexor(group);
accessIndex.setLibraryRights(address(this), accessIndex.TYPE_ACCESS(), accessIndex.ACCESS_TENTATIVE());
*/
setRights(group, 1 /*AccessIndexor TYPE_ACCESS*/, 1 /*AccessIndexor ACCESS_TENTATIVE*/);
}
}
function removeReviewerGroup(address payable group) public onlyEditor returns (bool) {
uint256 prevLen = reviewerGroupsLength;
reviewerGroupsLength = removeFromGroupList(group, reviewerGroups, prevLen);
if (reviewerGroupsLength < prevLen) {
emit ReviewerGroupRemoved(group);
if (!groupIsListed(group, accessorGroups)) {
/*
AccessIndexor accessIndex = AccessIndexor(group);
accessIndex.setLibraryRights(address(this), accessIndex.TYPE_ACCESS(), accessIndex.ACCESS_NONE());
*/
setRights(group, 1 /*AccessIndexor TYPE_ACCESS*/, 0 /*AccessIndexor ACCESS_NONE*/);
}
return true;
}
return false;
}
function addAccessorGroup(address payable group) public onlyEditor {
uint256 prevLen = accessorGroupsLength;
accessorGroupsLength = addToGroupList(group, accessorGroups, prevLen);
if (accessorGroupsLength > prevLen) {
emit AccessorGroupAdded(group);
/*
AccessIndexor accessIndex = AccessIndexor(group);
accessIndex.setLibraryRights(address(this), accessIndex.TYPE_ACCESS(), accessIndex.ACCESS_TENTATIVE());
*/
setRights(group, 1 /*AccessIndexor TYPE_ACCESS*/,1 /*AccessIndexor ACCESS_TENTATIVE*/);
}
}
function removeAccessorGroup(address payable group) public onlyEditor returns (bool) {
uint256 prevLen = accessorGroupsLength;
accessorGroupsLength = removeFromGroupList(group, accessorGroups, prevLen);
if (accessorGroupsLength < prevLen) {
emit AccessorGroupRemoved(group);
if (!groupIsListed(group, reviewerGroups)) {
/*
AccessIndexor accessIndex = AccessIndexor(group);
accessIndex.setLibraryRights(address(this), accessIndex.TYPE_ACCESS(), accessIndex.ACCESS_NONE());
*/
setRights(group, 1 /*AccessIndexor TYPE_ACCESS*/, 0 /*AccessIndexor ACCESS_NONE*/);
}
}
return false;
}
function hasGroupAccess(address _candidate, address payable[] memory _groupList) internal view returns (bool) {
for (uint i = 0; i < _groupList.length; i++) {
if (_groupList[i] != address(0x0)) {
BaseAccessControlGroup groupContract = BaseAccessControlGroup(_groupList[i]);
if (groupContract.hasAccess(_candidate)) {
return true;
}
}
}
return false;
}
/*
function hasAccess(address _candidate) public view returns (bool) {
if ((visibility >= 10) || (_candidate == owner)) {
return true;
}
address userWallet = IUserSpace(contentSpace).userWallets(_candidate);
AccessIndexor wallet = AccessIndexor(userWallet);
return wallet.checkLibraryRights(address(this), wallet.TYPE_ACCESS());
}
*/
function canContribute(address payable _candidate) public view returns (bool) {
return hasEditorRight(_candidate) || hasGroupAccess(_candidate, contributorGroups);
}
function canReview(address _candidate) public view returns (bool) {
if (_candidate == owner) {
return true;
}
return hasGroupAccess(_candidate, reviewerGroups);
}
function requiresReview() public view returns (bool) {
return (reviewerGroupsLength > 0);
}
function submitApprovalRequest() public returns (bool) {
address payable contentContract = msg.sender;
BaseContent c = BaseContent(contentContract);
if (requiresReview() == false) { //No review required
// 0 indicates approval, custom contract might overwrite that decision
c.updateStatus(0);
// Log event
emit ApproveContent(contentContract, true, "");
return true;
}
if (approvalRequestsMap[contentContract] != 0) {
return false;
}
// Create a new approval request and add to pending list
if (approvalRequestsLength < approvalRequests.length) {
approvalRequests[approvalRequestsLength] = contentContract;
} else {
approvalRequests.push(contentContract);
}
approvalRequestsMap[contentContract] = approvalRequestsLength + 1;
approvalRequestsLength++;
// Log event
emit ApproveContentRequest(contentContract, msg.sender);
return true;
}
function getPendingApprovalRequest(uint256 index) public view returns (address) {
// Read and process the first content in the approvalRequests list
if ((approvalRequestsLength == 0) || (approvalRequestsLength <= index)) {
return address(0x0);
}
return approvalRequests[index];
}
function approveContent(address payable content_contract, bool approved, string memory note) public returns ( bool ) {
require(canReview(msg.sender) == true);
// credit the account based on the percent_complete
uint256 index = approvalRequestsMap[content_contract] - 1;
BaseContent c = BaseContent(content_contract);
// remove the request from the list
delete approvalRequests[index];
approvalRequestsLength--;
approvalRequestsMap[content_contract] = 0;
if (approvalRequestsLength > index) {
address payable lastRequest = approvalRequests[approvalRequestsLength];
approvalRequests[index] = lastRequest;
delete approvalRequests[approvalRequestsLength];
approvalRequestsMap[lastRequest] = index + 1;
}
int currentStatus = c.statusCode();
if (currentStatus > 0) {
int newStatusCode;
if (approved == true) {
newStatusCode = 0; // indicates approval, custom contract might overwrite that decision
} else {
newStatusCode = -1; // alternatively we could use more complex logic like (currentStatus + 1) * -1
}
c.updateStatus(newStatusCode);
// Log event
emit ApproveContent(content_contract, approved, note);
return true;
} else {
return false;
}
}
function createContent(address payable content_type) public returns (address) {
require(msg.sender == tx.origin, "only direct calls allowed");
address content = IFactorySpace(contentSpace).createContent(address(this), content_type);
emit ContentObjectCreated(content, content_type, contentSpace);
return content;
}
// content can be deleted by content owner or the library owner - enforced inside the kill
function deleteContent(address payable _contentAddr) public onlyEditor {
BaseContent content = BaseContent(_contentAddr);
content.kill();
emit ContentObjectDeleted(_contentAddr, contentSpace);
}
function publish(address payable contentObj) public returns (bool) {
require(msg.sender == contentObj);
BaseContent content = BaseContent(contentObj);
// Update the content contract to reflect the approval process
content.updateStatus(1); //update status to in-review
// mark with statusCode 1, which is the default for in-review - NOTE: could be change to be (currentStatus * -1)
bool submitStatus = false;
if (content.statusCode() > 0) {
submitStatus = submitApprovalRequest();
}
return submitStatus;
}
function updateAddressKMS(address address_KMS) public onlyEditor {
addressKMS = address_KMS;
emit UpdateKmsAddress(addressKMS);
}
}