Made decorators views for Customize Interface
[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
447d1c25 123def path_subtitle(controller):
124 """Retrieve <path> URL pattern and pass in as path=..."""
125
126
127 @wraps(controller)
128 def wrapper(request, *args, **kwargs):
129 path_sub = request.matchdict['path']
130
131 return controller(request, *args, path=path_sub, **kwargs)
132
133 return wrapper
134
135def path_subtitle(controller):
136 """Retrieve <path> URL pattern and pass in as path=..."""
137
138
139 @wraps(controller)
140 def wrapper(request, *args, **kwargs):
141 path_sub = request.matchdict['path']
142
143 return controller(request, *args, path=path_sub, **kwargs)
144
145 return wrapper
146
8e91df87 147
502073f2
JW
148def user_may_delete_media(controller):
149 """
53c5e0b0 150 Require user ownership of the MediaEntry to delete.
502073f2 151 """
1e03504e 152 @wraps(controller)
502073f2 153 def wrapper(request, *args, **kwargs):
0f3bf8d4 154 uploader_id = kwargs['media'].actor
8394febb 155 if not (request.user.has_privilege(u'admin') or
5c2b8486 156 request.user.id == uploader_id):
cfa92229 157 raise Forbidden()
502073f2
JW
158
159 return controller(request, *args, **kwargs)
160
1e03504e 161 return wrapper
502073f2 162
3eb6fc4f 163
be5be115
AW
164def user_may_alter_collection(controller):
165 """
166 Require user ownership of the Collection to modify.
167 """
168 @wraps(controller)
169 def wrapper(request, *args, **kwargs):
d88fcb03 170 creator_id = request.db.LocalUser.query.filter_by(
44082b12 171 username=request.matchdict['user']).first().id
8394febb 172 if not (request.user.has_privilege(u'admin') or
5c2b8486 173 request.user.id == creator_id):
cfa92229 174 raise Forbidden()
be5be115
AW
175
176 return controller(request, *args, **kwargs)
177
178 return wrapper
179
180
3eb6fc4f
BK
181def uses_pagination(controller):
182 """
183 Check request GET 'page' key for wrong values
184 """
1e03504e 185 @wraps(controller)
3eb6fc4f
BK
186 def wrapper(request, *args, **kwargs):
187 try:
1301a8ad 188 page = int(request.GET.get('page', 1))
3eb6fc4f 189 if page < 0:
de12b4e7 190 return render_404(request)
3eb6fc4f 191 except ValueError:
de12b4e7 192 return render_404(request)
3eb6fc4f 193
439e37f7 194 return controller(request, page=page, *args, **kwargs)
3eb6fc4f 195
1e03504e 196 return wrapper
724933b1
CAW
197
198
01674e10 199def get_user_media_entry(controller):
724933b1
CAW
200 """
201 Pass in a MediaEntry based off of a url component
202 """
1e03504e 203 @wraps(controller)
724933b1 204 def wrapper(request, *args, **kwargs):
d88fcb03 205 user = LocalUser.query.filter_by(username=request.matchdict['user']).first()
01674e10 206 if not user:
7f4e42b0
SS
207 raise NotFound()
208
7de20e52
CAW
209 media = None
210
211 # might not be a slug, might be an id, but whatever
212 media_slug = request.matchdict['media']
213
e4e50a27
CAW
214 # if it starts with id: it actually isn't a slug, it's an id.
215 if media_slug.startswith(u'id:'):
216 try:
217 media = MediaEntry.query.filter_by(
218 id=int(media_slug[3:]),
219 state=u'processed',
0f3bf8d4 220 actor=user.id).first()
e4e50a27
CAW
221 except ValueError:
222 raise NotFound()
7de20e52
CAW
223 else:
224 # no magical id: stuff? It's a slug!
225 media = MediaEntry.query.filter_by(
697c74c2 226 slug=media_slug,
7de20e52 227 state=u'processed',
0f3bf8d4 228 actor=user.id).first()
724933b1 229
724933b1 230 if not media:
7de20e52 231 # Didn't find anything? Okay, 404.
7f4e42b0 232 raise NotFound()
724933b1
CAW
233
234 return controller(request, media=media, *args, **kwargs)
235
1e03504e 236 return wrapper
aba81c9f 237
243c3843 238
be5be115
AW
239def get_user_collection(controller):
240 """
241 Pass in a Collection based off of a url component
242 """
243 @wraps(controller)
244 def wrapper(request, *args, **kwargs):
d88fcb03 245 user = request.db.LocalUser.query.filter_by(
44082b12 246 username=request.matchdict['user']).first()
be5be115
AW
247
248 if not user:
249 return render_404(request)
250
44082b12
RE
251 collection = request.db.Collection.query.filter_by(
252 slug=request.matchdict['collection'],
0f3bf8d4 253 actor=user.id).first()
be5be115
AW
254
255 # Still no collection? Okay, 404.
256 if not collection:
257 return render_404(request)
258
259 return controller(request, collection=collection, *args, **kwargs)
260
261 return wrapper
262
263
264def get_user_collection_item(controller):
265 """
266 Pass in a CollectionItem based off of a url component
267 """
268 @wraps(controller)
269 def wrapper(request, *args, **kwargs):
d88fcb03 270 user = request.db.LocalUser.query.filter_by(
44082b12 271 username=request.matchdict['user']).first()
be5be115
AW
272
273 if not user:
274 return render_404(request)
275
44082b12
RE
276 collection_item = request.db.CollectionItem.query.filter_by(
277 id=request.matchdict['collection_item']).first()
be5be115
AW
278
279 # Still no collection item? Okay, 404.
280 if not collection_item:
281 return render_404(request)
282
283 return controller(request, collection_item=collection_item, *args, **kwargs)
284
285 return wrapper
286
287
aba81c9f
E
288def get_media_entry_by_id(controller):
289 """
290 Pass in a MediaEntry based off of a url component
291 """
1e03504e 292 @wraps(controller)
aba81c9f 293 def wrapper(request, *args, **kwargs):
71717fd5 294 media = MediaEntry.query.filter_by(
461dd971 295 id=request.matchdict['media_id'],
71717fd5 296 state=u'processed').first()
aba81c9f
E
297 # Still no media? Okay, 404.
298 if not media:
de12b4e7 299 return render_404(request)
aba81c9f 300
461dd971 301 given_username = request.matchdict.get('user')
0f3bf8d4 302 if given_username and (given_username != media.get_actor.username):
461dd971
E
303 return render_404(request)
304
aba81c9f
E
305 return controller(request, media=media, *args, **kwargs)
306
1e03504e 307 return wrapper
f91dcc9d
SS
308
309
310def get_workbench(func):
311 """Decorator, passing in a workbench as kwarg which is cleaned up afterwards"""
312
313 @wraps(func)
314 def new_func(*args, **kwargs):
bd6fe977 315 with mgg.workbench_manager.create() as workbench:
f91dcc9d
SS
316 return func(*args, workbench=workbench, **kwargs)
317
318 return new_func
5adb906a
RE
319
320
321def allow_registration(controller):
322 """ Decorator for if registration is enabled"""
323 @wraps(controller)
324 def wrapper(request, *args, **kwargs):
325 if not mgg.app_config["allow_registration"]:
326 messages.add_message(
327 request,
328 messages.WARNING,
329 _('Sorry, registration is disabled on this instance.'))
330 return redirect(request, "index")
331
332 return controller(request, *args, **kwargs)
333
334 return wrapper
335
6483b370 336def allow_reporting(controller):
337 """ Decorator for if reporting is enabled"""
338 @wraps(controller)
339 def wrapper(request, *args, **kwargs):
340 if not mgg.app_config["allow_reporting"]:
341 messages.add_message(
342 request,
343 messages.WARNING,
344 _('Sorry, reporting is disabled on this instance.'))
345 return redirect(request, 'index')
346
347 return controller(request, *args, **kwargs)
348
349 return wrapper
350
8e91df87 351def get_optional_media_comment_by_id(controller):
30a9fe7c 352 """
64a456a4
JT
353 Pass in a Comment based off of a url component. Because of this decor-
354 -ator's use in filing Reports, it has two valid outcomes.
8e91df87 355
356 :returns The view function being wrapped with kwarg `comment` set to
64a456a4 357 the Comment who's id is in the URL. If there is a
8e91df87 358 comment id in the URL and if it is valid.
359 :returns The view function being wrapped with kwarg `comment` set to
360 None. If there is no comment id in the URL.
361 :returns A 404 Error page, if there is a comment if in the URL and it
362 is invalid.
30a9fe7c 363 """
364 @wraps(controller)
365 def wrapper(request, *args, **kwargs):
8e91df87 366 if 'comment' in request.matchdict:
64a456a4
JT
367 comment = Comment.query.filter_by(
368 id=request.matchdict['comment']
369 ).first()
30a9fe7c 370
8e91df87 371 if comment is None:
372 return render_404(request)
30a9fe7c 373
8e91df87 374 return controller(request, comment=comment, *args, **kwargs)
375 else:
376 return controller(request, comment=None, *args, **kwargs)
30a9fe7c 377 return wrapper
378
379
5adb906a
RE
380def auth_enabled(controller):
381 """Decorator for if an auth plugin is enabled"""
382 @wraps(controller)
383 def wrapper(request, *args, **kwargs):
384 if not mgg.app.auth:
385 messages.add_message(
386 request,
387 messages.WARNING,
388 _('Sorry, authentication is disabled on this instance.'))
389 return redirect(request, 'index')
390
52a355b2 391 return controller(request, *args, **kwargs)
392
393 return wrapper
30a9fe7c 394
6bba33d7 395def require_admin_or_moderator_login(controller):
9b8ef022 396 """
8e91df87 397 Require a login from an administrator or a moderator.
9b8ef022 398 """
399 @wraps(controller)
400 def new_controller_func(request, *args, **kwargs):
401 if request.user and \
7bfc81b2
JT
402 not (request.user.has_privilege(u'admin')
403 or request.user.has_privilege(u'moderator')):
6bba33d7 404
9b8ef022 405 raise Forbidden()
406 elif not request.user:
407 next_url = urljoin(
408 request.urlgen('mediagoblin.auth.login',
409 qualified=True),
410 request.url)
411
412 return redirect(request, 'mediagoblin.auth.login',
413 next=next_url)
414
415 return controller(request, *args, **kwargs)
416
417 return new_controller_func
418
9e204e49 419
9e204e49 420
1e2675b0 421def oauth_required(controller):
2b60a56c 422 """ Used to wrap API endpoints where oauth is required """
423 @wraps(controller)
424 def wrapper(request, *args, **kwargs):
425 data = request.headers
426 authorization = decode_authorization_header(data)
427
428 if authorization == dict():
429 error = "Missing required parameter."
430 return json_response({"error": error}, status=400)
431
dfd66b78 432
1e2675b0 433 request_validator = GMGRequestValidator()
434 resource_endpoint = ResourceEndpoint(request_validator)
d7b3805f 435 valid, r = resource_endpoint.validate_protected_resource_request(
3b4ad554 436 uri=request.url,
1e2675b0 437 http_method=request.method,
d4a21d7e 438 body=request.data,
1e2675b0 439 headers=dict(request.headers),
440 )
49a47ec9 441
442 if not valid:
1db8690f 443 error = "Invalid oauth parameter."
49a47ec9 444 return json_response({"error": error}, status=400)
1e2675b0 445
c894b424
JT
446 # Fill user if not already
447 token = authorization[u"oauth_token"]
5436d980
JT
448 request.access_token = AccessToken.query.filter_by(token=token).first()
449 if request.access_token is not None and request.user is None:
fd703bb4 450 user_id = request.access_token.actor
d88fcb03 451 request.user = LocalUser.query.filter_by(id=user_id).first()
c894b424 452
1e2675b0 453 return controller(request, *args, **kwargs)
454
455 return wrapper