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