Completed
Push — master ( 0b5289...e5d674 )
by Matěj
11s queued 10s
created

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