Passed
Pull Request — master (#14)
by
unknown
01:49
created

FederatedCloudShare.list_accepted_federated_cloudshares()   A

Complexity

Conditions 1

Size

Total Lines 4
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 4
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 1
nop 1
1
import enum
2
import requests
3
4
PUBLIC_API_NAME_CLASS_MAP = dict()
5
6
7
class Requester(object):
8
    def __init__(self, endpoint, user, passwd, js=False):
9
        self.query_components = []
10
11
        self.to_json = js
12
13
        self.base_url = endpoint
14
        # GroupFolders.url = endpoint + "/ocs/v2.php/apps/groupfolders/folders"
15
16
        self.h_get = {"OCS-APIRequest": "true"}
17
        self.h_post = {"OCS-APIRequest": "true",
18
                       "Content-Type": "application/x-www-form-urlencoded"}
19
        self.auth_pk = (user, passwd)
20
        self.API_URL = None
21
22
    def rtn(self, resp):
23
        if self.to_json:
24
            return resp.json()
25
        else:
26
            return resp.content.decode("UTF-8")
27
28
    def get(self, url="", params=None):
29
        url = self.get_full_url(url)
30
        res = requests.get(url, auth=self.auth_pk, headers=self.h_get, params=params)
31
        return self.rtn(res)
32
33
    def post(self, url="", data=None):
34
        url = self.get_full_url(url)
35
        res = requests.post(url, auth=self.auth_pk, data=data, headers=self.h_post)
36
        return self.rtn(res)
37
38
    def put(self, url="", data=None):
39
        url = self.get_full_url(url)
40
        res = requests.put(url, auth=self.auth_pk, data=data, headers=self.h_post)
41
        return self.rtn(res)
42
43
    def delete(self, url="", data=None):
44
        url = self.get_full_url(url)
45
        res = requests.delete(url, auth=self.auth_pk, data=data, headers=self.h_post)
46
        return self.rtn(res)
47
48
    def get_full_url(self, additional_url=""):
49
        """
50
        Build full url for request to NextCloud api
51
52
        Construct url from self.base_url, self.API_URL, additional_url (if given), add format=json param if self.json
53
54
        :param additional_url: str
55
            add to url after api_url
56
        :return: str
57
        """
58
        if additional_url and not str(additional_url).startswith("/"):
59
            additional_url = "/{}".format(additional_url)
60
61
        if self.to_json:
62
            self.query_components.append("format=json")
63
64
        ret = "{base_url}{api_url}{additional_url}".format(
65
            base_url=self.base_url, api_url=self.API_URL, additional_url=additional_url)
66
67
        if self.to_json:
68
            ret += "?format=json"
69
        return ret
70
71
72
def nextcloud_method(method_to_wrap):
73
    class_name = method_to_wrap.__qualname__.split(".", 1)[0]
74
    PUBLIC_API_NAME_CLASS_MAP[method_to_wrap.__name__] = class_name
75
    return method_to_wrap
76
77
78
class NextCloud(object):
79
80
    def __init__(self, endpoint, user, password, js=False):
81
        self.query_components = []
82
83
        requester = Requester(endpoint, user, password, js)
84
85
        self.functionality = {
86
            "Apps": Apps(requester),
87
            "Group": Group(requester),
88
            "GroupFolders": GroupFolders(requester),
89
            "Share": Share(requester),
90
            "User": User(requester),
91
            "FederatedCloudShare": FederatedCloudShare(requester),
92
            "Capabilities": Capabilities(requester)
93
        }
94
        for name, location in PUBLIC_API_NAME_CLASS_MAP.items():
95
            setattr(self, name, getattr(self.functionality[location], name))
96
97
98
class WithRequester(object):
99
100
    API_URL = NotImplementedError
101
102
    def __init__(self, requester):
103
        self._requester = requester
104
105
    @property
106
    def requester(self):
107
        """ Get requester instance """
108
        # dynamically set API_URL for requester
109
        self._requester.API_URL = self.API_URL
110
        return self._requester
111
112
113
class Capabilities(WithRequester):
114
    API_URL = "/ocs/v1.php/cloud/capabilities"
115
116
    @nextcloud_method
117
    def get_capabilities(self):
118
        """ Obtain capabilities provided by the Nextcloud server and its apps """
119
        return self.requester.get()
120
121
122
class GroupFolders(WithRequester):
123
    API_URL = "/apps/groupfolders/folders"
124
125
    @nextcloud_method
126
    def get_group_folders(self):
127
        """
128
        Return a list of call configured folders and their settings
129
130
        Returns:
131
132
        """
133
        return self.requester.get()
134
135
    @nextcloud_method
136
    def get_group_folder(self, fid):
137
        """
138
        Return a specific configured folder and it's settings
139
140
        Args:
141
            fid (int/str): group folder id
142
143
        Returns:
144
145
        """
146
        return self.requester.get(fid)
147
148
    @nextcloud_method
149
    def create_group_folder(self, mountpoint):
150
        """
151
        Create a new group folder
152
153
        Args:
154
            mountpoint (str): name for the new folder
155
156
        Returns:
157
158
        """
159
        return self.requester.post(data={"mountpoint": mountpoint})
160
161
    @nextcloud_method
162
    def delete_group_folder(self, fid):
163
        """
164
        Delete a group folder
165
166
        Args:
167
            fid (int/str): group folder id
168
169
        Returns:
170
171
        """
172
        return self.requester.delete(fid)
173
174
    @nextcloud_method
175
    def grant_access_to_group_folder(self, fid, gid):
176
        """
177
        Give a group access to a folder
178
179
        Args:
180
            fid (int/str): group folder id
181
            gid (str): group to share with id
182
183
        Returns:
184
185
        """
186
        url = "/".join([str(fid), "groups"])
187
        return self.requester.post(url, data={"group": gid})
188
189
    @nextcloud_method
190
    def revoke_access_to_group_folder(self, fid, gid):
191
        """
192
        Remove access from a group to a folder
193
194
        Args:
195
            fid (int/str): group folder id
196
            gid (str): group id
197
198
        Returns:
199
200
        """
201
        url = "/".join([str(fid), "groups", gid])
202
        return self.requester.delete(url)
203
204
    @nextcloud_method
205
    def set_permissions_to_group_folder(self, fid, gid, permissions):
206
        """
207
        Set the permissions a group has in a folder
208
209
        Args:
210
            fid (int/str): group folder id
211
            gid (str): group id
212
            permissions (int): The new permissions for the group as attribute of Permission class
213
214
        Returns:
215
216
        """
217
        url = "/".join([str(fid), "groups", gid])
218
        return self.requester.post(url=url, data={"permissions": permissions})
219
220
    @nextcloud_method
221
    def set_quota_of_group_folder(self, fid, quota):
222
        """
223
        Set the quota for a folder in bytes
224
225
        Args:
226
            fid (int/str): group folder id
227
            quota (int/str): The new quota for the folder in bytes, user -3 for unlimited
228
229
        Returns:
230
231
        """
232
        url = "/".join([str(fid), "quota"])
233
        return self.requester.post(url, {"quota": quota})
234
235
    @nextcloud_method
236
    def rename_group_folder(self, fid, mountpoint):
237
        """
238
        Change the name of a folder
239
240
        Args:
241
            fid (int/str): group folder id
242
            mountpoint (str): The new name for the folder
243
244
        Returns:
245
246
        """
247
        url = "/".join([str(fid), "mountpoint"])
248
        return self.requester.post(url=url, data={"mountpoint": mountpoint})
249
250
251
class Share(WithRequester):
252
    API_URL = "/ocs/v2.php/apps/files_sharing/api/v1"
253
    LOCAL = "shares"
254
255
    def get_local_url(self, additional_url=""):
256
        if additional_url:
257
            return "/".join([self.LOCAL, additional_url])
258
        return self.LOCAL
259
260
    @staticmethod
261
    def validate_share_parameters(path, share_type, share_with):
262
        """
263
        Check if share parameters make sense
264
265
        Args:
266
            path (str): path to the file/folder which should be shared
267
            share_type (int): ShareType attribute
268
            share_with (str): user/group id with which the file should be shared
269
270
        Returns:
271
            bool: True if parameters make sense together, False otherwise
272
        """
273
        if (path is None or not isinstance(share_type, int)) \
274
                or (share_type in [ShareType.GROUP, ShareType.USER, ShareType.FEDERATED_CLOUD_SHARE]
275
                    and share_with is None):
276
            return False
277
        return True
278
279
    @nextcloud_method
280
    def get_shares(self):
281
        """ Get all shares from the user """
282
        return self.requester.get(self.get_local_url())
283
284
    @nextcloud_method
285
    def get_shares_from_path(self, path, reshares=None, subfiles=None):
286
        """
287
        Get all shares from a given file/folder
288
289
        Args:
290
            path (str): path to file/folder
291
            reshares (bool): (optional) return not only the shares from the current user but all shares from the given file
292
            subfiles (bool): (optional) return all shares within a folder, given that path defines a folder
293
294
        Returns:
295
296
        """
297
        url = self.get_local_url()
298
        params = {
299
            "path": path,
300
            "reshares": None if reshares is None else str(bool(reshares)).lower(),  # TODO: test reshares, subfiles
301
            "subfiles": None if subfiles is None else str(bool(subfiles)).lower(),
302
        }
303
        return self.requester.get(url, params=params)
304
305
    @nextcloud_method
306
    def get_share_info(self, sid):
307
        """
308
        Get information about a given share
309
310
        Args:
311
            sid (int): share id
312
313
        Returns:
314
        """
315
        return self.requester.get(self.get_local_url(sid))
316
317
    @nextcloud_method
318
    def create_share(
319
            self, path, share_type, share_with=None, public_upload=None,
320
            password=None, permissions=None):
321
        """
322
        Share a file/folder with a user/group or as public link
323
324
        Mandatory fields: share_type, path and share_with for share_type USER (0) or GROUP (1).
325
326
        Args:
327
            path (str): path to the file/folder which should be shared
328
            share_type (int): ShareType attribute
329
            share_with (str): user/group id with which the file should be shared
330
            public_upload (bool): bool, allow public upload to a public shared folder (true/false)
331
            password (str): password to protect public link Share with
332
            permissions (int): sum of selected Permission attributes
333
334
        Returns:
335
336
        """
337
        if not self.validate_share_parameters(path, share_type, share_with):
338
            return False
339
340
        url = self.get_local_url()
341
        if public_upload:
342
            public_upload = "true"
343
344
        data = {"path": path, "shareType": share_type}
345
        if share_type in [ShareType.GROUP, ShareType.USER, ShareType.FEDERATED_CLOUD_SHARE]:
346
            data["shareWith"] = share_with
347
        if public_upload:
348
            data["publicUpload"] = public_upload
349
        if share_type == ShareType.PUBLIC_LINK and password is not None:
350
            data["password"] = str(password)
351
        if permissions is not None:
352
            data["permissions"] = permissions
353
        return self.requester.post(url, data)
354
355
    @nextcloud_method
356
    def delete_share(self, sid):
357
        """
358
        Remove the given share
359
360
        Args:
361
            sid (str): share id
362
363
        Returns:
364
365
        """
366
        return self.requester.delete(self.get_local_url(sid))
367
368
    @nextcloud_method
369
    def update_share(self, sid, permissions=None, password=None, public_upload=None, expire_date=""):
370
        """
371
        Update a given share, only one value can be updated per request
372
373
        Args:
374
            sid (str): share id
375
            permissions (int): sum of selected Permission attributes
376
            password (str): password to protect public link Share with
377
            public_upload (bool): bool, allow public upload to a public shared folder (true/false)
378
            expire_date (str): set an expire date for public link shares. Format: ‘YYYY-MM-DD’
379
380
        Returns:
381
382
        """
383
        params = dict(
384
            permissions=permissions,
385
            password=password,
386
            expireDate=expire_date
387
        )
388
        if public_upload:
389
            params["publicUpload"] = "true"
390
        if public_upload is False:
391
            params["publicUpload"] = "false"
392
393
        # check if only one param specified
394
        specified_params_count = sum([int(bool(each)) for each in params.values()])
395
        if specified_params_count > 1:
396
            raise ValueError("Only one parameter for update can be specified per request")
397
398
        url = self.get_local_url(sid)
399
        return self.requester.put(url, data=params)
400
401
402
class FederatedCloudShare(WithRequester):
403
    API_URL = "/ocs/v2.php/apps/files_sharing/api/v1"
404
    FEDERATED = "remote_shares"
405
406
    def get_federated_url(self, additional_url=""):
407
        if additional_url:
408
            return "/".join([self.FEDERATED, additional_url])
409
        return self.FEDERATED
410
411
    @nextcloud_method
412
    def list_accepted_federated_cloudshares(self):
413
        url = self.get_federated_url()
414
        return self.requester.get(url)
415
416
    @nextcloud_method
417
    def get_known_federated_cloudshare(self, sid):
418
        url = self.get_federated_url(sid)
419
        return self.requester.get(url)
420
421
    @nextcloud_method
422
    def delete_accepted_federated_cloudshare(self, sid):
423
        url = self.get_federated_url(sid)
424
        return self.requester.delete(url)
425
426
    @nextcloud_method
427
    def list_pending_federated_cloudshares(self, sid):
428
        url = self.get_federated_url("pending")
429
        return self.requester.get(url)
430
431
    @nextcloud_method
432
    def accept_pending_federated_cloudshare(self, sid):
433
        url = self.get_federated_url("pending/{sid}".format(sid=sid))
434
        return self.requester.post(url)
435
436
    @nextcloud_method
437
    def decline_pending_federated_cloudshare(self, sid):
438
        url = self.get_federated_url("pending/{sid}".format(sid=sid))
439
        return self.requester.delete(url)
440
441
442
class Apps(WithRequester):
443
    API_URL = "/ocs/v1.php/cloud/apps"
444
445
    @nextcloud_method
446
    def get_apps(self, filter=None):
447
        """
448
        Get a list of apps installed on the Nextcloud server
449
450
        :param filter: str, optional "enabled" or "disabled"
451
        :return:
452
        """
453
        params = {
454
            "filter": filter
455
        }
456
        return self.requester.get(params=params)
457
458
    @nextcloud_method
459
    def get_app(self, app_id):
460
        """
461
        Provide information on a specific application
462
463
        :param app_id: str, app id
464
        :return:
465
        """
466
        return self.requester.get(app_id)
467
468
    @nextcloud_method
469
    def enable_app(self, app_id):
470
        """
471
        Enable an app
472
473
        :param app_id: str, app id
474
        :return:
475
        """
476
        return self.requester.post(app_id)
477
478
    @nextcloud_method
479
    def disable_app(self, app_id):
480
        """
481
        Disable the specified app
482
483
        :param app_id: str, app id
484
        :return:
485
        """
486
        return self.requester.delete(app_id)
487
488
489
class Group(WithRequester):
490
    API_URL = "/ocs/v1.php/cloud/groups"
491
492
    @nextcloud_method
493
    def get_groups(self, search=None, limit=None, offset=None):
494
        """
495
        Retrieve a list of groups from the Nextcloud server
496
497
        :param search: string, optional search string
498
        :param limit: int, optional limit value
499
        :param offset: int, optional offset value
500
        :return:
501
        """
502
        params = {
503
            'search': search,
504
            'limit': limit,
505
            'offset': offset
506
        }
507
        return self.requester.get(params=params)
508
509
    @nextcloud_method
510
    def add_group(self, gid):
511
        """
512
        Add a new group
513
514
        :param gid: str, group name
515
        :return:
516
        """
517
        msg = {"groupid": gid}
518
        return self.requester.post("", msg)
519
520
    @nextcloud_method
521
    def get_group(self, gid):
522
        """
523
        Retrieve a list of group members
524
525
        :param gid: str, group name
526
        :return:
527
        """
528
        return self.requester.get("{gid}".format(gid=gid))
529
530
    @nextcloud_method
531
    def get_subadmins(self, gid):
532
        """
533
        List subadmins of the group
534
535
        :param gid: str, group name
536
        :return:
537
        """
538
        return self.requester.get("{gid}/subadmins".format(gid=gid))
539
540
    @nextcloud_method
541
    def delete_group(self, gid):
542
        """
543
        Remove a group
544
545
        :param gid: str, group name
546
        :return:
547
        """
548
        return self.requester.delete("{gid}".format(gid=gid))
549
550
551
class User(WithRequester):
552
    API_URL = "/ocs/v1.php/cloud/users"
553
554
    @nextcloud_method
555
    def add_user(self, uid, passwd):
556
        """
557
        Create a new user on the Nextcloud server
558
559
        :param uid: str, uid of new user
560
        :param passwd: str, password of new user
561
        :return:
562
        """
563
        msg = {'userid': uid, 'password': passwd}
564
        return self.requester.post("", msg)
565
566
    @nextcloud_method
567
    def get_users(self, search=None, limit=None, offset=None):
568
        """
569
        Retrieve a list of users from the Nextcloud server
570
571
        :param search: string, optional search string
572
        :param limit: int, optional limit value
573
        :param offset: int, optional offset value
574
        :return:
575
        """
576
        params = {
577
            'search': search,
578
            'limit': limit,
579
            'offset': offset
580
        }
581
        return self.requester.get(params=params)
582
583
    @nextcloud_method
584
    def get_user(self, uid):
585
        """
586
        Retrieve information about a single user
587
588
        :param uid: str, uid of user
589
        :return:
590
        """
591
        return self.requester.get("{uid}".format(uid=uid))
592
593
    @nextcloud_method
594
    def edit_user(self, uid, what, value):
595
        """
596
        Edit attributes related to a user
597
598
        Users are able to edit email, displayname and password; admins can also edit the quota value
599
600
        :param uid: str, uid of user
601
        :param what: str, the field to edit
602
        :param value: str, the new value for the field
603
        :return:
604
        """
605
        what_to_key_map = dict(
606
            email="email", quota="quote", phone="phone", address="address", website="website",
607
            twitter="twitter", displayname="displayname", password="password",
608
        )
609
        assert what in what_to_key_map, (
610
            "You have chosen to edit user's '{what}', but you can choose only from: {choices}"
611
            .format(what=what, choices=", ".join(what_to_key_map.keys()))
612
        )
613
614
        url = "{uid}".format(uid=uid)
615
        msg = dict(
616
            key=what_to_key_map[what],
617
            value=value,
618
        )
619
        return self.requester.put(url, msg)
620
621
    @nextcloud_method
622
    def disable_user(self, uid):
623
        """
624
        Disable a user on the Nextcloud server so that the user cannot login anymore
625
626
        :param uid: str, uid of user
627
        :return:
628
        """
629
        return self.requester.put("{uid}/disable".format(uid=uid))
630
631
    @nextcloud_method
632
    def enable_user(self, uid):
633
        """
634
        Enable a user on the Nextcloud server so that the user can login again
635
636
        :param uid: str, uid of user
637
        :return:
638
        """
639
        return self.requester.put("{uid}/enable".format(uid=uid))
640
641
    @nextcloud_method
642
    def delete_user(self, uid):
643
        """
644
        Delete a user from the Nextcloud server
645
646
        :param uid: str, uid of user
647
        :return:
648
        """
649
        return self.requester.delete("{uid}".format(uid=uid))
650
651
    @nextcloud_method
652
    def add_to_group(self, uid, gid):
653
        """
654
        Add the specified user to the specified group
655
656
        :param uid: str, uid of user
657
        :param gid: str, name of group
658
        :return:
659
        """
660
        url = "{uid}/groups".format(uid=uid)
661
        msg = {'groupid': gid}
662
        return self.requester.post(url, msg)
663
664
    @nextcloud_method
665
    def remove_from_group(self, uid, gid):
666
        """
667
        Remove the specified user from the specified group
668
669
        :param uid: str, uid of user
670
        :param gid: str, name of group
671
        :return:
672
        """
673
        url = "{uid}/groups".format(uid=uid)
674
        msg = {'groupid': gid}
675
        return self.requester.delete(url, msg)
676
677
    @nextcloud_method
678
    def create_subadmin(self, uid, gid):
679
        """
680
        Make a user the subadmin of a group
681
682
        :param uid: str, uid of user
683
        :param gid: str, name of group
684
        :return:
685
        """
686
        url = "{uid}/subadmins".format(uid=uid)
687
        msg = {'groupid': gid}
688
        return self.requester.post(url, msg)
689
690
    @nextcloud_method
691
    def remove_subadmin(self, uid, gid):
692
        """
693
        Remove the subadmin rights for the user specified from the group specified
694
695
        :param uid: str, uid of user
696
        :param gid: str, name of group
697
        :return:
698
        """
699
        url = "{uid}/subadmins".format(uid=uid)
700
        msg = {'groupid': gid}
701
        return self.requester.delete(url, msg)
702
703
    @nextcloud_method
704
    def get_subadmin_groups(self, uid):
705
        """
706
        Get the groups in which the user is a subadmin
707
708
        :param uid: str, uid of user
709
        :return:
710
        """
711
        url = "{uid}/subadmins".format(uid=uid)
712
        return self.requester.get(url)
713
714
    @nextcloud_method
715
    def resend_welcome_mail(self, uid):
716
        """
717
        Trigger the welcome email for this user again
718
719
        :param uid: str, uid of user
720
        :return:
721
        """
722
        url = "{uid}/welcome".format(uid=uid)
723
        return self.requester.post(url)
724
725
726
class OCSCode(enum.IntEnum):
727
    OK = 100
728
    SERVER_ERROR = 996
729
    NOT_AUTHORIZED = 997
730
    NOT_FOUND = 998
731
    UNKNOWN_ERROR = 999
732
733
734
class ShareType(enum.IntEnum):
735
    USER = 0
736
    GROUP = 1
737
    PUBLIC_LINK = 3
738
    FEDERATED_CLOUD_SHARE = 6
739
740
741
class Permission(enum.IntEnum):
742
    """ Permission for Share have to be sum of selected permissions """
743
    READ = 1
744
    UPDATE = 2
745
    CREATE = 4
746
    DELETE = 8
747
    SHARE = 16
748
    ALL = 31
749
750
751
QUOTA_UNLIMITED = -3
752
753
754
def datetime_to_expire_date(date):
755
    return date.strftime("%Y-%m-%d")
756