Skip to content

Commit

Permalink
md5 -> hash
Browse files Browse the repository at this point in the history
  • Loading branch information
kroman0 committed Jul 4, 2016
1 parent bbea59a commit ae3e267
Show file tree
Hide file tree
Showing 8 changed files with 64 additions and 48 deletions.
12 changes: 5 additions & 7 deletions docs.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,13 @@ def do_request(self, req, status=None, expect_errors=None):
req.headers.environ["HTTP_HOST"] = "docs.api-sandbox.openprocurement.org"
if hasattr(self, 'file_obj') and not self.file_obj.closed:
self.file_obj.write(req.as_bytes(True))
self.file_obj.write("\n")
self.file_obj.write("\n\n")
if req.body:
try:
self.file_obj.write(
'\n' + json.dumps(json.loads(req.body), indent=2, ensure_ascii=False).encode('utf8'))
self.file_obj.write("\n")
self.file_obj.write(json.dumps(json.loads(req.body), indent=2, ensure_ascii=False).encode('utf8'))
except:
pass
self.file_obj.write("\n")
self.file_obj.write(req.body.encode('utf8'))
self.file_obj.write("\n\n")
resp = super(DumpsTestAppwebtest, self).do_request(req, status=status, expect_errors=expect_errors)
if hasattr(self, 'file_obj') and not self.file_obj.closed:
headers = [(n.title(), v)
Expand Down Expand Up @@ -57,7 +55,7 @@ def setUp(self):
def test_docs(self):
with open('docs/source/tutorial/register.http', 'w') as self.app.file_obj:
md5hash = md5('content').hexdigest()
response = self.app.post('/register', {'md5': md5hash, 'filename': 'file.txt'})
response = self.app.post('/register', {'hash': md5hash, 'filename': 'file.txt'})
self.assertEqual(response.status, '201 Created')
self.assertEqual(response.content_type, 'application/json')
self.assertIn('http://docs.api-sandbox.openprocurement.org/upload/', response.json['upload_url'])
Expand Down
2 changes: 1 addition & 1 deletion docs/source/tutorial/get.http
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
GET /get/0aa911d0639349d19bfdb4ddc162af50?Expires=1467631359&KeyID=fa2a2dca&Signature=MGQCMFELd5pT656G%2FrphkVBIgI4lJyyLqcZq8qQoxmr1a56Q2JazflmxL3LkNlQ7CfG9PwIwIdsSpJ9otndxsdEewl5PTEuDwm3ITU2m%2FApSCB6g90%2FM41L1dA9j3762OMFKuqWe HTTP/1.0
GET /get/5c521a51a3dc417e9c718fd4e6a85157?Expires=1467637367&KeyID=ad1c8f4f&Signature=MGYCMQCJc8ZgfJXH%252BvKGogrFZmqvgINEmVo1neZiSwIKDOhlvRxCBJBcytbsZYNnF%2Fs8wRECMQCdtfP%2FT4vd%252B49gvwR0GA1%2F17pqPlIIntdC8AzzYMzXWCKnDsCInX2LOzYcRzxi7kU%253D HTTP/1.0
Authorization: Basic YnJva2VyOmJyb2tlcg==
Host: docs.api-sandbox.openprocurement.org

Expand Down
12 changes: 7 additions & 5 deletions docs/source/tutorial/register.http
Original file line number Diff line number Diff line change
@@ -1,19 +1,21 @@
POST /register HTTP/1.0
Authorization: Basic YnJva2VyOmJyb2tlcg==
Content-Length: 54
Content-Length: 55
Content-Type: application/x-www-form-urlencoded
Host: docs.api-sandbox.openprocurement.org

hash=9a0364b9e99bb480dd25e1f0284c8555&filename=file.txt


201 Created
Content-Type: application/json; charset=UTF-8
Location: http://docs.api-sandbox.openprocurement.org/upload/0aa911d0639349d19bfdb4ddc162af50?KeyID=fa2a2dca&Signature=MGYCMQCekJ%252ByOjVmxOGzAO2PvA0I0oUHNOXHhFckwdyiIMLIf1TS2S%2F8Kc9SDIL6PlATen4CMQDbkEtxtj65pKvA25wZuvofNq20drWDWCqNrReltt7GGHar7NTbhrvV%2FGevJUI6nqU%253D
Location: http://docs.api-sandbox.openprocurement.org/upload/5c521a51a3dc417e9c718fd4e6a85157?KeyID=ad1c8f4f&Signature=MGUCMGoyv%252BXNkAoYbPChhPZ6MnBJK3ggQJURglrm70ozbwi1D4kLgvkdy8pOWeyU2W7OiAIxALh4s0oX0XuVlvGbBBPEgNhZs8vo1nm1e6Iog%252B7S5cfFAM7jdx1Eam6dV2GbDUN1cQ%253D%253D

{
"upload_url": "http://docs.api-sandbox.openprocurement.org/upload/0aa911d0639349d19bfdb4ddc162af50?KeyID=fa2a2dca&Signature=MGYCMQCekJ%252ByOjVmxOGzAO2PvA0I0oUHNOXHhFckwdyiIMLIf1TS2S%2F8Kc9SDIL6PlATen4CMQDbkEtxtj65pKvA25wZuvofNq20drWDWCqNrReltt7GGHar7NTbhrvV%2FGevJUI6nqU%253D",
"upload_url": "http://docs.api-sandbox.openprocurement.org/upload/5c521a51a3dc417e9c718fd4e6a85157?KeyID=ad1c8f4f&Signature=MGUCMGoyv%252BXNkAoYbPChhPZ6MnBJK3ggQJURglrm70ozbwi1D4kLgvkdy8pOWeyU2W7OiAIxALh4s0oX0XuVlvGbBBPEgNhZs8vo1nm1e6Iog%252B7S5cfFAM7jdx1Eam6dV2GbDUN1cQ%253D%253D",
"data": {
"url": "http://docs.api-sandbox.openprocurement.org/get/0aa911d0639349d19bfdb4ddc162af50?KeyID=fa2a2dca&Signature=MGQCMHwm4oUjDOO%252BRqvmS9Fe%2FawfuewLQKlsUW7R5GmEkrbO1%252BhTME14sv%2FLr67hnHE8LgIwb33TfxdjAmqiJ3kt70Dg%2Fkrt6WMY4JBzo0%252B4pRE8BCSgqLHoirGOMq7GImRSDa%2Fg",
"md5": "9a0364b9e99bb480dd25e1f0284c8555"
"url": "http://docs.api-sandbox.openprocurement.org/get/5c521a51a3dc417e9c718fd4e6a85157?KeyID=ad1c8f4f&Signature=MGYCMQDFt2N1hVQzSS2TZPTy7%252BdmRwb5PMxVMof2VcMk4UiO9wbPEQWF%2FYURusMGQSVgLRoCMQD2SuipLsTlEUozUvYcuqrX9Siss6knlvllRwZNuWAIKzTJcSEL3oPjzG%2FUdJumSOo%253D",
"hash": "9a0364b9e99bb480dd25e1f0284c8555"
}
}

20 changes: 14 additions & 6 deletions docs/source/tutorial/upload-file.http
Original file line number Diff line number Diff line change
@@ -1,20 +1,28 @@
POST /upload HTTP/1.0
Authorization: Basic YnJva2VyOmJyb2tlcg==
Content-Length: 173
Content-Type: multipart/form-data; boundary=----------a_BoUnDaRy902553822$
Content-Length: 179
Content-Type: multipart/form-data; boundary=----------a_BoUnDaRy510302448724$
Host: docs.api-sandbox.openprocurement.org

------------a_BoUnDaRy510302448724$
Content-Disposition: form-data; name="file"; filename="file.txt"
Content-Type: text/plain

content
------------a_BoUnDaRy510302448724$--



200 OK
Content-Type: application/json; charset=UTF-8
Location: http://docs.api-sandbox.openprocurement.org/get/68bebefc92f949269501ddd9ef9e0b37?Expires=1467631359&KeyID=fa2a2dca&Signature=MGQCMGWCHGEdJCyVoHecQWUhhNaD54f9%2F9YhNdyzUkcKrQbwDtxkaHuFe91H5qtNqtomnwIwHs90h3qq2VRCipoljZK7bh8HJHgu%2FRmkSCxwJFNKgJPHdrI291j2cTRA4qqRADqk
Location: http://docs.api-sandbox.openprocurement.org/get/1d8f71249398428792506f12c6071f24?Expires=1467637367&KeyID=ad1c8f4f&Signature=MGUCMQCcwML4hKp8CDJyzkDU%2F0LBxoNdgMqOusm6LdXH33hjpT73RmXvLSqMsOg%2F%2FHde2bYCMFMquxwj4imqKlG0FpnX7vuzn5xKgOydvGdaae6rna3jEfRAg%2Fg876vDF%252BfBMXTQGg%253D%253D

{
"get_url": "http://docs.api-sandbox.openprocurement.org/get/68bebefc92f949269501ddd9ef9e0b37?Expires=1467631359&KeyID=fa2a2dca&Signature=MGQCMGWCHGEdJCyVoHecQWUhhNaD54f9%2F9YhNdyzUkcKrQbwDtxkaHuFe91H5qtNqtomnwIwHs90h3qq2VRCipoljZK7bh8HJHgu%2FRmkSCxwJFNKgJPHdrI291j2cTRA4qqRADqk",
"get_url": "http://docs.api-sandbox.openprocurement.org/get/1d8f71249398428792506f12c6071f24?Expires=1467637367&KeyID=ad1c8f4f&Signature=MGUCMQCcwML4hKp8CDJyzkDU%2F0LBxoNdgMqOusm6LdXH33hjpT73RmXvLSqMsOg%2F%2FHde2bYCMFMquxwj4imqKlG0FpnX7vuzn5xKgOydvGdaae6rna3jEfRAg%2Fg876vDF%252BfBMXTQGg%253D%253D",
"data": {
"url": "http://docs.api-sandbox.openprocurement.org/get/68bebefc92f949269501ddd9ef9e0b37?KeyID=fa2a2dca&Signature=MGQCMDCvVM4uau%252Bz3JPTXra4RDIjAhzcUHo1ccjpVpxAVpJQJcUKpusxK5IHmLjN3GB%2FVQIwJwyeogaf3yuc893OowtV4b%2FFKknoLzXsZ82vV5SeNON%2FItPYy6rVX1MDIfZ40P4U",
"url": "http://docs.api-sandbox.openprocurement.org/get/1d8f71249398428792506f12c6071f24?KeyID=ad1c8f4f&Signature=MGUCMQCfllpotQXrT%2FRyVKFCSWwiAdebP2VNSu%2F6qxFYeaSjYHUWZ0XdFXshuKwFZA1xjwoCMDVFPQ42ILxenhJWUR8xz1THjwCEQvJJvccpvHipCW3IBR9RKPtafF6yXD4DVRKajw%253D%253D",
"format": "text/plain",
"md5": "9a0364b9e99bb480dd25e1f0284c8555",
"hash": "9a0364b9e99bb480dd25e1f0284c8555",
"title": "file.txt"
}
}
Expand Down
18 changes: 13 additions & 5 deletions docs/source/tutorial/upload.http
Original file line number Diff line number Diff line change
@@ -1,19 +1,27 @@
POST /upload/0aa911d0639349d19bfdb4ddc162af50?KeyID=fa2a2dca&Signature=MGYCMQCekJ%252ByOjVmxOGzAO2PvA0I0oUHNOXHhFckwdyiIMLIf1TS2S%2F8Kc9SDIL6PlATen4CMQDbkEtxtj65pKvA25wZuvofNq20drWDWCqNrReltt7GGHar7NTbhrvV%2FGevJUI6nqU%253D HTTP/1.0
POST /upload/5c521a51a3dc417e9c718fd4e6a85157?KeyID=ad1c8f4f&Signature=MGUCMGoyv%252BXNkAoYbPChhPZ6MnBJK3ggQJURglrm70ozbwi1D4kLgvkdy8pOWeyU2W7OiAIxALh4s0oX0XuVlvGbBBPEgNhZs8vo1nm1e6Iog%252B7S5cfFAM7jdx1Eam6dV2GbDUN1cQ%253D%253D HTTP/1.0
Authorization: Basic YnJva2VyOmJyb2tlcg==
Content-Length: 179
Content-Type: multipart/form-data; boundary=----------a_BoUnDaRy266804977683$
Content-Type: multipart/form-data; boundary=----------a_BoUnDaRy674897056583$
Host: docs.api-sandbox.openprocurement.org

------------a_BoUnDaRy674897056583$
Content-Disposition: form-data; name="file"; filename="file.txt"
Content-Type: text/plain

content
------------a_BoUnDaRy674897056583$--



200 OK
Content-Type: application/json; charset=UTF-8

{
"get_url": "http://docs.api-sandbox.openprocurement.org/get/0aa911d0639349d19bfdb4ddc162af50?Expires=1467631359&KeyID=fa2a2dca&Signature=MGQCMFELd5pT656G%2FrphkVBIgI4lJyyLqcZq8qQoxmr1a56Q2JazflmxL3LkNlQ7CfG9PwIwIdsSpJ9otndxsdEewl5PTEuDwm3ITU2m%2FApSCB6g90%2FM41L1dA9j3762OMFKuqWe",
"get_url": "http://docs.api-sandbox.openprocurement.org/get/5c521a51a3dc417e9c718fd4e6a85157?Expires=1467637367&KeyID=ad1c8f4f&Signature=MGYCMQCJc8ZgfJXH%252BvKGogrFZmqvgINEmVo1neZiSwIKDOhlvRxCBJBcytbsZYNnF%2Fs8wRECMQCdtfP%2FT4vd%252B49gvwR0GA1%2F17pqPlIIntdC8AzzYMzXWCKnDsCInX2LOzYcRzxi7kU%253D",
"data": {
"url": "http://docs.api-sandbox.openprocurement.org/get/0aa911d0639349d19bfdb4ddc162af50?KeyID=fa2a2dca&Signature=MGUCMQCmdubDLB6Q8wOaFHAp7zidI5sI8At6qhxl8qUafAe%252B%2FhRC3YBf44x93AWfB9p1LwQCMHbs5U5mYazFabpV%252BGTl3muZ8HybmiR2xZUNhFdK6VuAdd%2F2DWtvLJh88dYoFkrDXA%253D%253D",
"url": "http://docs.api-sandbox.openprocurement.org/get/5c521a51a3dc417e9c718fd4e6a85157?KeyID=ad1c8f4f&Signature=MGYCMQDCSlYiZi2F81shpDJB8%252BTqJdWDuBEOllSKPOHiUCpDrWtjstpAyEUEbTwksrZ0OoECMQDgeqykJoF76JPn%2FM%2FKISnByQQzKeFvYJcOpdqqqwoIg7ZQmzvTdB8CmBvzaOggbjk%253D",
"format": "text/plain",
"md5": "9a0364b9e99bb480dd25e1f0284c8555",
"hash": "9a0364b9e99bb480dd25e1f0284c8555",
"title": "file.txt"
}
}
Expand Down
10 changes: 5 additions & 5 deletions openprocurement/documentservice/storage.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ class KeyNotFound(KeyError):
pass


class MD5Invalid(ValueError):
class HashInvalid(ValueError):
pass


Expand All @@ -49,7 +49,7 @@ def __init__(self):
def register(self, md5hash):
uuid = uuid4().hex
self.storage[uuid] = {
'md5': md5hash,
'hash': md5hash,
'Content': '',
}
return uuid
Expand All @@ -68,11 +68,11 @@ def upload(self, post_file, uuid=None):
uuid = uuid4().hex
key = self.storage[uuid] = {}
content = in_file.read()
key_md5 = key.get('md5')
key_md5 = key.get('hash')
md5hash = md5(content).hexdigest()
if key_md5 and md5(content).hexdigest() != key_md5:
raise MD5Invalid(key_md5)
key['md5'] = md5hash
raise HashInvalid(key_md5)
key['hash'] = md5hash
key['Content-Type'] = content_type
key["Content-Disposition"] = build_header(filename, filename_compat=quote(filename.encode('utf-8')))
key['Content'] = content
Expand Down
20 changes: 10 additions & 10 deletions openprocurement/documentservice/tests/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,19 +34,19 @@ def test_register_invalid(self):
self.assertEqual(response.content_type, 'application/json')
self.assertEqual(response.json['status'], 'error')
self.assertEqual(response.json['errors'], [
{u'description': u'Not Found', u'location': u'body', u'name': u'md5'}
{u'description': u'Not Found', u'location': u'body', u'name': u'hash'}
])

response = self.app.post(url, {'not_md5': 'hash'}, status=404)
response = self.app.post(url, {'not_hash': 'hash'}, status=404)
self.assertEqual(response.status, '404 Not Found')
self.assertEqual(response.content_type, 'application/json')
self.assertEqual(response.json['status'], 'error')
self.assertEqual(response.json['errors'], [
{u'description': u'Not Found', u'location': u'body', u'name': u'md5'}
{u'description': u'Not Found', u'location': u'body', u'name': u'hash'}
])

def test_register_post(self):
response = self.app.post('/register', {'md5': 'hash', 'filename': 'file.txt'})
response = self.app.post('/register', {'hash': 'hash', 'filename': 'file.txt'})
self.assertEqual(response.status, '201 Created')
self.assertEqual(response.content_type, 'application/json')
self.assertIn('http://localhost/upload/', response.json['upload_url'])
Expand Down Expand Up @@ -101,8 +101,8 @@ def test_upload_file_invalid(self):
{u'description': u'Signature does not match', u'name': u'Signature', u'location': u'url'}
])

def test_upload_file_md5(self):
response = self.app.post('/register', {'md5': 'hash', 'filename': 'file.txt'})
def test_upload_file_hash(self):
response = self.app.post('/register', {'hash': 'hash', 'filename': 'file.txt'})
self.assertEqual(response.status, '201 Created')
self.assertEqual(response.content_type, 'application/json')
self.assertIn('http://localhost/upload/', response.json['upload_url'])
Expand All @@ -112,13 +112,13 @@ def test_upload_file_md5(self):
self.assertEqual(response.content_type, 'application/json')
self.assertEqual(response.json['status'], 'error')
self.assertEqual(response.json['errors'], [
{u'description': u'Invalid MD5 checksum', u'name': u'file', u'location': u'body'}
{u'description': u'Invalid checksum', u'name': u'file', u'location': u'body'}
])

def test_upload_file_post(self):
content = 'content'
md5hash = md5(content).hexdigest()
response = self.app.post('/register', {'md5': md5hash, 'filename': 'file.txt'})
response = self.app.post('/register', {'hash': md5hash, 'filename': 'file.txt'})
self.assertEqual(response.status, '201 Created')
self.assertEqual(response.content_type, 'application/json')
self.assertIn('http://localhost/upload/', response.json['upload_url'])
Expand Down Expand Up @@ -201,9 +201,9 @@ def test_get_invalid(self):
response = self.app.get(response.json['get_url'])
self.assertEqual(response.status, '204 No Content')

def test_get_md5(self):
def test_get_hash(self):
md5hash = md5('content').hexdigest()
response = self.app.post('/register', {'md5': md5hash, 'filename': 'file.txt'})
response = self.app.post('/register', {'hash': md5hash, 'filename': 'file.txt'})
self.assertEqual(response.status, '201 Created')
self.assertEqual(response.content_type, 'application/json')
self.assertIn('http://localhost/upload/', response.json['upload_url'])
Expand Down
18 changes: 9 additions & 9 deletions openprocurement/documentservice/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,28 +2,28 @@
from base64 import b64encode, b64decode
from time import time
from urllib import quote, unquote
from openprocurement.documentservice.storage import StorageRedirect, MD5Invalid, KeyNotFound, NoContent, ContentUploaded
from openprocurement.documentservice.storage import StorageRedirect, HashInvalid, KeyNotFound, NoContent, ContentUploaded

EXPIRES = 300


@view_config(route_name='register', renderer='json', request_method='POST', permission='upload')
def register_view(request):
if 'md5' not in request.POST:
if 'hash' not in request.POST:
request.response.status = 404
return {
"status": "error",
"errors": [{"location": "body", "name": "md5", "description": "Not Found"}]
"errors": [{"location": "body", "name": "hash", "description": "Not Found"}]
}
md5 = request.POST['md5']
md5 = request.POST['hash']
uuid = request.registry.storage.register(md5)
signature = quote(b64encode(request.registry.dockeyring[request.registry.dockey].sign(uuid)))
upload_url = request.route_url('upload_file', doc_id=uuid, _query={'Signature': signature, 'KeyID': request.registry.dockey})
signature = quote(b64encode(request.registry.keyring[request.registry.dockey].sign("{}\0{}".format(uuid, md5))))
url = request.route_url('get', doc_id=uuid, _query={'Signature': signature, 'KeyID': request.registry.dockey})
request.response.status = 201
request.response.headers['Location'] = upload_url
return {'data': {'url': url, 'md5': md5}, 'upload_url': upload_url}
return {'data': {'url': url, 'hash': md5}, 'upload_url': upload_url}


@view_config(route_name='upload', renderer='json', request_method='POST', permission='upload')
Expand All @@ -43,7 +43,7 @@ def upload_view(request):
signature = quote(b64encode(request.registry.keyring[request.registry.dockey].sign(mess)))
get_url = request.route_url('get', doc_id=uuid, _query={'Signature': signature, 'Expires': expires, 'KeyID': request.registry.dockey})
request.response.headers['Location'] = get_url
return {'data': {'url': url, 'md5': md5, 'format': content_type, 'title': filename}, 'get_url': get_url}
return {'data': {'url': url, 'hash': md5, 'format': content_type, 'title': filename}, 'get_url': get_url}


@view_config(route_name='upload_file', renderer='json', request_method='POST', permission='upload')
Expand Down Expand Up @@ -99,19 +99,19 @@ def upload_file_view(request):
"status": "error",
"errors": [{"location": "url", "name": "doc_id", "description": "Content already uploaded"}]
}
except MD5Invalid:
except HashInvalid:
request.response.status = 403
return {
"status": "error",
"errors": [{"location": "body", "name": "file", "description": "Invalid MD5 checksum"}]
"errors": [{"location": "body", "name": "file", "description": "Invalid checksum"}]
}
expires = int(time()) + EXPIRES
mess = "{}\0{}".format(uuid, expires)
signature = quote(b64encode(request.registry.keyring[request.registry.dockey].sign("{}\0{}".format(uuid, md5))))
url = request.route_url('get', doc_id=uuid, _query={'Signature': signature, 'KeyID': request.registry.dockey})
signature = quote(b64encode(request.registry.keyring[request.registry.dockey].sign(mess)))
get_url = request.route_url('get', doc_id=uuid, _query={'Signature': signature, 'Expires': expires, 'KeyID': request.registry.dockey})
return {'data': {'url': url, 'md5': md5, 'format': content_type, 'title': filename}, 'get_url': get_url}
return {'data': {'url': url, 'hash': md5, 'format': content_type, 'title': filename}, 'get_url': get_url}


@view_config(route_name='get', renderer='json', request_method='GET')
Expand Down

0 comments on commit ae3e267

Please sign in to comment.