Add static pump_io to API and fix problem where null appeared in profile
[mediagoblin.git] / mediagoblin / decorators.py
CommitLineData
bb3eaf20 1# GNU MediaGoblin -- federated, autonomous media hosting
cf29e8a8 2# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
bb3eaf20
CAW
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
1e03504e 17from functools import wraps
bb3eaf20 18
3a199328 19from urlparse import urljoin
7f4e42b0 20from werkzeug.exceptions import Forbidden, NotFound
1e2675b0 21from oauthlib.oauth1 import ResourceEndpoint
bb3eaf20 22
f91dcc9d 23from mediagoblin import mg_globals as mgg
5adb906a 24from mediagoblin import messages
a523ffce 25from mediagoblin.db.models import MediaEntry, User, MediaComment
a4bf3e58
CAW
26from mediagoblin.tools.response import (
27 redirect, render_404,
28 render_user_banned, json_response)
5adb906a 29from mediagoblin.tools.translate import pass_to_ugettext as _
bb3eaf20 30
0ec89cb2 31from mediagoblin.oauth.tools.request import decode_authorization_header
32from mediagoblin.oauth.oauth import GMGRequestValidator
bb3eaf20 33
8e91df87 34
35def user_not_banned(controller):
36 """
37 Requires that the user has not been banned. Otherwise redirects to the page
38 explaining why they have been banned
39 """
40 @wraps(controller)
41 def wrapper(request, *args, **kwargs):
42 if request.user:
43 if request.user.is_banned():
44 return render_user_banned(request)
45 return controller(request, *args, **kwargs)
46
47 return wrapper
48
49
bb3eaf20
CAW
50def require_active_login(controller):
51 """
8e91df87 52 Require an active login from the user. If the user is banned, redirects to
53 the "You are Banned" page.
bb3eaf20 54 """
1e03504e 55 @wraps(controller)
8e91df87 56 @user_not_banned
bb3eaf20 57 def new_controller_func(request, *args, **kwargs):
a72c504b 58 if request.user and \
8394febb 59 not request.user.has_privilege(u'active'):
d43b472a
CAW
60 return redirect(
61 request, 'mediagoblin.user_pages.user_home',
5a4e3ff1 62 user=request.user.username)
8394febb 63 elif not request.user or not request.user.has_privilege(u'active'):
3a199328
JW
64 next_url = urljoin(
65 request.urlgen('mediagoblin.auth.login',
66 qualified=True),
67 request.url)
68
059eaee4 69 return redirect(request, 'mediagoblin.auth.login',
56c113c7 70 next=next_url)
bb3eaf20
CAW
71
72 return controller(request, *args, **kwargs)
73
1e03504e 74 return new_controller_func
3eb6fc4f 75
ad742028 76
3fb96fc9 77def user_has_privilege(privilege_name):
8e91df87 78 """
79 Requires that a user have a particular privilege in order to access a page.
80 In order to require that a user have multiple privileges, use this
81 decorator twice on the same view. This decorator also makes sure that the
82 user is not banned, or else it redirects them to the "You are Banned" page.
83
84 :param privilege_name A unicode object that is that represents
85 the privilege object. This object is
86 the name of the privilege, as assigned
87 in the Privilege.privilege_name column
88 """
6bba33d7 89
3fb96fc9 90 def user_has_privilege_decorator(controller):
9b8ef022 91 @wraps(controller)
8e91df87 92 @require_active_login
9b8ef022 93 def wrapper(request, *args, **kwargs):
94 user_id = request.user.id
8e91df87 95 if not request.user.has_privilege(privilege_name):
9b8ef022 96 raise Forbidden()
97
98 return controller(request, *args, **kwargs)
99
100 return wrapper
3fb96fc9 101 return user_has_privilege_decorator
9b8ef022 102
53c5e0b0 103
8e91df87 104def active_user_from_url(controller):
105 """Retrieve User() from <user> URL pattern and pass in as url_user=...
106
107 Returns a 404 if no such active user has been found"""
108 @wraps(controller)
109 def wrapper(request, *args, **kwargs):
110 user = User.query.filter_by(username=request.matchdict['user']).first()
111 if user is None:
112 return render_404(request)
113
114 return controller(request, *args, url_user=user, **kwargs)
115
116 return wrapper
117
118
502073f2
JW
119def user_may_delete_media(controller):
120 """
53c5e0b0 121 Require user ownership of the MediaEntry to delete.
502073f2 122 """
1e03504e 123 @wraps(controller)
502073f2 124 def wrapper(request, *args, **kwargs):
461dd971 125 uploader_id = kwargs['media'].uploader
8394febb 126 if not (request.user.has_privilege(u'admin') or
5c2b8486 127 request.user.id == uploader_id):
cfa92229 128 raise Forbidden()
502073f2
JW
129
130 return controller(request, *args, **kwargs)
131
1e03504e 132 return wrapper
502073f2 133
3eb6fc4f 134
be5be115
AW
135def user_may_alter_collection(controller):
136 """
137 Require user ownership of the Collection to modify.
138 """
139 @wraps(controller)
140 def wrapper(request, *args, **kwargs):
44082b12
RE
141 creator_id = request.db.User.query.filter_by(
142 username=request.matchdict['user']).first().id
8394febb 143 if not (request.user.has_privilege(u'admin') or
5c2b8486 144 request.user.id == creator_id):
cfa92229 145 raise Forbidden()
be5be115
AW
146
147 return controller(request, *args, **kwargs)
148
149 return wrapper
150
151
3eb6fc4f
BK
152def uses_pagination(controller):
153 """
154 Check request GET 'page' key for wrong values
155 """
1e03504e 156 @wraps(controller)
3eb6fc4f
BK
157 def wrapper(request, *args, **kwargs):
158 try:
1301a8ad 159 page = int(request.GET.get('page', 1))
3eb6fc4f 160 if page < 0:
de12b4e7 161 return render_404(request)
3eb6fc4f 162 except ValueError:
de12b4e7 163 return render_404(request)
3eb6fc4f 164
439e37f7 165 return controller(request, page=page, *args, **kwargs)
3eb6fc4f 166
1e03504e 167 return wrapper
724933b1
CAW
168
169
01674e10 170def get_user_media_entry(controller):
724933b1
CAW
171 """
172 Pass in a MediaEntry based off of a url component
173 """
1e03504e 174 @wraps(controller)
724933b1 175 def wrapper(request, *args, **kwargs):
7f4e42b0 176 user = User.query.filter_by(username=request.matchdict['user']).first()
01674e10 177 if not user:
7f4e42b0
SS
178 raise NotFound()
179
7de20e52
CAW
180 media = None
181
182 # might not be a slug, might be an id, but whatever
183 media_slug = request.matchdict['media']
184
e4e50a27
CAW
185 # if it starts with id: it actually isn't a slug, it's an id.
186 if media_slug.startswith(u'id:'):
187 try:
188 media = MediaEntry.query.filter_by(
189 id=int(media_slug[3:]),
190 state=u'processed',
191 uploader=user.id).first()
192 except ValueError:
193 raise NotFound()
7de20e52
CAW
194 else:
195 # no magical id: stuff? It's a slug!
196 media = MediaEntry.query.filter_by(
697c74c2 197 slug=media_slug,
7de20e52
CAW
198 state=u'processed',
199 uploader=user.id).first()
724933b1 200
724933b1 201 if not media:
7de20e52 202 # Didn't find anything? Okay, 404.
7f4e42b0 203 raise NotFound()
724933b1
CAW
204
205 return controller(request, media=media, *args, **kwargs)
206
1e03504e 207 return wrapper
aba81c9f 208
243c3843 209
be5be115
AW
210def get_user_collection(controller):
211 """
212 Pass in a Collection based off of a url component
213 """
214 @wraps(controller)
215 def wrapper(request, *args, **kwargs):
44082b12
RE
216 user = request.db.User.query.filter_by(
217 username=request.matchdict['user']).first()
be5be115
AW
218
219 if not user:
220 return render_404(request)
221
44082b12
RE
222 collection = request.db.Collection.query.filter_by(
223 slug=request.matchdict['collection'],
224 creator=user.id).first()
be5be115
AW
225
226 # Still no collection? Okay, 404.
227 if not collection:
228 return render_404(request)
229
230 return controller(request, collection=collection, *args, **kwargs)
231
232 return wrapper
233
234
235def get_user_collection_item(controller):
236 """
237 Pass in a CollectionItem based off of a url component
238 """
239 @wraps(controller)
240 def wrapper(request, *args, **kwargs):
44082b12
RE
241 user = request.db.User.query.filter_by(
242 username=request.matchdict['user']).first()
be5be115
AW
243
244 if not user:
245 return render_404(request)
246
44082b12
RE
247 collection_item = request.db.CollectionItem.query.filter_by(
248 id=request.matchdict['collection_item']).first()
be5be115
AW
249
250 # Still no collection item? Okay, 404.
251 if not collection_item:
252 return render_404(request)
253
254 return controller(request, collection_item=collection_item, *args, **kwargs)
255
256 return wrapper
257
258
aba81c9f
E
259def get_media_entry_by_id(controller):
260 """
261 Pass in a MediaEntry based off of a url component
262 """
1e03504e 263 @wraps(controller)
aba81c9f 264 def wrapper(request, *args, **kwargs):
71717fd5 265 media = MediaEntry.query.filter_by(
461dd971 266 id=request.matchdict['media_id'],
71717fd5 267 state=u'processed').first()
aba81c9f
E
268 # Still no media? Okay, 404.
269 if not media:
de12b4e7 270 return render_404(request)
aba81c9f 271
461dd971
E
272 given_username = request.matchdict.get('user')
273 if given_username and (given_username != media.get_uploader.username):
274 return render_404(request)
275
aba81c9f
E
276 return controller(request, media=media, *args, **kwargs)
277
1e03504e 278 return wrapper
f91dcc9d
SS
279
280
281def get_workbench(func):
282 """Decorator, passing in a workbench as kwarg which is cleaned up afterwards"""
283
284 @wraps(func)
285 def new_func(*args, **kwargs):
bd6fe977 286 with mgg.workbench_manager.create() as workbench:
f91dcc9d
SS
287 return func(*args, workbench=workbench, **kwargs)
288
289 return new_func
5adb906a
RE
290
291
292def allow_registration(controller):
293 """ Decorator for if registration is enabled"""
294 @wraps(controller)
295 def wrapper(request, *args, **kwargs):
296 if not mgg.app_config["allow_registration"]:
297 messages.add_message(
298 request,
299 messages.WARNING,
300 _('Sorry, registration is disabled on this instance.'))
301 return redirect(request, "index")
302
303 return controller(request, *args, **kwargs)
304
305 return wrapper
306
6483b370 307def allow_reporting(controller):
308 """ Decorator for if reporting is enabled"""
309 @wraps(controller)
310 def wrapper(request, *args, **kwargs):
311 if not mgg.app_config["allow_reporting"]:
312 messages.add_message(
313 request,
314 messages.WARNING,
315 _('Sorry, reporting is disabled on this instance.'))
316 return redirect(request, 'index')
317
318 return controller(request, *args, **kwargs)
319
320 return wrapper
321
8e91df87 322def get_optional_media_comment_by_id(controller):
30a9fe7c 323 """
8e91df87 324 Pass in a MediaComment based off of a url component. Because of this decor-
325 -ator's use in filing Media or Comment Reports, it has two valid outcomes.
326
327 :returns The view function being wrapped with kwarg `comment` set to
328 the MediaComment who's id is in the URL. If there is a
329 comment id in the URL and if it is valid.
330 :returns The view function being wrapped with kwarg `comment` set to
331 None. If there is no comment id in the URL.
332 :returns A 404 Error page, if there is a comment if in the URL and it
333 is invalid.
30a9fe7c 334 """
335 @wraps(controller)
336 def wrapper(request, *args, **kwargs):
8e91df87 337 if 'comment' in request.matchdict:
338 comment = MediaComment.query.filter_by(
339 id=request.matchdict['comment']).first()
30a9fe7c 340
8e91df87 341 if comment is None:
342 return render_404(request)
30a9fe7c 343
8e91df87 344 return controller(request, comment=comment, *args, **kwargs)
345 else:
346 return controller(request, comment=None, *args, **kwargs)
30a9fe7c 347 return wrapper
348
349
5adb906a
RE
350def auth_enabled(controller):
351 """Decorator for if an auth plugin is enabled"""
352 @wraps(controller)
353 def wrapper(request, *args, **kwargs):
354 if not mgg.app.auth:
355 messages.add_message(
356 request,
357 messages.WARNING,
358 _('Sorry, authentication is disabled on this instance.'))
359 return redirect(request, 'index')
360
52a355b2 361 return controller(request, *args, **kwargs)
362
363 return wrapper
30a9fe7c 364
6bba33d7 365def require_admin_or_moderator_login(controller):
9b8ef022 366 """
8e91df87 367 Require a login from an administrator or a moderator.
9b8ef022 368 """
369 @wraps(controller)
370 def new_controller_func(request, *args, **kwargs):
371 if request.user and \
8394febb 372 not request.user.has_privilege(u'admin',u'moderator'):
6bba33d7 373
9b8ef022 374 raise Forbidden()
375 elif not request.user:
376 next_url = urljoin(
377 request.urlgen('mediagoblin.auth.login',
378 qualified=True),
379 request.url)
380
381 return redirect(request, 'mediagoblin.auth.login',
382 next=next_url)
383
384 return controller(request, *args, **kwargs)
385
386 return new_controller_func
387
9e204e49 388
9e204e49 389
1e2675b0 390def oauth_required(controller):
2b60a56c 391 """ Used to wrap API endpoints where oauth is required """
392 @wraps(controller)
393 def wrapper(request, *args, **kwargs):
394 data = request.headers
395 authorization = decode_authorization_header(data)
396
397 if authorization == dict():
398 error = "Missing required parameter."
399 return json_response({"error": error}, status=400)
400
dfd66b78 401
1e2675b0 402 request_validator = GMGRequestValidator()
403 resource_endpoint = ResourceEndpoint(request_validator)
d7b3805f 404 valid, r = resource_endpoint.validate_protected_resource_request(
1e2675b0 405 uri=request.url,
406 http_method=request.method,
407 body=request.get_data(),
408 headers=dict(request.headers),
409 )
49a47ec9 410
411 if not valid:
412 error = "Invalid oauth prarameter."
413 return json_response({"error": error}, status=400)
1e2675b0 414
415 return controller(request, *args, **kwargs)
416
417 return wrapper