aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--docs/source/siteadmin/deploying.rst5
-rw-r--r--docs/source/siteadmin/relnotes.rst22
-rw-r--r--mediagoblin.ini7
-rw-r--r--mediagoblin/_version.py2
-rw-r--r--mediagoblin/app.py2
-rw-r--r--mediagoblin/auth/tools.py9
-rw-r--r--mediagoblin/auth/views.py24
-rw-r--r--mediagoblin/config_spec.ini4
-rw-r--r--mediagoblin/db/base.py12
-rw-r--r--mediagoblin/db/migrations.py2
-rw-r--r--mediagoblin/db/util.py2
-rw-r--r--mediagoblin/decorators.py57
-rw-r--r--mediagoblin/edit/forms.py1
-rw-r--r--mediagoblin/edit/views.py64
-rw-r--r--mediagoblin/gmg_commands/dbupdate.py18
-rw-r--r--mediagoblin/gmg_commands/import_export.py4
-rw-r--r--mediagoblin/gmg_commands/users.py12
-rw-r--r--mediagoblin/init/__init__.py14
-rw-r--r--mediagoblin/listings/views.py17
-rw-r--r--mediagoblin/meddleware/csrf.py2
-rw-r--r--mediagoblin/media_types/pdf/processing.py4
-rw-r--r--mediagoblin/plugins/basic_auth/__init__.py21
-rw-r--r--mediagoblin/plugins/basic_auth/forms.py3
-rw-r--r--mediagoblin/plugins/openid/__init__.py123
-rw-r--r--mediagoblin/plugins/openid/forms.py41
-rw-r--r--mediagoblin/plugins/openid/models.py65
-rw-r--r--mediagoblin/plugins/openid/store.py127
-rw-r--r--mediagoblin/plugins/openid/templates/mediagoblin/plugins/openid/add.html44
-rw-r--r--mediagoblin/plugins/openid/templates/mediagoblin/plugins/openid/delete.html43
-rw-r--r--mediagoblin/plugins/openid/templates/mediagoblin/plugins/openid/edit_link.html25
-rw-r--r--mediagoblin/plugins/openid/templates/mediagoblin/plugins/openid/login.html65
-rw-r--r--mediagoblin/plugins/openid/templates/mediagoblin/plugins/openid/login_link.html25
-rw-r--r--mediagoblin/plugins/openid/templates/mediagoblin/plugins/openid/register_link.html27
-rw-r--r--mediagoblin/plugins/openid/templates/mediagoblin/plugins/openid/request_form.html24
-rw-r--r--mediagoblin/plugins/openid/views.py404
-rw-r--r--mediagoblin/static/css/base.css4
-rw-r--r--mediagoblin/submit/views.py13
-rw-r--r--mediagoblin/templates/mediagoblin/auth/login.html14
-rw-r--r--mediagoblin/templates/mediagoblin/auth/register.html3
-rw-r--r--mediagoblin/templates/mediagoblin/edit/change_pass.html2
-rw-r--r--mediagoblin/templates/mediagoblin/edit/edit_account.html5
-rw-r--r--mediagoblin/templates/mediagoblin/media_displays/stl.html16
-rw-r--r--mediagoblin/templates/mediagoblin/utils/comment-subscription.html4
-rw-r--r--mediagoblin/templates/mediagoblin/utils/wtforms.html52
-rw-r--r--mediagoblin/tests/appconfig_context_modified.ini5
-rw-r--r--mediagoblin/tests/appconfig_static_plugin.ini5
-rw-r--r--mediagoblin/tests/auth_configs/openid_appconfig.ini41
-rw-r--r--mediagoblin/tests/test_auth.py14
-rw-r--r--mediagoblin/tests/test_edit.py4
-rw-r--r--mediagoblin/tests/test_mgoblin_app.ini6
-rw-r--r--mediagoblin/tests/test_openid.py372
-rw-r--r--mediagoblin/tests/test_submission.py10
-rw-r--r--mediagoblin/tests/tools.py2
-rw-r--r--mediagoblin/tools/session.py9
-rw-r--r--setup.py2
55 files changed, 1722 insertions, 182 deletions
diff --git a/docs/source/siteadmin/deploying.rst b/docs/source/siteadmin/deploying.rst
index 0ee6b5b4..9d7f83c1 100644
--- a/docs/source/siteadmin/deploying.rst
+++ b/docs/source/siteadmin/deploying.rst
@@ -157,9 +157,10 @@ directory. Modify these commands to reflect your own environment::
mkdir -p /srv/mediagoblin.example.org/
cd /srv/mediagoblin.example.org/
-Clone the MediaGoblin repository::
+Clone the MediaGoblin repository and set up the git submodules::
git clone git://gitorious.org/mediagoblin/mediagoblin.git
+ git submodule init && git submodule fetch
And set up the in-package virtualenv::
@@ -194,7 +195,7 @@ This concludes the initial configuration of the development
environment. In the future, when you update your
codebase, you should also run::
- ./bin/python setup.py develop --upgrade && ./bin/gmg dbupdate
+ ./bin/python setup.py develop --upgrade && ./bin/gmg dbupdate && git submodule fetch
Note: If you are running an active site, depending on your server
configuration, you may need to stop it first or the dbupdate command
diff --git a/docs/source/siteadmin/relnotes.rst b/docs/source/siteadmin/relnotes.rst
index 7b6d8353..8e7ba396 100644
--- a/docs/source/siteadmin/relnotes.rst
+++ b/docs/source/siteadmin/relnotes.rst
@@ -21,11 +21,28 @@ This chapter has important information for releases in it.
If you're upgrading from a previous release, please read it
carefully, or at least skim over it.
+
+0.4.1
+=====
+
+This is a bugfix release for 0.4.0. This only implements one major
+fix in the newly released document support which prevented the
+"conversion via libreoffice" feature.
+
+If you were running 0.4.0 you can upgrade to v0.4.1 via a simple
+switch and restarting mediagoblin/celery with no other actions.
+
+Otherwise, follow 0.4.0 instructions.
+
+
0.4.0
=====
**Do this to upgrade**
-1. Make sure to run ``bin/gmg dbupdate`` after upgrading.
+
+1. Make sure to run
+ ``./bin/python setup.py develop --upgrade && ./bin/gmg dbupdate``
+ after upgrading.
2. See "For Theme authors" if you have a custom theme.
3. Note that ``./bin/gmg theme assetlink`` is now just
``./bin/gmg assetlink`` and covers both plugins and assets.
@@ -45,6 +62,9 @@ carefully, or at least skim over it.
5. We now use itsdangerous for sessions; if you had any references to
beaker in your paste config you can remove them. Again, see the
default paste.ini config
+6. We also now use git submodules. Please do:
+ ``git submodule init && git submodule fetch``
+ You will need to do this to use the new PDF support.
**For theme authors**
diff --git a/mediagoblin.ini b/mediagoblin.ini
index 035e7bcf..30dacadf 100644
--- a/mediagoblin.ini
+++ b/mediagoblin.ini
@@ -11,9 +11,10 @@ email_sender_address = "notice@mediagoblin.example.org"
## Uncomment and change to your DB's appropiate setting.
## Default is a local sqlite db "mediagoblin.db".
+## Don't forget to run `./bin/gmg dbupdate` after having changed it.
# sql_engine = postgresql:///mediagoblin
-# set to false to enable sending notices
+# Set to false to enable sending notices
email_debug_mode = true
# Set to false to disable registrations
@@ -38,8 +39,8 @@ base_url = /mgoblin_media/
[celery]
# Put celery stuff here
-# place plugins here---each in their own subsection of [plugins]. see
-# documentation for details.
+# Place plugins here, each in their own subsection of [plugins].
+# See http://docs.mediagoblin.org/siteadmin/plugins.html for details.
[plugins]
[[mediagoblin.plugins.geolocation]]
[[mediagoblin.plugins.basic_auth]]
diff --git a/mediagoblin/_version.py b/mediagoblin/_version.py
index 2abc105f..94629775 100644
--- a/mediagoblin/_version.py
+++ b/mediagoblin/_version.py
@@ -23,4 +23,4 @@
# see http://www.python.org/dev/peps/pep-0386/
-__version__ = "0.4.1.dev"
+__version__ = "0.5.0.dev"
diff --git a/mediagoblin/app.py b/mediagoblin/app.py
index 11464a1f..57e09e49 100644
--- a/mediagoblin/app.py
+++ b/mediagoblin/app.py
@@ -90,7 +90,7 @@ class MediaGoblinApp(object):
setup_plugins()
# Set up the database
- self.db = setup_database()
+ self.db = setup_database(app_config['run_migrations'])
# Register themes
self.theme_registry, self.current_theme = register_themes(app_config)
diff --git a/mediagoblin/auth/tools.py b/mediagoblin/auth/tools.py
index f3f92414..579775ff 100644
--- a/mediagoblin/auth/tools.py
+++ b/mediagoblin/auth/tools.py
@@ -116,6 +116,7 @@ def send_fp_verification_email(user, request):
"""
fp_verification_key = get_timed_signer_url('mail_verification_token') \
.dumps(user.id)
+
rendered_email = render_template(
request, 'mediagoblin/auth/fp_verification_email.txt',
{'username': user.username,
@@ -199,3 +200,11 @@ def no_auth_logout(request):
if not mg_globals.app.auth and 'user_id' in request.session:
del request.session['user_id']
request.session.save()
+
+
+def create_basic_user(form):
+ user = User()
+ user.username = form.username.data
+ user.email = form.email.data
+ user.save()
+ return user
diff --git a/mediagoblin/auth/views.py b/mediagoblin/auth/views.py
index 34500f91..d54762b0 100644
--- a/mediagoblin/auth/views.py
+++ b/mediagoblin/auth/views.py
@@ -14,12 +14,12 @@
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
-import uuid
from itsdangerous import BadSignature
from mediagoblin import messages, mg_globals
from mediagoblin.db.models import User
from mediagoblin.tools.crypto import get_timed_signer_url
+from mediagoblin.decorators import auth_enabled, allow_registration
from mediagoblin.tools.response import render_to_response, redirect, render_404
from mediagoblin.tools.translate import pass_to_ugettext as _
from mediagoblin.tools.mail import email_debug_message
@@ -31,21 +31,14 @@ from mediagoblin.auth.tools import (send_verification_email, register_user,
from mediagoblin import auth
+@allow_registration
+@auth_enabled
def register(request):
"""The registration view.
Note that usernames will always be lowercased. Email domains are lowercased while
the first part remains case-sensitive.
"""
- # Redirects to indexpage if registrations are disabled or no authentication
- # is enabled
- if not mg_globals.app_config["allow_registration"] or not mg_globals.app.auth:
- messages.add_message(
- request,
- messages.WARNING,
- _('Sorry, registration is disabled on this instance.'))
- return redirect(request, "index")
-
if 'pass_auth' not in request.template_env.globals:
redirect_name = hook_handle('auth_no_pass_redirect')
return redirect(request, 'mediagoblin.plugins.{0}.register'.format(
@@ -71,20 +64,13 @@ def register(request):
'post_url': request.urlgen('mediagoblin.auth.register')})
+@auth_enabled
def login(request):
"""
MediaGoblin login view.
If you provide the POST with 'next', it'll redirect to that view.
"""
- # Redirects to index page if no authentication is enabled
- if not mg_globals.app.auth:
- messages.add_message(
- request,
- messages.WARNING,
- _('Sorry, authentication is disabled on this instance.'))
- return redirect(request, 'index')
-
if 'pass_auth' not in request.template_env.globals:
redirect_name = hook_handle('auth_no_pass_redirect')
return redirect(request, 'mediagoblin.plugins.{0}.login'.format(
@@ -102,6 +88,8 @@ def login(request):
if user:
# set up login in session
+ if login_form.stay_logged_in.data:
+ request.session['stay_logged_in'] = True
request.session['user_id'] = unicode(user.id)
request.session.save()
diff --git a/mediagoblin/config_spec.ini b/mediagoblin/config_spec.ini
index 93643ee1..d2ada163 100644
--- a/mediagoblin/config_spec.ini
+++ b/mediagoblin/config_spec.ini
@@ -8,6 +8,10 @@ source_link = string(default="https://gitorious.org/mediagoblin/mediagoblin")
# database stuff
sql_engine = string(default="sqlite:///%(here)s/mediagoblin.db")
+# This flag is used during testing to allow use of in-memory SQLite
+# databases. It is not recommended to be used on a running instance.
+run_migrations = boolean(default=False)
+
# Where temporary files used in processing and etc are kept
workbench_path = string(default="%(here)s/user_dev/media/workbench")
diff --git a/mediagoblin/db/base.py b/mediagoblin/db/base.py
index 699a503a..c0cefdc2 100644
--- a/mediagoblin/db/base.py
+++ b/mediagoblin/db/base.py
@@ -24,18 +24,6 @@ Session = scoped_session(sessionmaker())
class GMGTableBase(object):
query = Session.query_property()
- @classmethod
- def find(cls, query_dict):
- return cls.query.filter_by(**query_dict)
-
- @classmethod
- def find_one(cls, query_dict):
- return cls.query.filter_by(**query_dict).first()
-
- @classmethod
- def one(cls, query_dict):
- return cls.find(query_dict).one()
-
def get(self, key):
return getattr(self, key)
diff --git a/mediagoblin/db/migrations.py b/mediagoblin/db/migrations.py
index 98e8b139..fe4ffb3e 100644
--- a/mediagoblin/db/migrations.py
+++ b/mediagoblin/db/migrations.py
@@ -307,6 +307,7 @@ def drop_token_related_User_columns(db):
db.commit()
+
class CommentSubscription_v0(declarative_base()):
__tablename__ = 'core__comment_subscriptions'
id = Column(Integer, primary_key=True)
@@ -378,4 +379,3 @@ def pw_hash_nullable(db):
constraint.create()
db.commit()
-
diff --git a/mediagoblin/db/util.py b/mediagoblin/db/util.py
index 6ffec44d..8431361a 100644
--- a/mediagoblin/db/util.py
+++ b/mediagoblin/db/util.py
@@ -24,7 +24,7 @@ from mediagoblin.db.models import MediaEntry, Tag, MediaTag, Collection
def atomic_update(table, query_dict, update_values):
- table.find(query_dict).update(update_values,
+ table.query.filter_by(**query_dict).update(update_values,
synchronize_session=False)
Session.commit()
diff --git a/mediagoblin/decorators.py b/mediagoblin/decorators.py
index f3535fcf..ca7be53c 100644
--- a/mediagoblin/decorators.py
+++ b/mediagoblin/decorators.py
@@ -18,11 +18,12 @@ from functools import wraps
from urlparse import urljoin
from werkzeug.exceptions import Forbidden, NotFound
-from werkzeug.urls import url_quote
from mediagoblin import mg_globals as mgg
+from mediagoblin import messages
from mediagoblin.db.models import MediaEntry, User
from mediagoblin.tools.response import redirect, render_404
+from mediagoblin.tools.translate import pass_to_ugettext as _
def require_active_login(controller):
@@ -86,8 +87,8 @@ def user_may_alter_collection(controller):
"""
@wraps(controller)
def wrapper(request, *args, **kwargs):
- creator_id = request.db.User.find_one(
- {'username': request.matchdict['user']}).id
+ creator_id = request.db.User.query.filter_by(
+ username=request.matchdict['user']).first().id
if not (request.user.is_admin or
request.user.id == creator_id):
raise Forbidden()
@@ -161,15 +162,15 @@ def get_user_collection(controller):
"""
@wraps(controller)
def wrapper(request, *args, **kwargs):
- user = request.db.User.find_one(
- {'username': request.matchdict['user']})
+ user = request.db.User.query.filter_by(
+ username=request.matchdict['user']).first()
if not user:
return render_404(request)
- collection = request.db.Collection.find_one(
- {'slug': request.matchdict['collection'],
- 'creator': user.id})
+ collection = request.db.Collection.query.filter_by(
+ slug=request.matchdict['collection'],
+ creator=user.id).first()
# Still no collection? Okay, 404.
if not collection:
@@ -186,14 +187,14 @@ def get_user_collection_item(controller):
"""
@wraps(controller)
def wrapper(request, *args, **kwargs):
- user = request.db.User.find_one(
- {'username': request.matchdict['user']})
+ user = request.db.User.query.filter_by(
+ username=request.matchdict['user']).first()
if not user:
return render_404(request)
- collection_item = request.db.CollectionItem.find_one(
- {'id': request.matchdict['collection_item'] })
+ collection_item = request.db.CollectionItem.query.filter_by(
+ id=request.matchdict['collection_item']).first()
# Still no collection item? Okay, 404.
if not collection_item:
@@ -235,3 +236,35 @@ def get_workbench(func):
return func(*args, workbench=workbench, **kwargs)
return new_func
+
+
+def allow_registration(controller):
+ """ Decorator for if registration is enabled"""
+ @wraps(controller)
+ def wrapper(request, *args, **kwargs):
+ if not mgg.app_config["allow_registration"]:
+ messages.add_message(
+ request,
+ messages.WARNING,
+ _('Sorry, registration is disabled on this instance.'))
+ return redirect(request, "index")
+
+ return controller(request, *args, **kwargs)
+
+ return wrapper
+
+
+def auth_enabled(controller):
+ """Decorator for if an auth plugin is enabled"""
+ @wraps(controller)
+ def wrapper(request, *args, **kwargs):
+ if not mgg.app.auth:
+ messages.add_message(
+ request,
+ messages.WARNING,
+ _('Sorry, authentication is disabled on this instance.'))
+ return redirect(request, 'index')
+
+ return controller(request, *args, **kwargs)
+
+ return wrapper
diff --git a/mediagoblin/edit/forms.py b/mediagoblin/edit/forms.py
index e0147a0c..85c243a0 100644
--- a/mediagoblin/edit/forms.py
+++ b/mediagoblin/edit/forms.py
@@ -66,7 +66,6 @@ class EditAccountForm(wtforms.Form):
[wtforms.validators.Optional(),
normalize_user_or_email_field(allow_user=False)])
wants_comment_notification = wtforms.BooleanField(
- label='',
description=_("Email me when others comment on my media"))
license_preference = wtforms.SelectField(
_('License preference'),
diff --git a/mediagoblin/edit/views.py b/mediagoblin/edit/views.py
index 25a02446..6aa2acd9 100644
--- a/mediagoblin/edit/views.py
+++ b/mediagoblin/edit/views.py
@@ -236,30 +236,7 @@ def edit_account(request):
user.license_preference = form.license_preference.data
if form.new_email.data:
- new_email = form.new_email.data
- users_with_email = User.query.filter_by(
- email=new_email).count()
- if users_with_email:
- form.new_email.errors.append(
- _('Sorry, a user with that email address'
- ' already exists.'))
- else:
- verification_key = get_timed_signer_url(
- 'mail_verification_token').dumps({
- 'user': user.id,
- 'email': new_email})
-
- rendered_email = render_template(
- request, 'mediagoblin/edit/verification.txt',
- {'username': user.username,
- 'verification_url': EMAIL_VERIFICATION_TEMPLATE.format(
- uri=request.urlgen('mediagoblin.edit.verify_email',
- qualified=True),
- verification_key=verification_key)})
-
- email_debug_message(request)
- auth_tools.send_verification_email(user, request, new_email,
- rendered_email)
+ _update_email(request, form, user)
if not form.errors:
user.save()
@@ -328,9 +305,9 @@ def edit_collection(request, collection):
form.slug.data, collection.id)
# Make sure there isn't already a Collection with this title
- existing_collection = request.db.Collection.find_one({
- 'creator': request.user.id,
- 'title':form.title.data})
+ existing_collection = request.db.Collection.query.filter_by(
+ creator=request.user.id,
+ title=form.title.data).first()
if existing_collection and existing_collection.id != collection.id:
messages.add_message(
@@ -365,6 +342,10 @@ def edit_collection(request, collection):
@require_active_login
def change_pass(request):
+ # If no password authentication, no need to change your password
+ if 'pass_auth' not in request.template_env.globals:
+ return redirect(request, 'index')
+
form = forms.ChangePassForm(request.form)
user = request.user
@@ -442,3 +423,32 @@ def verify_email(request):
return redirect(
request, 'mediagoblin.user_pages.user_home',
user=user.username)
+
+
+def _update_email(request, form, user):
+ new_email = form.new_email.data
+ users_with_email = User.query.filter_by(
+ email=new_email).count()
+
+ if users_with_email:
+ form.new_email.errors.append(
+ _('Sorry, a user with that email address'
+ ' already exists.'))
+
+ elif not users_with_email:
+ verification_key = get_timed_signer_url(
+ 'mail_verification_token').dumps({
+ 'user': user.id,
+ 'email': new_email})
+
+ rendered_email = render_template(
+ request, 'mediagoblin/edit/verification.txt',
+ {'username': user.username,
+ 'verification_url': EMAIL_VERIFICATION_TEMPLATE.format(
+ uri=request.urlgen('mediagoblin.edit.verify_email',
+ qualified=True),
+ verification_key=verification_key)})
+
+ email_debug_message(request)
+ auth_tools.send_verification_email(user, request, new_email,
+ rendered_email)
diff --git a/mediagoblin/gmg_commands/dbupdate.py b/mediagoblin/gmg_commands/dbupdate.py
index 4dfd7e92..00007567 100644
--- a/mediagoblin/gmg_commands/dbupdate.py
+++ b/mediagoblin/gmg_commands/dbupdate.py
@@ -103,13 +103,25 @@ def run_dbupdate(app_config, global_config):
in the future, plugins)
"""
+ # Set up the database
+ db = setup_connection_and_db_from_config(app_config, migrations=True)
+ #Run the migrations
+ run_all_migrations(db, app_config, global_config)
+
+
+def run_all_migrations(db, app_config, global_config):
+ """
+ Initializes or migrates a database that already has a
+ connection setup and also initializes or migrates all
+ extensions based on the config files.
+
+ It can be used to initialize an in-memory database for
+ testing.
+ """
# Gather information from all media managers / projects
dbdatas = gather_database_data(
global_config.get('plugins', {}).keys())
- # Set up the database
- db = setup_connection_and_db_from_config(app_config, migrations=True)
-
Session = sessionmaker(bind=db.engine)
# Setup media managers for all dbdata, run init/migrate and print info
diff --git a/mediagoblin/gmg_commands/import_export.py b/mediagoblin/gmg_commands/import_export.py
index d51a1e3e..98ec617d 100644
--- a/mediagoblin/gmg_commands/import_export.py
+++ b/mediagoblin/gmg_commands/import_export.py
@@ -63,7 +63,7 @@ def _import_media(db, args):
# TODO: Add import of queue files
queue_cache = BasicFileStorage(args._cache_path['queue'])
- for entry in db.MediaEntry.find():
+ for entry in db.MediaEntry.query.filter_by():
for name, path in entry.media_files.items():
_log.info('Importing: {0} - {1}'.format(
entry.title.encode('ascii', 'replace'),
@@ -204,7 +204,7 @@ def _export_media(db, args):
# TODO: Add export of queue files
queue_cache = BasicFileStorage(args._cache_path['queue'])
- for entry in db.MediaEntry.find():
+ for entry in db.MediaEntry.query.filter_by():
for name, path in entry.media_files.items():
_log.info(u'Exporting {0} - {1}'.format(
entry.title,
diff --git a/mediagoblin/gmg_commands/users.py b/mediagoblin/gmg_commands/users.py
index 1f329459..e44b0aa9 100644
--- a/mediagoblin/gmg_commands/users.py
+++ b/mediagoblin/gmg_commands/users.py
@@ -40,9 +40,9 @@ def adduser(args):
db = mg_globals.database
users_with_username = \
- db.User.find({
- 'username': args.username.lower(),
- }).count()
+ db.User.query.filter_by(
+ username=args.username.lower()
+ ).count()
if users_with_username:
print u'Sorry, a user with that name already exists.'
@@ -71,7 +71,8 @@ def makeadmin(args):
db = mg_globals.database
- user = db.User.one({'username': unicode(args.username.lower())})
+ user = db.User.query.filter_by(
+ username=unicode(args.username.lower())).one()
if user:
user.is_admin = True
user.save()
@@ -94,7 +95,8 @@ def changepw(args):
db = mg_globals.database
- user = db.User.one({'username': unicode(args.username.lower())})
+ user = db.User.query.filter_by(
+ username=unicode(args.username.lower())).one()
if user:
user.pw_hash = auth.gen_password_hash(args.password)
user.save()
diff --git a/mediagoblin/init/__init__.py b/mediagoblin/init/__init__.py
index 444c624f..e0711416 100644
--- a/mediagoblin/init/__init__.py
+++ b/mediagoblin/init/__init__.py
@@ -58,16 +58,20 @@ def setup_global_and_app_config(config_path):
return global_config, app_config
-def setup_database():
+def setup_database(run_migrations=False):
app_config = mg_globals.app_config
+ global_config = mg_globals.global_config
# Load all models for media types (plugins, ...)
load_models(app_config)
-
# Set up the database
- db = setup_connection_and_db_from_config(app_config)
-
- check_db_migrations_current(db)
+ db = setup_connection_and_db_from_config(app_config, run_migrations)
+ if run_migrations:
+ #Run the migrations to initialize/update the database.
+ from mediagoblin.gmg_commands.dbupdate import run_all_migrations
+ run_all_migrations(db, app_config, global_config)
+ else:
+ check_db_migrations_current(db)
setup_globals(database=db)
diff --git a/mediagoblin/listings/views.py b/mediagoblin/listings/views.py
index 35af7148..07dbb3d5 100644
--- a/mediagoblin/listings/views.py
+++ b/mediagoblin/listings/views.py
@@ -14,6 +14,7 @@
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
+from mediagoblin import mg_globals
from mediagoblin.db.models import MediaEntry
from mediagoblin.db.util import media_entries_for_tag_slug
from mediagoblin.tools.pagination import Pagination
@@ -80,6 +81,17 @@ def atom_feed(request):
link = request.urlgen('index', qualified=True)
feed_title += "for all recent items"
+ atomlinks = [
+ {'href': link,
+ 'rel': 'alternate',
+ 'type': 'text/html'}]
+
+ if mg_globals.app_config["push_urls"]:
+ for push_url in mg_globals.app_config["push_urls"]:
+ atomlinks.append({
+ 'rel': 'hub',
+ 'href': push_url})
+
cursor = cursor.order_by(MediaEntry.created.desc())
cursor = cursor.limit(ATOM_DEFAULT_NR_OF_UPDATED_ITEMS)
@@ -87,9 +99,8 @@ def atom_feed(request):
feed_title,
feed_url=request.url,
id=link,
- links=[{'href': link,
- 'rel': 'alternate',
- 'type': 'text/html'}])
+ links=atomlinks)
+
for entry in cursor:
feed.add(entry.get('title'),
entry.description_html,
diff --git a/mediagoblin/meddleware/csrf.py b/mediagoblin/meddleware/csrf.py
index 661f0ba2..44d42d75 100644
--- a/mediagoblin/meddleware/csrf.py
+++ b/mediagoblin/meddleware/csrf.py
@@ -111,7 +111,7 @@ class CsrfMeddleware(BaseMeddleware):
httponly=True)
# update the Vary header
- response.vary = (getattr(response, 'vary', None) or []) + ['Cookie']
+ response.vary = list(getattr(response, 'vary', None) or []) + ['Cookie']
def _make_token(self, request):
"""Generate a new token to use for CSRF protection."""
diff --git a/mediagoblin/media_types/pdf/processing.py b/mediagoblin/media_types/pdf/processing.py
index a1eeb307..f35b4376 100644
--- a/mediagoblin/media_types/pdf/processing.py
+++ b/mediagoblin/media_types/pdf/processing.py
@@ -253,8 +253,8 @@ def process_pdf(proc_state):
else:
pdf_filename = queued_filename.rsplit('.', 1)[0] + '.pdf'
unoconv = where('unoconv')
- call(executable=unoconv,
- args=[unoconv, '-v', '-f', 'pdf', queued_filename])
+ Popen(executable=unoconv,
+ args=[unoconv, '-v', '-f', 'pdf', queued_filename]).wait()
if not os.path.exists(pdf_filename):
_log.debug('unoconv failed to convert file to pdf')
raise BadMediaFail()
diff --git a/mediagoblin/plugins/basic_auth/__init__.py b/mediagoblin/plugins/basic_auth/__init__.py
index a2efae92..33a554b0 100644
--- a/mediagoblin/plugins/basic_auth/__init__.py
+++ b/mediagoblin/plugins/basic_auth/__init__.py
@@ -15,13 +15,14 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from mediagoblin.plugins.basic_auth import forms as auth_forms
from mediagoblin.plugins.basic_auth import tools as auth_tools
+from mediagoblin.auth.tools import create_basic_user
from mediagoblin.db.models import User
from mediagoblin.tools import pluginapi
from sqlalchemy import or_
def setup_plugin():
- config = pluginapi.get_config('mediagoblin.pluginapi.basic_auth')
+ config = pluginapi.get_config('mediagoblin.plugins.basic_auth')
def get_user(**kwargs):
@@ -38,9 +39,7 @@ def get_user(**kwargs):
def create_user(registration_form):
user = get_user(username=registration_form.username.data)
if not user and 'password' in registration_form:
- user = User()
- user.username = registration_form.username.data
- user.email = registration_form.email.data
+ user = create_basic_user(registration_form)
user.pw_hash = gen_password_hash(
registration_form.password.data)
user.save()
@@ -60,7 +59,10 @@ def gen_password_hash(raw_pass, extra_salt=None):
def check_password(raw_pass, stored_hash, extra_salt=None):
- return auth_tools.bcrypt_check_password(raw_pass, stored_hash, extra_salt)
+ if stored_hash:
+ return auth_tools.bcrypt_check_password(raw_pass,
+ stored_hash, extra_salt)
+ return None
def auth():
@@ -72,11 +74,6 @@ def append_to_global_context(context):
return context
-def add_to_form_context(context):
- context['pass_auth_link'] = True
- return context
-
-
hooks = {
'setup': setup_plugin,
'authentication': auth,
@@ -88,8 +85,4 @@ hooks = {
'auth_check_password': check_password,
'auth_fake_login_attempt': auth_tools.fake_login_attempt,
'template_global_context': append_to_global_context,
- ('mediagoblin.plugins.openid.register',
- 'mediagoblin/auth/register.html'): add_to_form_context,
- ('mediagoblin.plugins.openid.login',
- 'mediagoblin/auth/login.html'): add_to_form_context,
}
diff --git a/mediagoblin/plugins/basic_auth/forms.py b/mediagoblin/plugins/basic_auth/forms.py
index 72d99dff..6cf01b38 100644
--- a/mediagoblin/plugins/basic_auth/forms.py
+++ b/mediagoblin/plugins/basic_auth/forms.py
@@ -41,3 +41,6 @@ class LoginForm(wtforms.Form):
normalize_user_or_email_field()])
password = wtforms.PasswordField(
_('Password'))
+ stay_logged_in = wtforms.BooleanField(
+ label='',
+ description=_('Stay logged in'))
diff --git a/mediagoblin/plugins/openid/__init__.py b/mediagoblin/plugins/openid/__init__.py
new file mode 100644
index 00000000..ee88808c
--- /dev/null
+++ b/mediagoblin/plugins/openid/__init__.py
@@ -0,0 +1,123 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+import os
+import uuid
+
+from sqlalchemy import or_
+
+from mediagoblin.auth.tools import create_basic_user
+from mediagoblin.db.models import User
+from mediagoblin.plugins.openid.models import OpenIDUserURL
+from mediagoblin.tools import pluginapi
+from mediagoblin.tools.translate import lazy_pass_to_ugettext as _
+
+PLUGIN_DIR = os.path.dirname(__file__)
+
+
+def setup_plugin():
+ config = pluginapi.get_config('mediagoblin.plugins.openid')
+
+ routes = [
+ ('mediagoblin.plugins.openid.register',
+ '/auth/openid/register/',
+ 'mediagoblin.plugins.openid.views:register'),
+ ('mediagoblin.plugins.openid.login',
+ '/auth/openid/login/',
+ 'mediagoblin.plugins.openid.views:login'),
+ ('mediagoblin.plugins.openid.finish_login',
+ '/auth/openid/login/finish/',
+ 'mediagoblin.plugins.openid.views:finish_login'),
+ ('mediagoblin.plugins.openid.edit',
+ '/edit/openid/',
+ 'mediagoblin.plugins.openid.views:start_edit'),
+ ('mediagoblin.plugins.openid.finish_edit',
+ '/edit/openid/finish/',
+ 'mediagoblin.plugins.openid.views:finish_edit'),
+ ('mediagoblin.plugins.openid.delete',
+ '/edit/openid/delete/',
+ 'mediagoblin.plugins.openid.views:delete_openid'),
+ ('mediagoblin.plugins.openid.finish_delete',
+ '/edit/openid/delete/finish/',
+ 'mediagoblin.plugins.openid.views:finish_delete')]
+
+ pluginapi.register_routes(routes)
+ pluginapi.register_template_path(os.path.join(PLUGIN_DIR, 'templates'))
+
+ pluginapi.register_template_hooks(
+ {'register_link': 'mediagoblin/plugins/openid/register_link.html',
+ 'login_link': 'mediagoblin/plugins/openid/login_link.html',
+ 'edit_link': 'mediagoblin/plugins/openid/edit_link.html'})
+
+
+def create_user(register_form):
+ if 'openid' in register_form:
+ username = register_form.username.data
+ user = User.query.filter(
+ or_(
+ User.username == username,
+ User.email == username,
+ )).first()
+
+ if not user:
+ user = create_basic_user(register_form)
+
+ new_entry = OpenIDUserURL()
+ new_entry.openid_url = register_form.openid.data
+ new_entry.user_id = user.id
+ new_entry.save()
+
+ return user
+
+
+def extra_validation(register_form):
+ openid = register_form.openid.data if 'openid' in \
+ register_form else None
+ if openid:
+ openid_url_exists = OpenIDUserURL.query.filter_by(
+ openid_url=openid
+ ).count()
+
+ extra_validation_passes = True
+
+ if openid_url_exists:
+ register_form.openid.errors.append(
+ _('Sorry, an account is already registered to that OpenID.'))
+ extra_validation_passes = False
+
+ return extra_validation_passes
+
+
+def no_pass_redirect():
+ return 'openid'
+
+
+def add_to_form_context(context):
+ context['openid_link'] = True
+ return context
+
+
+def Auth():
+ return True
+
+hooks = {
+ 'setup': setup_plugin,
+ 'authentication': Auth,
+ 'auth_extra_validation': extra_validation,
+ 'auth_create_user': create_user,
+ 'auth_no_pass_redirect': no_pass_redirect,
+ ('mediagoblin.auth.register',
+ 'mediagoblin/auth/register.html'): add_to_form_context,
+}
diff --git a/mediagoblin/plugins/openid/forms.py b/mediagoblin/plugins/openid/forms.py
new file mode 100644
index 00000000..f26024bd
--- /dev/null
+++ b/mediagoblin/plugins/openid/forms.py
@@ -0,0 +1,41 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+import wtforms
+
+from mediagoblin.tools.translate import lazy_pass_to_ugettext as _
+from mediagoblin.auth.tools import normalize_user_or_email_field
+
+
+class RegistrationForm(wtforms.Form):
+ openid = wtforms.HiddenField(
+ '',
+ [wtforms.validators.Required()])
+ username = wtforms.TextField(
+ _('Username'),
+ [wtforms.validators.Required(),
+ normalize_user_or_email_field(allow_email=False)])
+ email = wtforms.TextField(
+ _('Email address'),
+ [wtforms.validators.Required(),
+ normalize_user_or_email_field(allow_user=False)])
+
+
+class LoginForm(wtforms.Form):
+ openid = wtforms.TextField(
+ _('OpenID'),
+ [wtforms.validators.Required(),
+ # Can openid's only be urls?
+ wtforms.validators.URL(message='Please enter a valid url.')])
diff --git a/mediagoblin/plugins/openid/models.py b/mediagoblin/plugins/openid/models.py
new file mode 100644
index 00000000..6773f0ad
--- /dev/null
+++ b/mediagoblin/plugins/openid/models.py
@@ -0,0 +1,65 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+from sqlalchemy import Column, Integer, Unicode, ForeignKey
+from sqlalchemy.orm import relationship, backref
+
+from mediagoblin.db.models import User
+from mediagoblin.db.base import Base
+
+
+class OpenIDUserURL(Base):
+ __tablename__ = "openid__user_urls"
+
+ id = Column(Integer, primary_key=True)
+ openid_url = Column(Unicode, nullable=False)
+ user_id = Column(Integer, ForeignKey(User.id), nullable=False)
+
+ # OpenID's are owned by their user, so do the full thing.
+ user = relationship(User, backref=backref('openid_urls',
+ cascade='all, delete-orphan'))
+
+
+# OpenID Store Models
+class Nonce(Base):
+ __tablename__ = "openid__nonce"
+
+ server_url = Column(Unicode, primary_key=True)
+ timestamp = Column(Integer, primary_key=True)
+ salt = Column(Unicode, primary_key=True)
+
+ def __unicode__(self):
+ return u'Nonce: %r, %r' % (self.server_url, self.salt)
+
+
+class Association(Base):
+ __tablename__ = "openid__association"
+
+ server_url = Column(Unicode, primary_key=True)
+ handle = Column(Unicode, primary_key=True)
+ secret = Column(Unicode)
+ issued = Column(Integer)
+ lifetime = Column(Integer)
+ assoc_type = Column(Unicode)
+
+ def __unicode__(self):
+ return u'Association: %r, %r' % (self.server_url, self.handle)
+
+
+MODELS = [
+ OpenIDUserURL,
+ Nonce,
+ Association
+]
diff --git a/mediagoblin/plugins/openid/store.py b/mediagoblin/plugins/openid/store.py
new file mode 100644
index 00000000..8f9a7012
--- /dev/null
+++ b/mediagoblin/plugins/openid/store.py
@@ -0,0 +1,127 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+import base64
+import time
+
+from openid.association import Association as OIDAssociation
+from openid.store.interface import OpenIDStore
+from openid.store import nonce
+
+from mediagoblin.plugins.openid.models import Association, Nonce
+
+
+class SQLAlchemyOpenIDStore(OpenIDStore):
+ def __init__(self):
+ self.max_nonce_age = 6 * 60 * 60
+
+ def storeAssociation(self, server_url, association):
+ assoc = Association.query.filter_by(
+ server_url=server_url, handle=association.handle
+ ).first()
+
+ if not assoc:
+ assoc = Association()
+ assoc.server_url = unicode(server_url)
+ assoc.handle = association.handle
+
+ # django uses base64 encoding, python-openid uses a blob field for
+ # secret
+ assoc.secret = unicode(base64.encodestring(association.secret))
+ assoc.issued = association.issued
+ assoc.lifetime = association.lifetime
+ assoc.assoc_type = association.assoc_type
+ assoc.save()
+
+ def getAssociation(self, server_url, handle=None):
+ assocs = []
+ if handle is not None:
+ assocs = Association.query.filter_by(
+ server_url=server_url, handle=handle
+ )
+ else:
+ assocs = Association.query.filter_by(
+ server_url=server_url
+ )
+
+ if assocs.count() == 0:
+ return None
+ else:
+ associations = []
+ for assoc in assocs:
+ association = OIDAssociation(
+ assoc.handle, base64.decodestring(assoc.secret),
+ assoc.issued, assoc.lifetime, assoc.assoc_type
+ )
+ if association.getExpiresIn() == 0:
+ assoc.delete()
+ else:
+ associations.append((association.issued, association))
+
+ if not associations:
+ return None
+ associations.sort()
+ return associations[-1][1]
+
+ def removeAssociation(self, server_url, handle):
+ assocs = Association.query.filter_by(
+ server_url=server_url, handle=handle
+ ).first()
+
+ assoc_exists = True if assocs else False
+ for assoc in assocs:
+ assoc.delete()
+ return assoc_exists
+
+ def useNonce(self, server_url, timestamp, salt):
+ if abs(timestamp - time.time()) > nonce.SKEW:
+ return False
+
+ ononce = Nonce.query.filter_by(
+ server_url=server_url,
+ timestamp=timestamp,
+ salt=salt
+ ).first()
+
+ if ononce:
+ return False
+ else:
+ ononce = Nonce()
+ ononce.server_url = server_url
+ ononce.timestamp = timestamp
+ ononce.salt = salt
+ ononce.save()
+ return True
+
+ def cleanupNonces(self, _now=None):
+ if _now is None:
+ _now = int(time.time())
+ expired = Nonce.query.filter(
+ Nonce.timestamp < (_now - nonce.SKEW)
+ )
+ count = expired.count()
+ for each in expired:
+ each.delete()
+ return count
+
+ def cleanupAssociations(self):
+ now = int(time.time())
+ assoc = Association.query.all()
+ count = 0
+ for each in assoc:
+ if (each.lifetime + each.issued) <= now:
+ each.delete()
+ count = count + 1
+ return count
diff --git a/mediagoblin/plugins/openid/templates/mediagoblin/plugins/openid/add.html b/mediagoblin/plugins/openid/templates/mediagoblin/plugins/openid/add.html
new file mode 100644
index 00000000..8d308c81
--- /dev/null
+++ b/mediagoblin/plugins/openid/templates/mediagoblin/plugins/openid/add.html
@@ -0,0 +1,44 @@
+{#
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#}
+{% extends "mediagoblin/base.html" %}
+
+{% import "/mediagoblin/utils/wtforms.html" as wtforms_util %}
+
+{% block title -%}
+ {% trans %}Add an OpenID{% endtrans %} &mdash; {{ super() }}
+{%- endblock %}
+
+{% block mediagoblin_content %}
+ <form action="{{ request.urlgen('mediagoblin.plugins.openid.edit') }}"
+ method="POST" enctype="multipart/form-data">
+ {{ csrf_token }}
+ <div class="form_box">
+ <h1>{% trans %}Add an OpenID{% endtrans %}</h1>
+ <p>
+ <a href="{{ request.urlgen('mediagoblin.plugins.openid.delete') }}">
+ {% trans %}Delete an OpenID{% endtrans %}
+ </a>
+ </p>
+ {{ wtforms_util.render_divs(form, True) }}
+ <div class="form_submit_buttons">
+ <input type="submit" value="{% trans %}Add{% endtrans %}" class="button_form"/>
+ </div>
+ </div>
+ </form>
+{% endblock %}
+
diff --git a/mediagoblin/plugins/openid/templates/mediagoblin/plugins/openid/delete.html b/mediagoblin/plugins/openid/templates/mediagoblin/plugins/openid/delete.html
new file mode 100644
index 00000000..84301b9e
--- /dev/null
+++ b/mediagoblin/plugins/openid/templates/mediagoblin/plugins/openid/delete.html
@@ -0,0 +1,43 @@
+{#
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#}
+{% extends "mediagoblin/base.html" %}
+
+{% import "/mediagoblin/utils/wtforms.html" as wtforms_util %}
+
+{% block title -%}
+ {% trans %}Delete an OpenID{% endtrans %} &mdash; {{ super() }}
+{%- endblock %}
+
+{% block mediagoblin_content %}
+ <form action="{{ request.urlgen('mediagoblin.plugins.openid.delete') }}"
+ method="POST" enctype="multipart/form-data">
+ {{ csrf_token }}
+ <div class="form_box">
+ <h1>{% trans %}Delete an OpenID{% endtrans %}</h1>
+ <p>
+ <a href="{{ request.urlgen('mediagoblin.plugins.openid.edit') }}">
+ {% trans %}Add an OpenID{% endtrans %}
+ </a>
+ </p>
+ {{ wtforms_util.render_divs(form, True) }}
+ <div class="form_submit_buttons">
+ <input type="submit" value="{% trans %}Delete{% endtrans %}" class="button_form"/>
+ </div>
+ </div>
+ </form>
+{% endblock %}
diff --git a/mediagoblin/plugins/openid/templates/mediagoblin/plugins/openid/edit_link.html b/mediagoblin/plugins/openid/templates/mediagoblin/plugins/openid/edit_link.html
new file mode 100644
index 00000000..2e63e1f8
--- /dev/null
+++ b/mediagoblin/plugins/openid/templates/mediagoblin/plugins/openid/edit_link.html
@@ -0,0 +1,25 @@
+{#
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#}
+
+{% block openid_edit_link %}
+ <p>
+ <a href="{{ request.urlgen('mediagoblin.plugins.openid.edit') }}">
+ {% trans %}Edit your OpenID's{% endtrans %}
+ </a>
+ </p>
+{% endblock %}
diff --git a/mediagoblin/plugins/openid/templates/mediagoblin/plugins/openid/login.html b/mediagoblin/plugins/openid/templates/mediagoblin/plugins/openid/login.html
new file mode 100644
index 00000000..33df7200
--- /dev/null
+++ b/mediagoblin/plugins/openid/templates/mediagoblin/plugins/openid/login.html
@@ -0,0 +1,65 @@
+{#
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#}
+{% extends "mediagoblin/base.html" %}
+
+{% import "/mediagoblin/utils/wtforms.html" as wtforms_util %}
+
+{% block mediagoblin_head %}
+ <script type="text/javascript"
+ src="{{ request.staticdirect('/js/autofilledin_password.js') }}"></script>
+{% endblock %}
+
+{% block title -%}
+ {% trans %}Log in{% endtrans %} &mdash; {{ super() }}
+{%- endblock %}
+
+{% block mediagoblin_content %}
+ <form action="{{ post_url }}"
+ method="POST" enctype="multipart/form-data">
+ {{ csrf_token }}
+ <div class="form_box">
+ <h1>{% trans %}Log in{% endtrans %}</h1>
+ {% if login_failed %}
+ <div class="form_field_error">
+ {% trans %}Logging in failed!{% endtrans %}
+ </div>
+ {% endif %}
+ {% if allow_registration %}
+ <p>
+ {% trans %}Log in to create an account!{% endtrans %}
+ </p>
+ {% endif %}
+ {% if pass_auth is defined %}
+ <p>
+ <a href="{{ request.urlgen('mediagoblin.auth.login') }}?{{ request.query_string }}">
+ {%- trans %}Or login with a password!{% endtrans %}
+ </a>
+ </p>
+ {% endif %}
+ {{ wtforms_util.render_divs(login_form, True) }}
+ <div class="form_submit_buttons">
+ <input type="submit" value="{% trans %}Log in{% endtrans %}" class="button_form"/>
+ </div>
+ {% if next %}
+ <input type="hidden" name="next" value="{{ next }}" class="button_form"
+ style="display: none;"/>
+ {% endif %}
+ </div>
+ </form>
+{% endblock %}
+
diff --git a/mediagoblin/plugins/openid/templates/mediagoblin/plugins/openid/login_link.html b/mediagoblin/plugins/openid/templates/mediagoblin/plugins/openid/login_link.html
new file mode 100644
index 00000000..e5e77d01
--- /dev/null
+++ b/mediagoblin/plugins/openid/templates/mediagoblin/plugins/openid/login_link.html
@@ -0,0 +1,25 @@
+{#
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#}
+
+{% block openid_login_link %}
+ <p>
+ <a href="{{ request.urlgen('mediagoblin.plugins.openid.login') }}?{{ request.query_string }}">
+ {%- trans %}Or login with OpenID!{% endtrans %}
+ </a>
+ </p>
+{% endblock %}
diff --git a/mediagoblin/plugins/openid/templates/mediagoblin/plugins/openid/register_link.html b/mediagoblin/plugins/openid/templates/mediagoblin/plugins/openid/register_link.html
new file mode 100644
index 00000000..9bccb4d8
--- /dev/null
+++ b/mediagoblin/plugins/openid/templates/mediagoblin/plugins/openid/register_link.html
@@ -0,0 +1,27 @@
+{#
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#}
+
+{% block openid_register_link %}
+ {% if openid_link is defined %}
+ <p>
+ <a href="{{ request.urlgen('mediagoblin.plugins.openid.login') }}">
+ {%- trans %}Or register with OpenID!{% endtrans %}
+ </a>
+ </p>
+ {% endif %}
+{% endblock %}
diff --git a/mediagoblin/plugins/openid/templates/mediagoblin/plugins/openid/request_form.html b/mediagoblin/plugins/openid/templates/mediagoblin/plugins/openid/request_form.html
new file mode 100644
index 00000000..68d028d0
--- /dev/null
+++ b/mediagoblin/plugins/openid/templates/mediagoblin/plugins/openid/request_form.html
@@ -0,0 +1,24 @@
+{#
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#}
+{% extends "mediagoblin/base.html" %}
+
+{% block mediagoblin_content %}
+ <div onload="document.getElementById('openid_message').submit()">
+ {{ html|safe }}
+ </div>
+{% endblock %}
diff --git a/mediagoblin/plugins/openid/views.py b/mediagoblin/plugins/openid/views.py
new file mode 100644
index 00000000..b639a4cb
--- /dev/null
+++ b/mediagoblin/plugins/openid/views.py
@@ -0,0 +1,404 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+from openid.consumer import consumer
+from openid.consumer.discover import DiscoveryFailure
+from openid.extensions.sreg import SRegRequest, SRegResponse
+
+from mediagoblin import mg_globals, messages
+from mediagoblin.db.models import User
+from mediagoblin.decorators import (auth_enabled, allow_registration,
+ require_active_login)
+from mediagoblin.tools.response import redirect, render_to_response
+from mediagoblin.tools.translate import pass_to_ugettext as _
+from mediagoblin.plugins.openid import forms as auth_forms
+from mediagoblin.plugins.openid.models import OpenIDUserURL
+from mediagoblin.plugins.openid.store import SQLAlchemyOpenIDStore
+from mediagoblin.auth.tools import register_user
+
+
+def _start_verification(request, form, return_to, sreg=True):
+ """
+ Start OpenID Verification.
+
+ Returns False if verification fails, otherwise, will return either a
+ redirect or render_to_response object
+ """
+ openid_url = form.openid.data
+ c = consumer.Consumer(request.session, SQLAlchemyOpenIDStore())
+
+ # Try to discover provider
+ try:
+ auth_request = c.begin(openid_url)
+ except DiscoveryFailure:
+ # Discovery failed, return to login page
+ form.openid.errors.append(
+ _('Sorry, the OpenID server could not be found'))
+
+ return False
+
+ host = 'http://' + request.host
+
+ if sreg:
+ # Ask provider for email and nickname
+ auth_request.addExtension(SRegRequest(required=['email', 'nickname']))
+
+ # Do we even need this?
+ if auth_request is None:
+ form.openid.errors.append(
+ _('No OpenID service was found for %s' % openid_url))
+
+ elif auth_request.shouldSendRedirect():
+ # Begin the authentication process as a HTTP redirect
+ redirect_url = auth_request.redirectURL(
+ host, return_to)
+
+ return redirect(
+ request, location=redirect_url)
+
+ else:
+ # Send request as POST
+ form_html = auth_request.htmlMarkup(
+ host, host + return_to,
+ # Is this necessary?
+ form_tag_attrs={'id': 'openid_message'})
+
+ # Beware: this renders a template whose content is a form
+ # and some javascript to submit it upon page load. Non-JS
+ # users will have to click the form submit button to
+ # initiate OpenID authentication.
+ return render_to_response(
+ request,
+ 'mediagoblin/plugins/openid/request_form.html',
+ {'html': form_html})
+
+ return False
+
+
+def _finish_verification(request):
+ """
+ Complete OpenID Verification Process.
+
+ If the verification failed, will return false, otherwise, will return
+ the response
+ """
+ c = consumer.Consumer(request.session, SQLAlchemyOpenIDStore())
+
+ # Check the response from the provider
+ response = c.complete(request.args, request.base_url)
+ if response.status == consumer.FAILURE:
+ messages.add_message(
+ request,
+ messages.WARNING,
+ _('Verification of %s failed: %s' %
+ (response.getDisplayIdentifier(), response.message)))
+
+ elif response.status == consumer.SUCCESS:
+ # Verification was successfull
+ return response
+
+ elif response.status == consumer.CANCEL:
+ # Verification canceled
+ messages.add_message(
+ request,
+ messages.WARNING,
+ _('Verification cancelled'))
+
+ return False
+
+
+def _response_email(response):
+ """ Gets the email from the OpenID providers response"""
+ sreg_response = SRegResponse.fromSuccessResponse(response)
+ if sreg_response and 'email' in sreg_response:
+ return sreg_response.data['email']
+ return None
+
+
+def _response_nickname(response):
+ """ Gets the nickname from the OpenID providers response"""
+ sreg_response = SRegResponse.fromSuccessResponse(response)
+ if sreg_response and 'nickname' in sreg_response:
+ return sreg_response.data['nickname']
+ return None
+
+
+@auth_enabled
+def login(request):
+ """OpenID Login View"""
+ login_form = auth_forms.LoginForm(request.form)
+ allow_registration = mg_globals.app_config["allow_registration"]
+
+ # Can't store next in request.GET because of redirects to OpenID provider
+ # Store it in the session
+ next = request.GET.get('next')
+ request.session['next'] = next
+
+ login_failed = False
+
+ if request.method == 'POST' and login_form.validate():
+ return_to = request.urlgen(
+ 'mediagoblin.plugins.openid.finish_login')
+
+ success = _start_verification(request, login_form, return_to)
+
+ if success:
+ return success
+
+ login_failed = True
+
+ return render_to_response(
+ request,
+ 'mediagoblin/plugins/openid/login.html',
+ {'login_form': login_form,
+ 'next': request.session.get('next'),
+ 'login_failed': login_failed,
+ 'post_url': request.urlgen('mediagoblin.plugins.openid.login'),
+ 'allow_registration': allow_registration})
+
+
+@auth_enabled
+def finish_login(request):
+ """Complete OpenID Login Process"""
+ response = _finish_verification(request)
+
+ if not response:
+ # Verification failed, redirect to login page.
+ return redirect(request, 'mediagoblin.plugins.openid.login')
+
+ # Verification was successfull
+ query = OpenIDUserURL.query.filter_by(
+ openid_url=response.identity_url,
+ ).first()
+ user = query.user if query else None
+
+ if user:
+ # Set up login in session
+ request.session['user_id'] = unicode(user.id)
+ request.session.save()
+
+ if request.session.get('next'):
+ return redirect(request, location=request.session.pop('next'))
+ else:
+ return redirect(request, "index")
+ else:
+ # No user, need to register
+ if not mg_globals.app.auth:
+ messages.add_message(
+ request,
+ messages.WARNING,
+ _('Sorry, authentication is disabled on this instance.'))
+ return redirect(request, 'index')
+
+ # Get email and nickname from response
+ email = _response_email(response)
+ username = _response_nickname(response)
+
+ register_form = auth_forms.RegistrationForm(request.form,
+ openid=response.identity_url,
+ email=email,
+ username=username)
+ return render_to_response(
+ request,
+ 'mediagoblin/auth/register.html',
+ {'register_form': register_form,
+ 'post_url': request.urlgen('mediagoblin.plugins.openid.register')})
+
+
+@allow_registration
+@auth_enabled
+def register(request):
+ """OpenID Registration View"""
+ if request.method == 'GET':
+ # Need to connect to openid provider before registering a user to
+ # get the users openid url. If method is 'GET', then this page was
+ # acessed without logging in first.
+ return redirect(request, 'mediagoblin.plugins.openid.login')
+
+ register_form = auth_forms.RegistrationForm(request.form)
+
+ if register_form.validate():
+ user = register_user(request, register_form)
+
+ if user:
+ # redirect the user to their homepage... there will be a
+ # message waiting for them to verify their email
+ return redirect(
+ request, 'mediagoblin.user_pages.user_home',
+ user=user.username)
+
+ return render_to_response(
+ request,
+ 'mediagoblin/auth/register.html',
+ {'register_form': register_form,
+ 'post_url': request.urlgen('mediagoblin.plugins.openid.register')})
+
+
+@require_active_login
+def start_edit(request):
+ """Starts the process of adding an openid url to a users account"""
+ form = auth_forms.LoginForm(request.form)
+
+ if request.method == 'POST' and form.validate():
+ query = OpenIDUserURL.query.filter_by(
+ openid_url=form.openid.data
+ ).first()
+ user = query.user if query else None
+
+ if not user:
+ return_to = request.urlgen('mediagoblin.plugins.openid.finish_edit')
+ success = _start_verification(request, form, return_to, False)
+
+ if success:
+ return success
+ else:
+ form.openid.errors.append(
+ _('Sorry, an account is already registered to that OpenID.'))
+
+ return render_to_response(
+ request,
+ 'mediagoblin/plugins/openid/add.html',
+ {'form': form,
+ 'post_url': request.urlgen('mediagoblin.plugins.openid.edit')})
+
+
+@require_active_login
+def finish_edit(request):
+ """Finishes the process of adding an openid url to a user"""
+ response = _finish_verification(request)
+
+ if not response:
+ # Verification failed, redirect to add openid page.
+ return redirect(request, 'mediagoblin.plugins.openid.edit')
+
+ # Verification was successfull
+ query = OpenIDUserURL.query.filter_by(
+ openid_url=response.identity_url,
+ ).first()
+ user_exists = query.user if query else None
+
+ if user_exists:
+ # user exists with that openid url, redirect back to edit page
+ messages.add_message(
+ request,
+ messages.WARNING,
+ _('Sorry, an account is already registered to that OpenID.'))
+ return redirect(request, 'mediagoblin.plugins.openid.edit')
+
+ else:
+ # Save openid to user
+ user = User.query.filter_by(
+ id=request.session['user_id']
+ ).first()
+
+ new_entry = OpenIDUserURL()
+ new_entry.openid_url = response.identity_url
+ new_entry.user_id = user.id
+ new_entry.save()
+
+ messages.add_message(
+ request,
+ messages.SUCCESS,
+ _('Your OpenID url was saved successfully.'))
+
+ return redirect(request, 'mediagoblin.edit.account')
+
+
+@require_active_login
+def delete_openid(request):
+ """View to remove an openid from a users account"""
+ form = auth_forms.LoginForm(request.form)
+
+ if request.method == 'POST' and form.validate():
+ # Check if a user has this openid
+ query = OpenIDUserURL.query.filter_by(
+ openid_url=form.openid.data
+ )
+ user = query.first().user if query.first() else None
+
+ if user and user.id == int(request.session['user_id']):
+ count = len(user.openid_urls)
+ if not count > 1 and not user.pw_hash:
+ # Make sure the user has a pw or another OpenID
+ messages.add_message(
+ request,
+ messages.WARNING,
+ _("You can't delete your only OpenID URL unless you"
+ " have a password set"))
+ elif user:
+ # There is a user, but not the same user who is logged in
+ form.openid.errors.append(
+ _('That OpenID is not registered to this account.'))
+
+ if not form.errors and not request.session.get('messages'):
+ # Okay to continue with deleting openid
+ return_to = request.urlgen(
+ 'mediagoblin.plugins.openid.finish_delete')
+ success = _start_verification(request, form, return_to, False)
+
+ if success:
+ return success
+
+ return render_to_response(
+ request,
+ 'mediagoblin/plugins/openid/delete.html',
+ {'form': form,
+ 'post_url': request.urlgen('mediagoblin.plugins.openid.delete')})
+
+
+@require_active_login
+def finish_delete(request):
+ """Finishes the deletion of an OpenID from an user's account"""
+ response = _finish_verification(request)
+
+ if not response:
+ # Verification failed, redirect to delete openid page.
+ return redirect(request, 'mediagoblin.plugins.openid.delete')
+
+ query = OpenIDUserURL.query.filter_by(
+ openid_url=response.identity_url
+ )
+ user = query.first().user if query.first() else None
+
+ # Need to check this again because of generic openid urls such as google's
+ if user and user.id == int(request.session['user_id']):
+ count = len(user.openid_urls)
+ if count > 1 or user.pw_hash:
+ # User has more then one openid or also has a password.
+ query.first().delete()
+
+ messages.add_message(
+ request,
+ messages.SUCCESS,
+ _('OpenID was successfully removed.'))
+
+ return redirect(request, 'mediagoblin.edit.account')
+
+ elif not count > 1:
+ messages.add_message(
+ request,
+ messages.WARNING,
+ _("You can't delete your only OpenID URL unless you have a "
+ "password set"))
+
+ return redirect(request, 'mediagoblin.plugins.openid.delete')
+
+ else:
+ messages.add_message(
+ request,
+ messages.WARNING,
+ _('That OpenID is not registered to this account.'))
+
+ return redirect(request, 'mediagoblin.plugins.openid.delete')
diff --git a/mediagoblin/static/css/base.css b/mediagoblin/static/css/base.css
index 8b57584d..84d274d1 100644
--- a/mediagoblin/static/css/base.css
+++ b/mediagoblin/static/css/base.css
@@ -334,6 +334,10 @@ text-align: center;
width: 20px;
}
+.boolean {
+ margin-bottom: 8px;
+ }
+
textarea#description, textarea#bio {
resize: vertical;
height: 100px;
diff --git a/mediagoblin/submit/views.py b/mediagoblin/submit/views.py
index 64e6791b..3f9d5b2d 100644
--- a/mediagoblin/submit/views.py
+++ b/mediagoblin/submit/views.py
@@ -19,6 +19,7 @@ import mediagoblin.mg_globals as mg_globals
from os.path import splitext
import logging
+import uuid
_log = logging.getLogger(__name__)
@@ -53,6 +54,10 @@ def submit_start(request):
try:
filename = request.files['file'].filename
+ # If the filename contains non ascii generate a unique name
+ if not all(ord(c) < 128 for c in filename):
+ filename = unicode(uuid.uuid4()) + splitext(filename)[-1]
+
# Sniff the submitted media to determine which
# media plugin should handle processing
media_type, media_manager = sniff_media(
@@ -63,7 +68,7 @@ def submit_start(request):
entry.media_type = unicode(media_type)
entry.title = (
unicode(submit_form.title.data)
- or unicode(splitext(filename)[0]))
+ or unicode(splitext(request.files['file'].filename)[0]))
entry.description = unicode(submit_form.description.data)
@@ -133,9 +138,9 @@ def add_collection(request, media=None):
collection.generate_slug()
# Make sure this user isn't duplicating an existing collection
- existing_collection = request.db.Collection.find_one({
- 'creator': request.user.id,
- 'title':collection.title})
+ existing_collection = request.db.Collection.query.filter_by(
+ creator=request.user.id,
+ title=collection.title).first()
if existing_collection:
add_message(request, messages.ERROR,
diff --git a/mediagoblin/templates/mediagoblin/auth/login.html b/mediagoblin/templates/mediagoblin/auth/login.html
index d9f92557..3329b5d0 100644
--- a/mediagoblin/templates/mediagoblin/auth/login.html
+++ b/mediagoblin/templates/mediagoblin/auth/login.html
@@ -29,7 +29,7 @@
{%- endblock %}
{% block mediagoblin_content %}
- <form action="{{ request.urlgen('mediagoblin.auth.login') }}"
+ <form action="{{ post_url }}"
method="POST" enctype="multipart/form-data">
{{ csrf_token }}
<div class="form_box">
@@ -41,16 +41,18 @@
{% endif %}
{% if allow_registration %}
<p>
- {% trans %}Don't have an account yet?{% endtrans %} <a href="{{ request.urlgen('mediagoblin.auth.register') }}">
+ {% trans %}Don't have an account yet?{% endtrans %}
+ <a href="{{ request.urlgen('mediagoblin.auth.register') }}">
{%- trans %}Create one here!{% endtrans %}</a>
</p>
{% endif %}
+ {% template_hook("login_link") %}
{{ wtforms_util.render_divs(login_form, True) }}
{% if pass_auth %}
- <p>
- <a href="{{ request.urlgen('mediagoblin.auth.forgot_password') }}" id="forgot_password">
- {% trans %}Forgot your password?{% endtrans %}</a>
- </p>
+ <p>
+ <a href="{{ request.urlgen('mediagoblin.auth.forgot_password') }}" id="forgot_password">
+ {% trans %}Forgot your password?{% endtrans %}</a>
+ </p>
{% endif %}
<div class="form_submit_buttons">
<input type="submit" value="{% trans %}Log in{% endtrans %}" class="button_form"/>
diff --git a/mediagoblin/templates/mediagoblin/auth/register.html b/mediagoblin/templates/mediagoblin/auth/register.html
index b315975c..a7b8033f 100644
--- a/mediagoblin/templates/mediagoblin/auth/register.html
+++ b/mediagoblin/templates/mediagoblin/auth/register.html
@@ -30,10 +30,11 @@
{% block mediagoblin_content %}
- <form action="{{ request.urlgen('mediagoblin.auth.register') }}"
+ <form action="{{ post_url }}"
method="POST" enctype="multipart/form-data">
<div class="form_box">
<h1>{% trans %}Create an account!{% endtrans %}</h1>
+ {% template_hook("register_link") %}
{{ wtforms_util.render_divs(register_form, True) }}
{{ csrf_token }}
<div class="form_submit_buttons">
diff --git a/mediagoblin/templates/mediagoblin/edit/change_pass.html b/mediagoblin/templates/mediagoblin/edit/change_pass.html
index ff909b07..2a1ffee0 100644
--- a/mediagoblin/templates/mediagoblin/edit/change_pass.html
+++ b/mediagoblin/templates/mediagoblin/edit/change_pass.html
@@ -39,7 +39,7 @@
Changing {{ username }}'s password
{%- endtrans -%}
</h1>
- {{ wtforms_util.render_divs(form) }}
+ {{ wtforms_util.render_divs(form, True) }}
{{ csrf_token }}
<div class="form_submit_buttons">
<input type="submit" value="{% trans %}Save{% endtrans %}"
diff --git a/mediagoblin/templates/mediagoblin/edit/edit_account.html b/mediagoblin/templates/mediagoblin/edit/edit_account.html
index 98b1b224..51293acb 100644
--- a/mediagoblin/templates/mediagoblin/edit/edit_account.html
+++ b/mediagoblin/templates/mediagoblin/edit/edit_account.html
@@ -41,13 +41,16 @@
Changing {{ username }}'s account settings
{%- endtrans -%}
</h1>
+ {% if pass_auth is defined %}
<p>
<a href="{{ request.urlgen('mediagoblin.edit.pass') }}">
{% trans %}Change your password.{% endtrans %}
</a>
</p>
+ {% endif %}
+ {% template_hook("edit_link") %}
{{ wtforms_util.render_divs(form, True) }}
- <div class="form_submit_buttons">
+ <div class="form_submit_buttons">
<input type="submit" value="{% trans %}Save changes{% endtrans %}" class="button_form" />
{{ csrf_token }}
</div>
diff --git a/mediagoblin/templates/mediagoblin/media_displays/stl.html b/mediagoblin/templates/mediagoblin/media_displays/stl.html
index a89e0b4f..bc12ce4e 100644
--- a/mediagoblin/templates/mediagoblin/media_displays/stl.html
+++ b/mediagoblin/templates/mediagoblin/media_displays/stl.html
@@ -108,32 +108,26 @@ window.show_things = function () {
<div style="padding: 4px;">
- <a class="button_action" onclick="show('perspective');"
- title="{%- trans %}Toggle Rotate{% endtrans -%}">
+ <a class="button_action" onclick="show('perspective');">
{%- trans %}Perspective{% endtrans -%}
</a>
- <a class="button_action" onclick="show('front_view');"
- title="{%- trans %}Front{% endtrans -%}">
+ <a class="button_action" onclick="show('front_view');">
{%- trans %}Front{% endtrans -%}
</a>
- <a class="button_action" onclick="show('top_view');"
- title="{%- trans %}Top{% endtrans -%}">
+ <a class="button_action" onclick="show('top_view');">
{%- trans %}Top{% endtrans -%}
</a>
- <a class="button_action" onclick="show('side_view');"
- title="{%- trans %}Side{% endtrans -%}">
+ <a class="button_action" onclick="show('side_view');">
{%- trans %}Side{% endtrans -%}
</a>
{% if media.media_data.file_type == "stl" %}
<a id="webgl_button" class="button_action"
- onclick="show_things();"
- title="{%- trans %}WebGL{% endtrans -%}">
+ onclick="show_things();">
{%- trans %}WebGL{% endtrans -%}
</a>
{% endif %}
<a class="button_action" href="{{ model_download }}"
- title="{%- trans %}Download{% endtrans -%}"
style="float:right;">
{%- trans %}Download model{% endtrans -%}
</a>
diff --git a/mediagoblin/templates/mediagoblin/utils/comment-subscription.html b/mediagoblin/templates/mediagoblin/utils/comment-subscription.html
index 8ee8c883..bd367e80 100644
--- a/mediagoblin/templates/mediagoblin/utils/comment-subscription.html
+++ b/mediagoblin/templates/mediagoblin/utils/comment-subscription.html
@@ -21,13 +21,13 @@
{% if not subscription or not subscription.notify %}
<a type="submit" href="{{ request.urlgen('mediagoblin.notifications.subscribe_comments',
user=media.get_uploader.username,
- media=media.slug)}}"
+ media=media.slug_or_id)}}"
class="button_action">Subscribe to comments
</a>
{% else %}
<a type="submit" href="{{ request.urlgen('mediagoblin.notifications.silence_comments',
user=media.get_uploader.username,
- media=media.slug)}}"
+ media=media.slug_or_id)}}"
class="button_action">Silence comments
</a>
{% endif %}
diff --git a/mediagoblin/templates/mediagoblin/utils/wtforms.html b/mediagoblin/templates/mediagoblin/utils/wtforms.html
index a4c33f1a..e079274e 100644
--- a/mediagoblin/templates/mediagoblin/utils/wtforms.html
+++ b/mediagoblin/templates/mediagoblin/utils/wtforms.html
@@ -34,26 +34,26 @@
{# Generically render a field #}
{% macro render_field_div(field, autofocus_first=False) %}
- {{- render_label_p(field) }}
- <div class="form_field_input">
- {% if autofocus_first %}
- {{ field(autofocus=True) }}
- {% else %}
- {{ field }}
- {% endif %}
- {%- if field.errors -%}
- {% for error in field.errors %}
- <p class="form_field_error">{{ error }}</p>
- {% endfor %}
- {%- endif %}
- {%- if field.description %}
- {% if field.type == 'BooleanField' %}
- <label for="{{ field.label.field_id }}">{{ field.description|safe }}</label>
+ {% if field.type == 'BooleanField' %}
+ {{ render_bool(field) }}
+ {% else %}
+ {{- render_label_p(field) }}
+ <div class="form_field_input">
+ {% if autofocus_first %}
+ {{ field(autofocus=True) }}
{% else %}
- <p class="form_field_description">{{ field.description|safe }}</p>
+ {{ field }}
{% endif %}
- {%- endif %}
- </div>
+ {%- if field.errors -%}
+ {% for error in field.errors %}
+ <p class="form_field_error">{{ error }}</p>
+ {% endfor %}
+ {%- endif %}
+ {%- if field.description %}
+ <p class="form_field_description">{{ field.description|safe }}</p>
+ {%- endif %}
+ </div>
+ {% endif %}
{%- endmacro %}
{# Auto-render a form as a series of divs #}
@@ -86,3 +86,19 @@
</tr>
{% endfor %}
{%- endmacro %}
+
+{# Render a boolean field #}
+{% macro render_bool(field) %}
+ <div class="boolean">
+ <label for="{{ field.label.field_id }}">
+ {{ field }}</input>
+ {{ field.description|safe }}
+ </label>
+ {%- if field.errors -%}
+ {% for error in field.errors %}
+ <p class="form_field_error">{{ error }}</p>
+ {% endfor %}
+ {% endif %}
+ </div>
+{% endmacro %}
+
diff --git a/mediagoblin/tests/appconfig_context_modified.ini b/mediagoblin/tests/appconfig_context_modified.ini
index 80ca69b1..cc6721f5 100644
--- a/mediagoblin/tests/appconfig_context_modified.ini
+++ b/mediagoblin/tests/appconfig_context_modified.ini
@@ -3,8 +3,9 @@ direct_remote_path = /test_static/
email_sender_address = "notice@mediagoblin.example.org"
email_debug_mode = true
-# TODO: Switch to using an in-memory database
-sql_engine = "sqlite:///%(here)s/user_dev/mediagoblin.db"
+#Runs with an in-memory sqlite db for speed.
+sql_engine = "sqlite://"
+run_migrations = true
# Celery shouldn't be set up by the application as it's setup via
# mediagoblin.init.celery.from_celery
diff --git a/mediagoblin/tests/appconfig_static_plugin.ini b/mediagoblin/tests/appconfig_static_plugin.ini
index dc251171..5ce5c5bd 100644
--- a/mediagoblin/tests/appconfig_static_plugin.ini
+++ b/mediagoblin/tests/appconfig_static_plugin.ini
@@ -3,8 +3,9 @@ direct_remote_path = /test_static/
email_sender_address = "notice@mediagoblin.example.org"
email_debug_mode = true
-# TODO: Switch to using an in-memory database
-sql_engine = "sqlite:///%(here)s/user_dev/mediagoblin.db"
+#Runs with an in-memory sqlite db for speed.
+sql_engine = "sqlite://"
+run_migrations = true
# Celery shouldn't be set up by the application as it's setup via
# mediagoblin.init.celery.from_celery
diff --git a/mediagoblin/tests/auth_configs/openid_appconfig.ini b/mediagoblin/tests/auth_configs/openid_appconfig.ini
new file mode 100644
index 00000000..c2bd82fd
--- /dev/null
+++ b/mediagoblin/tests/auth_configs/openid_appconfig.ini
@@ -0,0 +1,41 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+[mediagoblin]
+direct_remote_path = /test_static/
+email_sender_address = "notice@mediagoblin.example.org"
+email_debug_mode = true
+
+# TODO: Switch to using an in-memory database
+sql_engine = "sqlite:///%(here)s/user_dev/mediagoblin.db"
+
+# Celery shouldn't be set up by the application as it's setup via
+# mediagoblin.init.celery.from_celery
+celery_setup_elsewhere = true
+
+[storage:publicstore]
+base_dir = %(here)s/user_dev/media/public
+base_url = /mgoblin_media/
+
+[storage:queuestore]
+base_dir = %(here)s/user_dev/media/queue
+
+[celery]
+CELERY_ALWAYS_EAGER = true
+CELERY_RESULT_DBURI = "sqlite:///%(here)s/user_dev/celery.db"
+BROKER_HOST = "sqlite:///%(here)s/user_dev/kombu.db"
+
+[plugins]
+[[mediagoblin.plugins.openid]]
diff --git a/mediagoblin/tests/test_auth.py b/mediagoblin/tests/test_auth.py
index f973ebd8..61503d32 100644
--- a/mediagoblin/tests/test_auth.py
+++ b/mediagoblin/tests/test_auth.py
@@ -14,7 +14,6 @@
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import urlparse
-import datetime
import pkg_resources
import pytest
@@ -94,8 +93,8 @@ def test_register_views(test_app):
assert 'mediagoblin/user_pages/user.html' in template.TEMPLATE_TEST_CONTEXT
## Make sure user is in place
- new_user = mg_globals.database.User.find_one(
- {'username': u'happygirl'})
+ new_user = mg_globals.database.User.query.filter_by(
+ username=u'happygirl').first()
assert new_user
assert new_user.status == u'needs_email_verification'
assert new_user.email_verified == False
@@ -129,8 +128,8 @@ def test_register_views(test_app):
# assert context['verification_successful'] == True
# TODO: Would be good to test messages here when we can do so...
- new_user = mg_globals.database.User.find_one(
- {'username': u'happygirl'})
+ new_user = mg_globals.database.User.query.filter_by(
+ username=u'happygirl').first()
assert new_user
assert new_user.status == u'needs_email_verification'
assert new_user.email_verified == False
@@ -143,8 +142,8 @@ def test_register_views(test_app):
'mediagoblin/user_pages/user.html']
# assert context['verification_successful'] == True
# TODO: Would be good to test messages here when we can do so...
- new_user = mg_globals.database.User.find_one(
- {'username': u'happygirl'})
+ new_user = mg_globals.database.User.query.filter_by(
+ username=u'happygirl').first()
assert new_user
assert new_user.status == u'active'
assert new_user.email_verified == True
@@ -236,6 +235,7 @@ def test_authentication_views(test_app):
# Make a new user
test_user = fixture_add_user(active_user=False)
+
# Get login
# ---------
test_app.get('/auth/login/')
diff --git a/mediagoblin/tests/test_edit.py b/mediagoblin/tests/test_edit.py
index acc638d9..d70d0478 100644
--- a/mediagoblin/tests/test_edit.py
+++ b/mediagoblin/tests/test_edit.py
@@ -190,8 +190,8 @@ class TestUserEdit(object):
assert urlparse.urlsplit(res.location)[2] == '/'
# Email shouldn't be saved
- email_in_db = mg_globals.database.User.find_one(
- {'email': 'new@example.com'})
+ email_in_db = mg_globals.database.User.query.filter_by(
+ email='new@example.com').first()
email = User.query.filter_by(username='chris').first().email
assert email_in_db is None
assert email == 'chris@example.com'
diff --git a/mediagoblin/tests/test_mgoblin_app.ini b/mediagoblin/tests/test_mgoblin_app.ini
index ecd6c9d2..535cf1c1 100644
--- a/mediagoblin/tests/test_mgoblin_app.ini
+++ b/mediagoblin/tests/test_mgoblin_app.ini
@@ -3,8 +3,9 @@ direct_remote_path = /test_static/
email_sender_address = "notice@mediagoblin.example.org"
email_debug_mode = true
-# TODO: Switch to using an in-memory database
-sql_engine = "sqlite:///%(here)s/user_dev/mediagoblin.db"
+#Runs with an in-memory sqlite db for speed.
+sql_engine = "sqlite://"
+run_migrations = true
# tag parsing
tags_max_length = 50
@@ -30,5 +31,6 @@ BROKER_HOST = "sqlite:///%(here)s/user_dev/kombu.db"
[[mediagoblin.plugins.httpapiauth]]
[[mediagoblin.plugins.piwigo]]
[[mediagoblin.plugins.basic_auth]]
+[[mediagoblin.plugins.openid]]
[[mediagoblin.media_types.image]]
[[mediagoblin.media_types.pdf]]
diff --git a/mediagoblin/tests/test_openid.py b/mediagoblin/tests/test_openid.py
new file mode 100644
index 00000000..bba46db8
--- /dev/null
+++ b/mediagoblin/tests/test_openid.py
@@ -0,0 +1,372 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+import urlparse
+import pkg_resources
+import pytest
+import mock
+
+from openid.consumer.consumer import SuccessResponse
+
+from mediagoblin import mg_globals
+from mediagoblin.db.base import Session
+from mediagoblin.db.models import User
+from mediagoblin.plugins.openid.models import OpenIDUserURL
+from mediagoblin.tests.tools import get_app, fixture_add_user
+from mediagoblin.tools import template
+
+
+# App with plugin enabled
+@pytest.fixture()
+def openid_plugin_app(request):
+ return get_app(
+ request,
+ mgoblin_config=pkg_resources.resource_filename(
+ 'mediagoblin.tests.auth_configs',
+ 'openid_appconfig.ini'))
+
+
+class TestOpenIDPlugin(object):
+ def _setup(self, openid_plugin_app, value=True, edit=False, delete=False):
+ if value:
+ response = SuccessResponse(mock.Mock(), mock.Mock())
+ if edit or delete:
+ response.identity_url = u'http://add.myopenid.com'
+ else:
+ response.identity_url = u'http://real.myopenid.com'
+ self._finish_verification = mock.Mock(return_value=response)
+ else:
+ self._finish_verification = mock.Mock(return_value=False)
+
+ @mock.patch('mediagoblin.plugins.openid.views._response_email', mock.Mock(return_value=None))
+ @mock.patch('mediagoblin.plugins.openid.views._response_nickname', mock.Mock(return_value=None))
+ @mock.patch('mediagoblin.plugins.openid.views._finish_verification', self._finish_verification)
+ def _setup_start(self, openid_plugin_app, edit, delete):
+ if edit:
+ self._start_verification = mock.Mock(return_value=openid_plugin_app.post(
+ '/edit/openid/finish/'))
+ elif delete:
+ self._start_verification = mock.Mock(return_value=openid_plugin_app.post(
+ '/edit/openid/delete/finish/'))
+ else:
+ self._start_verification = mock.Mock(return_value=openid_plugin_app.post(
+ '/auth/openid/login/finish/'))
+ _setup_start(self, openid_plugin_app, edit, delete)
+
+ def test_bad_login(self, openid_plugin_app):
+ """ Test that attempts to login with invalid paramaters"""
+
+ # Test GET request for auth/register page
+ res = openid_plugin_app.get('/auth/register/').follow()
+
+ # Make sure it redirected to the correct place
+ assert urlparse.urlsplit(res.location)[2] == '/auth/openid/login/'
+
+ # Test GET request for auth/login page
+ res = openid_plugin_app.get('/auth/login/')
+ res.follow()
+
+ # Correct redirect?
+ assert urlparse.urlsplit(res.location)[2] == '/auth/openid/login/'
+
+ # Test GET request for auth/openid/register page
+ res = openid_plugin_app.get('/auth/openid/register/')
+ res.follow()
+
+ # Correct redirect?
+ assert urlparse.urlsplit(res.location)[2] == '/auth/openid/login/'
+
+ # Test GET request for auth/openid/login/finish page
+ res = openid_plugin_app.get('/auth/openid/login/finish/')
+ res.follow()
+
+ # Correct redirect?
+ assert urlparse.urlsplit(res.location)[2] == '/auth/openid/login/'
+
+ # Test GET request for auth/openid/login page
+ res = openid_plugin_app.get('/auth/openid/login/')
+
+ # Correct place?
+ assert 'mediagoblin/plugins/openid/login.html' in template.TEMPLATE_TEST_CONTEXT
+
+ # Try to login with an empty form
+ template.clear_test_template_context()
+ openid_plugin_app.post(
+ '/auth/openid/login/', {})
+ context = template.TEMPLATE_TEST_CONTEXT['mediagoblin/plugins/openid/login.html']
+ form = context['login_form']
+ assert form.openid.errors == [u'This field is required.']
+
+ # Try to login with wrong form values
+ template.clear_test_template_context()
+ openid_plugin_app.post(
+ '/auth/openid/login/', {
+ 'openid': 'not_a_url.com'})
+ context = template.TEMPLATE_TEST_CONTEXT['mediagoblin/plugins/openid/login.html']
+ form = context['login_form']
+ assert form.openid.errors == [u'Please enter a valid url.']
+
+ # Should be no users in the db
+ assert User.query.count() == 0
+
+ # Phony OpenID URl
+ template.clear_test_template_context()
+ openid_plugin_app.post(
+ '/auth/openid/login/', {
+ 'openid': 'http://phoney.myopenid.com/'})
+ context = template.TEMPLATE_TEST_CONTEXT['mediagoblin/plugins/openid/login.html']
+ form = context['login_form']
+ assert form.openid.errors == [u'Sorry, the OpenID server could not be found']
+
+ def test_login(self, openid_plugin_app):
+ """Tests that test login and registion with openid"""
+ # Test finish_login redirects correctly when response = False
+ self._setup(openid_plugin_app, False)
+
+ @mock.patch('mediagoblin.plugins.openid.views._finish_verification', self._finish_verification)
+ @mock.patch('mediagoblin.plugins.openid.views._start_verification', self._start_verification)
+ def _test_non_response():
+ template.clear_test_template_context()
+ res = openid_plugin_app.post(
+ '/auth/openid/login/', {
+ 'openid': 'http://phoney.myopenid.com/'})
+ res.follow()
+
+ # Correct Place?
+ assert urlparse.urlsplit(res.location)[2] == '/auth/openid/login/'
+ assert 'mediagoblin/plugins/openid/login.html' in template.TEMPLATE_TEST_CONTEXT
+ _test_non_response()
+
+ # Test login with new openid
+ # Need to clear_test_template_context before calling _setup
+ template.clear_test_template_context()
+ self._setup(openid_plugin_app)
+
+ @mock.patch('mediagoblin.plugins.openid.views._finish_verification', self._finish_verification)
+ @mock.patch('mediagoblin.plugins.openid.views._start_verification', self._start_verification)
+ def _test_new_user():
+ openid_plugin_app.post(
+ '/auth/openid/login/', {
+ 'openid': u'http://real.myopenid.com'})
+
+ # Right place?
+ assert 'mediagoblin/auth/register.html' in template.TEMPLATE_TEST_CONTEXT
+ context = template.TEMPLATE_TEST_CONTEXT['mediagoblin/auth/register.html']
+ register_form = context['register_form']
+
+ # Register User
+ res = openid_plugin_app.post(
+ '/auth/openid/register/', {
+ 'openid': register_form.openid.data,
+ 'username': u'chris',
+ 'email': u'chris@example.com'})
+ res.follow()
+
+ # Correct place?
+ assert urlparse.urlsplit(res.location)[2] == '/u/chris/'
+ assert 'mediagoblin/user_pages/user.html' in template.TEMPLATE_TEST_CONTEXT
+
+ # No need to test if user is in logged in and verification email
+ # awaits, since openid uses the register_user function which is
+ # tested in test_auth
+
+ # Logout User
+ openid_plugin_app.get('/auth/logout')
+
+ # Get user and detach from session
+ test_user = mg_globals.database.User.query.filter_by(
+ username=u'chris').first()
+ Session.expunge(test_user)
+
+ # Log back in
+ # Could not get it to work by 'POST'ing to /auth/openid/login/
+ template.clear_test_template_context()
+ res = openid_plugin_app.post(
+ '/auth/openid/login/finish/', {
+ 'openid': u'http://real.myopenid.com'})
+ res.follow()
+
+ assert urlparse.urlsplit(res.location)[2] == '/'
+ assert 'mediagoblin/root.html' in template.TEMPLATE_TEST_CONTEXT
+
+ # Make sure user is in the session
+ context = template.TEMPLATE_TEST_CONTEXT['mediagoblin/root.html']
+ session = context['request'].session
+ assert session['user_id'] == unicode(test_user.id)
+
+ _test_new_user()
+
+ # Test register with empty form
+ template.clear_test_template_context()
+ openid_plugin_app.post(
+ '/auth/openid/register/', {})
+ context = template.TEMPLATE_TEST_CONTEXT['mediagoblin/auth/register.html']
+ register_form = context['register_form']
+
+ assert register_form.openid.errors == [u'This field is required.']
+ assert register_form.email.errors == [u'This field is required.']
+ assert register_form.username.errors == [u'This field is required.']
+
+ # Try to register with existing username and email
+ template.clear_test_template_context()
+ openid_plugin_app.post(
+ '/auth/openid/register/', {
+ 'openid': 'http://real.myopenid.com',
+ 'email': 'chris@example.com',
+ 'username': 'chris'})
+ context = template.TEMPLATE_TEST_CONTEXT['mediagoblin/auth/register.html']
+ register_form = context['register_form']
+
+ assert register_form.username.errors == [u'Sorry, a user with that name already exists.']
+ assert register_form.email.errors == [u'Sorry, a user with that email address already exists.']
+ assert register_form.openid.errors == [u'Sorry, an account is already registered to that OpenID.']
+
+ def test_add_delete(self, openid_plugin_app):
+ """Test adding and deleting openids"""
+ # Add user
+ test_user = fixture_add_user(password='')
+ openid = OpenIDUserURL()
+ openid.openid_url = 'http://real.myopenid.com'
+ openid.user_id = test_user.id
+ openid.save()
+
+ # Log user in
+ template.clear_test_template_context()
+ self._setup(openid_plugin_app)
+
+ @mock.patch('mediagoblin.plugins.openid.views._finish_verification', self._finish_verification)
+ @mock.patch('mediagoblin.plugins.openid.views._start_verification', self._start_verification)
+ def _login_user():
+ openid_plugin_app.post(
+ '/auth/openid/login/finish/', {
+ 'openid': u'http://real.myopenid.com'})
+
+ _login_user()
+
+ # Try and delete only OpenID url
+ template.clear_test_template_context()
+ res = openid_plugin_app.post(
+ '/edit/openid/delete/', {
+ 'openid': 'http://real.myopenid.com'})
+ assert 'mediagoblin/plugins/openid/delete.html' in template.TEMPLATE_TEST_CONTEXT
+
+ # Add OpenID to user
+ # Empty form
+ template.clear_test_template_context()
+ res = openid_plugin_app.post(
+ '/edit/openid/', {})
+ context = template.TEMPLATE_TEST_CONTEXT['mediagoblin/plugins/openid/add.html']
+ form = context['form']
+ assert form.openid.errors == [u'This field is required.']
+
+ # Try with a bad url
+ template.clear_test_template_context()
+ openid_plugin_app.post(
+ '/edit/openid/', {
+ 'openid': u'not_a_url.com'})
+ context = template.TEMPLATE_TEST_CONTEXT['mediagoblin/plugins/openid/add.html']
+ form = context['form']
+ assert form.openid.errors == [u'Please enter a valid url.']
+
+ # Try with a url that's already registered
+ template.clear_test_template_context()
+ openid_plugin_app.post(
+ '/edit/openid/', {
+ 'openid': 'http://real.myopenid.com'})
+ context = template.TEMPLATE_TEST_CONTEXT['mediagoblin/plugins/openid/add.html']
+ form = context['form']
+ assert form.openid.errors == [u'Sorry, an account is already registered to that OpenID.']
+
+ # Test adding openid to account
+ # Need to clear_test_template_context before calling _setup
+ template.clear_test_template_context()
+ self._setup(openid_plugin_app, edit=True)
+
+ # Need to remove openid_url from db because it was added at setup
+ openid = OpenIDUserURL.query.filter_by(
+ openid_url=u'http://add.myopenid.com')
+ openid.delete()
+
+ @mock.patch('mediagoblin.plugins.openid.views._finish_verification', self._finish_verification)
+ @mock.patch('mediagoblin.plugins.openid.views._start_verification', self._start_verification)
+ def _test_add():
+ # Successful add
+ template.clear_test_template_context()
+ res = openid_plugin_app.post(
+ '/edit/openid/', {
+ 'openid': u'http://add.myopenid.com'})
+ res.follow()
+
+ # Correct place?
+ assert urlparse.urlsplit(res.location)[2] == '/edit/account/'
+ assert 'mediagoblin/edit/edit_account.html' in template.TEMPLATE_TEST_CONTEXT
+
+ # OpenID Added?
+ new_openid = mg_globals.database.OpenIDUserURL.query.filter_by(
+ openid_url=u'http://add.myopenid.com').first()
+ assert new_openid
+
+ _test_add()
+
+ # Test deleting openid from account
+ # Need to clear_test_template_context before calling _setup
+ template.clear_test_template_context()
+ self._setup(openid_plugin_app, delete=True)
+
+ # Need to add OpenID back to user because it was deleted during
+ # patch
+ openid = OpenIDUserURL()
+ openid.openid_url = 'http://add.myopenid.com'
+ openid.user_id = test_user.id
+ openid.save()
+
+ @mock.patch('mediagoblin.plugins.openid.views._finish_verification', self._finish_verification)
+ @mock.patch('mediagoblin.plugins.openid.views._start_verification', self._start_verification)
+ def _test_delete(self, test_user):
+ # Delete openid from user
+ # Create another user to test deleting OpenID that doesn't belong to them
+ new_user = fixture_add_user(username='newman')
+ openid = OpenIDUserURL()
+ openid.openid_url = 'http://realfake.myopenid.com/'
+ openid.user_id = new_user.id
+ openid.save()
+
+ # Try and delete OpenID url that isn't the users
+ template.clear_test_template_context()
+ res = openid_plugin_app.post(
+ '/edit/openid/delete/', {
+ 'openid': 'http://realfake.myopenid.com/'})
+ context = template.TEMPLATE_TEST_CONTEXT['mediagoblin/plugins/openid/delete.html']
+ form = context['form']
+ assert form.openid.errors == [u'That OpenID is not registered to this account.']
+
+ # Delete OpenID
+ # Kind of weird to POST to delete/finish
+ template.clear_test_template_context()
+ res = openid_plugin_app.post(
+ '/edit/openid/delete/finish/', {
+ 'openid': u'http://add.myopenid.com'})
+ res.follow()
+
+ # Correct place?
+ assert urlparse.urlsplit(res.location)[2] == '/edit/account/'
+ assert 'mediagoblin/edit/edit_account.html' in template.TEMPLATE_TEST_CONTEXT
+
+ # OpenID deleted?
+ new_openid = mg_globals.database.OpenIDUserURL.query.filter_by(
+ openid_url=u'http://add.myopenid.com').first()
+ assert not new_openid
+
+ _test_delete(self, test_user)
diff --git a/mediagoblin/tests/test_submission.py b/mediagoblin/tests/test_submission.py
index dd7db12a..ac941063 100644
--- a/mediagoblin/tests/test_submission.py
+++ b/mediagoblin/tests/test_submission.py
@@ -77,7 +77,7 @@ class TestSubmission:
return {'upload_files': [('file', filename)]}
def check_comments(self, request, media_id, count):
- comments = request.db.MediaComment.find({'media_entry': media_id})
+ comments = request.db.MediaComment.query.filter_by(media_entry=media_id)
assert count == len(list(comments))
def test_missing_fields(self):
@@ -122,7 +122,7 @@ class TestSubmission:
assert 'mediagoblin/user_pages/user.html' in context
def check_media(self, request, find_data, count=None):
- media = MediaEntry.find(find_data)
+ media = MediaEntry.query.filter_by(**find_data)
if count is not None:
assert media.count() == count
if count == 0:
@@ -240,8 +240,8 @@ class TestSubmission:
request = context['request']
- media = request.db.MediaEntry.find_one({
- u'title': u'UNIQUE_TITLE_PLS_DONT_CREATE_OTHER_MEDIA_WITH_THIS_TITLE'})
+ media = request.db.MediaEntry.query.filter_by(
+ title=u'UNIQUE_TITLE_PLS_DONT_CREATE_OTHER_MEDIA_WITH_THIS_TITLE').first()
assert media.media_type == 'mediagoblin.media_types.image'
@@ -252,7 +252,7 @@ class TestSubmission:
response, context = self.do_post({'title': title}, do_follow=True,
**self.upload_data(filename))
self.check_url(response, '/u/{0}/'.format(self.test_user.username))
- entry = mg_globals.database.MediaEntry.find_one({'title': title})
+ entry = mg_globals.database.MediaEntry.query.filter_by(title=title).first()
assert entry.state == 'failed'
assert entry.fail_error == u'mediagoblin.processing:BadMediaFail'
diff --git a/mediagoblin/tests/tools.py b/mediagoblin/tests/tools.py
index 2584c62f..98361adc 100644
--- a/mediagoblin/tests/tools.py
+++ b/mediagoblin/tests/tools.py
@@ -164,7 +164,7 @@ def assert_db_meets_expected(db, expected):
for collection_name, collection_data in expected.iteritems():
collection = db[collection_name]
for expected_document in collection_data:
- document = collection.find_one({'id': expected_document['id']})
+ document = collection.query.filter_by(id=expected_document['id']).first()
assert document is not None # make sure it exists
assert document == expected_document # make sure it matches
diff --git a/mediagoblin/tools/session.py b/mediagoblin/tools/session.py
index fdc32523..d79afb66 100644
--- a/mediagoblin/tools/session.py
+++ b/mediagoblin/tools/session.py
@@ -21,6 +21,8 @@ import crypto
_log = logging.getLogger(__name__)
+MAX_AGE = 30 * 24 * 60 * 60
+
class Session(dict):
def __init__(self, *args, **kwargs):
self.send_new_cookie = False
@@ -64,5 +66,10 @@ class SessionManager(object):
elif not session:
response.delete_cookie(self.cookie_name)
else:
+ if session.get('stay_logged_in', False):
+ max_age = MAX_AGE
+ else:
+ max_age = None
+
response.set_cookie(self.cookie_name, self.signer.dumps(session),
- httponly=True)
+ max_age=max_age, httponly=True)
diff --git a/setup.py b/setup.py
index aaf5cda9..6e026f30 100644
--- a/setup.py
+++ b/setup.py
@@ -45,7 +45,7 @@ setup(
'PasteScript',
'wtforms',
'py-bcrypt',
- 'pytest>=2.3',
+ 'pytest>=2.3.1',
'pytest-xdist',
'werkzeug>=0.7',
'celery==2.5.3',