Retire project

In Queens development cycle, openstack_auth code was merged
into the horizon repository.

blueprint merge-openstack-auth

Change-Id: I74b10a90fe79fc768cfb8de6f68d3cd2f4938e51
changes/28/523928/5
Akihiro Motoki 2017-11-30 01:45:01 +09:00
parent 1fa9ae26cc
commit 2baea728dd
72 changed files with 8 additions and 8265 deletions

14
.gitignore vendored
View File

@ -1,14 +0,0 @@
*.pyc
*.egg
*.egg-info
*.mo
.DS_STORE
doc/build
build
dist
.tox
AUTHORS
ChangeLog
.coverage
reports
coverage.xml

View File

@ -1,4 +0,0 @@
[gerrit]
host=review.openstack.org
port=29418
project=openstack/django_openstack_auth.git

View File

@ -1,5 +0,0 @@
<david.lyle@hp.com> <david-lyle@davidlyle-VirtualBox.(none)>
<lin-hua.cheng@hp.com> <lin_hua_cheng@yahoo.com>
Eric Peterson <ericpeterson@hp.com> ericpeterson-l <ericpeterson@hp.com>
Eric Peterson <ericpeterson@hp.com> erpet <ericpeterson@hp.com>
Lin Hua Cheng <lin-hua.cheng@hp.com> linhuacheng <lin-hua.cheng@hp.com>

View File

@ -1,17 +0,0 @@
If you would like to contribute to the development of OpenStack,
you must follow the steps documented at:
http://docs.openstack.org/infra/manual/developers.html
If you already have a good understanding of how the system works and
your OpenStack accounts are set up, you can skip to the development
workflow section of this documentation to learn how changes to
OpenStack should be submitted for review via the Gerrit tool:
http://docs.openstack.org/infra/manual/developers.html#development-workflow
Pull requests submitted through GitHub will be ignored.
Bugs should be filed on Launchpad, not GitHub:
https://bugs.launchpad.net/django-openstack-auth

176
LICENSE
View File

@ -1,176 +0,0 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.

View File

@ -1,6 +0,0 @@
include AUTHORS
include ChangeLog
exclude .gitignore
exclude .gitreview
global-exclude *.pyc

View File

@ -1,27 +1,10 @@
========================
Team and repository tags
========================
The code has been merged into openstack/horizon.
.. image:: http://governance.openstack.org/badges/django_openstack_auth.svg
:target: http://governance.openstack.org/reference/tags/index.html
The contents of this repository are still available in the Git
source code management system. To see the contents of this
repository before it was merged into the horizon repo,
please check out the previous commit with "git checkout HEAD^1".
.. Change things from this point on
=====================
Django OpenStack Auth
=====================
Django OpenStack Auth is a pluggable Django authentication backend that
works with Django's ``contrib.auth`` framework to authenticate a user against
OpenStack's Keystone Identity API.
The current version is designed to work with the Keystone v2.0 and v3 API.
You can `view the installation instructions`_ on Read The Docs.
.. _view the installation instructions: http://docs.openstack.org/developer/django_openstack_auth/
* License: Apache License, Version 2.0
* Documentation: http://django-openstack-auth.readthedocs.org/en/latest/
* Source: http://git.openstack.org/cgit/openstack/django_openstack_auth/
* Bugs: https://bugs.launchpad.net/django-openstack-auth
For any further questions, please email
openstack-dev@lists.openstack.org or
join #openstack-horizon on Freenode.

View File

@ -1 +0,0 @@
[python: **.py]

View File

@ -1,170 +0,0 @@
# -*- coding: utf-8 -*-
#
# Django OpenStack Auth documentation build configuration file, created by
# sphinx-quickstart on Sun Jul 8 15:13:36 2012.
#
# This file is execfile()d with the current directory set to its containing dir.
#
# Note that not all possible configuration values are present in this
# autogenerated file.
#
# All configuration values have a default; values that are commented out
# serve to show the default.
import os
import django
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'openstack_auth.tests.settings')
django.setup()
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
#sys.path.insert(0, os.path.abspath('.'))
# -- General configuration -----------------------------------------------------
# If your documentation needs a minimal Sphinx version, state it here.
#needs_sphinx = '1.0'
# Add any Sphinx extension module names here, as strings. They can be extensions
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
extensions = ['sphinx.ext.autodoc', 'sphinx.ext.viewcode', 'openstackdocstheme']
# openstackdocstheme options
repository_name = 'openstack/django_openstack_auth'
bug_project = 'django-openstack-auth'
bug_tag = ''
html_last_updated_fmt = '%Y-%m-%d %H:%M'
# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']
# The suffix of source filenames.
source_suffix = '.rst'
# The encoding of source files.
#source_encoding = 'utf-8-sig'
# The master toctree document.
master_doc = 'index'
# General information about the project.
project = u'Django OpenStack Auth'
copyright = u'2012, Gabriel Hurley'
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
#language = None
# There are two options for replacing |today|: either, you set today to some
# non-false value, then it is used:
#today = ''
# Else, today_fmt is used as the format for a strftime call.
#today_fmt = '%B %d, %Y'
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
exclude_patterns = ['_build']
# The reST default role (used for this markup: `text`) to use for all documents.
#default_role = None
# If true, '()' will be appended to :func: etc. cross-reference text.
#add_function_parentheses = True
# If true, the current module name will be prepended to all description
# unit titles (such as .. function::).
#add_module_names = True
# If true, sectionauthor and moduleauthor directives will be shown in the
# output. They are ignored by default.
#show_authors = False
# The name of the Pygments (syntax highlighting) style to use.
pygments_style = 'sphinx'
# A list of ignored prefixes for module index sorting.
modindex_common_prefix = ['openstack_auth.']
# -- Options for HTML output ---------------------------------------------------
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
html_theme = 'openstackdocs'
# Theme options are theme-specific and customize the look and feel of a theme
# further. For a list of options available for each theme, see the
# documentation.
#html_theme_options = {}
# Add any paths that contain custom themes here, relative to this directory.
#html_theme_path = []
# The name for this set of Sphinx documents. If None, it defaults to
# "<project> v<release> documentation".
#html_title = None
# A shorter title for the navigation bar. Default is the same as html_title.
#html_short_title = None
# The name of an image file (relative to this directory) to place at the top
# of the sidebar.
#html_logo = None
# The name of an image file (within the static path) to use as favicon of the
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
# pixels large.
#html_favicon = None
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
#html_static_path = []
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
# using the given strftime format.
#html_last_updated_fmt = '%b %d, %Y'
# If true, SmartyPants will be used to convert quotes and dashes to
# typographically correct entities.
#html_use_smartypants = True
# Custom sidebar templates, maps document names to template names.
#html_sidebars = {}
# Additional templates that should be rendered to pages, maps page names to
# template names.
#html_additional_pages = {}
# If false, no module index is generated.
#html_domain_indices = True
# If false, no index is generated.
#html_use_index = True
# If true, the index is split into individual pages for each letter.
#html_split_index = False
# If true, links to the reST sources are added to the pages.
#html_show_sourcelink = True
# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
#html_show_sphinx = True
# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
#html_show_copyright = True
# If true, an OpenSearch description file will be output, and all pages will
# contain a <link> tag referring to it. The value of this option must be the
# base URL from which the finished HTML is served.
#html_use_opensearch = ''
# This is the file name suffix for HTML files (e.g. ".xhtml").
#html_file_suffix = None
# Output file base name for HTML help builder.
htmlhelp_basename = 'DjangoOpenStackAuthdoc'

View File

@ -1,442 +0,0 @@
=============
Configuration
=============
Django OpenStack Auth is configured through Django ``settings.py`` file.
In most cases it is used combined with the OpenStack Dashboard,
so the settings file will be ``local/local_settings.py`` file
in your OpenStack Dashboard deployment.
This page covers the configuration options referred by Django OpenStack Auth.
:ref:`Some settings <settings-shared-with-horizon>` are also referred to
by Horizon. Configure them carefully.
General settings
================
``AUTHENTICATION_PLUGINS``
--------------------------
Default: ``['openstack_auth.plugin.password.PasswordPlugin', 'openstack_auth.plugin.token.TokenPlugin']``
A list of authentication plugins to be used.
In most cases, there is no need to configure this.
``AVAILABLE_REGIONS``
---------------------
Default: ``None``
A list of tuples which define multiple regions. The tuple format is
``('http://{{ keystone_host }}:5000/v2.0', '{{ region_name }}')``. If any regions
are specified the login form will have a dropdown selector for authenticating
to the appropriate region, and there will be a region switcher dropdown in
the site header when logged in.
You should also define ``OPENSTACK_KEYSTONE_URL`` to indicate which of
the regions is the default one.
``DEFAULT_SERVICE_REGIONS``
---------------------------
Default: ``{}``
The default service region is set on a per-endpoint basis, meaning that once
the user logs into some Keystone endpoint, if a default service region is
defined for it in this setting and exists within Keystone catalog, it will be
set as the initial service region in this endpoint. By default it is an empty
dictionary because upstream can neither predict service region names in a
specific deployment, nor tell whether this behavior is desired. The key of the
dictionary is a full url of a Keystone endpoint with version suffix, the value
is a region name.
Example::
DEFAULT_SERVICE_REGIONS = {
OPENSTACK_KEYSTONE_URL: 'RegionOne'
}
``OPENSTACK_API_VERSIONS``
--------------------------
Default::
{
"identity": 2.0,
...,
}
Overrides for OpenStack API versions. Use this setting to force the
OpenStack dashboard to use a specific API version for a given service API.
Django OpenStack Auth refers to only the ``"identity"`` entry.
The current valid values are "2.0" or "3".
.. note::
See `Horizon settings
<https://docs.openstack.org/developer/horizon/install/settings.html#openstack-api-versions>`__
for the full description of this setting.
``OPENSTACK_ENDPOINT_TYPE``
---------------------------
Default: ``"publicURL"``
A string which specifies the endpoint type to use for the endpoints in the
Keystone service catalog. The default value for all services except for
identity is ``"publicURL"``. The default value for the identity service is
``"internalURL"``.
``OPENSTACK_KEYSTONE_ADMIN_ROLES``
----------------------------------
Default: ``["admin"]``
The list of roles that have administrator privileges in this OpenStack
installation. This check is very basic and essentially only works with
keystone v2.0 and v3 with the default policy file. The setting assumes there
is a common ``admin`` like role(s) across services. Example uses of this
setting are:
* to rename the ``admin`` role to ``cloud-admin``
* allowing multiple roles to have administrative privileges, like
``["admin", "cloud-admin", "net-op"]``
``OPENSTACK_KEYSTONE_DEFAULT_DOMAIN``
-------------------------------------
Default: ``"Default"``
Overrides the default domain used when running on single-domain model
with Keystone V3. All entities will be created in the default domain.
.. note::
This value must be the name of the default domain, NOT the ID.
Also, you will most likely have a value in the keystone policy file like
``"cloud_admin": "rule:admin_required and domain_id:<your domain id>"``.
This value must be the name of the domain whose ID is specified there.
``OPENSTACK_KEYSTONE_DOMAIN_CHOICES``
-------------------------------------
.. versionadded:: 12.0.0(Pike)
Default::
(
('Default', 'Default'),
)
If OPENSTACK_KEYSTONE_DOMAIN_DROPDOWN is enabled, this option can be used to
set the available domains to choose from. This is a list of pairs whose first
value is the domain name and the second is the display name.
``OPENSTACK_KEYSTONE_DOMAIN_DROPDOWN``
--------------------------------------
.. versionadded:: 12.0.0(Pike)
Default: ``False``
Set this to True if you want available domains displayed as a dropdown menu on
the login screen. It is strongly advised NOT to enable this for public clouds,
as advertising enabled domains to unauthenticated customers irresponsibly
exposes private information. This should only be used for private clouds where
the dashboard sits behind a corporate firewall.
``OPENSTACK_KEYSTONE_MULTIDOMAIN_SUPPORT``
------------------------------------------
Default: ``False``
Set this to True if running on multi-domain model. When this is enabled, it
will require user to enter the Domain name in addition to username for login.
``OPENSTACK_KEYSTONE_URL``
--------------------------
Default: ``"http://%s:5000/v2.0" % OPENSTACK_HOST``
The full URL for the Keystone endpoint used for authentication. Unless you
are using HTTPS, running your Keystone server on a nonstandard port, or using
a nonstandard URL scheme you shouldn't need to touch this setting.
``OPENSTACK_SSL_CACERT``
------------------------
Default: ``None``
When unset or set to ``None`` the default CA certificate on the system is used
for SSL verification.
When set with the path to a custom CA certificate file, this overrides use of
the default system CA certificate. This custom certificate is used to verify all
connections to openstack services when making API calls.
``OPENSTACK_SSL_NO_VERIFY``
---------------------------
Default: ``False``
Disable SSL certificate checks in the OpenStack clients (useful for self-signed
certificates).
``OPENSTACK_TOKEN_HASH_ALGORITHM``
----------------------------------
Default: ``"md5"``
The hash algorithm to use for authentication tokens. This must match the hash
algorithm that the identity (Keystone) server and the auth_token middleware
are using. Allowed values are the algorithms supported by Python's hashlib
library.
``OPENSTACK_TOKEN_HASH_ENABLED``
--------------------------------
(Deprecated)
Default: ``True``
Hashing tokens from Keystone keeps the Horizon session data smaller, but it
doesn't work in some cases when using PKI tokens. Uncomment this value and
set it to False if using PKI tokens and there are 401 errors due to token
hashing.
This option is now marked as "deprecated" and will be removed in Ocata or a
later release. PKI tokens currently work with hashing, and Keystone will soon
deprecate usage of PKI tokens.
``PASSWORD_EXPIRES_WARNING_THRESHOLD_DAYS``
-------------------------------------------
Default: ``-1``
Password will have an expiration date when using keystone v3 and enabling the
feature. This setting allows you to set the number of days that the user will
be alerted prior to the password expiration. Once the password expires keystone
will deny the access and users must contact an admin to change their password.
Setting this value to ``N`` days means the user will be alerted when the
password expires in less than ``N+1`` days. ``-1`` disables the feature.
``POLICY_DIRS``
----------------
Default: ``{}``
Specifies a list of policy directories per service types. The directories
are relative to ``POLICY_FILES_PATH``. Services whose additional policies
are defined here must be defined in ``POLICY_FILES`` too. Otherwise,
additional policies specified in ``POLICY_DIRS`` are not loaded.
Example::
POLICY_DIRS = {
'identity': 'keystone_policy.d',
'compute': 'nova_policy.d'
}
``POLICY_FILES``
----------------
Default: ``{'identity': 'keystone_policy.json', 'compute': 'nova_policy.json'}``
This should essentially be the mapping of the contents of ``POLICY_FILES_PATH``
to service types. When policy.json files are added to ``POLICY_FILES_PATH``,
they should be included here too.
``POLICY_FILES_PATH``
---------------------
Default: ``os.path.join(ROOT_PATH, "conf")``
Specifies where service based policy files are located. These are used to
define the policy rules actions are verified against.
``SECURE_PROXY_ADDR_HEADER``
----------------------------
Default: ``False``
If horizon is behind a proxy server and the proxy is configured, the IP address
from request is passed using header variables inside the request. The header
name depends on a proxy or a load-balancer. This setting specifies the name of
the header with remote IP address. The main use is for authentication log
(success or fail) displaing the IP address of the user.
The commom value for this setting is ``HTTP_X_REAL_IP`` or
``HTTP_X_FORWARDED_FOR``.
If not present, then ``REMOTE_ADDR`` header is used. (``REMOTE_ADDR`` is the
field of Django HttpRequest object which contains IP address of the client.)
``SESSION_TIMEOUT``
-------------------
Default: ``"3600"``
This ``SESSION_TIMEOUT`` is a method to supercede the token timeout with a
shorter horizon session timeout (in seconds). So if your token expires in
60 minutes, a value of 1800 will log users out after 30 minutes.
``TOKEN_DELETION_DISABLED``
---------------------------
Default: ``False``
This setting allows deployers to control whether a token is deleted on log out.
This can be helpful when there are often long running processes being run
in the Horizon environment.
``TOKEN_TIMEOUT_MARGIN``
------------------------
Default: ``0``
A time margin in seconds to subtract from the real token's validity.
An example usage is that the token can be valid once the middleware
passed, and invalid (timed-out) during a view rendering and this
generates authorization errors during the view rendering.
By setting this value to some smaller seconds, you can avoid token
expiration during a view rendering.
``WEBROOT``
-----------
Default: ``"/"``
Specifies the location where the access to the dashboard is configured in
the web server.
For example, if you're accessing the Dashboard via
https://<your server>/dashboard, you would set this to ``"/dashboard/"``.
.. note::
Additional settings may be required in the config files of your webserver
of choice. For example to make ``"/dashboard/"`` the web root in Apache,
the ``"sites-available/horizon.conf"`` requires a couple of additional
aliases set::
Alias /dashboard/static %HORIZON_DIR%/static
Alias /dashboard/media %HORIZON_DIR%/openstack_dashboard/static
Apache also requires changing your WSGIScriptAlias to reflect the desired
path. For example, you'd replace ``/`` with ``/dashboard`` for the
alias.
Web SSO (Single Sign On) settings
=================================
``WEBSSO_ENABLED``
------------------
Default: ``False``
Enables keystone web single-sign-on if set to True. For this feature to work,
make sure that you are using Keystone V3 and Django OpenStack Auth V1.2.0 or
later.
``WEBSSO_INITIAL_CHOICE``
-------------------------
Default: ``"credentials"``
Determines the default authentication mechanism. When user lands on the login
page, this is the first choice they will see.
``WEBSSO_CHOICES``
------------------
Default::
(
("credentials", _("Keystone Credentials")),
("oidc", _("OpenID Connect")),
("saml2", _("Security Assertion Markup Language"))
)
This is the list of authentication mechanisms available to the user. It
includes Keystone federation protocols such as OpenID Connect and SAML, and
also keys that map to specific identity provider and federation protocol
combinations (as defined in ``WEBSSO_IDP_MAPPING``). The list of choices is
completely configurable, so as long as the id remains intact. Do not remove
the credentials mechanism unless you are sure. Once removed, even admins will
have no way to log into the system via the dashboard.
``WEBSSO_IDP_MAPPING``
----------------------
Default: ``{}``
A dictionary of specific identity provider and federation protocol combinations.
From the selected authentication mechanism, the value will be looked up as keys
in the dictionary. If a match is found, it will redirect the user to a identity
provider and federation protocol specific WebSSO endpoint in keystone, otherwise
it will use the value as the protocol_id when redirecting to the WebSSO by
protocol endpoint.
Example::
WEBSSO_CHOICES = (
("credentials", _("Keystone Credentials")),
("oidc", _("OpenID Connect")),
("saml2", _("Security Assertion Markup Language")),
("acme_oidc", "ACME - OpenID Connect"),
("acme_saml2", "ACME - SAML2")
)
WEBSSO_IDP_MAPPING = {
"acme_oidc": ("acme", "oidc"),
"acme_saml2": ("acme", "saml2")
}
.. note::
The value is expected to be a tuple formatted as: (<idp_id>, <protocol_id>).
K2K (Keystone to Keystone) Federation settings
==============================================
``KEYSTONE_PROVIDER_IDP_NAME``
------------------------------
Default: ``Local Keystone``
The Keystone Provider drop down uses Keystone to Keystone federation
to switch between Keystone service providers.
This sets display name for Identity Provider (dropdown display name).
``KEYSTONE_PROVIDER_IDP_ID``
----------------------------
Default:: ``localkeystone``
This ID is used for only for comparison with the service provider IDs.
This ID should not match any service provider IDs.
.. _settings-shared-with-horizon:
Settings shared with Horizon
============================
The following settings in Django OpenStack Auth are also used by Horizon.
* ``AVAILABLE_REGIONS``
* ``OPENSTACK_API_VERSIONS``
* ``OPENSTACK_KEYSTONE_URL``
* ``OPENSTACK_ENDPOINT_TYPE``
* ``OPENSTACK_SSL_CACERT``
* ``OPENSTACK_SSL_NO_VERIFY``
* ``WEBROOT``
Django OpenStack Auth also refers to the following Django settings.
For more detail, see `Django settings documentation
<https://docs.djangoproject.com/en/1.11/ref/settings/#auth>`__.
They are usually configured as part of Horizon settings.
* ``LOGIN_REDIRECT_URL``
* ``LOGIN_URL``
* ``SESSION_ENGINE``
* ``USE_TZ``

View File

@ -1,21 +0,0 @@
=====================
Django OpenStack Auth
=====================
Django OpenStack Auth is a pluggable Django authentication backend that
works with Django's ``contrib.auth`` framework to authenticate a user against
OpenStack's Keystone Identity API.
The current version is designed to work with the Keystone V2 or V3 API.
.. toctree::
:maxdepth: 2
install/index
configuration/index
reference/index
* :ref:`genindex`
* :ref:`modindex`
* :ref:`search`

View File

@ -1,63 +0,0 @@
===============
Getting Started
===============
Installation
============
Installing is quick and easy:
#. Run ``pip install django_openstack_auth``.
#. Add ``openstack_auth`` to ``settings.INSTALLED_APPS``.
#. Add ``'openstack_auth.backend.KeystoneBackend'`` to your
``settings.AUTHENTICATION_BACKENDS``, e.g.::
AUTHENTICATION_BACKENDS = ('openstack_auth.backend.KeystoneBackend',)
#. Configure your API endpoint(s) in ``settings.py``::
OPENSTACK_KEYSTONE_URL = "http://example.com:5000/v3"
#. Include ``'openstack_auth.urls'`` somewhere in your ``urls.py`` file.
#. Use it as you would any other Django auth backend.
Running Tests
=============
Before running tests, you should have ``tox`` installed and available in your
environment:
.. code-block:: bash
$ pip install tox
.. NOTE::
You may need to perform both the above operation and the next inside a
python virtualenv, or prefix the above command with ``sudo``, depending on
your preference.
To execute the full suite of tests maintained within the project, simply run:
.. code-block:: bash
$ tox
.. NOTE::
The first time you run ``tox``, it will take additional time to build
virtualenvs. You can later use the ``-r`` option with ``tox`` to rebuild
your virtualenv in a similar manner.
To run tests for one or more specific test environments (for example, the most
common configuration of Python 2.7 and PEP-8), list the environments with the
``-e`` option, separated by spaces:
.. code-block:: bash
$ tox -e py27,pep8
See ``tox.ini`` for the full list of available test environments.

View File

@ -1,6 +0,0 @@
==================
The Backend Module
==================
.. automodule:: openstack_auth.backend
:members:

View File

@ -1,6 +0,0 @@
================
The Forms Module
================
.. automodule:: openstack_auth.forms
:members:

View File

@ -1,13 +0,0 @@
=====================================
Django OpenStack Auth API Reference
=====================================
.. toctree::
:maxdepth: 2
user
views
forms
backend
utils

View File

@ -1,6 +0,0 @@
==============
The User Class
==============
.. automodule:: openstack_auth.user
:members:

View File

@ -1,6 +0,0 @@
================
The Utils Module
================
.. automodule:: openstack_auth.utils
:members:

View File

@ -1,6 +0,0 @@
================
The Views Module
================
.. automodule:: openstack_auth.views
:members:

View File

@ -1,17 +0,0 @@
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import pbr.version
__version__ = pbr.version.VersionInfo('django_openstack_auth').version_string()

View File

@ -1,282 +0,0 @@
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.
""" Module defining the Django auth backend class for the Keystone API. """
import datetime
import logging
import pytz
from django.conf import settings
from django.utils.module_loading import import_string
from django.utils.translation import ugettext_lazy as _
from openstack_auth import exceptions
from openstack_auth import user as auth_user
from openstack_auth import utils
LOG = logging.getLogger(__name__)
KEYSTONE_CLIENT_ATTR = "_keystoneclient"
class KeystoneBackend(object):
"""Django authentication backend for use with ``django.contrib.auth``."""
def __init__(self):
self._auth_plugins = None
@property
def auth_plugins(self):
if self._auth_plugins is None:
plugins = getattr(
settings,
'AUTHENTICATION_PLUGINS',
['openstack_auth.plugin.password.PasswordPlugin',
'openstack_auth.plugin.token.TokenPlugin'])
self._auth_plugins = [import_string(p)() for p in plugins]
return self._auth_plugins
def check_auth_expiry(self, auth_ref, margin=None):
if not utils.is_token_valid(auth_ref, margin):
msg = _("The authentication token issued by the Identity service "
"has expired.")
LOG.warning("The authentication token issued by the Identity "
"service appears to have expired before it was "
"issued. This may indicate a problem with either your "
"server or client configuration.")
raise exceptions.KeystoneAuthException(msg)
return True
def get_user(self, user_id):
"""Returns the current user from the session data.
If authenticated, this return the user object based on the user ID
and session data.
.. note::
This required monkey-patching the ``contrib.auth`` middleware
to make the ``request`` object available to the auth backend class.
"""
if (hasattr(self, 'request') and
user_id == self.request.session["user_id"]):
token = self.request.session['token']
endpoint = self.request.session['region_endpoint']
services_region = self.request.session['services_region']
user = auth_user.create_user_from_token(self.request, token,
endpoint, services_region)
return user
else:
return None
def authenticate(self, auth_url=None, **kwargs):
"""Authenticates a user via the Keystone Identity API."""
LOG.debug('Beginning user authentication')
if not auth_url:
auth_url = settings.OPENSTACK_KEYSTONE_URL
auth_url, url_fixed = utils.fix_auth_url_version_prefix(auth_url)
if url_fixed:
LOG.warning("The OPENSTACK_KEYSTONE_URL setting points to a v2.0 "
"Keystone endpoint, but v3 is specified as the API "
"version to use by Horizon. Using v3 endpoint for "
"authentication.")
for plugin in self.auth_plugins:
unscoped_auth = plugin.get_plugin(auth_url=auth_url, **kwargs)
if unscoped_auth:
break
else:
msg = _('No authentication backend could be determined to '
'handle the provided credentials.')
LOG.warning('No authentication backend could be determined to '
'handle the provided credentials. This is likely a '
'configuration error that should be addressed.')
raise exceptions.KeystoneAuthException(msg)
# the recent project id a user might have set in a cookie
recent_project = None
request = kwargs.get('request')
if request:
# Grab recent_project found in the cookie, try to scope
# to the last project used.
recent_project = request.COOKIES.get('recent_project')
unscoped_auth_ref = plugin.get_access_info(unscoped_auth)
# Check expiry for our unscoped auth ref.
self.check_auth_expiry(unscoped_auth_ref)
domain_name = kwargs.get('user_domain_name', None)
domain_auth, domain_auth_ref = plugin.get_domain_scoped_auth(
unscoped_auth, unscoped_auth_ref, domain_name)
scoped_auth, scoped_auth_ref = plugin.get_project_scoped_auth(
unscoped_auth, unscoped_auth_ref, recent_project=recent_project)
# Abort if there are no projects for this user and a valid domain
# token has not been obtained
#
# The valid use cases for a user login are:
# Keystone v2: user must have a role on a project and be able
# to obtain a project scoped token
# Keystone v3: 1) user can obtain a domain scoped token (user
# has a role on the domain they authenticated to),
# only, no roles on a project
# 2) user can obtain a domain scoped token and has
# a role on a project in the domain they
# authenticated to (and can obtain a project scoped
# token)
# 3) user cannot obtain a domain scoped token, but can
# obtain a project scoped token
if not scoped_auth_ref and domain_auth_ref:
# if the user can't obtain a project scoped token, set the scoped
# token to be the domain token, if valid
scoped_auth = domain_auth
scoped_auth_ref = domain_auth_ref
elif not scoped_auth_ref and not domain_auth_ref:
msg = _('You are not authorized for any projects.')
if utils.get_keystone_version() >= 3:
msg = _('You are not authorized for any projects or domains.')
raise exceptions.KeystoneAuthException(msg)
# Check expiry for our new scoped token.
self.check_auth_expiry(scoped_auth_ref)
# We want to try to use the same region we just logged into
# which may or may not be the default depending upon the order
# keystone uses
region_name = None
id_endpoints = scoped_auth_ref.service_catalog.\
get_endpoints(service_type='identity')
for id_endpoint in [cat for cat in id_endpoints['identity']]:
if auth_url in id_endpoint.values():
region_name = id_endpoint['region']
break
interface = getattr(settings, 'OPENSTACK_ENDPOINT_TYPE', 'public')
endpoint, url_fixed = utils.fix_auth_url_version_prefix(
scoped_auth_ref.service_catalog.url_for(
service_type='identity',
interface=interface,
region_name=region_name))
if url_fixed:
LOG.warning("The Keystone URL in service catalog points to a v2.0 "
"Keystone endpoint, but v3 is specified as the API "
"version to use by Horizon. Using v3 endpoint for "
"authentication.")
# If we made it here we succeeded. Create our User!
unscoped_token = unscoped_auth_ref.auth_token
user = auth_user.create_user_from_token(
request,
auth_user.Token(scoped_auth_ref, unscoped_token=unscoped_token),
endpoint,
services_region=region_name)
if request is not None:
# if no k2k providers exist then the function returns quickly
utils.store_initial_k2k_session(auth_url, request, scoped_auth_ref,
unscoped_auth_ref)
request.session['unscoped_token'] = unscoped_token
if domain_auth_ref:
# check django session engine, if using cookies, this will not
# work, as it will overflow the cookie so don't add domain
# scoped token to the session and put error in the log
if utils.using_cookie_backed_sessions():
LOG.error('Using signed cookies as SESSION_ENGINE with '
'OPENSTACK_KEYSTONE_MULTIDOMAIN_SUPPORT is '
'enabled. This disables the ability to '
'perform identity operations due to cookie size '
'constraints.')
else:
request.session['domain_token'] = domain_auth_ref
request.user = user
timeout = getattr(settings, "SESSION_TIMEOUT", 3600)
token_life = user.token.expires - datetime.datetime.now(pytz.utc)
session_time = min(timeout, int(token_life.total_seconds()))
request.session.set_expiry(session_time)
keystone_client_class = utils.get_keystone_client().Client
session = utils.get_session()
scoped_client = keystone_client_class(session=session,
auth=scoped_auth)
# Support client caching to save on auth calls.
setattr(request, KEYSTONE_CLIENT_ATTR, scoped_client)
LOG.debug('Authentication completed.')
return user
def get_group_permissions(self, user, obj=None):
"""Returns an empty set since Keystone doesn't support "groups"."""
# Keystone V3 added "groups". The Auth token response includes the
# roles from the user's Group assignment. It should be fine just
# returning an empty set here.
return set()
def get_all_permissions(self, user, obj=None):
"""Returns a set of permission strings that the user has.
This permission available to the user is derived from the user's
Keystone "roles".
The permissions are returned as ``"openstack.{{ role.name }}"``.
"""
if user.is_anonymous() or obj is not None:
return set()
# TODO(gabrielhurley): Integrate policy-driven RBAC
# when supported by Keystone.
role_perms = {utils.get_role_permission(role['name'])
for role in user.roles}
services = []
for service in user.service_catalog:
try:
service_type = service['type']
except KeyError:
continue
service_regions = [utils.get_endpoint_region(endpoint) for endpoint
in service.get('endpoints', [])]
if user.services_region in service_regions:
services.append(service_type.lower())
service_perms = {"openstack.services.%s" % service
for service in services}
return role_perms | service_perms
def has_perm(self, user, perm, obj=None):
"""Returns True if the given user has the specified permission."""
if not user.is_active:
return False
return perm in self.get_all_permissions(user, obj)
def has_module_perms(self, user, app_label):
"""Returns True if user has any permissions in the given app_label.
Currently this matches for the app_label ``"openstack"``.
"""
if not user.is_active:
return False
for perm in self.get_all_permissions(user):
if perm[:perm.index('.')] == app_label:
return True
return False

View File

@ -1,17 +0,0 @@
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.
class KeystoneAuthException(Exception):
"""Generic error class to identify and catch our own errors."""
pass

View File

@ -1,155 +0,0 @@
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import collections
import logging
from django.conf import settings
from django.contrib.auth import authenticate
from django.contrib.auth import forms as django_auth_forms
from django import forms
from django.utils.translation import ugettext_lazy as _
from django.views.decorators.debug import sensitive_variables
from openstack_auth import exceptions
from openstack_auth import utils
LOG = logging.getLogger(__name__)
class Login(django_auth_forms.AuthenticationForm):
"""Form used for logging in a user.
Handles authentication with Keystone by providing the domain name, username
and password. A scoped token is fetched after successful authentication.
A domain name is required if authenticating with Keystone V3 running
multi-domain configuration.
If the user authenticated has a default project set, the token will be
automatically scoped to their default project.
If the user authenticated has no default project set, the authentication
backend will try to scope to the projects returned from the user's assigned
projects. The first successful project scoped will be returned.
Inherits from the base ``django.contrib.auth.forms.AuthenticationForm``
class for added security features.
"""
use_required_attribute = False
region = forms.ChoiceField(label=_("Region"), required=False)
username = forms.CharField(
label=_("User Name"),
widget=forms.TextInput(attrs={"autofocus": "autofocus"}))
password = forms.CharField(label=_("Password"),
widget=forms.PasswordInput(render_value=False))
def __init__(self, *args, **kwargs):
super(Login, self).__init__(*args, **kwargs)
fields_ordering = ['username', 'password', 'region']
if getattr(settings,
'OPENSTACK_KEYSTONE_MULTIDOMAIN_SUPPORT',
False):
last_domain = self.request.COOKIES.get('login_domain', None)
if getattr(settings,
'OPENSTACK_KEYSTONE_DOMAIN_DROPDOWN',
False):
self.fields['domain'] = forms.ChoiceField(
label=_("Domain"),
initial=last_domain,
required=True,
choices=getattr(settings,
'OPENSTACK_KEYSTONE_DOMAIN_CHOICES',
()))
else:
self.fields['domain'] = forms.CharField(
initial=last_domain,
label=_("Domain"),
required=True,
widget=forms.TextInput(attrs={"autofocus": "autofocus"}))
self.fields['username'].widget = forms.widgets.TextInput()
fields_ordering = ['domain', 'username', 'password', 'region']
self.fields['region'].choices = self.get_region_choices()
if len(self.fields['region'].choices) == 1:
self.fields['region'].initial = self.fields['region'].choices[0][0]
self.fields['region'].widget = forms.widgets.HiddenInput()
elif len(self.fields['region'].choices) > 1:
self.fields['region'].initial = self.request.COOKIES.get(
'login_region')
# if websso is enabled and keystone version supported
# prepend the websso_choices select input to the form
if utils.is_websso_enabled():
initial = getattr(settings, 'WEBSSO_INITIAL_CHOICE', 'credentials')
self.fields['auth_type'] = forms.ChoiceField(
label=_("Authenticate using"),
choices=getattr(settings, 'WEBSSO_CHOICES', ()),
required=False,
initial=initial)
# add auth_type to the top of the list
fields_ordering.insert(0, 'auth_type')
# websso is enabled, but keystone version is not supported
elif getattr(settings, 'WEBSSO_ENABLED', False):
msg = ("Websso is enabled but horizon is not configured to work " +
"with keystone version 3 or above.")
LOG.warning(msg)
self.fields = collections.OrderedDict(
(key, self.fields[key]) for key in fields_ordering)
@staticmethod
def get_region_choices():
default_region = (settings.OPENSTACK_KEYSTONE_URL, "Default Region")
regions = getattr(settings, 'AVAILABLE_REGIONS', [])
if not regions:
regions = [default_region]
return regions
@sensitive_variables()
def clean(self):
default_domain = getattr(settings,
'OPENSTACK_KEYSTONE_DEFAULT_DOMAIN',
'Default')
username = self.cleaned_data.get('username')
password = self.cleaned_data.get('password')
region = self.cleaned_data.get('region')
domain = self.cleaned_data.get('domain', default_domain)
if not (username and password):
# Don't authenticate, just let the other validators handle it.
return self.cleaned_data
try:
self.user_cache = authenticate(request=self.request,
username=username,
password=password,
user_domain_name=domain,
auth_url=region)
msg = 'Login successful for user "%(username)s", remote address '\
'%(remote_ip)s.' % {
'username': username,
'remote_ip': utils.get_client_ip(self.request)
}
LOG.info(msg)
except exceptions.KeystoneAuthException as exc:
msg = 'Login failed for user "%(username)s", remote address '\
'%(remote_ip)s.' % {
'username': username,
'remote_ip': utils.get_client_ip(self.request)
}
LOG.warning(msg)
raise forms.ValidationError(exc)
if hasattr(self, 'check_for_test_cookie'): # Dropped in django 1.7
self.check_for_test_cookie()
return self.cleaned_data

View File

@ -1,74 +0,0 @@
# Translations template for django_openstack_auth.
# Copyright (C) 2015 ORGANIZATION
# This file is distributed under the same license as the
# django_openstack_auth project.
#
# Translators:
# Zbyněk Schwarz <zbynek.schwarz@gmail.com>, 2014-2015
# Stanislav Ulrych <stanislav.ulrych@ultimum.io>, 2016. #zanata
msgid ""
msgstr ""
"Project-Id-Version: django_openstack_auth 3.1.1.dev1\n"
"Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n"
"POT-Creation-Date: 2017-01-25 19:41+0000\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"PO-Revision-Date: 2016-06-09 07:46+0000\n"
"Last-Translator: Lenka Husáková <lenka.husakova@ultimum.io>\n"
"Language: cs\n"
"Plural-Forms: nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;\n"
"Generated-By: Babel 2.0\n"
"X-Generator: Zanata 3.7.3\n"
"Language-Team: Czech\n"
msgid "An error occurred authenticating. Please try again later."
msgstr "Při ověřování se vyskytla chyba. Zkuste to prosím znovu později."
msgid "Authenticate using"
msgstr "Přihlásit se pomocí"
msgid "Domain"
msgstr "Doména"
msgid "Invalid credentials."
msgstr "Neplatné přihlašovací údaje."
msgid ""
"No authentication backend could be determined to handle the provided "
"credentials."
msgstr ""
"Nebyl rozpoznán žádný vhodný autentizační backend pro ověření zadaných "
"přihlašovacích údajů."
msgid "Password"
msgstr "Heslo"
#, python-format
msgid "Project switch failed for user \"%(username)s\"."
msgstr "Změna projektu selhala u uživatele \"%(username)s\"."
msgid "Region"
msgstr "Region"
#, python-format
msgid "Switch to project \"%(project_name)s\" successful."
msgstr "Změna projektu \"%(project_name)s\" byla úspěšná."
msgid "The authentication token issued by the Identity service has expired."
msgstr "Autentizační token poskytnutý službou identit vypršel."
msgid "Unable to establish connection to keystone endpoint."
msgstr "Nelze se připojit ke keystone endpointu."
msgid "Unable to retrieve authorized projects."
msgstr "Nelze získat oprávněné projekty."
msgid "User Name"
msgstr "Uživatelské jméno"
msgid "You are not authorized for any projects or domains."
msgstr "Nemáte oprávnění k žádným projektům či doménám."
msgid "You are not authorized for any projects."
msgstr "Nemáte oprávnění k žádným projektům."

View File

@ -1,103 +0,0 @@
# Translations template for django_openstack_auth.
# Copyright (C) 2015 ORGANIZATION
# This file is distributed under the same license as the
# django_openstack_auth project.
#
# Translators:
# Andreas Jaeger <jaegerandi@gmail.com>, 2014
# Robert Simai, 2015
# Robert Simai, 2015
# Frank Kloeker <eumel@arcor.de>, 2016. #zanata
# Andreas Jaeger <jaegerandi@gmail.com>, 2017. #zanata
# Robert Simai <robert.simai@suse.com>, 2017. #zanata
msgid ""
msgstr ""
"Project-Id-Version: django_openstack_auth 3.1.2.dev14\n"
"Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n"
"POT-Creation-Date: 2017-04-07 13:52+0000\n"