tools.py 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143
  1. # GNU MediaGoblin -- federated, autonomous media hosting
  2. # Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
  3. #
  4. # This program is free software: you can redistribute it and/or modify
  5. # it under the terms of the GNU Affero General Public License as published by
  6. # the Free Software Foundation, either version 3 of the License, or
  7. # (at your option) any later version.
  8. #
  9. # This program is distributed in the hope that it will be useful,
  10. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. # GNU Affero General Public License for more details.
  13. #
  14. # You should have received a copy of the GNU Affero General Public License
  15. # along with this program. If not, see <http://www.gnu.org/licenses/>.
  16. import logging
  17. import json
  18. from functools import wraps
  19. from werkzeug.exceptions import Forbidden
  20. from werkzeug.wrappers import Response
  21. from six.moves.urllib.parse import urljoin
  22. from mediagoblin import mg_globals
  23. from mediagoblin.tools.pluginapi import PluginManager
  24. from mediagoblin.storage.filestorage import BasicFileStorage
  25. _log = logging.getLogger(__name__)
  26. class Auth(object):
  27. '''
  28. An object with two significant methods, 'trigger' and 'run'.
  29. Using a similar object to this, plugins can register specific
  30. authentication logic, for example the GET param 'access_token' for OAuth.
  31. - trigger: Analyze the 'request' argument, return True if you think you
  32. can handle the request, otherwise return False
  33. - run: The authentication logic, set the request.user object to the user
  34. you intend to authenticate and return True, otherwise return False.
  35. If run() returns False, an HTTP 403 Forbidden error will be shown.
  36. You may also display custom errors, just raise them within the run()
  37. method.
  38. '''
  39. def trigger(self, request):
  40. raise NotImplemented()
  41. def __call__(self, request, *args, **kw):
  42. raise NotImplemented()
  43. def get_entry_serializable(entry, urlgen):
  44. '''
  45. Returns a serializable dict() of a MediaEntry instance.
  46. :param entry: A MediaEntry instance
  47. :param urlgen: An urlgen instance, can be found on the request object passed
  48. to views.
  49. '''
  50. return {
  51. 'user': entry.get_uploader.username,
  52. 'user_id': entry.get_uploader.id,
  53. 'user_bio': entry.get_uploader.bio,
  54. 'user_bio_html': entry.get_uploader.bio_html,
  55. 'user_permalink': urlgen('mediagoblin.user_pages.user_home',
  56. user=entry.get_uploader.username,
  57. qualified=True),
  58. 'id': entry.id,
  59. 'created': entry.created.isoformat(),
  60. 'title': entry.title,
  61. 'license': entry.license,
  62. 'description': entry.description,
  63. 'description_html': entry.description_html,
  64. 'media_type': entry.media_type,
  65. 'state': entry.state,
  66. 'permalink': entry.url_for_self(urlgen, qualified=True),
  67. 'media_files': get_media_file_paths(entry.media_files, urlgen)}
  68. def get_media_file_paths(media_files, urlgen):
  69. '''
  70. Returns a dictionary of media files with `file_handle` => `qualified URL`
  71. :param media_files: dict-like object consisting of `file_handle => `listy
  72. filepath` pairs.
  73. :param urlgen: An urlgen object, usually found on request.urlgen.
  74. '''
  75. media_urls = {}
  76. for key, val in media_files.items():
  77. if isinstance(mg_globals.public_store, BasicFileStorage):
  78. # BasicFileStorage does not provide a qualified URI
  79. media_urls[key] = urljoin(
  80. urlgen('index', qualified=True),
  81. mg_globals.public_store.file_url(val))
  82. else:
  83. media_urls[key] = mg_globals.public_store.file_url(val)
  84. return media_urls
  85. def api_auth(controller):
  86. '''
  87. Decorator, allows plugins to register auth methods that will then be
  88. evaluated against the request, finally a worthy authenticator object is
  89. chosen and used to decide whether to grant or deny access.
  90. '''
  91. @wraps(controller)
  92. def wrapper(request, *args, **kw):
  93. auth_candidates = []
  94. for auth in PluginManager().get_hook_callables('auth'):
  95. if auth.trigger(request):
  96. _log.debug('{0} believes it is capable of authenticating this request.'.format(auth))
  97. auth_candidates.append(auth)
  98. # If we can't find any authentication methods, we should not let them
  99. # pass.
  100. if not auth_candidates:
  101. raise Forbidden()
  102. # For now, just select the first one in the list
  103. auth = auth_candidates[0]
  104. _log.debug('Using {0} to authorize request {1}'.format(
  105. auth, request.url))
  106. if not auth(request, *args, **kw):
  107. if getattr(auth, 'errors', []):
  108. return json_response({
  109. 'status': 403,
  110. 'errors': auth.errors})
  111. raise Forbidden()
  112. return controller(request, *args, **kw)
  113. return wrapper