Merge branch '512_bump_video_js'
[mediagoblin.git] / mediagoblin / plugins / api / tools.py
CommitLineData
a062149e
JW
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
17import logging
42c83752 18import json
a062149e
JW
19
20from functools import wraps
c92aa0d0 21from urlparse import urljoin
62d14bf5 22from werkzeug.exceptions import Forbidden
cc195d5b 23from werkzeug.wrappers import Response
42c83752 24from mediagoblin import mg_globals
a062149e 25from mediagoblin.tools.pluginapi import PluginManager
42c83752 26from mediagoblin.storage.filestorage import BasicFileStorage
a062149e
JW
27
28_log = logging.getLogger(__name__)
29
30
42c83752
JW
31class Auth(object):
32 '''
33 An object with two significant methods, 'trigger' and 'run'.
34
35 Using a similar object to this, plugins can register specific
36 authentication logic, for example the GET param 'access_token' for OAuth.
37
38 - trigger: Analyze the 'request' argument, return True if you think you
39 can handle the request, otherwise return False
40 - run: The authentication logic, set the request.user object to the user
41 you intend to authenticate and return True, otherwise return False.
42
43 If run() returns False, an HTTP 403 Forbidden error will be shown.
44
45 You may also display custom errors, just raise them within the run()
46 method.
47 '''
48 def trigger(self, request):
49 raise NotImplemented()
50
51 def __call__(self, request, *args, **kw):
52 raise NotImplemented()
53
54
88a9662b 55def json_response(serializable, _disable_cors=False, *args, **kw):
85726f73 56 '''
cc195d5b 57 Serializes a json objects and returns a werkzeug Response object with the
85726f73
JW
58 serialized value as the response body and Content-Type: application/json.
59
60 :param serializable: A json-serializable object
61
62 Any extra arguments and keyword arguments are passed to the
cc195d5b 63 Response.__init__ method.
85726f73 64 '''
cc195d5b 65 response = Response(json.dumps(serializable), *args, content_type='application/json', **kw)
88a9662b
JW
66
67 if not _disable_cors:
68 cors_headers = {
69 'Access-Control-Allow-Origin': '*',
70 'Access-Control-Allow-Methods': 'POST, GET, OPTIONS',
71 'Access-Control-Allow-Headers': 'Content-Type, X-Requested-With'}
7c552c0b 72 (response.headers.set(key, value) for key, value in cors_headers)
88a9662b 73
42c83752
JW
74 return response
75
76
85726f73
JW
77def get_entry_serializable(entry, urlgen):
78 '''
79 Returns a serializable dict() of a MediaEntry instance.
80
81 :param entry: A MediaEntry instance
82 :param urlgen: An urlgen instance, can be found on the request object passed
83 to views.
84 '''
42c83752
JW
85 return {
86 'user': entry.get_uploader.username,
87 'user_id': entry.get_uploader.id,
85726f73
JW
88 'user_bio': entry.get_uploader.bio,
89 'user_bio_html': entry.get_uploader.bio_html,
90 'user_permalink': urlgen('mediagoblin.user_pages.user_home',
91 user=entry.get_uploader.username,
92 qualified=True),
42c83752
JW
93 'id': entry.id,
94 'created': entry.created.isoformat(),
95 'title': entry.title,
96 'license': entry.license,
97 'description': entry.description,
98 'description_html': entry.description_html,
99 'media_type': entry.media_type,
09e528ac 100 'state': entry.state,
85726f73
JW
101 'permalink': entry.url_for_self(urlgen, qualified=True),
102 'media_files': get_media_file_paths(entry.media_files, urlgen)}
103
42c83752 104
85726f73
JW
105def get_media_file_paths(media_files, urlgen):
106 '''
107 Returns a dictionary of media files with `file_handle` => `qualified URL`
42c83752 108
85726f73
JW
109 :param media_files: dict-like object consisting of `file_handle => `listy
110 filepath` pairs.
111 :param urlgen: An urlgen object, usually found on request.urlgen.
112 '''
42c83752
JW
113 media_urls = {}
114
115 for key, val in media_files.items():
c92aa0d0
JW
116 if isinstance(mg_globals.public_store, BasicFileStorage):
117 # BasicFileStorage does not provide a qualified URI
118 media_urls[key] = urljoin(
119 urlgen('index', qualified=True),
120 mg_globals.public_store.file_url(val))
121 else:
122 media_urls[key] = mg_globals.public_store.file_url(val)
42c83752
JW
123
124 return media_urls
125
126
a062149e 127def api_auth(controller):
85726f73
JW
128 '''
129 Decorator, allows plugins to register auth methods that will then be
130 evaluated against the request, finally a worthy authenticator object is
131 chosen and used to decide whether to grant or deny access.
132 '''
a062149e
JW
133 @wraps(controller)
134 def wrapper(request, *args, **kw):
135 auth_candidates = []
136
137 for auth in PluginManager().get_hook_callables('auth'):
a062149e 138 if auth.trigger(request):
57c6473a 139 _log.debug('{0} believes it is capable of authenticating this request.'.format(auth))
a062149e
JW
140 auth_candidates.append(auth)
141
142 # If we can't find any authentication methods, we should not let them
143 # pass.
144 if not auth_candidates:
cfa92229 145 raise Forbidden()
a062149e
JW
146
147 # For now, just select the first one in the list
148 auth = auth_candidates[0]
149
150 _log.debug('Using {0} to authorize request {1}'.format(
151 auth, request.url))
152
153 if not auth(request, *args, **kw):
88a9662b
JW
154 if getattr(auth, 'errors', []):
155 return json_response({
156 'status': 403,
157 'errors': auth.errors})
158
cfa92229 159 raise Forbidden()
a062149e
JW
160
161 return controller(request, *args, **kw)
162
163 return wrapper