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

NextCloud.Group.get_group()   A

Complexity

Conditions 1

Size

Total Lines 9
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

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