This commit was solely to remove unused imports in the code that I have written
[mediagoblin: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 urlparse import urljoin
20 from werkzeug.exceptions import Forbidden, NotFound
21 from oauthlib.oauth1 import ResourceEndpoint
22
23 from mediagoblin import mg_globals as mgg
24 from mediagoblin import messages
25 from mediagoblin.db.models import MediaEntry, User, MediaComment
26 from mediagoblin.tools.response import (redirect, render_404,
27                                                                 render_user_banned, json_response)
28 from mediagoblin.tools.translate import pass_to_ugettext as _
29
30 from mediagoblin.oauth.tools.request import decode_authorization_header
31 from mediagoblin.oauth.oauth import GMGRequestValidator
32
33
34 def 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
49 def require_active_login(controller):
50     """
51     Require an active login from the user. If the user is banned, redirects to
52     the "You are Banned" page.
53     """
54     @wraps(controller)
55     @user_not_banned
56     def new_controller_func(request, *args, **kwargs):
57         if request.user and \
58                 not request.user.has_privilege(u'active'):
59             return redirect(
60                 request, 'mediagoblin.user_pages.user_home',
61                 user=request.user.username)
62         elif not request.user or not request.user.has_privilege(u'active'):
63             next_url = urljoin(
64                     request.urlgen('mediagoblin.auth.login',
65                         qualified=True),
66                     request.url)
67
68             return redirect(request, 'mediagoblin.auth.login',
69                             next=next_url)
70
71         return controller(request, *args, **kwargs)
72
73     return new_controller_func
74
75
76 def user_has_privilege(privilege_name):
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     """
88
89     def user_has_privilege_decorator(controller):
90         @wraps(controller)
91         @require_active_login
92         def wrapper(request, *args, **kwargs):
93             user_id = request.user.id
94             if not request.user.has_privilege(privilege_name):
95                 raise Forbidden()
96
97             return controller(request, *args, **kwargs)
98
99         return wrapper
100     return user_has_privilege_decorator
101
102
103 def 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
118 def user_may_delete_media(controller):
119     """
120     Require user ownership of the MediaEntry to delete.
121     """
122     @wraps(controller)
123     def wrapper(request, *args, **kwargs):
124         uploader_id = kwargs['media'].uploader
125         if not (request.user.has_privilege(u'admin') or
126                 request.user.id == uploader_id):
127             raise Forbidden()
128
129         return controller(request, *args, **kwargs)
130
131     return wrapper
132
133
134 def 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):
140         creator_id = request.db.User.query.filter_by(
141             username=request.matchdict['user']).first().id
142         if not (request.user.has_privilege(u'admin') or
143                 request.user.id == creator_id):
144             raise Forbidden()
145
146         return controller(request, *args, **kwargs)
147
148     return wrapper
149
150
151 def uses_pagination(controller):
152     """
153     Check request GET 'page' key for wrong values
154     """
155     @wraps(controller)
156     def wrapper(request, *args, **kwargs):
157         try:
158             page = int(request.GET.get('page', 1))
159             if page < 0:
160                 return render_404(request)
161         except ValueError:
162             return render_404(request)
163
164         return controller(request, page=page, *args, **kwargs)
165
166     return wrapper
167
168
169 def get_user_media_entry(controller):
170     """
171     Pass in a MediaEntry based off of a url component
172     """
173     @wraps(controller)
174     def wrapper(request, *args, **kwargs):
175         user = User.query.filter_by(username=request.matchdict['user']).first()
176         if not user:
177             raise NotFound()
178
179         media = None
180
181         # might not be a slug, might be an id, but whatever
182         media_slug = request.matchdict['media']
183
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()
193         else:
194             # no magical id: stuff?  It's a slug!
195             media = MediaEntry.query.filter_by(
196                 slug=media_slug,
197                 state=u'processed',
198                 uploader=user.id).first()
199
200         if not media:
201             # Didn't find anything?  Okay, 404.
202             raise NotFound()
203
204         return controller(request, media=media, *args, **kwargs)
205
206     return wrapper
207
208
209 def 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):
215         user = request.db.User.query.filter_by(
216             username=request.matchdict['user']).first()
217
218         if not user:
219             return render_404(request)
220
221         collection = request.db.Collection.query.filter_by(
222             slug=request.matchdict['collection'],
223             creator=user.id).first()
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
234 def 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):
240         user = request.db.User.query.filter_by(
241             username=request.matchdict['user']).first()
242
243         if not user:
244             return render_404(request)
245
246         collection_item = request.db.CollectionItem.query.filter_by(
247             id=request.matchdict['collection_item']).first()
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
258 def get_media_entry_by_id(controller):
259     """
260     Pass in a MediaEntry based off of a url component
261     """
262     @wraps(controller)
263     def wrapper(request, *args, **kwargs):
264         media = MediaEntry.query.filter_by(
265                 id=request.matchdict['media_id'],
266                 state=u'processed').first()
267         # Still no media?  Okay, 404.
268         if not media:
269             return render_404(request)
270
271         given_username = request.matchdict.get('user')
272         if given_username and (given_username != media.get_uploader.username):
273             return render_404(request)
274
275         return controller(request, media=media, *args, **kwargs)
276
277     return wrapper
278
279
280 def 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):
285         with mgg.workbench_manager.create() as workbench:
286             return func(*args, workbench=workbench, **kwargs)
287
288     return new_func
289
290
291 def 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
306 def 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
321 def get_optional_media_comment_by_id(controller):
322     """
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.
333     """
334     @wraps(controller)
335     def wrapper(request, *args, **kwargs):
336         if 'comment' in request.matchdict:
337             comment = MediaComment.query.filter_by(
338                     id=request.matchdict['comment']).first()
339
340             if comment is None:
341                 return render_404(request)
342
343             return controller(request, comment=comment, *args, **kwargs)
344         else:
345             return controller(request, comment=None, *args, **kwargs)
346     return wrapper
347
348
349 def 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
360         return controller(request, *args, **kwargs)
361
362     return wrapper
363
364 def require_admin_or_moderator_login(controller):
365     """
366     Require a login from an administrator or a moderator.
367     """
368     @wraps(controller)
369     def new_controller_func(request, *args, **kwargs):
370         if request.user and \
371             not request.user.has_privilege(u'admin',u'moderator'):
372
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
387
388
389 def oauth_required(controller):
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
400
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                 )
409
410         if not valid:
411             error = "Invalid oauth prarameter."
412             return json_response({"error": error}, status=400)
413
414         return controller(request, *args, **kwargs)
415
416     return wrapper