diff --git a/.gitignore b/.gitignore index 710dcbb..67436d6 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,5 @@ .vscode/settings.json uplink-c/ libuplinkc.so +secret.txt +__pycache__/ \ No newline at end of file diff --git a/docs/types.md b/docs/types.md index ec022d0..332a964 100644 --- a/docs/types.md +++ b/docs/types.md @@ -389,7 +389,9 @@ StorjException is further sub-categorized into various error specific classes, T UploadDoneError CancelledError InvalidHandleError - + StorageLimitExceededError + SegmentsLimitExceededError + PermissionDeniedError ### InternalError @@ -435,6 +437,18 @@ UploadDoneError is raised when either Abort or Commit has already been called. InvalidHandleError is raised when object handle passes is either invalid or already freed. +### StorageLimitExceededError + +StorageLimitExceededError is raised when allowed storage limit exceeded. + +### SegmentsLimitExceededError + +SegmentsLimitExceededError is raised when allowed segments limit exceeded. + +### PermissionDeniedError + +PermissionDeniedError is raised when permission is denied. + ## Constants ERROR_INTERNAL = 0x02 @@ -442,6 +456,10 @@ InvalidHandleError is raised when object handle passes is either invalid or alre ERROR_INVALID_HANDLE = 0x04 ERROR_TOO_MANY_REQUESTS = 0x05 ERROR_BANDWIDTH_LIMIT_EXCEEDED = 0x06 + ERROR_STORAGE_LIMIT_EXCEEDED = 0x07 + ERROR_SEGMENTS_LIMIT_EXCEEDED = 0x08 + ERROR_PERMISSION_DENIED = 0x09 + # Types, Errors, and Constants ## Types diff --git a/test/test_data/project_test.py b/test/test_data/project_test.py index c953303..0d524bb 100644 --- a/test/test_data/project_test.py +++ b/test/test_data/project_test.py @@ -1,5 +1,7 @@ # pylint: disable=missing-docstring import unittest +from uplink_python.errors import BucketAlreadyExistError +from uplink_python.module_classes import Permission, SharePrefix from .helper import TestPy @@ -15,5 +17,42 @@ def test1_open_project(self): self.assertIsNotNone(project, "open_project failed") + def test2_revoke_access(self): + # sanity check current credentials + project = self.access.open_project() + self.assertIsNotNone(project, "open_project failed") + try: + _ = project.create_bucket("alpha") + except BucketAlreadyExistError: + pass + test_access_availability(project, "1") + + # create derived credentials to be revoked + permissions = Permission(allow_list=True, allow_download=True, allow_upload=True, allow_delete=True) + shared_prefix = [SharePrefix(bucket="alpha", prefix="")] + access = self.access.share(permissions, shared_prefix) + self.assertTrue(access.access.contents._handle != 0, "got empty access") + self.assertIsNotNone(access, "access_share failed") + + # sanity check derived credentials + project2 = access.open_project() + self.assertIsNotNone(project2, "open_project2 failed") + test_access_availability(project2, "2") + + # revoke derived credentials + project.revoke_access(access) + + # expect derived credentials to fail + # this test is non-deterministic due to credential caching + # it can be reasonable proven with a time.sleep() however + #with self.assertRaises(PermissionDeniedError) as context: + # test_access_availability(project2, "3") + +def test_access_availability(project, num: str): + data_bytes = bytes("!" * 1024 , 'utf-8') + upload = project.upload_object("alpha", "test_object_" + num) + _ = upload.write(data_bytes, len(data_bytes)) + upload.commit() + if __name__ == '__main__': unittest.main() diff --git a/uplink_python/errors.py b/uplink_python/errors.py index 7317f2c..328ce44 100644 --- a/uplink_python/errors.py +++ b/uplink_python/errors.py @@ -5,6 +5,9 @@ ERROR_INVALID_HANDLE = 0x04 ERROR_TOO_MANY_REQUESTS = 0x05 ERROR_BANDWIDTH_LIMIT_EXCEEDED = 0x06 +ERROR_STORAGE_LIMIT_EXCEEDED = 0x07 +ERROR_SEGMENTS_LIMIT_EXCEEDED = 0x08 +ERROR_PERMISSION_DENIED = 0x09 ERROR_BUCKET_NAME_INVALID = 0x10 ERROR_BUCKET_ALREADY_EXISTS = 0x11 @@ -14,6 +17,10 @@ ERROR_OBJECT_KEY_INVALID = 0x20 ERROR_OBJECT_NOT_FOUND = 0x21 ERROR_UPLOAD_DONE = 0x22 + +ERROR_AUTH_DIAL_FAILED = 0x30 +ERROR_REGISTER_ACCESS_FAILED = 0x31 + ERROR_LIBUPLINK_SO_NOT_FOUND = 0x9999 """_Error defines""" @@ -92,6 +99,38 @@ def __init__(self, details): super().__init__("bandwidth limit exceeded", ERROR_BANDWIDTH_LIMIT_EXCEEDED, details) +class StorageLimitExceededError(StorjException): + """Exception raised if allowed storage limit exceeded. + + Attributes: + details -- error message from uplink-c + """ + + def __init__(self, details): + super().__init__("storage limit exceeded", ERROR_STORAGE_LIMIT_EXCEEDED, details) + +class SegmentsLimitExceededError(StorjException): + """Exception raised if allowed segments limit exceeded. + + Attributes: + details -- error message from uplink-c + """ + + def __init__(self, details): + super().__init__("segments limit exceeded", ERROR_SEGMENTS_LIMIT_EXCEEDED, details) + + +class PermissionDeniedError(StorjException): + """Exception raised if permission denied. + + Attributes: + details -- error message from uplink-c + """ + + def __init__(self, details): + super().__init__("permission denied", ERROR_PERMISSION_DENIED, details) + + class BucketNameInvalidError(StorjException): """Exception raised if bucket name is invalid. @@ -169,6 +208,27 @@ def __init__(self, details): super().__init__("upload completed", ERROR_UPLOAD_DONE, details) +class AuthDialFailedError(StorjException): + """Exception raised if failure to dial the Auth Service. + + Attributes: + details -- error message from uplink-c + """ + + def __init__(self, details): + super().__init__("Auth dial failed", ERROR_AUTH_DIAL_FAILED, details) + +class RegisterAccessFailedError(StorjException): + """Exception raised if failure to register access grant with Auth Service. + + Attributes: + details -- error message from uplink-c + """ + + def __init__(self, details): + super().__init__("register accees failed", ERROR_REGISTER_ACCESS_FAILED, details) + + class LibUplinkSoError(StorjException): """Exception raised if reason is unknown. @@ -191,12 +251,17 @@ def _storj_exception(code, details): ERROR_INVALID_HANDLE: InvalidHandleError, ERROR_TOO_MANY_REQUESTS: TooManyRequestsError, ERROR_BANDWIDTH_LIMIT_EXCEEDED: BandwidthLimitExceededError, + ERROR_STORAGE_LIMIT_EXCEEDED:StorageLimitExceededError, + ERROR_SEGMENTS_LIMIT_EXCEEDED:SegmentsLimitExceededError, + ERROR_PERMISSION_DENIED:PermissionDeniedError, ERROR_BUCKET_NAME_INVALID: BucketNameInvalidError, ERROR_BUCKET_ALREADY_EXISTS: BucketAlreadyExistError, ERROR_BUCKET_NOT_EMPTY: BucketNotEmptyError, ERROR_BUCKET_NOT_FOUND: BucketNotFoundError, ERROR_OBJECT_KEY_INVALID: ObjectKeyInvalidError, ERROR_OBJECT_NOT_FOUND: ObjectNotFoundError, - ERROR_UPLOAD_DONE: UploadDoneError + ERROR_UPLOAD_DONE: UploadDoneError, + ERROR_AUTH_DIAL_FAILED:AuthDialFailedError, + ERROR_REGISTER_ACCESS_FAILED:RegisterAccessFailedError, } return switcher.get(code, StorjException)(details=details) diff --git a/uplink_python/project.py b/uplink_python/project.py index 1e6b267..e3d0db5 100644 --- a/uplink_python/project.py +++ b/uplink_python/project.py @@ -6,12 +6,11 @@ from uplink_python.module_def import _BucketStruct, _ObjectStruct, _ListObjectsOptionsStruct,\ _ObjectResult, _ListBucketsOptionsStruct, _UploadOptionsStruct, _DownloadOptionsStruct,\ _ProjectStruct, _BucketResult, _BucketIterator, _ObjectIterator, _DownloadResult,\ - _UploadResult, _Error + _UploadResult, _Error, _AccessStruct from uplink_python.upload import Upload from uplink_python.download import Download from uplink_python.errors import _storj_exception - class Project: """ Project provides access to managing buckets and objects. @@ -391,6 +390,33 @@ def close(self): raise _storj_exception(error.contents.code, error.contents.message.decode("utf-8")) + def revoke_access(self, access): + """ + revoke_access revokes the API key embedded in the provided access grant. + + Returns + ------- + None + """ + # + # declare types of arguments and response of the corresponding golang function + self.uplink.m_libuplink.uplink_revoke_access.argtypes =\ + [ctypes.POINTER(_ProjectStruct), ctypes.POINTER(_AccessStruct)] + self.uplink.m_libuplink.uplink_revoke_access.restype = ctypes.POINTER(_Error) + + # get uploader by calling the exported golang function + error = self.uplink.m_libuplink.uplink_revoke_access(self.project, access.access) + has_err = bool(error) + if bool(error): + err_code = error.contents.code + err_msg = error.contents.message.decode("utf-8") + self.uplink.m_libuplink.uplink_free_error.argtypes = [ctypes.POINTER(_Error)] + self.uplink.m_libuplink.uplink_free_error(error) + if has_err: + raise _storj_exception(err_code, err_msg) + + return None + def upload_object(self, bucket_name: str, storj_path: str, upload_options: UploadOptions = None): """