Test with short and long username
[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, LocalUser, TextComment, \
27 AccessToken, Comment
28 from mediagoblin.tools.response import (
29 redirect, render_404,
30 render_user_banned, json_response)
31 from mediagoblin.tools.translate import pass_to_ugettext as _
32
33 from mediagoblin.oauth.tools.request import decode_authorization_header
34 from mediagoblin.oauth.oauth import GMGRequestValidator
35
36
37 def 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
52 def require_active_login(controller):
53 """
54 Require an active login from the user. If the user is banned, redirects to
55 the "You are Banned" page.
56 """
57 @wraps(controller)
58 @user_not_banned
59 def new_controller_func(request, *args, **kwargs):
60 if request.user and \
61 not request.user.has_privilege(u'active'):
62 return redirect(
63 request, 'mediagoblin.user_pages.user_home',
64 user=request.user.username)
65 elif not request.user or not request.user.has_privilege(u'active'):
66 next_url = urljoin(
67 request.urlgen('mediagoblin.auth.login',
68 qualified=True),
69 request.url)
70
71 return redirect(request, 'mediagoblin.auth.login',
72 next=next_url)
73
74 return controller(request, *args, **kwargs)
75
76 return new_controller_func
77
78
79 def user_has_privilege(privilege_name, allow_admin=True):
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
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.
94 """
95
96 def user_has_privilege_decorator(controller):
97 @wraps(controller)
98 @require_active_login
99 def wrapper(request, *args, **kwargs):
100 if not request.user.has_privilege(privilege_name, allow_admin):
101 raise Forbidden()
102
103 return controller(request, *args, **kwargs)
104
105 return wrapper
106 return user_has_privilege_decorator
107
108
109 def active_user_from_url(controller):
110 """Retrieve LocalUser() from <user> URL pattern and pass in as url_user=...
111
112 Returns a 404 if no such active user has been found"""
113 @wraps(controller)
114 def wrapper(request, *args, **kwargs):
115 user = LocalUser.query.filter_by(username=request.matchdict['user']).first()
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
124 def user_may_delete_media(controller):
125 """
126 Require user ownership of the MediaEntry to delete.
127 """
128 @wraps(controller)
129 def wrapper(request, *args, **kwargs):
130 uploader_id = kwargs['media'].actor
131 if not (request.user.has_privilege(u'admin') or
132 request.user.id == uploader_id):
133 raise Forbidden()
134
135 return controller(request, *args, **kwargs)
136
137 return wrapper
138
139
140 def 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):
146 creator_id = request.db.LocalUser.query.filter_by(
147 username=request.matchdict['user']).first().id
148 if not (request.user.has_privilege(u'admin') or
149 request.user.id == creator_id):
150 raise Forbidden()
151
152 return controller(request, *args, **kwargs)
153
154 return wrapper
155
156
157 def uses_pagination(controller):
158 """
159 Check request GET 'page' key for wrong values
160 """
161 @wraps(controller)
162 def wrapper(request, *args, **kwargs):
163 try:
164 page = int(request.GET.get('page', 1))
165 if page < 0:
166 return render_404(request)
167 except ValueError:
168 return render_404(request)
169
170 return controller(request, page=page, *args, **kwargs)
171
172 return wrapper
173
174
175 def get_user_media_entry(controller):
176 """
177 Pass in a MediaEntry based off of a url component
178 """
179 @wraps(controller)
180 def wrapper(request, *args, **kwargs):
181 user = LocalUser.query.filter_by(username=request.matchdict['user']).first()
182 if not user:
183 raise NotFound()
184
185 media = None
186
187 # might not be a slug, might be an id, but whatever
188 media_slug = request.matchdict['media']
189
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',
196 actor=user.id).first()
197 except ValueError:
198 raise NotFound()
199 else:
200 # no magical id: stuff? It's a slug!
201 media = MediaEntry.query.filter_by(
202 slug=media_slug,
203 state=u'processed',
204 actor=user.id).first()
205
206 if not media:
207 # Didn't find anything? Okay, 404.
208 raise NotFound()
209
210 return controller(request, media=media, *args, **kwargs)
211
212 return wrapper
213
214
215 def 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):
221 user = request.db.LocalUser.query.filter_by(
222 username=request.matchdict['user']).first()
223
224 if not user:
225 return render_404(request)
226
227 collection = request.db.Collection.query.filter_by(
228 slug=request.matchdict['collection'],
229 actor=user.id).first()
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
240 def 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):
246 user = request.db.LocalUser.query.filter_by(
247 username=request.matchdict['user']).first()
248
249 if not user:
250 return render_404(request)
251
252 collection_item = request.db.CollectionItem.query.filter_by(
253 id=request.matchdict['collection_item']).first()
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
264 def get_media_entry_by_id(controller):
265 """
266 Pass in a MediaEntry based off of a url component
267 """
268 @wraps(controller)
269 def wrapper(request, *args, **kwargs):
270 media = MediaEntry.query.filter_by(
271 id=request.matchdict['media_id'],
272 state=u'processed').first()
273 # Still no media? Okay, 404.
274 if not media:
275 return render_404(request)
276
277 given_username = request.matchdict.get('user')
278 if given_username and (given_username != media.get_actor.username):
279 return render_404(request)
280
281 return controller(request, media=media, *args, **kwargs)
282
283 return wrapper
284
285
286 def 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):
291 with mgg.workbench_manager.create() as workbench:
292 return func(*args, workbench=workbench, **kwargs)
293
294 return new_func
295
296
297 def 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
312 def 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
327 def get_optional_media_comment_by_id(controller):
328 """
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.
331
332 :returns The view function being wrapped with kwarg `comment` set to
333 the Comment who's id is in the URL. If there is a
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.
339 """
340 @wraps(controller)
341 def wrapper(request, *args, **kwargs):
342 if 'comment' in request.matchdict:
343 comment = Comment.query.filter_by(
344 id=request.matchdict['comment']
345 ).first()
346
347 if comment is None:
348 return render_404(request)
349
350 return controller(request, comment=comment, *args, **kwargs)
351 else:
352 return controller(request, comment=None, *args, **kwargs)
353 return wrapper
354
355
356 def 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
367 return controller(request, *args, **kwargs)
368
369 return wrapper
370
371 def require_admin_or_moderator_login(controller):
372 """
373 Require a login from an administrator or a moderator.
374 """
375 @wraps(controller)
376 def new_controller_func(request, *args, **kwargs):
377 if request.user and \
378 not (request.user.has_privilege(u'admin')
379 or request.user.has_privilege(u'moderator')):
380
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
395
396
397 def oauth_required(controller):
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
408
409 request_validator = GMGRequestValidator()
410 resource_endpoint = ResourceEndpoint(request_validator)
411 valid, r = resource_endpoint.validate_protected_resource_request(
412 uri=request.url,
413 http_method=request.method,
414 body=request.data,
415 headers=dict(request.headers),
416 )
417
418 if not valid:
419 error = "Invalid oauth parameter."
420 return json_response({"error": error}, status=400)
421
422 # Fill user if not already
423 token = authorization[u"oauth_token"]
424 request.access_token = AccessToken.query.filter_by(token=token).first()
425 if request.access_token is not None and request.user is None:
426 user_id = request.access_token.actor
427 request.user = LocalUser.query.filter_by(id=user_id).first()
428
429 return controller(request, *args, **kwargs)
430
431 return wrapper