aboutsummaryrefslogtreecommitdiffstats
path: root/mediagoblin/plugins
diff options
context:
space:
mode:
Diffstat (limited to 'mediagoblin/plugins')
-rw-r--r--mediagoblin/plugins/api/tools.py24
-rw-r--r--mediagoblin/plugins/api/views.py4
-rw-r--r--mediagoblin/plugins/basic_auth/README.rst24
-rw-r--r--mediagoblin/plugins/oauth/__init__.py12
-rw-r--r--mediagoblin/plugins/oauth/tools.py2
-rw-r--r--mediagoblin/plugins/oauth/views.py3
-rw-r--r--mediagoblin/plugins/openid/README.rst34
-rw-r--r--mediagoblin/plugins/openid/__init__.py2
-rw-r--r--mediagoblin/plugins/openid/templates/mediagoblin/plugins/openid/login.html1
-rw-r--r--mediagoblin/plugins/openid/templates/mediagoblin/plugins/openid/login_link.html2
-rw-r--r--mediagoblin/plugins/persona/README.rst41
-rw-r--r--mediagoblin/plugins/persona/__init__.py116
-rw-r--r--mediagoblin/plugins/persona/forms.py41
-rw-r--r--mediagoblin/plugins/persona/models.py36
-rw-r--r--mediagoblin/plugins/persona/static/js/persona.js51
-rw-r--r--mediagoblin/plugins/persona/templates/mediagoblin/plugins/persona/edit.html43
-rw-r--r--mediagoblin/plugins/persona/templates/mediagoblin/plugins/persona/edit_link.html25
-rw-r--r--mediagoblin/plugins/persona/templates/mediagoblin/plugins/persona/login_link.html25
-rw-r--r--mediagoblin/plugins/persona/templates/mediagoblin/plugins/persona/persona.html32
-rw-r--r--mediagoblin/plugins/persona/templates/mediagoblin/plugins/persona/persona_js_end.html21
-rw-r--r--mediagoblin/plugins/persona/templates/mediagoblin/plugins/persona/register_link.html25
-rw-r--r--mediagoblin/plugins/persona/views.py191
22 files changed, 720 insertions, 35 deletions
diff --git a/mediagoblin/plugins/api/tools.py b/mediagoblin/plugins/api/tools.py
index 92411f4b..d1b3ebb1 100644
--- a/mediagoblin/plugins/api/tools.py
+++ b/mediagoblin/plugins/api/tools.py
@@ -51,30 +51,6 @@ class Auth(object):
def __call__(self, request, *args, **kw):
raise NotImplemented()
-
-def json_response(serializable, _disable_cors=False, *args, **kw):
- '''
- Serializes a json objects and returns a werkzeug Response object with the
- serialized value as the response body and Content-Type: application/json.
-
- :param serializable: A json-serializable object
-
- Any extra arguments and keyword arguments are passed to the
- Response.__init__ method.
- '''
- response = Response(json.dumps(serializable), *args, content_type='application/json', **kw)
-
- if not _disable_cors:
- cors_headers = {
- 'Access-Control-Allow-Origin': '*',
- 'Access-Control-Allow-Methods': 'POST, GET, OPTIONS',
- 'Access-Control-Allow-Headers': 'Content-Type, X-Requested-With'}
- for key, value in cors_headers.iteritems():
- response.headers.set(key, value)
-
- return response
-
-
def get_entry_serializable(entry, urlgen):
'''
Returns a serializable dict() of a MediaEntry instance.
diff --git a/mediagoblin/plugins/api/views.py b/mediagoblin/plugins/api/views.py
index 9159fe65..b7e74799 100644
--- a/mediagoblin/plugins/api/views.py
+++ b/mediagoblin/plugins/api/views.py
@@ -21,11 +21,11 @@ from os.path import splitext
from werkzeug.exceptions import BadRequest, Forbidden
from werkzeug.wrappers import Response
+from mediagoblin.tools.response import json_response
from mediagoblin.decorators import require_active_login
from mediagoblin.meddleware.csrf import csrf_exempt
from mediagoblin.media_types import sniff_media
-from mediagoblin.plugins.api.tools import api_auth, get_entry_serializable, \
- json_response
+from mediagoblin.plugins.api.tools import api_auth, get_entry_serializable
from mediagoblin.submit.lib import check_file_field, prepare_queue_task, \
run_process_media, new_upload_entry
diff --git a/mediagoblin/plugins/basic_auth/README.rst b/mediagoblin/plugins/basic_auth/README.rst
new file mode 100644
index 00000000..82f247ed
--- /dev/null
+++ b/mediagoblin/plugins/basic_auth/README.rst
@@ -0,0 +1,24 @@
+.. _basic_auth-chapter:
+
+===================
+ basic_auth plugin
+===================
+
+The basic_auth plugin is enabled by default in mediagoblin.ini. This plugin
+provides basic username and password authentication for GNU Mediagoblin.
+
+This plugin can be enabled alongside :ref:`openid-chapter` and
+:ref:`persona-chapter`.
+
+Set up the basic_auth plugin
+============================
+
+1. Add the following to your MediaGoblin .ini file in the ``[plugins]`` section::
+
+ [[mediagoblin.plugins.basic_auth]]
+
+2. Run::
+
+ gmg assetlink
+
+ in order to link basic_auth's static assets
diff --git a/mediagoblin/plugins/oauth/__init__.py b/mediagoblin/plugins/oauth/__init__.py
index 5762379d..82c1f380 100644
--- a/mediagoblin/plugins/oauth/__init__.py
+++ b/mediagoblin/plugins/oauth/__init__.py
@@ -35,22 +35,22 @@ def setup_plugin():
routes = [
('mediagoblin.plugins.oauth.authorize',
- '/oauth/authorize',
+ '/oauth-2/authorize',
'mediagoblin.plugins.oauth.views:authorize'),
('mediagoblin.plugins.oauth.authorize_client',
- '/oauth/client/authorize',
+ '/oauth-2/client/authorize',
'mediagoblin.plugins.oauth.views:authorize_client'),
('mediagoblin.plugins.oauth.access_token',
- '/oauth/access_token',
+ '/oauth-2/access_token',
'mediagoblin.plugins.oauth.views:access_token'),
('mediagoblin.plugins.oauth.list_connections',
- '/oauth/client/connections',
+ '/oauth-2/client/connections',
'mediagoblin.plugins.oauth.views:list_connections'),
('mediagoblin.plugins.oauth.register_client',
- '/oauth/client/register',
+ '/oauth-2/client/register',
'mediagoblin.plugins.oauth.views:register_client'),
('mediagoblin.plugins.oauth.list_clients',
- '/oauth/client/list',
+ '/oauth-2/client/list',
'mediagoblin.plugins.oauth.views:list_clients')]
pluginapi.register_routes(routes)
diff --git a/mediagoblin/plugins/oauth/tools.py b/mediagoblin/plugins/oauth/tools.py
index 27ff32b4..af0a3305 100644
--- a/mediagoblin/plugins/oauth/tools.py
+++ b/mediagoblin/plugins/oauth/tools.py
@@ -23,7 +23,7 @@ from datetime import datetime
from functools import wraps
-from mediagoblin.plugins.api.tools import json_response
+from mediagoblin.tools.response import json_response
def require_client_auth(controller):
diff --git a/mediagoblin/plugins/oauth/views.py b/mediagoblin/plugins/oauth/views.py
index d6fd314f..de637d6b 100644
--- a/mediagoblin/plugins/oauth/views.py
+++ b/mediagoblin/plugins/oauth/views.py
@@ -21,7 +21,7 @@ from urllib import urlencode
from werkzeug.exceptions import BadRequest
-from mediagoblin.tools.response import render_to_response, redirect
+from mediagoblin.tools.response import render_to_response, redirect, json_response
from mediagoblin.decorators import require_active_login
from mediagoblin.messages import add_message, SUCCESS
from mediagoblin.tools.translate import pass_to_ugettext as _
@@ -31,7 +31,6 @@ from mediagoblin.plugins.oauth.forms import ClientRegistrationForm, \
AuthorizationForm
from mediagoblin.plugins.oauth.tools import require_client_auth, \
create_token
-from mediagoblin.plugins.api.tools import json_response
_log = logging.getLogger(__name__)
diff --git a/mediagoblin/plugins/openid/README.rst b/mediagoblin/plugins/openid/README.rst
new file mode 100644
index 00000000..870a2b58
--- /dev/null
+++ b/mediagoblin/plugins/openid/README.rst
@@ -0,0 +1,34 @@
+.. _openid-chapter:
+
+===================
+ openid plugin
+===================
+
+The openid plugin allows user to login to your GNU Mediagoblin instance using
+their openid url.
+
+This plugin can be enabled alongside :ref:`basic_auth-chapter` and
+:ref:`persona-chapter`.
+
+.. note::
+ When :ref:`basic_auth-chapter` is enabled alongside this openid plugin, and
+ a user creates an account using their openid. If they would like to add a
+ password to their account, they can use the forgot password feature to do
+ so.
+
+
+Set up the openid plugin
+============================
+
+1. Install the ``python-openid`` package.
+
+2. Add the following to your MediaGoblin .ini file in the ``[plugins]`` section::
+
+ [[mediagoblin.plugins.openid]]
+
+3. Run::
+
+ gmg dbupdate
+
+ in order to create and apply migrations to any database tables that the
+ plugin requires.
diff --git a/mediagoblin/plugins/openid/__init__.py b/mediagoblin/plugins/openid/__init__.py
index ee88808c..ca17a7e8 100644
--- a/mediagoblin/plugins/openid/__init__.py
+++ b/mediagoblin/plugins/openid/__init__.py
@@ -120,4 +120,6 @@ hooks = {
'auth_no_pass_redirect': no_pass_redirect,
('mediagoblin.auth.register',
'mediagoblin/auth/register.html'): add_to_form_context,
+ ('mediagoblin.auth.login',
+ 'mediagoblin/auth/login.html'): add_to_form_context
}
diff --git a/mediagoblin/plugins/openid/templates/mediagoblin/plugins/openid/login.html b/mediagoblin/plugins/openid/templates/mediagoblin/plugins/openid/login.html
index 33df7200..8d74c2b9 100644
--- a/mediagoblin/plugins/openid/templates/mediagoblin/plugins/openid/login.html
+++ b/mediagoblin/plugins/openid/templates/mediagoblin/plugins/openid/login.html
@@ -44,6 +44,7 @@
{% trans %}Log in to create an account!{% endtrans %}
</p>
{% endif %}
+ {% template_hook('login_link') %}
{% if pass_auth is defined %}
<p>
<a href="{{ request.urlgen('mediagoblin.auth.login') }}?{{ request.query_string }}">
diff --git a/mediagoblin/plugins/openid/templates/mediagoblin/plugins/openid/login_link.html b/mediagoblin/plugins/openid/templates/mediagoblin/plugins/openid/login_link.html
index e5e77d01..fa4d5e85 100644
--- a/mediagoblin/plugins/openid/templates/mediagoblin/plugins/openid/login_link.html
+++ b/mediagoblin/plugins/openid/templates/mediagoblin/plugins/openid/login_link.html
@@ -17,9 +17,11 @@
#}
{% block openid_login_link %}
+ {% if openid_link is defined %}
<p>
<a href="{{ request.urlgen('mediagoblin.plugins.openid.login') }}?{{ request.query_string }}">
{%- trans %}Or login with OpenID!{% endtrans %}
</a>
</p>
+ {% endif %}
{% endblock %}
diff --git a/mediagoblin/plugins/persona/README.rst b/mediagoblin/plugins/persona/README.rst
new file mode 100644
index 00000000..ef19ac5d
--- /dev/null
+++ b/mediagoblin/plugins/persona/README.rst
@@ -0,0 +1,41 @@
+.. _persona-chapter:
+
+================
+ persona plugin
+================
+
+The persona plugin allows users to login to you GNU MediaGoblin instance using
+`Mozilla Persona`_.
+
+This plugin can be enabled alongside :ref:`openid-chapter` and
+:ref:`basic_auth-chapter`.
+
+.. note::
+ When :ref:`basic_auth-chapter` is enabled alongside this persona plugin, and
+ a user creates an account using their persona. If they would like to add a
+ password to their account, they can use the forgot password feature to do
+ so.
+
+.. _Mozilla Persona: https://www.mozilla.org/en-US/persona/
+
+Set up the persona plugin
+=========================
+
+1. Install the ``requests`` package.
+
+2. Add the following to your MediaGoblin .ini file in the ``[plugins]`` section::
+
+ [[mediagoblin.plugins.persona]]
+
+3. Run::
+
+ gmg dbupdate
+
+ in order to create and apply migrations to any database tables that the
+ plugin requires.
+
+4. Run::
+
+ gmg assetlink
+
+ in order to persona's static assets.
diff --git a/mediagoblin/plugins/persona/__init__.py b/mediagoblin/plugins/persona/__init__.py
new file mode 100644
index 00000000..700c18e2
--- /dev/null
+++ b/mediagoblin/plugins/persona/__init__.py
@@ -0,0 +1,116 @@
+# 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 pkg_resources import resource_filename
+import os
+
+from sqlalchemy import or_
+
+from mediagoblin.auth.tools import create_basic_user
+from mediagoblin.db.models import User
+from mediagoblin.plugins.persona.models import PersonaUserEmails
+from mediagoblin.tools import pluginapi
+from mediagoblin.tools.staticdirect import PluginStatic
+from mediagoblin.tools.translate import pass_to_ugettext as _
+
+PLUGIN_DIR = os.path.dirname(__file__)
+
+
+def setup_plugin():
+ config = pluginapi.get_config('mediagoblin.plugins.persona')
+
+ routes = [
+ ('mediagoblin.plugins.persona.login',
+ '/auth/persona/login/',
+ 'mediagoblin.plugins.persona.views:login'),
+ ('mediagoblin.plugins.persona.register',
+ '/auth/persona/register/',
+ 'mediagoblin.plugins.persona.views:register'),
+ ('mediagoblin.plugins.persona.edit',
+ '/edit/persona/',
+ 'mediagoblin.plugins.persona.views:edit'),
+ ('mediagoblin.plugins.persona.add',
+ '/edit/persona/add/',
+ 'mediagoblin.plugins.persona.views:add')]
+
+ pluginapi.register_routes(routes)
+ pluginapi.register_template_path(os.path.join(PLUGIN_DIR, 'templates'))
+ pluginapi.register_template_hooks(
+ {'persona_end': 'mediagoblin/plugins/persona/persona_js_end.html',
+ 'persona_form': 'mediagoblin/plugins/persona/persona.html',
+ 'edit_link': 'mediagoblin/plugins/persona/edit_link.html',
+ 'login_link': 'mediagoblin/plugins/persona/login_link.html',
+ 'register_link': 'mediagoblin/plugins/persona/register_link.html'})
+
+
+def create_user(register_form):
+ if 'persona_email' 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 = PersonaUserEmails()
+ new_entry.persona_email = register_form.persona_email.data
+ new_entry.user_id = user.id
+ new_entry.save()
+
+ return user
+
+
+def extra_validation(register_form):
+ persona_email = register_form.persona_email.data if 'persona_email' in \
+ register_form else None
+ if persona_email:
+ persona_email_exists = PersonaUserEmails.query.filter_by(
+ persona_email=persona_email
+ ).count()
+
+ extra_validation_passes = True
+
+ if persona_email_exists:
+ register_form.persona_email.errors.append(
+ _('Sorry, an account is already registered to that Persona'
+ ' email.'))
+ extra_validation_passes = False
+
+ return extra_validation_passes
+
+
+def Auth():
+ return True
+
+
+def add_to_global_context(context):
+ if len(pluginapi.hook_runall('authentication')) == 1:
+ context['persona_auth'] = True
+ context['persona'] = True
+ return context
+
+hooks = {
+ 'setup': setup_plugin,
+ 'authentication': Auth,
+ 'auth_extra_validation': extra_validation,
+ 'auth_create_user': create_user,
+ 'template_global_context': add_to_global_context,
+ 'static_setup': lambda: PluginStatic(
+ 'coreplugin_persona',
+ resource_filename('mediagoblin.plugins.persona', 'static'))
+}
diff --git a/mediagoblin/plugins/persona/forms.py b/mediagoblin/plugins/persona/forms.py
new file mode 100644
index 00000000..608be0c7
--- /dev/null
+++ b/mediagoblin/plugins/persona/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):
+ 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)])
+ persona_email = wtforms.HiddenField(
+ '',
+ [wtforms.validators.Required(),
+ normalize_user_or_email_field(allow_user=False)])
+
+
+class EditForm(wtforms.Form):
+ email = wtforms.TextField(
+ _('Email address'),
+ [wtforms.validators.Required(),
+ normalize_user_or_email_field(allow_user=False)])
diff --git a/mediagoblin/plugins/persona/models.py b/mediagoblin/plugins/persona/models.py
new file mode 100644
index 00000000..ff3c525a
--- /dev/null
+++ b/mediagoblin/plugins/persona/models.py
@@ -0,0 +1,36 @@
+# 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 PersonaUserEmails(Base):
+ __tablename__ = "persona__user_emails"
+
+ id = Column(Integer, primary_key=True)
+ persona_email = Column(Unicode, nullable=False)
+ user_id = Column(Integer, ForeignKey(User.id), nullable=False)
+
+ # Persona's are owned by their user, so do the full thing.
+ user = relationship(User, backref=backref('persona_emails',
+ cascade='all, delete-orphan'))
+
+MODELS = [
+ PersonaUserEmails
+]
diff --git a/mediagoblin/plugins/persona/static/js/persona.js b/mediagoblin/plugins/persona/static/js/persona.js
new file mode 100644
index 00000000..a6def398
--- /dev/null
+++ b/mediagoblin/plugins/persona/static/js/persona.js
@@ -0,0 +1,51 @@
+/**
+ * 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/>.
+ */
+
+$(document).ready(function () {
+ var signinLink = document.getElementById('persona_login');
+ if (signinLink) {
+ signinLink.onclick = function() { navigator.id.request(); };
+ }
+
+ var signinLink1 = document.getElementById('persona_login1');
+ if (signinLink1) {
+ signinLink1.onclick = function() { navigator.id.request(); };
+ }
+
+ var signoutLink = document.getElementById('logout');
+ if (signoutLink) {
+ signoutLink.onclick = function() { navigator.id.logout(); };
+ }
+
+ var logout_url = document.getElementById('_logout_url').value;
+
+ navigator.id.watch({
+ onlogin: function(assertion) {
+ document.getElementById('_assertion').value = assertion;
+ document.getElementById('_persona_login').submit()
+ },
+ onlogout: function() {
+ $.ajax({
+ type: 'GET',
+ url: logout_url,
+ success: function(res, status, xhr) { window.location.reload(); },
+ error: function(xhr, status, err) { alert("Logout failure: " + err); }
+ });
+ }
+ });
+});
diff --git a/mediagoblin/plugins/persona/templates/mediagoblin/plugins/persona/edit.html b/mediagoblin/plugins/persona/templates/mediagoblin/plugins/persona/edit.html
new file mode 100644
index 00000000..be62b8cc
--- /dev/null
+++ b/mediagoblin/plugins/persona/templates/mediagoblin/plugins/persona/edit.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 %}Add an OpenID{% endtrans %} &mdash; {{ super() }}
+{%- endblock %}
+
+{% block mediagoblin_content %}
+ <form action="{{ request.urlgen('mediagoblin.plugins.persona.edit') }}"
+ method="POST" enctype="multipart/form-data">
+ {{ csrf_token }}
+ <div class="form_box">
+ <h1>{% trans %}Delete a Persona email address{% endtrans %}</h1>
+ <p>
+ <a href="javascript:;" id="persona_login">
+ {% trans %}Add a Persona email address{% 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/persona/templates/mediagoblin/plugins/persona/edit_link.html b/mediagoblin/plugins/persona/templates/mediagoblin/plugins/persona/edit_link.html
new file mode 100644
index 00000000..08879da5
--- /dev/null
+++ b/mediagoblin/plugins/persona/templates/mediagoblin/plugins/persona/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 persona_edit_link %}
+ <p>
+ <a href="{{ request.urlgen('mediagoblin.plugins.persona.edit') }}">
+ {% trans %}Edit your Persona email addresses{% endtrans %}
+ </a>
+ </p>
+{% endblock %}
diff --git a/mediagoblin/plugins/persona/templates/mediagoblin/plugins/persona/login_link.html b/mediagoblin/plugins/persona/templates/mediagoblin/plugins/persona/login_link.html
new file mode 100644
index 00000000..975683da
--- /dev/null
+++ b/mediagoblin/plugins/persona/templates/mediagoblin/plugins/persona/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 person_login_link %}
+ <p>
+ <a href="javascript:;" id="persona_login">
+ {% trans %}Or login with Persona!{% endtrans %}
+ </a>
+ </p>
+{% endblock %}
diff --git a/mediagoblin/plugins/persona/templates/mediagoblin/plugins/persona/persona.html b/mediagoblin/plugins/persona/templates/mediagoblin/plugins/persona/persona.html
new file mode 100644
index 00000000..372bd246
--- /dev/null
+++ b/mediagoblin/plugins/persona/templates/mediagoblin/plugins/persona/persona.html
@@ -0,0 +1,32 @@
+{#
+# 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 persona %}
+ <form id="_persona_login"
+ action=
+ {%- if edit_persona is defined -%}
+ "{{ request.urlgen('mediagoblin.plugins.persona.add') }}"
+ {%- else -%}
+ "{{ request.urlgen('mediagoblin.plugins.persona.login') }}"
+ {%- endif %}
+ method="POST">
+ {{ csrf_token }}
+ <input type="hidden" name="assertion" type="text" id="_assertion"/>
+ <input type="hidden" name="_logout_url" type="text" id="_logout_url"
+ value="{{ request.urlgen('mediagoblin.auth.logout') }}"/>
+ </form>
+{% endblock %}
diff --git a/mediagoblin/plugins/persona/templates/mediagoblin/plugins/persona/persona_js_end.html b/mediagoblin/plugins/persona/templates/mediagoblin/plugins/persona/persona_js_end.html
new file mode 100644
index 00000000..8c0d72d5
--- /dev/null
+++ b/mediagoblin/plugins/persona/templates/mediagoblin/plugins/persona/persona_js_end.html
@@ -0,0 +1,21 @@
+{#
+# 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/>.
+#}
+
+<script src="https://login.persona.org/include.js"></script>
+<script type="text/javascript"
+ src="{{ request.staticdirect('/js/persona.js', 'coreplugin_persona') }}"></script>
diff --git a/mediagoblin/plugins/persona/templates/mediagoblin/plugins/persona/register_link.html b/mediagoblin/plugins/persona/templates/mediagoblin/plugins/persona/register_link.html
new file mode 100644
index 00000000..bcd9ae2b
--- /dev/null
+++ b/mediagoblin/plugins/persona/templates/mediagoblin/plugins/persona/register_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 persona_register_link %}
+ <p>
+ <a href="javascript:;" id="persona_login">
+ {% trans %}Or register with Persona!{% endtrans %}
+ </a>
+ </p>
+{% endblock %}
diff --git a/mediagoblin/plugins/persona/views.py b/mediagoblin/plugins/persona/views.py
new file mode 100644
index 00000000..f3aff38d
--- /dev/null
+++ b/mediagoblin/plugins/persona/views.py
@@ -0,0 +1,191 @@
+# 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 json
+import logging
+import requests
+
+from werkzeug.exceptions import BadRequest
+
+from mediagoblin import messages, mg_globals
+from mediagoblin.auth.tools import register_user
+from mediagoblin.decorators import (auth_enabled, allow_registration,
+ require_active_login)
+from mediagoblin.tools.response import render_to_response, redirect
+from mediagoblin.tools.translate import pass_to_ugettext as _
+from mediagoblin.plugins.persona import forms
+from mediagoblin.plugins.persona.models import PersonaUserEmails
+
+_log = logging.getLogger(__name__)
+
+
+def _get_response(request):
+ if 'assertion' not in request.form:
+ _log.debug('assertion not in request.form')
+ raise BadRequest()
+
+ data = {'assertion': request.form['assertion'],
+ 'audience': request.urlgen('index', qualified=True)}
+ resp = requests.post('https://verifier.login.persona.org/verify',
+ data=data, verify=True)
+
+ if resp.ok:
+ verification_data = json.loads(resp.content)
+
+ if verification_data['status'] == 'okay':
+ return verification_data['email']
+
+ return None
+
+
+@auth_enabled
+def login(request):
+ if request.method == 'GET':
+ return redirect(request, 'mediagoblin.auth.login')
+
+ email = _get_response(request)
+ if email:
+ query = PersonaUserEmails.query.filter_by(
+ persona_email=email
+ ).first()
+ user = query.user if query else None
+
+ if user:
+ request.session['user_id'] = unicode(user.id)
+ request.session.save()
+
+ return redirect(request, "index")
+
+ else:
+ if not mg_globals.app.auth:
+ messages.add_message(
+ request,
+ messages.WARNING,
+ _('Sorry, authentication is disabled on this instance.'))
+
+ return redirect(request, 'index')
+
+ register_form = forms.RegistrationForm(email=email,
+ persona_email=email)
+ return render_to_response(
+ request,
+ 'mediagoblin/auth/register.html',
+ {'register_form': register_form,
+ 'post_url': request.urlgen(
+ 'mediagoblin.plugins.persona.register')})
+
+ return redirect(request, 'mediagoblin.auth.login')
+
+
+@allow_registration
+@auth_enabled
+def register(request):
+ if request.method == 'GET':
+ # Need to connect to persona before registering a user. If method is
+ # 'GET', then this page was acessed without logging in first.
+ return redirect(request, 'mediagoblin.auth.login')
+ register_form = 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.persona.register')})
+
+
+@require_active_login
+def edit(request):
+ form = forms.EditForm(request.form)
+
+ if request.method == 'POST' and form.validate():
+ query = PersonaUserEmails.query.filter_by(
+ persona_email=form.email.data)
+ user = query.first().user if query.first() else None
+
+ if user and user.id == int(request.user.id):
+ count = len(user.persona_emails)
+
+ if count > 1 or user.pw_hash:
+ # User has more then one Persona email or also has a password.
+ query.first().delete()
+
+ messages.add_message(
+ request,
+ messages.SUCCESS,
+ _('The Persona email address was successfully removed.'))
+
+ return redirect(request, 'mediagoblin.edit.account')
+
+ elif not count > 1:
+ form.email.errors.append(
+ _("You can't delete your only Persona email address unless"
+ " you have a password set."))
+
+ else:
+ form.email.errors.append(
+ _('That Persona email address is not registered to this'
+ ' account.'))
+
+ return render_to_response(
+ request,
+ 'mediagoblin/plugins/persona/edit.html',
+ {'form': form,
+ 'edit_persona': True})
+
+
+@require_active_login
+def add(request):
+ if request.method == 'GET':
+ return redirect(request, 'mediagoblin.plugins.persona.edit')
+
+ email = _get_response(request)
+
+ if email:
+ query = PersonaUserEmails.query.filter_by(
+ persona_email=email
+ ).first()
+ user_exists = query.user if query else None
+
+ if user_exists:
+ messages.add_message(
+ request,
+ messages.WARNING,
+ _('Sorry, an account is already registered with that Persona'
+ ' email address.'))
+ return redirect(request, 'mediagoblin.plugins.persona.edit')
+
+ else:
+ # Save the Persona Email to the user
+ new_entry = PersonaUserEmails()
+ new_entry.persona_email = email
+ new_entry.user_id = request.user.id
+ new_entry.save()
+
+ messages.add_message(
+ request,
+ messages.SUCCESS,
+ _('Your Person email address was saved successfully.'))
+
+ return redirect(request, 'mediagoblin.edit.account')