MetaData Sharing
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

208 lines
5.3 KiB

  1. #!/usr/bin/env python
  2. # Notes:
  3. # Python requests: https://2.python-requests.org/en/master/
  4. # IRequest interface: https://twistedmatrix.com/documents/current/api/twisted.web.iweb.IRequest.html
  5. # IResource interface: https://twistedmatrix.com/documents/current/api/twisted.web.resource.IResource.html
  6. # Twisted TDD: https://twistedmatrix.com/documents/current/core/howto/trial.html
  7. # Hypothesis: https://hypothesis.readthedocs.io/en/latest/
  8. # Going Async from Flask to Twisted Klein: https://crossbario.com/blog/Going-Asynchronous-from-Flask-to-Twisted-Klein/
  9. # Klein POST docs: https://klein.readthedocs.io/en/latest/examples/handlingpost.html
  10. from contextlib import nested
  11. from klein import Klein
  12. from kleintest import *
  13. from twisted.trial import unittest
  14. from twisted.web.iweb import IRequest
  15. from cli import Persona, MDBase
  16. import hashlib
  17. import mock
  18. import os.path
  19. import shutil
  20. import tempfile
  21. import uuid
  22. defaultfile = 'mediaserver.store.pasn1'
  23. class MEDAServer:
  24. def __init__(self, fname):
  25. self._trustedkeys = {}
  26. self._objstore = {}
  27. app = Klein()
  28. def addpubkey(self, pubkey):
  29. persona = Persona.from_pubkey(pubkey)
  30. self._trustedkeys[persona.uuid] = persona
  31. def store(self):
  32. pass
  33. @app.route('/obj/<id>')
  34. def obj_lookup(self, request, id):
  35. try:
  36. id = uuid.UUID(id)
  37. return self._objstore[id][-1].encode()
  38. except ValueError: # invalid format for uuid
  39. request.setResponseCode(400)
  40. except KeyError: # no object
  41. request.setResponseCode(404)
  42. @app.route('/store')
  43. def storeobj(self, request):
  44. try:
  45. obj = MDBase.decode(request.content.read())
  46. #if obj.type == 'identity':
  47. keyuuid = obj.uuid
  48. #else:
  49. # keyuuid = obj.created_by_ref
  50. persona = self._trustedkeys[keyuuid]
  51. persona.verify(obj)
  52. self._objstore.setdefault(obj.uuid, []).append(obj)
  53. request.setResponseCode(201)
  54. except Exception:
  55. request.setResponseCode(401)
  56. # twistd support
  57. #medaserver = MEDAServer()
  58. #resource = medaserver.app.resource
  59. def main():
  60. from optparse import OptionParser
  61. parser = OptionParser()
  62. parser.add_option('-a', action='append', dest='addpubkey',
  63. default=[], help='Add specified public key as a trusted key.')
  64. options, args = parser.parse_args()
  65. medaserver = MEDAServer(defaultfile)
  66. try:
  67. if options.addpubkey:
  68. for i in options.addpubkey:
  69. medaserver.addpubkey(i)
  70. return
  71. medaserver.app.run()
  72. finally:
  73. medaserver.store()
  74. if __name__ == '__main__': # pragma: no cover
  75. main()
  76. class _TestCases(unittest.TestCase):
  77. def setUp(self):
  78. d = os.path.realpath(tempfile.mkdtemp())
  79. self.basetempdir = d
  80. self.medaserverfile = os.path.join(self.basetempdir, 'serverstore.pasn1')
  81. self.medaserver = MEDAServer(self.medaserverfile)
  82. self.requests = FakeRequests(self.medaserver.app)
  83. def tearDown(self):
  84. shutil.rmtree(self.basetempdir)
  85. self.basetempdir = None
  86. def test_404(self):
  87. h = hashlib.sha256(open('fixtures/testfiles/test.txt').read()).hexdigest()
  88. r = self.requests.get('/chash/%s' % h)
  89. self.assertEqual(r.status_code, 404)
  90. def test_objlookup(self):
  91. # that when fetching an non-existant object
  92. r = self.requests.get('/obj/%s' % str(uuid.uuid4()))
  93. # it is 404, not found
  94. self.assertEqual(r.status_code, 404)
  95. # that passing an invalid uuid
  96. r = self.requests.get('/obj/bogusuuid')
  97. # it is 400, bad request
  98. self.assertEqual(r.status_code, 400)
  99. def test_pubkeystorage(self):
  100. import cli
  101. # that an identity
  102. persona = cli.Persona()
  103. persona.generate_key()
  104. # that by default, put's
  105. r = self.requests.put('/store', data=persona.get_identity().encode())
  106. # are denied
  107. self.assertEqual(r.status_code, 401)
  108. # can have it's public key added to the server
  109. self.medaserver.addpubkey(persona.get_pubkey())
  110. # that it can store the pubkey's identity
  111. r = self.requests.put('/store', data=persona.get_identity().encode())
  112. self.assertEqual(r.status_code, 201)
  113. # that an object with a bad signature
  114. badsigobj = persona.get_identity().new_version()
  115. # when stored
  116. r = self.requests.put('/store', data=badsigobj.encode())
  117. # is rejected
  118. self.assertEqual(r.status_code, 401)
  119. # that when fetching the object back
  120. r = self.requests.get('/obj/%s' % str(persona.uuid))
  121. # it is successful
  122. self.assertEqual(r.status_code, 200)
  123. # that the returned data
  124. fetchobj = MDBase.decode(r.text)
  125. # matches what was stored
  126. self.assertEqual(fetchobj, persona.get_identity())
  127. # that when stored
  128. self.medaserver.store()
  129. tmpmedaserver = MEDAServer(self.medaserverfile)
  130. @mock.patch('klein.Klein.run')
  131. def test_addpubkey(self, apprun):
  132. import cli
  133. persona = cli.Persona()
  134. persona.generate_key()
  135. with nested(mock.patch('server.MEDAServer.addpubkey'),
  136. mock.patch('sys.argv', [ 'progname', '-a',
  137. persona.get_pubkey() ])) as (addpub, argv):
  138. main()
  139. addpub.assert_called_with(persona.get_pubkey())
  140. apprun.assert_not_called()
  141. # Note: because of this mock, it hides the actual app.run call w/
  142. # a mock
  143. @mock.patch('server.MEDAServer')
  144. def test_medaserverinstanciated(self, medaserver):
  145. # that when main is run
  146. main()
  147. # that it gets called with the default storage file
  148. medaserver.assert_called_with('mediaserver.store.pasn1')
  149. @mock.patch('server.MEDAServer.store')
  150. @mock.patch('klein.Klein.run')
  151. def test_appruns(self, kleinrun, storefun):
  152. main()
  153. kleinrun.assert_called()
  154. storefun.assert_called()