X-Git-Url: https://vcs.fsf.org/?a=blobdiff_plain;f=mediagoblin%2Fedit%2Fviews.py;h=25a617fd3fdfab3dc537442661a54286ae96e05d;hb=066d49b2c1c8319a5b7805b4a68a8ee2b1c59ad1;hp=60a4cb5b1d13190e373ff0ca81c9854b19f900a1;hpb=a855e92a985a9bdc9c5062cad268eba3d8e19f84;p=mediagoblin.git diff --git a/mediagoblin/edit/views.py b/mediagoblin/edit/views.py index 60a4cb5b..25a617fd 100644 --- a/mediagoblin/edit/views.py +++ b/mediagoblin/edit/views.py @@ -14,10 +14,10 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . -from webob import exc from cgi import FieldStorage from datetime import datetime +from werkzeug.exceptions import Forbidden from werkzeug.utils import secure_filename from mediagoblin import messages @@ -26,19 +26,24 @@ from mediagoblin import mg_globals from mediagoblin.auth import lib as auth_lib from mediagoblin.edit import forms from mediagoblin.edit.lib import may_edit_media -from mediagoblin.decorators import require_active_login, get_user_media_entry +from mediagoblin.decorators import (require_active_login, active_user_from_url, + get_media_entry_by_id, + get_user_media_entry, user_may_alter_collection, get_user_collection) from mediagoblin.tools.response import render_to_response, redirect from mediagoblin.tools.translate import pass_to_ugettext as _ from mediagoblin.tools.text import ( convert_to_tag_list_of_dicts, media_tags_as_string) -from mediagoblin.db.util import check_media_slug_used +from mediagoblin.tools.url import slugify +from mediagoblin.db.util import check_media_slug_used, check_collection_slug_used +import mimetypes -@get_user_media_entry + +@get_media_entry_by_id @require_active_login def edit_media(request, media): if not may_edit_media(request, media): - return exc.HTTPForbidden() + raise Forbidden("User may not edit this media") defaults = dict( title=media.title, @@ -48,35 +53,33 @@ def edit_media(request, media): license=media.license) form = forms.EditForm( - request.POST, + request.form, **defaults) if request.method == 'POST' and form.validate(): # Make sure there isn't already a MediaEntry with such a slug # and userid. - slug_used = check_media_slug_used(request.db, media.uploader, - request.POST['slug'], media.id) + slug = slugify(request.form['slug']) + slug_used = check_media_slug_used(media.uploader, slug, media.id) if slug_used: form.slug.errors.append( _(u'An entry with that slug already exists for this user.')) else: - media.title = unicode(request.POST['title']) - media.description = unicode(request.POST.get('description')) + media.title = request.form['title'] + media.description = request.form.get('description') media.tags = convert_to_tag_list_of_dicts( - request.POST.get('tags')) - - media.license = unicode(request.POST.get('license', '')) or None - - media.slug = unicode(request.POST['slug']) + request.form.get('tags')) + media.license = unicode(request.form.get('license', '')) or None + media.slug = slug media.save() - return exc.HTTPFound( - location=media.url_for_self(request.urlgen)) + return redirect(request, + location=media.url_for_self(request.urlgen)) if request.user.is_admin \ - and media.uploader != request.user._id \ + and media.uploader != request.user.id \ and request.method != 'POST': messages.add_message( request, messages.WARNING, @@ -89,6 +92,13 @@ def edit_media(request, media): 'form': form}) +# Mimetypes that browsers parse scripts in. +# Content-sniffing isn't taken into consideration. +UNSAFE_MIMETYPES = [ + 'text/html', + 'text/svg+xml'] + + @get_user_media_entry @require_active_login def edit_attachments(request, media): @@ -96,27 +106,45 @@ def edit_attachments(request, media): form = forms.EditAttachmentsForm() # Add any attachements - if ('attachment_file' in request.POST - and isinstance(request.POST['attachment_file'], FieldStorage) - and request.POST['attachment_file'].file): + if 'attachment_file' in request.files \ + and request.files['attachment_file']: + + # Security measure to prevent attachments from being served as + # text/html, which will be parsed by web clients and pose an XSS + # threat. + # + # TODO + # This method isn't flawless as some browsers may perform + # content-sniffing. + # This method isn't flawless as we do the mimetype lookup on the + # machine parsing the upload form, and not necessarily the machine + # serving the attachments. + if mimetypes.guess_type( + request.files['attachment_file'].filename)[0] in \ + UNSAFE_MIMETYPES: + public_filename = secure_filename('{0}.notsafe'.format( + request.files['attachment_file'].filename)) + else: + public_filename = secure_filename( + request.files['attachment_file'].filename) attachment_public_filepath \ = mg_globals.public_store.get_unique_filepath( - ['media_entries', unicode(media._id), 'attachment', - secure_filename(request.POST['attachment_file'].filename)]) + ['media_entries', unicode(media.id), 'attachment', + public_filename]) attachment_public_file = mg_globals.public_store.get_file( attachment_public_filepath, 'wb') try: attachment_public_file.write( - request.POST['attachment_file'].file.read()) + request.files['attachment_file'].stream.read()) finally: - request.POST['attachment_file'].file.close() + request.files['attachment_file'].stream.close() media.attachment_files.append(dict( - name=request.POST['attachment_name'] \ - or request.POST['attachment_file'].filename, + name=request.form['attachment_name'] \ + or request.files['attachment_file'].filename, filepath=attachment_public_filepath, created=datetime.utcnow(), )) @@ -125,42 +153,50 @@ def edit_attachments(request, media): messages.add_message( request, messages.SUCCESS, - "You added the attachment %s!" \ - % (request.POST['attachment_name'] - or request.POST['attachment_file'].filename)) + _("You added the attachment %s!") \ + % (request.form['attachment_name'] + or request.files['attachment_file'].filename)) - return exc.HTTPFound( - location=media.url_for_self(request.urlgen)) + return redirect(request, + location=media.url_for_self(request.urlgen)) return render_to_response( request, 'mediagoblin/edit/attachments.html', {'media': media, 'form': form}) else: - return exc.HTTPForbidden() + raise Forbidden("Attachments are disabled") + +@require_active_login +def legacy_edit_profile(request): + """redirect the old /edit/profile/?username=USER to /u/USER/edit/""" + username = request.GET.get('username') or request.user.username + return redirect(request, 'mediagoblin.edit.profile', user=username) @require_active_login -def edit_profile(request): - # admins may edit any user profile given a username in the querystring - edit_username = request.GET.get('username') - if request.user.is_admin and request.user.username != edit_username: - user = request.db.User.find_one({'username': edit_username}) +@active_user_from_url +def edit_profile(request, url_user=None): + # admins may edit any user profile + if request.user.username != url_user.username: + if not request.user.is_admin: + raise Forbidden(_("You can only edit your own profile.")) + # No need to warn again if admin just submitted an edited profile if request.method != 'POST': messages.add_message( request, messages.WARNING, _("You are editing a user's profile. Proceed with caution.")) - else: - user = request.user - form = forms.EditProfileForm(request.POST, - url=user.get('url'), - bio=user.get('bio')) + user = url_user + + form = forms.EditProfileForm(request.form, + url=user.url, + bio=user.bio) if request.method == 'POST' and form.validate(): - user.url = unicode(request.POST['url']) - user.bio = unicode(request.POST['bio']) + user.url = unicode(request.form['url']) + user.bio = unicode(request.form['bio']) user.save() @@ -181,39 +217,134 @@ def edit_profile(request): @require_active_login def edit_account(request): user = request.user + form = forms.EditAccountForm(request.form, + wants_comment_notification=user.wants_comment_notification, + license_preference=user.license_preference) + + if request.method == 'POST': + form_validated = form.validate() + + if form_validated and \ + form.wants_comment_notification.validate(form): + user.wants_comment_notification = \ + form.wants_comment_notification.data + + if form_validated and \ + form.new_password.data or form.old_password.data: + password_matches = auth_lib.bcrypt_check_password( + form.old_password.data, + user.pw_hash) + if password_matches: + #the entire form validates and the password matches + user.pw_hash = auth_lib.bcrypt_gen_password_hash( + form.new_password.data) + else: + form.old_password.errors.append(_('Wrong password')) + + if form_validated and \ + form.license_preference.validate(form): + user.license_preference = \ + form.license_preference.data + + if form_validated and not form.errors: + user.save() + messages.add_message(request, + messages.SUCCESS, + _("Account settings saved")) + return redirect(request, + 'mediagoblin.user_pages.user_home', + user=user.username) + + return render_to_response( + request, + 'mediagoblin/edit/edit_account.html', + {'user': user, + 'form': form}) + - form = forms.EditAccountForm(request.POST) +@require_active_login +def delete_account(request): + """Delete a user completely""" + user = request.user + if request.method == 'POST': + if request.form.get(u'confirmed'): + # Form submitted and confirmed. Actually delete the user account + # Log out user and delete cookies etc. + # TODO: Should we be using MG.auth.views.py:logout for this? + request.session.delete() + + # Delete user account and all related media files etc.... + request.user.delete() + + # We should send a message that the user has been deleted + # successfully. But we just deleted the session, so we + # can't... + return redirect(request, 'index') + + else: # Did not check the confirmation box... + messages.add_message( + request, messages.WARNING, + _('You need to confirm the deletion of your account.')) + + # No POST submission or not confirmed, just show page + return render_to_response( + request, + 'mediagoblin/edit/delete_account.html', + {'user': user}) + + +@require_active_login +@user_may_alter_collection +@get_user_collection +def edit_collection(request, collection): + defaults = dict( + title=collection.title, + slug=collection.slug, + description=collection.description) + + form = forms.EditCollectionForm( + request.form, + **defaults) if request.method == 'POST' and form.validate(): - password_matches = auth_lib.bcrypt_check_password( - request.POST['old_password'], - user.pw_hash) + # Make sure there isn't already a Collection with such a slug + # and userid. + slug_used = check_collection_slug_used(request.db, collection.creator, + request.form['slug'], collection.id) - if (request.POST['old_password'] or request.POST['new_password']) and not \ - password_matches: - form.old_password.errors.append(_('Wrong password')) + # Make sure there isn't already a Collection with this title + existing_collection = request.db.Collection.find_one({ + 'creator': request.user.id, + 'title':request.form['title']}) - return render_to_response( - request, - 'mediagoblin/edit/edit_account.html', - {'user': user, - 'form': form}) + if existing_collection and existing_collection.id != collection.id: + messages.add_message( + request, messages.ERROR, + _('You already have a collection called "%s"!') % \ + request.form['title']) + elif slug_used: + form.slug.errors.append( + _(u'A collection with that slug already exists for this user.')) + else: + collection.title = unicode(request.form['title']) + collection.description = unicode(request.form.get('description')) + collection.slug = unicode(request.form['slug']) - if password_matches: - user.pw_hash = auth_lib.bcrypt_gen_password_hash( - request.POST['new_password']) + collection.save() - user.save() + return redirect(request, "mediagoblin.user_pages.user_collection", + user=collection.get_creator.username, + collection=collection.slug) - messages.add_message(request, - messages.SUCCESS, - _("Account settings saved")) - return redirect(request, - 'mediagoblin.user_pages.user_home', - user=user.username) + if request.user.is_admin \ + and collection.creator != request.user.id \ + and request.method != 'POST': + messages.add_message( + request, messages.WARNING, + _("You are editing another user's collection. Proceed with caution.")) return render_to_response( request, - 'mediagoblin/edit/edit_account.html', - {'user': user, + 'mediagoblin/edit/edit_collection.html', + {'collection': collection, 'form': form})