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
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
|
<?php
/**
* ownCloud
*
* @author Michael Gapczynski
* @copyright 2011 Michael Gapczynski GapczynskiM@gmail.com
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
* License as published by the Free Software Foundation; either
* version 3 of the License, or any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU AFFERO GENERAL PUBLIC LICENSE for more details.
*
* You should have received a copy of the GNU Affero General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
*/
/**
* This class manages shared items within the database.
*/
class OC_Share {
const WRITE = 1;
const DELETE = 2;
const UNSHARED = -1;
const PUBLICLINK = "public";
private $token;
/**
* Share an item, adds an entry into the database
* @param $source The source location of the item
* @param $uid_shared_with The user or group to share the item with
* @param $permissions The permissions, use the constants WRITE and DELETE
*/
public function __construct($source, $uid_shared_with, $permissions) {
$uid_owner = OCP\USER::getUser();
$query = OCP\DB::prepare("INSERT INTO *PREFIX*sharing VALUES(?,?,?,?,?)");
// Check if this is a reshare and use the original source
if ($result = OC_Share::getSource($source)) {
$source = $result;
}
if ($uid_shared_with == self::PUBLICLINK) {
$token = sha1("$uid_shared_with-$source");
OCP\Util::emitHook('OC_Share', 'public', array('source'=>$source, 'token'=>$token, 'permissions'=>$permissions));
$query->execute(array($uid_owner, self::PUBLICLINK, $source, $token, $permissions));
$this->token = $token;
} else {
if (OC_Group::groupExists($uid_shared_with)) {
$gid = $uid_shared_with;
$uid_shared_with = OC_Group::usersInGroup($gid);
// Remove the owner from the list of users in the group
$uid_shared_with = array_diff($uid_shared_with, array($uid_owner));
} else if (OCP\User::userExists($uid_shared_with)) {
$userGroups = OC_Group::getUserGroups($uid_owner);
// Check if the user is in one of the owner's groups
foreach ($userGroups as $group) {
if ($inGroup = OC_Group::inGroup($uid_shared_with, $group)) {
$gid = null;
$uid_shared_with = array($uid_shared_with);
break;
}
}
if (!$inGroup) {
throw new Exception("You can't share with ".$uid_shared_with);
}
} else {
throw new Exception($uid_shared_with." is not a user");
}
foreach ($uid_shared_with as $uid) {
// Check if this item is already shared with the user
$checkSource = OCP\DB::prepare("SELECT source FROM *PREFIX*sharing WHERE source = ? AND uid_shared_with ".self::getUsersAndGroups($uid, false));
$resultCheckSource = $checkSource->execute(array($source))->fetchAll();
// TODO Check if the source is inside a folder
if (count($resultCheckSource) > 0) {
if (!isset($gid)) {
throw new Exception("This item is already shared with ".$uid);
} else {
// Skip this user if sharing with a group
continue;
}
}
// Check if the target already exists for the user, if it does append a number to the name
$sharedFolder = '/'.$uid.'/files/Shared';
$target = $sharedFolder."/".basename($source);
$checkTarget = OCP\DB::prepare("SELECT source FROM *PREFIX*sharing WHERE target = ? AND uid_shared_with ".self::getUsersAndGroups($uid, false)." LIMIT 1");
$result = $checkTarget->execute(array($target))->fetchAll();
if (count($result) > 0) {
if ($pos = strrpos($target, ".")) {
$name = substr($target, 0, $pos);
$ext = substr($target, $pos);
} else {
$name = $target;
$ext = "";
}
$counter = 1;
while (count($result) > 0) {
$target = $name."_".$counter.$ext;
$result = $checkTarget->execute(array($target))->fetchAll();
$counter++;
}
}
// Update mtime of shared folder to invoke a file cache rescan
$rootView=new OC_FilesystemView('/');
if (!$rootView->is_dir($sharedFolder)) {
if (!$rootView->is_dir('/'.$uid.'/files')) {
OC_Util::tearDownFS();
OC_Util::setupFS($uid);
OC_Util::tearDownFS();
}
$rootView->mkdir($sharedFolder);
}
$rootView->touch($sharedFolder);
if (isset($gid)) {
$uid = $uid."@".$gid;
}
OCP\Util::emitHook('OC_Share', 'user', array('source'=>$source, 'target'=>$target, 'with'=>$uid, 'permissions'=>$permissions));
$query->execute(array($uid_owner, $uid, $source, $target, $permissions));
}
}
}
/**
* Remove any duplicate or trailing '/' from the path
* @return A clean path
*/
private static function cleanPath($path) {
$path = rtrim($path, "/");
return preg_replace('{(/)\1+}', "/", $path);
}
/**
* Generate a string to be used for searching for uid_shared_with that handles both users and groups
* @param $uid (Optional) The uid to get the user groups for, a gid to get the users in a group, or if not set the current user
* @return An IN operator as a string
*/
private static function getUsersAndGroups($uid = null, $includePrivateLinks = true) {
$in = " IN(";
if (isset($uid) && OC_Group::groupExists($uid)) {
$users = OC_Group::usersInGroup($uid);
foreach ($users as $user) {
// Add a comma only if the the current element isn't the last
if ($user !== end($users)) {
$in .= "'".$user."@".$uid."', ";
} else {
$in .= "'".$user."@".$uid."'";
}
}
} else if (isset($uid)) {
// TODO Check if this is necessary, only constructor needs it as IN. It would be better for other queries to just return =$uid
$in .= "'".$uid."'";
$groups = OC_Group::getUserGroups($uid);
foreach ($groups as $group) {
$in .= ", '".$uid."@".$group."'";
}
} else {
$uid = OCP\USER::getUser();
$in .= "'".$uid."'";
$groups = OC_Group::getUserGroups($uid);
foreach ($groups as $group) {
$in .= ", '".$uid."@".$group."'";
}
}
if ($includePrivateLinks) {
$in .= ", '".self::PUBLICLINK."'";
}
$in .= ")";
return $in;
}
private static function updateFolder($uid_shared_with) {
if ($uid_shared_with != self::PUBLICLINK) {
if (OC_Group::groupExists($uid_shared_with)) {
$uid_shared_with = OC_Group::usersInGroup($uid_shared_with);
// Remove the owner from the list of users in the group
$uid_shared_with = array_diff($uid_shared_with, array(OCP\USER::getUser()));
} else if ($uid = strstr($uid_shared_with, '@', true)) {
$uid_shared_with = array($uid);
} else {
$uid_shared_with = array($uid_shared_with);
}
foreach ($uid_shared_with as $uid) {
$sharedFolder = $uid.'/files/Shared';
// Update mtime of shared folder to invoke a file cache rescan
$rootView = new OC_FilesystemView('/');
$rootView->touch($sharedFolder);
}
}
}
/**
* Create a new entry in the database for a file inside a shared folder
*
* $oldTarget and $newTarget may be the same value. $oldTarget exists in case the file is being moved outside of the folder
*
* @param $oldTarget The current target location
* @param $newTarget The new target location
*/
public static function pullOutOfFolder($oldTarget, $newTarget) {
$folders = self::getParentFolders($oldTarget);
$source = $folders['source'].substr($oldTarget, strlen($folders['target']));
$item = self::getItem($folders['target']);
$query = OCP\DB::prepare("INSERT INTO *PREFIX*sharing VALUES(?,?,?,?,?)");
$query->execute(array($item[0]['uid_owner'], OCP\USER::getUser(), $source, $newTarget, $item[0]['permissions']));
}
/**
* Get the item with the specified target location
* @param $target The target location of the item
* @return An array with the item
*/
public static function getItem($target) {
$target = self::cleanPath($target);
$query = OCP\DB::prepare("SELECT uid_owner, source, permissions FROM *PREFIX*sharing WHERE target = ? AND uid_shared_with = ? LIMIT 1");
return $query->execute(array($target, OCP\USER::getUser()))->fetchAll();
}
/**
* Get the item with the specified source location
* @param $source The source location of the item
* @return An array with the users and permissions the item is shared with
*/
public static function getMySharedItem($source) {
$source = self::cleanPath($source);
$query = OCP\DB::prepare("SELECT uid_shared_with, permissions FROM *PREFIX*sharing WHERE source = ? AND uid_owner = ?");
$result = $query->execute(array($source, OCP\USER::getUser()))->fetchAll();
if (count($result) > 0) {
return $result;
} else if ($originalSource = self::getSource($source)) {
return $query->execute(array($originalSource, OCP\USER::getUser()))->fetchAll();
} else {
return false;
}
}
/**
* Get all items the current user is sharing
* @return An array with all items the user is sharing
*/
public static function getMySharedItems() {
$query = OCP\DB::prepare("SELECT uid_shared_with, source, permissions FROM *PREFIX*sharing WHERE uid_owner = ?");
return $query->execute(array(OCP\USER::getUser()))->fetchAll();
}
/**
* Get the items within a shared folder that have their own entry for the purpose of name, location, or permissions that differ from the folder itself
*
* Works for both target and source folders. Can be used for getting all items shared with you e.g. pass '/MTGap/files'
*
* @param $folder The folder of the items to look for
* @return An array with all items in the database that are in the folder
*/
public static function getItemsInFolder($folder) {
$folder = self::cleanPath($folder);
// Append '/' in order to filter out the folder itself if not already there
if (substr($folder, -1) !== "/") {
$folder .= "/";
}
$length = strlen($folder);
$query = OCP\DB::prepare("SELECT uid_owner, source, target, permissions FROM *PREFIX*sharing WHERE SUBSTR(source, 1, ?) = ? OR SUBSTR(target, 1, ?) = ? AND uid_shared_with ".self::getUsersAndGroups());
return $query->execute(array($length, $folder, $length, $folder))->fetchAll();
}
/**
* Get the source and target parent folders of the specified target location
* @param $target The target location of the item
* @return An array with the keys 'source' and 'target' with the values of the source and target parent folders
*/
public static function getParentFolders($target) {
$target = self::cleanPath($target);
$query = OCP\DB::prepare("SELECT source FROM *PREFIX*sharing WHERE target = ? AND uid_shared_with".self::getUsersAndGroups()." LIMIT 1");
// Prevent searching for user directory e.g. '/MTGap/files'
$userDirectory = substr($target, 0, strpos($target, "files") + 5);
$target = dirname($target);
$result = array();
while ($target != "" && $target != "/" && $target != "." && $target != $userDirectory && $target != "\\") {
// Check if the parent directory of this target location is shared
$result = $query->execute(array($target))->fetchAll();
if (count($result) > 0) {
break;
}
$target = dirname($target);
}
if (count($result) > 0) {
// Return both the source folder and the target folder
return array("source" => $result[0]['source'], "target" => $target);
} else {
return false;
}
}
/**
* Get the source location of the item at the specified target location
* @param $target The target location of the item
* @return Source location or false if target location is not valid
*/
public static function getSource($target) {
$target = self::cleanPath($target);
$query = OCP\DB::prepare("SELECT source FROM *PREFIX*sharing WHERE target = ? AND uid_shared_with ".self::getUsersAndGroups()." LIMIT 1");
$result = $query->execute(array($target))->fetchAll();
if (count($result) > 0) {
return $result[0]['source'];
} else {
$folders = self::getParentFolders($target);
if ($folders == true) {
return $folders['source'].substr($target, strlen($folders['target']));
} else {
return false;
}
}
}
public static function getTarget($source) {
$source = self::cleanPath($source);
$query = OCP\DB::prepare("SELECT target FROM *PREFIX*sharing WHERE source = ? AND uid_owner = ? LIMIT 1");
$result = $query->execute(array($source, OCP\USER::getUser()))->fetchAll();
if (count($result) > 0) {
return $result[0]['target'];
} else {
// TODO Check in folders
return false;
}
}
/**
* Get the user's permissions for the item at the specified target location
* @param $target The target location of the item
* @return The permissions, use bitwise operators to check against the constants WRITE and DELETE
*/
public static function getPermissions($target) {
$target = self::cleanPath($target);
$query = OCP\DB::prepare("SELECT permissions FROM *PREFIX*sharing WHERE target = ? AND uid_shared_with ".self::getUsersAndGroups()." LIMIT 1");
$result = $query->execute(array($target))->fetchAll();
if (count($result) > 0) {
return $result[0]['permissions'];
} else {
$folders = self::getParentFolders($target);
if ($folders == true) {
$result = $query->execute(array($folders['target']))->fetchAll();
if (count($result) > 0) {
return $result[0]['permissions'];
}
} else {
OCP\Util::writeLog('files_sharing',"Not existing parent folder : ".$target,OCP\Util::ERROR);
return false;
}
}
}
/**
* Get the token for a public link
* @return The token of the public link, a sha1 hash
*/
public function getToken() {
return $this->token;
}
/**
* Get the token for a public link
* @param $source The source location of the item
* @return The token of the public link, a sha1 hash
*/
public static function getTokenFromSource($source) {
$query = OCP\DB::prepare("SELECT target FROM *PREFIX*sharing WHERE source = ? AND uid_shared_with = ? AND uid_owner = ? LIMIT 1");
$result = $query->execute(array($source, self::PUBLICLINK, OCP\USER::getUser()))->fetchAll();
if (count($result) > 0) {
return $result[0]['target'];
} else {
return false;
}
}
/**
* Set the target location to a new value
*
* You must use the pullOutOfFolder() function to change the target location of a file inside a shared folder if the target location differs from the folder
*
* @param $oldTarget The current target location
* @param $newTarget The new target location
*/
public static function setTarget($oldTarget, $newTarget) {
$oldTarget = self::cleanPath($oldTarget);
$newTarget = self::cleanPath($newTarget);
$query = OCP\DB::prepare("UPDATE *PREFIX*sharing SET target = REPLACE(target, ?, ?) WHERE uid_shared_with ".self::getUsersAndGroups());
$query->execute(array($oldTarget, $newTarget));
}
/**
* Change the permissions for the specified item and user
*
* You must construct a new shared item to change the permissions of a file inside a shared folder if the permissions differ from the folder
*
* @param $source The source location of the item
* @param $uid_shared_with The user to change the permissions for
* @param $permissions The permissions, use the constants WRITE and DELETE
*/
public static function setPermissions($source, $uid_shared_with, $permissions) {
$source = self::cleanPath($source);
$query = OCP\DB::prepare("UPDATE *PREFIX*sharing SET permissions = ? WHERE SUBSTR(source, 1, ?) = ? AND uid_owner = ? AND uid_shared_with ".self::getUsersAndGroups($uid_shared_with));
$query->execute(array($permissions, strlen($source), $source, OCP\USER::getUser()));
}
/**
* Unshare the item, removes it from all specified users
*
* You must use the pullOutOfFolder() function to unshare a file inside a shared folder and set $newTarget to nothing
*
* @param $source The source location of the item
* @param $uid_shared_with Array of users to unshare the item from
*/
public static function unshare($source, $uid_shared_with) {
$source = self::cleanPath($source);
$uid_owner = OCP\USER::getUser();
$query = OCP\DB::prepare("DELETE FROM *PREFIX*sharing WHERE SUBSTR(source, 1, ?) = ? AND uid_owner = ? AND uid_shared_with ".self::getUsersAndGroups($uid_shared_with, false));
$query->execute(array(strlen($source), $source, $uid_owner));
self::updateFolder($uid_shared_with);
}
/**
* Unshare the item from the current user, removes it only from the database and doesn't touch the source file
*
* You must use the pullOutOfFolder() function before you call unshareFromMySelf() and set the delete parameter to false to unshare from self a file inside a shared folder
*
* @param $target The target location of the item
* @param $delete (Optional) If true delete the entry from the database, if false the permission is set to UNSHARED
*/
public static function unshareFromMySelf($target, $delete = true) {
$target = self::cleanPath($target);
if ($delete) {
$query = OCP\DB::prepare("DELETE FROM *PREFIX*sharing WHERE SUBSTR(target, 1, ?) = ? AND uid_shared_with ".self::getUsersAndGroups());
$query->execute(array(strlen($target), $target));
} else {
$query = OCP\DB::prepare("UPDATE *PREFIX*sharing SET permissions = ? WHERE SUBSTR(target, 1, ?) = ? AND uid_shared_with ".self::getUsersAndGroups());
$query->execute(array(self::UNSHARED, strlen($target), $target));
}
}
/**
* Remove the item from the database, the owner deleted the file
* @param $arguments Array of arguments passed from OC_Hook
*/
public static function deleteItem($arguments) {
$source = "/".OCP\USER::getUser()."/files".self::cleanPath($arguments['path']);
$result = self::getMySharedItem($source);
if (is_array($result)) {
foreach ($result as $item) {
self::updateFolder($item['uid_shared_with']);
}
}
$query = OCP\DB::prepare("DELETE FROM *PREFIX*sharing WHERE SUBSTR(source, 1, ?) = ? AND uid_owner = ?");
$query->execute(array(strlen($source), $source, OCP\USER::getUser()));
}
/**
* Rename the item in the database, the owner renamed the file
* @param $arguments Array of arguments passed from OC_Hook
*/
public static function renameItem($arguments) {
$oldSource = "/".OCP\USER::getUser()."/files".self::cleanPath($arguments['oldpath']);
$newSource = "/".OCP\USER::getUser()."/files".self::cleanPath($arguments['newpath']);
$query = OCP\DB::prepare("UPDATE *PREFIX*sharing SET source = REPLACE(source, ?, ?) WHERE uid_owner = ?");
$query->execute(array($oldSource, $newSource, OCP\USER::getUser()));
}
public static function updateItem($arguments) {
$source = "/".OCP\USER::getUser()."/files".self::cleanPath($arguments['path']);
$result = self::getMySharedItem($source);
if (is_array($result)) {
foreach ($result as $item) {
self::updateFolder($item['uid_shared_with']);
}
}
}
public static function removeUser($arguments) {
$query = OCP\DB::prepare("SELECT uid_shared_with FROM *PREFIX*sharing WHERE uid_owner = ?");
$result = $query->execute(array($arguments['uid']))->fetchAll();
if (is_array($result)) {
$result = array_unique($result);
foreach ($result as $item) {
self::updateFolder($item['uid_shared_with']);
}
$query = OCP\DB::prepare('DELETE FROM *PREFIX*sharing WHERE uid_owner = ? OR uid_shared_with '.self::getUsersAndGroups($arguments['uid']));
$query->execute(array($arguments['uid']));
}
}
public static function addToGroupShare($arguments) {
$length = -strlen($arguments['gid']) - 1;
$query = OCP\DB::prepare('SELECT uid_owner, source, permissions FROM *PREFIX*sharing WHERE SUBSTR(uid_shared_with, '.$length.') = ?');
$gid = '@'.$arguments['gid'];
$result = $query->execute(array($gid))->fetchAll();
if (count($result) > 0) {
$lastSource = '';
for ($i = 0; $i < count($result); $i++) {
if ($result[$i]['source'] != $lastSource) {
new OC_Share($result[$i]['source'], $arguments['gid'], $result[$i]['permissions']);
$lastSource = $result[$i]['source'];
}
}
}
}
public static function removeFromGroupShare($arguments) {
$query = OCP\DB::prepare('DELETE FROM *PREFIX*sharing WHERE uid_shared_with = ?');
$query->execute(array($arguments['uid'].'@'.$arguments['gid']));
self::updateFolder($arguments['uid']);
}
}
|