summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSam Thursfield <sam.thursfield@codethink.co.uk>2015-01-26 11:28:26 +0000
committerSam Thursfield <sam.thursfield@codethink.co.uk>2015-01-26 11:28:26 +0000
commitd5711ed1063fb5a24661b55403b38d9bd3863fde (patch)
treef8c764bb4a5ba87bd2c4c1f68c8ee0642ec7e1b4
parent87c26966e32fb668b607a4ee5b00768c63afcd47 (diff)
parenteefa3daab918835a60450a8def778c5e11d4fef0 (diff)
downloadinfrastructure-d5711ed1063fb5a24661b55403b38d9bd3863fde.tar.gz
Merge branch 'sam/openid-fixes'
-rw-r--r--README.mdwn2
-rw-r--r--baserock_openid_provider/baserock_openid_provider/forms.py29
-rw-r--r--baserock_openid_provider/baserock_openid_provider/settings.py47
-rw-r--r--baserock_openid_provider/baserock_openid_provider/static/style.css268
-rw-r--r--baserock_openid_provider/baserock_openid_provider/views.py36
-rw-r--r--baserock_openid_provider/cherokee.conf21
-rw-r--r--baserock_openid_provider/local.yml19
-rw-r--r--baserock_openid_provider/openid_provider/views.py61
-rw-r--r--baserock_openid_provider/templates/index.html6
-rw-r--r--baserock_openid_provider/templates/registration/registration_complete.html8
-rw-r--r--database/user_config.yml2
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