diff options
author | Sam Thursfield <sam.thursfield@codethink.co.uk> | 2015-01-26 11:28:26 +0000 |
---|---|---|
committer | Sam Thursfield <sam.thursfield@codethink.co.uk> | 2015-01-26 11:28:26 +0000 |
commit | d5711ed1063fb5a24661b55403b38d9bd3863fde (patch) | |
tree | f8c764bb4a5ba87bd2c4c1f68c8ee0642ec7e1b4 | |
parent | 87c26966e32fb668b607a4ee5b00768c63afcd47 (diff) | |
parent | eefa3daab918835a60450a8def778c5e11d4fef0 (diff) | |
download | infrastructure-d5711ed1063fb5a24661b55403b38d9bd3863fde.tar.gz |
Merge branch 'sam/openid-fixes'
-rw-r--r-- | README.mdwn | 2 | ||||
-rw-r--r-- | baserock_openid_provider/baserock_openid_provider/forms.py | 29 | ||||
-rw-r--r-- | baserock_openid_provider/baserock_openid_provider/settings.py | 47 | ||||
-rw-r--r-- | baserock_openid_provider/baserock_openid_provider/static/style.css | 268 | ||||
-rw-r--r-- | baserock_openid_provider/baserock_openid_provider/views.py | 36 | ||||
-rw-r--r-- | baserock_openid_provider/cherokee.conf | 21 | ||||
-rw-r--r-- | baserock_openid_provider/local.yml | 19 | ||||
-rw-r--r-- | baserock_openid_provider/openid_provider/views.py | 61 | ||||
-rw-r--r-- | baserock_openid_provider/templates/index.html | 6 | ||||
-rw-r--r-- | baserock_openid_provider/templates/registration/registration_complete.html | 8 | ||||
-rw-r--r-- | database/user_config.yml | 2 |
11 files changed, 475 insertions, 24 deletions
diff --git a/README.mdwn b/README.mdwn index d318eaec..deb3042d 100644 --- a/README.mdwn +++ b/README.mdwn @@ -133,7 +133,7 @@ To deploy this system to production: Now you need to SSH into the system (via the frontend system perhaps) and run the database migrations, before the app will work: - python /srv/baserock_openid_provider/manage.py migrate + sudo -u cherokee python /srv/baserock_openid_provider/manage.py migrate FIXME: I guess this could be done with cloud-init. diff --git a/baserock_openid_provider/baserock_openid_provider/forms.py b/baserock_openid_provider/baserock_openid_provider/forms.py new file mode 100644 index 00000000..dd6a414d --- /dev/null +++ b/baserock_openid_provider/baserock_openid_provider/forms.py @@ -0,0 +1,29 @@ +# Copyright (C) 2015 Codethink Limited +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 2 of the License. +# +# 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + + +from registration.forms import RegistrationForm + +from django import forms +from django.utils.translation import ugettext_lazy as _ + + +class RegistrationFormWithNames(RegistrationForm): + # I'd rather just have a 'Full name' box, but django.contrib.auth is + # already set up to separate first_name and last_name. + + first_name = forms.CharField(label=_("First name(s)"), + required=False) + last_name = forms.CharField(label=_("Surname")) diff --git a/baserock_openid_provider/baserock_openid_provider/settings.py b/baserock_openid_provider/baserock_openid_provider/settings.py index 3a704daf..65092221 100644 --- a/baserock_openid_provider/baserock_openid_provider/settings.py +++ b/baserock_openid_provider/baserock_openid_provider/settings.py @@ -14,7 +14,6 @@ import os BASE_DIR = os.path.dirname(os.path.dirname(__file__)) - # Quick-start development settings - unsuitable for production # See https://docs.djangoproject.com/en/1.7/howto/deployment/checklist/ @@ -32,6 +31,7 @@ ALLOWED_HOSTS = [] # Application definition INSTALLED_APPS = ( + 'baserock_openid_provider', 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', @@ -56,6 +56,35 @@ ROOT_URLCONF = 'baserock_openid_provider.urls' WSGI_APPLICATION = 'baserock_openid_provider.wsgi.application' +# Logging + +LOGGING = { + 'version': 1, + 'disable_existing_loggers': False, + 'handlers': { + 'file': { + 'level': 'DEBUG', + 'class': 'logging.handlers.RotatingFileHandler', + 'filename': '/var/log/baserock_openid_provider/debug.log', + 'maxBytes': 10 * 1024 * 1024, + 'backupCount': 0, + } + }, + 'loggers': { + 'django.request': { + 'handlers': ['file'], + 'level': 'DEBUG', + 'propagate': True, + }, + 'openid_provider.views': { + 'handlers': ['file'], + 'level': 'DEBUG', + 'propagate': True, + } + } +} + + # Database # https://docs.djangoproject.com/en/1.7/ref/settings/#databases @@ -71,7 +100,7 @@ DATABASES = { # gets the IP of the 'baserock-database' container from the # environment, which Docker will have set if you passed it # `--link=baseock-database:db`. - 'HOST': os.environ['DB_PORT_3306_TCP_ADDR'] + 'HOST': os.environ.get('DB_PORT_3306_TCP_ADDR', '192.168.222.30') } } @@ -105,9 +134,23 @@ USE_TZ = True STATIC_URL = '/static/' +STATIC_ROOT = '/var/www/static' + TEMPLATE_DIRS = [os.path.join(BASE_DIR, 'templates')] # Other stuff LOGIN_REDIRECT_URL = '/' + + +# We get mailed when stuff breaks. +ADMINS = ( + ('Sam Thursfield', 'sam.thursfield@codethink.co.uk'), +) + +# FIXME: this email address doesn't actually exist. +DEFAULT_FROM_EMAIL = 'openid@baserock.org' + +EMAIL_HOST = 'localhost' +EMAIL_PORT = 25 diff --git a/baserock_openid_provider/baserock_openid_provider/static/style.css b/baserock_openid_provider/baserock_openid_provider/static/style.css new file mode 100644 index 00000000..e8237b40 --- /dev/null +++ b/baserock_openid_provider/baserock_openid_provider/static/style.css @@ -0,0 +1,268 @@ +// Baserock-ish stylesheet +// Fetched from http://wiki.baserock.org/local.css/ on 2015-01-23. + +/* HTML5 display-role reset for older browsers */ +article, aside, details, figcaption, figure, +footer, header, hgroup, menu, nav, section { + display: block; +} +body { + line-height: 1; +} +ol, ul { + padding: 0 0 0 1.5em; + margin: 0 0 1.2em; +} +li > ul, li > ol { + margin: 0; +} +ul { + list-style: disc; +} +ol { + list-style: decimal; +} +blockquote, q { + quotes: none; +} +blockquote:before, blockquote:after, +q:before, q:after { + content: ''; + content: none; +} +table { + border-collapse: collapse; + border-spacing: 0; +} +i, em { + font-style: italic; +} +b, strong { + font-weight: bold; +} + +/* +Main elements +*/ + +html, body { + font-size: 15px; + font-family: 'Open Sans', sans-serif; + line-height: 1.6em; +} +h1 { + color: #58595B; + font-size: 1.6em; + font-weight: bold; + margin: 0 0 0.4em; + padding: 1em 0 0.3em; +} +h2 { + border-bottom: 2px solid #E0E0E0; + border-top: 2px solid #E0E0E0; + background: #fafafa; + color: #58595B; + font-size: 1.4em; + font-weight: bold; + margin: 1.2em 0 0.4em; + padding: 0.4em 0; +} +h3 { + border-bottom: 2px solid #E0E0E0; + color: #58595B; + font-size: 1.2em; + font-weight: bold; + margin: 2em 0 0.3em; +} +h4 { + color: #58595B; + font-size: 1.1em; + font-weight: bold; + margin: 1.7em 0 0.3em; +} +h5 { + color: #58595B; + font-size: 1em; + font-weight: bold; + margin: 1.7em 0 0.3em; +} +a { + color: #bf2400; +} +p { + padding: 0; + margin: 0 0 1.2em; +} +table { + margin-bottom: 1.2em; +} +th, td { + padding: 0.2em 1em; +} +th { + font-weight: bold; + text-align: left; + border-bottom: 1px solid #ddd; +} +pre { + border: 1px solid #aaa; + border-radius: 0.5em; + padding: 1em 2em; + margin: 0 0 1.2em 2em; + background: #faf8f7; + font-size: 80%; +} +pre, code { + font-family: monospace; +} +code { + background: #faf8f7; + padding: 0.2em 0.4em; + border: 1px solid #ddd; + border-radius: 0.3em; + font-size: 0.9em; +} +pre > code { + background: none; + padding: 0; + border: none; + font-size: 1em; +} +blockquote { + border: .4em solid #ffaa55; + border-left-width: 3em; + padding: 0.3em 1em; + margin: 1.2em 3em; + border-radius: 2.2em 0 0 2.2em; +} +blockquote p { + margin: 0; +} +/* +*/ +.max960 { + max-width: 960px; + margin: 0 auto; + position: relative; + height: 80px; +} +input#searchbox { + background: url("wikiicons/search-bg.gif") no-repeat scroll 100% 50% #FFFFFF; + color: #000000; + padding: 0 16px 0 10px; + border: solid 1px #CCC; + width: 180px; + height: 20px; + border-radius: 10px; +} +#searchform { + right: 0 !important; +} +.page { + max-width: 960px; + padding: 0 10px; + margin: 0 auto; +} +.pageheader { + background-color: #FFF; + border-bottom:2px solid #E65837; + color: #009099; + padding: 10px 10px 0 !important; + height: 80px; + background: #333; +} +.pageheader span a { + color: #FFF; +} +.pageheader span.title { + color: #E65837; +} +.pageheader .actions ul li { + background: none !important; + border-color: #28170B; + border-style: solid solid none; + border-width: 0; + margin: 0; + width: auto !important; + color: #FFF; + padding: 0 !important; +} +.pageheader li a:hover { + background: #E65837; + color: #FFF; +} +.header span { + display: inline-block; + padding: 6px 0; +} +.header span span { + padding: 0; +} +.parentlinks { + font: 13px 'Open Sans', sans-serif; +} + +.title { + font: 13px 'Open Sans', sans-serif; + margin-top: 0.2em; + display:inline; +} + +#logo a { + height: 40px; + width: 282px; + display: block; + padding-bottom: 10px; + background: url(logo.png) no-repeat; +} +#logo a span { + display: none; +} +#logo a:hover { + text-decoration: none; +} +.pageheader .actions { + position: static !important; + width: auto !important; + padding: 0 !important; +} +.pageheader .actions ul { + position: absolute; + right: 0; + bottom: 0; + height: auto !important; + padding: 0 !important; +} +.pageheader .actions a { + color: #FFF; + padding: 5px 0.5em; + display: inline-block; + background: #666; +} + +div.header { + background-repeat: no-repeat; + min-width: 282px; + padding-top: 0px; +} +#pageinfo { + border-top: 0; +} + +#content { + max-width: 51em; +} +#content, #comments, #footer { + margin: 1em 2em 1em 0 !important; +} +.pagedate { + font-size:10px; +} +.sidebar { + padding: 10px !important; + border: solid 1px #CCC !important; + background: #F2F2F2 !important; + margin: 1em 0 2em 1em !important; +} + + diff --git a/baserock_openid_provider/baserock_openid_provider/views.py b/baserock_openid_provider/baserock_openid_provider/views.py index 5d80186e..3efaf923 100644 --- a/baserock_openid_provider/baserock_openid_provider/views.py +++ b/baserock_openid_provider/baserock_openid_provider/views.py @@ -1,4 +1,4 @@ -# Copyright (C) 2014 Codethink Limited +# Copyright (C) 2015 Codethink Limited # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -14,8 +14,42 @@ # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +import registration.backends.simple.views + +from registration import signals +from registration.users import UserModel + +from django.contrib.auth import authenticate +from django.contrib.auth import login from django.shortcuts import render +from . import forms + def index(request): return render(request, '../templates/index.html') + + +class RegistrationViewWithNames(registration.backends.simple.views.RegistrationView): + # Overrides the django-registration default view so that the extended form + # including the full name gets used. + form_class = forms.RegistrationFormWithNames + + def register(self, request, **cleaned_data): + # It's a shame that we have to override the whole class here. We could + # patch django-registration(-redux) to avoid the need. + username, email, password = cleaned_data['username'], cleaned_data['email'], cleaned_data['password1'] + first_name, last_name = cleaned_data['first_name'], cleaned_data['last_name'] + UserModel().objects.create_user(username, email, password, + first_name=first_name, + last_name=last_name) + + new_user = authenticate(username=username, password=password) + login(request, new_user) + signals.user_registered.send(sender=self.__class__, + user=new_user, + request=request) + return new_user + + +registration.backends.simple.views.RegistrationView = RegistrationViewWithNames diff --git a/baserock_openid_provider/cherokee.conf b/baserock_openid_provider/cherokee.conf index b1e557fc..38c4f1fa 100644 --- a/baserock_openid_provider/cherokee.conf +++ b/baserock_openid_provider/cherokee.conf @@ -14,7 +14,8 @@ server!server_tokens = full server!timeout = 15 server!user = cherokee -# One virtual server which communicates with the uwsgi-django source. +# One virtual server which communicates with the uwsgi-django code and +# also serves static files. vserver!1!directory_index = index.html vserver!1!document_root = /var/www/cherokee vserver!1!error_writer!filename = /var/log/cherokee/error_log @@ -24,14 +25,16 @@ vserver!1!logger!access!buffsize = 16384 vserver!1!logger!access!filename = /var/log/cherokee/access_log vserver!1!logger!access!type = file vserver!1!nick = default -vserver!1!rule!1!document_root = /var/www -vserver!1!rule!1!handler = uwsgi -vserver!1!rule!1!handler!balancer = round_robin -vserver!1!rule!1!handler!balancer!source!10 = 1 -vserver!1!rule!1!handler!iocache = 1 -vserver!1!rule!1!match = default - -# The uWSGI communication is set up here. +vserver!1!rule!110!document_root = /var/www/static +vserver!1!rule!110!handler = file +vserver!1!rule!110!match = directory +vserver!1!rule!110!match!directory = /static +vserver!1!rule!10!document_root = /var/www +vserver!1!rule!10!handler = uwsgi +vserver!1!rule!10!handler!balancer = round_robin +vserver!1!rule!10!handler!balancer!source!10 = 1 +vserver!1!rule!10!handler!iocache = 1 +vserver!1!rule!10!match = default source!1!env_inherited = 1 source!1!host = 127.0.0.1:45023 source!1!interpreter = /usr/sbin/uwsgi --socket 127.0.0.1:45023 --ini=/srv/baserock_openid_provider/uwsgi.ini diff --git a/baserock_openid_provider/local.yml b/baserock_openid_provider/local.yml index 49d5c4aa..a09d03ba 100644 --- a/baserock_openid_provider/local.yml +++ b/baserock_openid_provider/local.yml @@ -12,6 +12,9 @@ - name: install PIP package manager yum: name=python-pip state=latest + - name: install Sendmail mail transfer agent + yum: name=sendmail state=latest + - name: install uWSGI application container server and Python plugin yum: name=uwsgi-plugin-python state=latest @@ -37,4 +40,18 @@ yum: name=MySQL-python state=latest - name: install Cherokee configuration - shell: ln -sf /srv/baserock_openid_provider/cherokee.conf /etc/cherokee/cherokee.conf + file: src=/srv/baserock_openid_provider/cherokee.conf dest=/etc/cherokee/cherokee.conf state=link + + - name: create log directory for baserock_openid_provider + file: path=/var/log/baserock_openid_provider owner=cherokee group=cherokee state=directory + + - name: create directory for static content + file: path=/var/www/static owner=cherokee group=cherokee state=directory + + - name: install static content + django_manage: app_path=/srv/baserock_openid_provider command=collectstatic + + # Default configuration of Sendmail in Fedora is to only accept connections from + # localhost. This is what we want. + - name: enable and start sendmail service + service: name=sendmail enabled=yes state=started diff --git a/baserock_openid_provider/openid_provider/views.py b/baserock_openid_provider/openid_provider/views.py index e7f6e4b7..55907aaf 100644 --- a/baserock_openid_provider/openid_provider/views.py +++ b/baserock_openid_provider/openid_provider/views.py @@ -91,8 +91,7 @@ def openid_server(request): validated = True if openid is not None and (validated or trust_root_valid == 'Valid'): - id_url = request.build_absolute_uri( - reverse('openid-provider-identity', args=[openid.openid])) + id_url = orequest.identity oresponse = orequest.answer(True, identity=id_url) logger.debug('orequest.answer(True, identity="%s")', id_url) elif orequest.immediate: @@ -101,6 +100,12 @@ def openid_server(request): else: request.session['OPENID_REQUEST'] = orequest.message.toPostArgs() request.session['OPENID_TRUSTROOT_VALID'] = trust_root_valid + logger.debug( + 'Set OPENID_REQUEST to %s in session %s', + request.session['OPENID_REQUEST'], request.session) + logger.debug( + 'Set OPENID_TRUSTROOT_VALID to %s in session %s', + request.session['OPENID_TRUSTROOT_VALID'], request.session) logger.debug('redirecting to decide page') return HttpResponseRedirect(reverse('openid-provider-decide')) else: @@ -126,6 +131,26 @@ def openid_xrds(request, identity=False, id=None): 'endpoints': endpoints, }, context_instance=RequestContext(request), content_type=YADIS_CONTENT_TYPE) + +def url_for_openid(request, openid): + return request.build_absolute_uri( + reverse('openid-provider-identity', args=[openid.openid])) + + +def openid_not_found_error_message(request, identity_url): + ids = request.user.openid_set + if ids.count() == 0: + message = "You have no OpenIDs configured. Contact the administrator." + else: + id_urls = [url_for_openid(request, id) for id in ids.iterator()] + id_urls = ', '.join(id_urls) + if ids.count() != 1: + message = "You somehow have multiple OpenIDs: " + id_urls + else: + message = "Your OpenID URL is: " + id_urls + return "You do not have the OpenID '%s'. %s" % (identity_url, message) + + def openid_decide(request): """ The page that asks the user if they really want to sign in to the site, and @@ -137,13 +162,26 @@ def openid_decide(request): orequest = server.decodeRequest(request.session.get('OPENID_REQUEST')) trust_root_valid = request.session.get('OPENID_TRUSTROOT_VALID') + logger.debug('Got OPENID_REQUEST %s, OPENID_TRUSTROOT_VALID %s from ' + 'session %s', orequest, trust_root_valid, request.session) + if not request.user.is_authenticated(): return landing_page(request, orequest) + if orequest is None: + # This isn't normal, but can occur if the user uses the 'back' button + # or if the session data is otherwise lost for some reason. + return error_page( + request, "I've lost track of your session now. Sorry! Please go " + "back to the site you are logging in to with a Baserock " + "OpenID and, if you're not yet logged in, try again.") + openid = openid_get_identity(request, orequest.identity) if openid is None: - return error_page( - request, "You are signed in but you don't have OpenID here!") + # User should only ever have one OpenID, created for them when they + # registered. + message = openid_not_found_error_message(request, orequest.identity) + return error_page(request, message) if request.method == 'POST' and request.POST.get('decide_page', False): if request.POST.get('allow', False): @@ -198,6 +236,9 @@ def landing_page(request, orequest, login_url=None, them to log in manually is displayed. """ request.session['OPENID_REQUEST'] = orequest.message.toPostArgs() + logger.debug( + 'Set OPENID_REQUEST to %s in session %s', + request.session['OPENID_REQUEST'], request.session) if not login_url: login_url = settings.LOGIN_URL path = request.get_full_path() @@ -234,11 +275,17 @@ def openid_get_identity(request, identity_url): - if user has no default one, return any - in other case return None! """ + logger.debug('Looking for %s in user %s set of OpenIDs %s', + identity_url, request.user, request.user.openid_set) + if not identity_url.endswith('/'): + identity_url += '/' for openid in request.user.openid_set.iterator(): - if identity_url == request.build_absolute_uri( - reverse('openid-provider-identity', args=[openid.openid])): + logger.debug( + 'Comparing: %s with %s', identity_url, + url_for_openid(request, openid)) + if identity_url == url_for_openid(request, openid): return openid - if identity_url == 'http://specs.openid.net/auth/2.0/identifier_select': + if identity_url == 'http://specs.openid.net/auth/2.0/identifier_select/': # no claim was made, choose user default openid: openids = request.user.openid_set.filter(default=True) if openids.count() == 1: diff --git a/baserock_openid_provider/templates/index.html b/baserock_openid_provider/templates/index.html index d5748a83..b2f46630 100644 --- a/baserock_openid_provider/templates/index.html +++ b/baserock_openid_provider/templates/index.html @@ -5,7 +5,11 @@ <p>This is the Baserock OpenID provider.</p> {% if user.is_authenticated %} - <p>Your OpenID is: <a href="http://openid.baserock.org/openid/{{ user.username }}">http://openid.baserock.org/openid/{{ user.username }}</a></p> + <p>You are registered as {{ user.get_full_name }}.</p> + + <p>Your OpenID is: + <a href="http://openid.baserock.org/openid/{{ user.username }}/">http://openid.baserock.org/openid/{{ user.username }}/</a> + </p> {% endif %} {% endblock %} diff --git a/baserock_openid_provider/templates/registration/registration_complete.html b/baserock_openid_provider/templates/registration/registration_complete.html index 96a22ac4..7e6670aa 100644 --- a/baserock_openid_provider/templates/registration/registration_complete.html +++ b/baserock_openid_provider/templates/registration/registration_complete.html @@ -2,5 +2,9 @@ {% load i18n %} {% block content %} -<p>{% trans "You are now registered. Activation email sent." %}</p> -{% endblock %}
\ No newline at end of file +<p>You are now registered as {{ user.get_full_name }}.</p> + +<p>Your OpenID is: +<a href="http://openid.baserock.org/openid/{{ user.username }}/">http://openid.baserock.org/openid/{{ user.username }}/</a> +</p> +{% endblock %} diff --git a/database/user_config.yml b/database/user_config.yml index 0318222a..a9be0332 100644 --- a/database/user_config.yml +++ b/database/user_config.yml @@ -39,6 +39,8 @@ login_host=127.0.0.1 login_user=root login_password={{ root_password }} + collation='utf8_unicode_ci', + encoding='utf8', with_items: - openid_provider - storyboard |