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.
 
 
 
 

173 lines
4.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. defaultfile = 'mediaserver.store.pasn1'
  22. class MEDAServer:
  23. def __init__(self, fname):
  24. self._trustedkeys = {}
  25. app = Klein()
  26. def addpubkey(self, pubkey):
  27. persona = Persona.from_pubkey(pubkey)
  28. self._trustedkeys[persona.uuid] = persona
  29. def store(self):
  30. pass
  31. @app.route('/')
  32. def home(request):
  33. return 'hello'
  34. @app.route('/store')
  35. def storeobj(self, request):
  36. try:
  37. obj = MDBase.decode(request.content.read())
  38. #if obj.type == 'identity':
  39. keyuuid = obj.uuid
  40. #else:
  41. # keyuuid = obj.created_by_ref
  42. persona = self._trustedkeys[keyuuid]
  43. persona.verify(obj)
  44. request.setResponseCode(201)
  45. except Exception:
  46. request.setResponseCode(401)
  47. # twistd support
  48. #medaserver = MEDAServer()
  49. #resource = medaserver.app.resource
  50. def main():
  51. from optparse import OptionParser
  52. parser = OptionParser()
  53. parser.add_option('-a', action='append', dest='addpubkey',
  54. default=[], help='Add specified public key as a trusted key.')
  55. options, args = parser.parse_args()
  56. medaserver = MEDAServer(defaultfile)
  57. try:
  58. if options.addpubkey:
  59. for i in options.addpubkey:
  60. medaserver.addpubkey(i)
  61. return
  62. medaserver.app.run()
  63. finally:
  64. medaserver.store()
  65. if __name__ == '__main__': # pragma: no cover
  66. main()
  67. class _TestCases(unittest.TestCase):
  68. def setUp(self):
  69. d = os.path.realpath(tempfile.mkdtemp())
  70. self.basetempdir = d
  71. self.medaserverfile = os.path.join(self.basetempdir, 'serverstore.pasn1')
  72. self.medaserver = MEDAServer(self.medaserverfile)
  73. self.requests = FakeRequests(self.medaserver.app)
  74. def tearDown(self):
  75. shutil.rmtree(self.basetempdir)
  76. self.basetempdir = None
  77. def test_404(self):
  78. h = hashlib.sha256(open('fixtures/testfiles/test.txt').read()).hexdigest()
  79. r = self.requests.get('/chash/%s' % h)
  80. self.assertEqual(r.status_code, 404)
  81. def test_pubkeystorage(self):
  82. import cli
  83. # that an identity
  84. persona = cli.Persona()
  85. persona.generate_key()
  86. # that by default, put's
  87. r = self.requests.put('/store', data=persona.get_identity().encode())
  88. # are denied
  89. self.assertEqual(r.status_code, 401)
  90. # can have it's public key added to the server
  91. self.medaserver.addpubkey(persona.get_pubkey())
  92. # that it can store the pubkey's identity
  93. r = self.requests.put('/store', data=persona.get_identity().encode())
  94. self.assertEqual(r.status_code, 201)
  95. # that an object with a bad signature
  96. badsigobj = persona.get_identity().new_version()
  97. # when stored
  98. r = self.requests.put('/store', data=badsigobj.encode())
  99. # is rejected
  100. self.assertEqual(r.status_code, 401)
  101. # that when stored
  102. self.medaserver.store()
  103. tmpmedaserver = MEDAServer(self.medaserverfile)
  104. @mock.patch('klein.Klein.run')
  105. def test_addpubkey(self, apprun):
  106. import cli
  107. persona = cli.Persona()
  108. persona.generate_key()
  109. with nested(mock.patch('server.MEDAServer.addpubkey'),
  110. mock.patch('sys.argv', [ 'progname', '-a',
  111. persona.get_pubkey() ])) as (addpub, argv):
  112. main()
  113. addpub.assert_called_with(persona.get_pubkey())
  114. apprun.assert_not_called()
  115. # Note: because of this mock, it hides the actual app.run call w/
  116. # a mock
  117. @mock.patch('server.MEDAServer')
  118. def test_medaserverinstanciated(self, medaserver):
  119. # that when main is run
  120. main()
  121. # that it gets called with the default storage file
  122. medaserver.assert_called_with('mediaserver.store.pasn1')
  123. @mock.patch('server.MEDAServer.store')
  124. @mock.patch('klein.Klein.run')
  125. def test_appruns(self, kleinrun, storefun):
  126. main()
  127. kleinrun.assert_called()
  128. storefun.assert_called()