Skip to content

Commit b5f36cb

Browse files
committed
signed URLs
1 parent c7dfe1d commit b5f36cb

4 files changed

Lines changed: 160 additions & 31 deletions

File tree

README.md

Lines changed: 59 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
## cloudconvert-python
22

3-
This is the official Python SDK for the [CloudConvert](https://cloudconvert.com/api/v2) **API v2**.
3+
This is the official Python SDK for the [CloudConvert](https://cloudconvert.com/api/v2) **API v2**.
44

55
[![Tests](https://github.com/cloudconvert/cloudconvert-python/actions/workflows/run-tests.yml/badge.svg)](https://github.com/cloudconvert/cloudconvert-python/actions/workflows/run-tests.yml)
66
![PyPI](https://img.shields.io/pypi/v/cloudconvert)
@@ -17,42 +17,42 @@ This is the official Python SDK for the [CloudConvert](https://cloudconvert.com/
1717
```py
1818
import cloudconvert
1919

20-
cloudconvert.configure(api_key = 'API_KEY', sandbox = False)
20+
cloudconvert.configure(api_key='API_KEY', sandbox=False)
2121
```
2222

2323
Or set the environment variable `CLOUDCONVERT_API_KEY` and use:
2424

2525
```py
2626
import cloudconvert
2727

28-
cloudconvert.default()
28+
cloudconvert.default()
2929
```
3030

3131
## Creating Jobs
3232

3333
```py
3434
import cloudconvert
3535

36-
cloudconvert.configure(api_key = 'API_KEY')
37-
38-
cloudconvert.Job.create(payload={
39-
"tasks": {
40-
'import-my-file': {
41-
'operation': 'import/url',
42-
'url': 'https://my-url'
43-
},
44-
'convert-my-file': {
45-
'operation': 'convert',
46-
'input': 'import-my-file',
47-
'output_format': 'pdf',
48-
'some_other_option': 'value'
49-
},
50-
'export-my-file': {
51-
'operation': 'export/url',
52-
'input': 'convert-my-file'
53-
}
54-
}
55-
})
36+
cloudconvert.configure(api_key='API_KEY')
37+
38+
cloudconvert.Job.create(payload={
39+
"tasks": {
40+
'import-my-file': {
41+
'operation': 'import/url',
42+
'url': 'https://my-url'
43+
},
44+
'convert-my-file': {
45+
'operation': 'convert',
46+
'input': 'import-my-file',
47+
'output_format': 'pdf',
48+
'some_other_option': 'value'
49+
},
50+
'export-my-file': {
51+
'operation': 'export/url',
52+
'input': 'convert-my-file'
53+
}
54+
}
55+
})
5656

5757
```
5858

@@ -62,15 +62,16 @@ CloudConvert can generate public URLs for using `export/url` tasks. You can use
6262

6363
```py
6464
exported_url_task_id = "84e872fc-d823-4363-baab-eade2e05ee54"
65-
res = cloudconvert.Task.wait(id=exported_url_task_id) # Wait for job completion
65+
res = cloudconvert.Task.wait(id=exported_url_task_id) # Wait for job completion
6666
file = res.get("result").get("files")[0]
6767
res = cloudconvert.download(filename=file['filename'], url=file['url'])
6868
print(res)
6969
```
7070

7171
## Uploading Files
7272

73-
Uploads to CloudConvert are done via `import/upload` tasks (see the [docs](https://cloudconvert.com/api/v2/import#import-upload-tasks)). This SDK offers a convenient upload method:
73+
Uploads to CloudConvert are done via `import/upload` tasks (see
74+
the [docs](https://cloudconvert.com/api/v2/import#import-upload-tasks)). This SDK offers a convenient upload method:
7475

7576
```py
7677
job = cloudconvert.Job.create(payload={
@@ -88,16 +89,44 @@ res = cloudconvert.Task.upload(file_name='path/to/sample.pdf', task=upload_task)
8889

8990
res = cloudconvert.Task.find(id=upload_task_id)
9091
```
92+
9193
## Webhook Signing
9294

9395
The node SDK allows to verify webhook requests received from CloudConvert.
9496

9597
```py
96-
payloadString = '...'; # The JSON string from the raw request body.
97-
signature = '...'; # The value of the "CloudConvert-Signature" header.
98-
signingSecret = '...'; # You can find it in your webhook settings.
98+
payloadString = '...'; # The JSON string from the raw request body.
99+
signature = '...'; # The value of the "CloudConvert-Signature" header.
100+
signingSecret = '...'; # You can find it in your webhook settings.
101+
102+
isValid = cloudconvert.Webhook.verify(payloadString, signature, signingSecret); # returns true or false
103+
```
104+
105+
## Signed URLs
106+
107+
Signed URLs allow converting files on demand only using URL query parameters. The Python SDK allows to generate such
108+
URLs. Therefore, you need to obtain a signed URL base and a signing secret on
109+
the [CloudConvert Dashboard](https://cloudconvert.com/dashboard/api/v2/signed-urls).
110+
111+
```py
112+
base = 'https://s.cloudconvert.com/...' # You can find it in your signed URL settings.
113+
signing_secret = '...' # You can find it in your signed URL settings.
114+
cache_key = 'cache-key' # Allows caching of the result file for 24h
115+
116+
job = {
117+
"tasks": {
118+
"import-file": {
119+
"operation": "import/url",
120+
"url": "https://github.com/cloudconvert/cloudconvert-php/raw/master/tests/Integration/files/input.pdf"
121+
},
122+
"export-file": {
123+
"operation": "export/url",
124+
"input": "import-file"
125+
}
126+
}
127+
}
99128

100-
isValid = cloudconvert.Webhook.verify(payloadString, signature, signingSecret); # returns true or false
129+
url = cloudconvert.SignedUrl.sign(base, signing_secret, job, cache_key); # returns the URL
101130
```
102131

103132
## Unit Tests
@@ -114,8 +143,8 @@ $ python tests/unit/testWebhookSignature.py
114143
115144
```
116145

117-
118146
## Integration Tests
147+
119148
```
120149
# Run Integration test for task
121150
$ python tests/integration/testTasks.py
@@ -124,7 +153,6 @@ $ python tests/integration/testTasks.py
124153
$ python tests/integration/testJobs.py
125154
126155
```
127-
128156

129157
## Resources
130158

cloudconvert/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
from cloudconvert.task import Task
33
from cloudconvert.job import Job
44
from cloudconvert.webhook import Webhook
5+
from cloudconvert.signed_url import SignedUrl
56

67
def configure(**config):
78
"""

cloudconvert/signed_url.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import hmac
2+
import hashlib
3+
import json
4+
import base64
5+
6+
7+
class SignedUrl():
8+
"""SignedUrl class for create signed URLs
9+
10+
Usage::
11+
>>> SignedUrl.create(base, signing_secret, job, cache_key) # return True or False
12+
"""
13+
14+
@classmethod
15+
def sign(cls, base, signing_secret, job, cache_key=None):
16+
jobJson = json.dumps(job)
17+
jobBase64 = base64.urlsafe_b64encode(bytes(jobJson, 'utf-8')).decode('utf-8')
18+
19+
url = base + "?job=" + jobBase64
20+
21+
if cache_key:
22+
url += "&cache_key=" + cache_key
23+
24+
signature = hmac.new(signing_secret.encode('utf-8'), url.encode('utf-8'),
25+
hashlib.sha256).hexdigest()
26+
27+
url += "&s=" + signature
28+
29+
return url

tests/unit/testSignedUrl.py

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
###################################################################
2+
## Test case for Signed URLs ##
3+
## ##
4+
## How to run ? : ##
5+
## $ python testSignedUrl.py ##
6+
###################################################################
7+
8+
import sys
9+
import os
10+
sys.path.append(os.getcwd())
11+
12+
import unittest
13+
import cloudconvert
14+
15+
16+
17+
class TestSignedUrl(unittest.TestCase):
18+
19+
def setUp(self):
20+
"""
21+
Test case setup method
22+
:return:
23+
"""
24+
print("Setting up signed URL test case")
25+
26+
def testVerifySignature(self):
27+
"""
28+
Test verify
29+
:return:
30+
"""
31+
print("Testcase for creating signed URL..")
32+
33+
# create dict for new Job
34+
job = {
35+
"tasks": {
36+
"import-file": {
37+
"operation": "import/url",
38+
"url": "https://github.com/cloudconvert/cloudconvert-php/raw/master/tests/Integration/files/input.pdf"
39+
},
40+
"export-file": {
41+
"operation": "export/url",
42+
"input": "import-file"
43+
}
44+
}
45+
}
46+
47+
base = "https://s.cloudconvert.com/b3d85428-584e-4639-bc11-76b7dee9c109"
48+
signing_secret = "NT8dpJkttEyfSk3qlRgUJtvTkx64vhyX"
49+
cache_key = "mykey"
50+
51+
url = cloudconvert.SignedUrl.sign(base, signing_secret, job, cache_key)
52+
53+
print(url)
54+
55+
self.assertIn("https://s.cloudconvert.com/", url)
56+
self.assertIn("?job=", url)
57+
self.assertIn("&cache_key=mykey", url)
58+
self.assertIn("&s=6dd147217a39534249a3cb418b357ba8cceacf74fc0db0d52630a07cac1ca268", url)
59+
60+
61+
62+
def tearDown(self):
63+
"""
64+
Teardown method
65+
:return:
66+
"""
67+
print("Tearing down test case for signed URL..")
68+
69+
70+
if __name__ == '__main__':
71+
unittest.main()

0 commit comments

Comments
 (0)