Completed
Push — master ( b3f222...0c1f9d )
by Matěj
15s queued 10s
created

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