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