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