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