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