Retire swauth
Change-Id: Ib8e22a1e2e35d22a754943e34501305a0cfdd9b9 Depends-On: https://review.opendev.org/678368 See: http://lists.openstack.org/pipermail/openstack-discuss/2019-August/008416.html
This commit is contained in:
parent
f91a945590
commit
022f688a7c
@ -1,6 +0,0 @@
|
|||||||
[run]
|
|
||||||
branch = True
|
|
||||||
omit = /usr*,setup.py,*egg*,.venv/*,.tox/*,test/*
|
|
||||||
|
|
||||||
[report]
|
|
||||||
ignore_errors = True
|
|
6
.gitignore
vendored
6
.gitignore
vendored
@ -1,6 +0,0 @@
|
|||||||
*.egg-info
|
|
||||||
*.py[co]
|
|
||||||
.DS_Store
|
|
||||||
.coverage
|
|
||||||
.tox
|
|
||||||
cover
|
|
12
.mailmap
12
.mailmap
@ -1,12 +0,0 @@
|
|||||||
Greg Holt <gholt@rackspace.com> <gholt@brim.net>
|
|
||||||
Greg Holt <gholt@rackspace.com> <gregory.holt+launchpad.net@gmail.com>
|
|
||||||
Greg Holt <gholt@rackspace.com> <z-github@brim.net>
|
|
||||||
Greg Holt <gholt@rackspace.com> <z-launchpad@brim.net>
|
|
||||||
Greg Holt <gholt@rackspace.com> <greg@brim.net>
|
|
||||||
Greg Holt <gholt@rackspace.com> <gregory.holt@gmail.com>
|
|
||||||
Greg Holt <gholt@rackspace.com> <gregory_holt@icloud.com>
|
|
||||||
Greg Holt <gholt@rackspace.com> <gholt@rackspace.com>
|
|
||||||
Christian Schwede <cschwede@redhat.com> <github@cschwede.de>
|
|
||||||
Christian Schwede <cschwede@redhat.com> <info@cschwede.de>
|
|
||||||
Ondřej Nový <ondrej.novy@firma.seznam.cz> <novy@ondrej.org>
|
|
||||||
Ron Pedde <ron@pedde.com> <ron@pedde.com>
|
|
@ -1,4 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
nosetests test/unit --exe --with-coverage --cover-package swauth --cover-erase
|
|
||||||
rm -f .coverage
|
|
56
AUTHORS
56
AUTHORS
@ -1,56 +0,0 @@
|
|||||||
Maintainers
|
|
||||||
-----------
|
|
||||||
Ondřej Nový
|
|
||||||
Peter Lisák
|
|
||||||
|
|
||||||
Original Authors
|
|
||||||
----------------
|
|
||||||
Chuck Thier
|
|
||||||
Greg Holt
|
|
||||||
Greg Lange
|
|
||||||
Jay Payne
|
|
||||||
John Dickinson
|
|
||||||
Michael Barton
|
|
||||||
Will Reese
|
|
||||||
|
|
||||||
Contributors
|
|
||||||
------------
|
|
||||||
Abdul Nizamuddin
|
|
||||||
Andreas Jaeger
|
|
||||||
Andrew Clay Shafer
|
|
||||||
Anne Gentle
|
|
||||||
Apollon Oikonomopoulos
|
|
||||||
Brian Cline
|
|
||||||
Brian K. Jones
|
|
||||||
Caleb Tennis
|
|
||||||
Chmouel Boudjnah
|
|
||||||
Chris Wedgwood
|
|
||||||
Christian Schwede
|
|
||||||
Christopher Bartz
|
|
||||||
Clay Gerrard
|
|
||||||
Colin Nicholson
|
|
||||||
Conrad Weidenkeller
|
|
||||||
Cory Wright
|
|
||||||
David Goetz
|
|
||||||
Ed Leafe
|
|
||||||
Eohyung Lee
|
|
||||||
Fujita Tomonori
|
|
||||||
jgrmnprz
|
|
||||||
Kapil Thangavelu
|
|
||||||
Marcelo Martins
|
|
||||||
Monty Taylor
|
|
||||||
Pablo Llopis
|
|
||||||
Paul Jimenez
|
|
||||||
Pawnesh Kumar
|
|
||||||
Pete Zaitcev
|
|
||||||
Prashanth Pai
|
|
||||||
Rodney Beede
|
|
||||||
Ron Pedde
|
|
||||||
Russ Nelson
|
|
||||||
Scott Simpson
|
|
||||||
SoftDed
|
|
||||||
Soren Hansen
|
|
||||||
Stephen Milton
|
|
||||||
Thiago da Silva
|
|
||||||
Tim Burke
|
|
||||||
Zhang Guoqing
|
|
105
CHANGELOG
105
CHANGELOG
@ -1,105 +0,0 @@
|
|||||||
swauth (1.3.0)
|
|
||||||
|
|
||||||
[SECURITY] Stop using client headers for cross-middleware communication
|
|
||||||
WARNING: You need to upgrade Swift3 to at least 1.12
|
|
||||||
|
|
||||||
[SECURITY] Hash token before storing it in Swift (CVE-2017-16613)
|
|
||||||
WARNING: In deployments without memcached this patch logs out all users
|
|
||||||
because tokens became invalid.
|
|
||||||
|
|
||||||
swauth (1.2.0)
|
|
||||||
|
|
||||||
Allow to set password by hash
|
|
||||||
|
|
||||||
Allow to set hash salt in config for S3 compatibility
|
|
||||||
|
|
||||||
Due to security reason, S3 support is disabled by default
|
|
||||||
|
|
||||||
Salt is not included in S3 HMAC computation
|
|
||||||
|
|
||||||
Use correct content type on JSON responses
|
|
||||||
|
|
||||||
Fix changing of auth_type in existing deployments
|
|
||||||
|
|
||||||
Remove outdated locale
|
|
||||||
|
|
||||||
swauth (1.1.0)
|
|
||||||
|
|
||||||
This is first release after move to OpenStack Infra
|
|
||||||
|
|
||||||
Allow users to change their own password/key
|
|
||||||
|
|
||||||
Add support for storage policy
|
|
||||||
|
|
||||||
Show password prompt if key is not specified
|
|
||||||
|
|
||||||
Allow to use Keystone at same time
|
|
||||||
|
|
||||||
Support SHA512 for password hashing
|
|
||||||
|
|
||||||
Code cleanup
|
|
||||||
|
|
||||||
Bugfixies a security fixies
|
|
||||||
|
|
||||||
swauth (1.0.8)
|
|
||||||
|
|
||||||
Added request.environ[reseller_request] = True if request is coming from an
|
|
||||||
user in .reseller_admin group
|
|
||||||
|
|
||||||
Fixed to work with newer Swift versions whose memcache clients require a
|
|
||||||
time keyword argument when the older versions required a timeout keyword
|
|
||||||
argument.
|
|
||||||
|
|
||||||
swauth (1.0.7)
|
|
||||||
|
|
||||||
New X-Auth-Token-Lifetime header a user can set to how long they'd like
|
|
||||||
their token to be good for.
|
|
||||||
|
|
||||||
New max_token_life config value for capping the above.
|
|
||||||
|
|
||||||
New X-Auth-Token-Expires header returned with the get token request.
|
|
||||||
|
|
||||||
Switchover to swift.common.swob instead of WebOb; requires Swift >= 1.7.6
|
|
||||||
now.
|
|
||||||
|
|
||||||
swauth (1.0.6)
|
|
||||||
|
|
||||||
Apparently I haven't been keeping up with this CHANGELOG. I'll try to be
|
|
||||||
better onward.
|
|
||||||
|
|
||||||
This release added passing OPTIONS requests through untouched, needed for
|
|
||||||
CORS support in Swift.
|
|
||||||
|
|
||||||
Also, Swauth is a bit more restrictive in deciding when it's the definitive
|
|
||||||
auth for a request.
|
|
||||||
|
|
||||||
swauth (1.0.3-dev)
|
|
||||||
|
|
||||||
This release is still under development. A full change log will be made at
|
|
||||||
release. Until then, you can see what has changed with:
|
|
||||||
|
|
||||||
git log 1.0.2..HEAD
|
|
||||||
|
|
||||||
swauth (1.0.2)
|
|
||||||
|
|
||||||
Fixed bug rejecting requests when using multiple instances of Swauth or
|
|
||||||
Swauth with other auth services.
|
|
||||||
|
|
||||||
Fixed bug interpreting URL-encoded user names and keys.
|
|
||||||
|
|
||||||
Added support for the Swift container sync feature.
|
|
||||||
|
|
||||||
Allowed /not/ setting super_admin_key to disable Swauth administration
|
|
||||||
features.
|
|
||||||
|
|
||||||
Added swauth_remote mode so the Swauth middleware for one Swift cluster
|
|
||||||
could be pointing to the Swauth service on another Swift cluster, sharing
|
|
||||||
account/user data sets.
|
|
||||||
|
|
||||||
Added ability to purge stored tokens.
|
|
||||||
|
|
||||||
Added API documentation for internal Swauth API.
|
|
||||||
|
|
||||||
swauth (1.0.1)
|
|
||||||
|
|
||||||
Initial release after separation from Swift.
|
|
@ -1,22 +0,0 @@
|
|||||||
If you would like to contribute to the development of OpenStack, you must
|
|
||||||
follow the steps in this page:
|
|
||||||
|
|
||||||
https://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:
|
|
||||||
|
|
||||||
https://docs.openstack.org/infra/manual/developers.html#development-workflow
|
|
||||||
|
|
||||||
Please don't feel offended by difference of opinion. Be prepared to advocate
|
|
||||||
for your change and iterate on it based on feedback. Reach out to other people
|
|
||||||
working on the project in #openstack-swauth on freenode
|
|
||||||
[IRC](http://eavesdrop.openstack.org/irclogs/%23openstack-swauth/) - we want to help.
|
|
||||||
|
|
||||||
Pull requests submitted through GitHub will be ignored.
|
|
||||||
|
|
||||||
Bugs should be filed on Launchpad, not GitHub:
|
|
||||||
|
|
||||||
https://bugs.launchpad.net/swauth
|
|
202
LICENSE
202
LICENSE
@ -1,202 +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.
|
|
||||||
|
|
||||||
END OF TERMS AND CONDITIONS
|
|
||||||
|
|
||||||
APPENDIX: How to apply the Apache License to your work.
|
|
||||||
|
|
||||||
To apply the Apache License to your work, attach the following
|
|
||||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
|
||||||
replaced with your own identifying information. (Don't include
|
|
||||||
the brackets!) The text should be enclosed in the appropriate
|
|
||||||
comment syntax for the file format. We also recommend that a
|
|
||||||
file or class name and description of purpose be included on the
|
|
||||||
same "printed page" as the copyright notice for easier
|
|
||||||
identification within third-party archives.
|
|
||||||
|
|
||||||
Copyright [yyyy] [name of copyright owner]
|
|
||||||
|
|
||||||
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.
|
|
87
README.md
87
README.md
@ -1,87 +0,0 @@
|
|||||||
Swauth
|
|
||||||
------
|
|
||||||
|
|
||||||
An Auth Service for Swift as WSGI Middleware that uses Swift itself as a
|
|
||||||
backing store. Docs at: <https://swauth.readthedocs.io/> or ask in #openstack-swauth on
|
|
||||||
freenode [IRC](http://eavesdrop.openstack.org/irclogs/%23openstack-swauth/).
|
|
||||||
|
|
||||||
See also <https://github.com/openstack/keystone> for the standard OpenStack
|
|
||||||
auth service.
|
|
||||||
|
|
||||||
|
|
||||||
NOTE
|
|
||||||
----
|
|
||||||
|
|
||||||
**Be sure to review the docs at:
|
|
||||||
<https://swauth.readthedocs.io/>**
|
|
||||||
|
|
||||||
|
|
||||||
Quick Install
|
|
||||||
-------------
|
|
||||||
|
|
||||||
1) Install Swauth with ``sudo python setup.py install`` or ``sudo python
|
|
||||||
setup.py develop`` or via whatever packaging system you may be using.
|
|
||||||
|
|
||||||
2) Alter your proxy-server.conf pipeline to have swauth instead of tempauth:
|
|
||||||
|
|
||||||
Was:
|
|
||||||
|
|
||||||
[pipeline:main]
|
|
||||||
pipeline = catch_errors cache tempauth proxy-server
|
|
||||||
|
|
||||||
Change To:
|
|
||||||
|
|
||||||
[pipeline:main]
|
|
||||||
pipeline = catch_errors cache swauth proxy-server
|
|
||||||
|
|
||||||
3) Add to your proxy-server.conf the section for the Swauth WSGI filter:
|
|
||||||
|
|
||||||
[filter:swauth]
|
|
||||||
use = egg:swauth#swauth
|
|
||||||
set log_name = swauth
|
|
||||||
super_admin_key = swauthkey
|
|
||||||
|
|
||||||
4) Be sure your proxy server allows account management:
|
|
||||||
|
|
||||||
[app:proxy-server]
|
|
||||||
...
|
|
||||||
allow_account_management = true
|
|
||||||
|
|
||||||
5) Restart your proxy server ``swift-init proxy reload``
|
|
||||||
|
|
||||||
6) Initialize the Swauth backing store in Swift ``swauth-prep -K swauthkey``
|
|
||||||
|
|
||||||
7) Add an account/user ``swauth-add-user -A http://127.0.0.1:8080/auth/ -K
|
|
||||||
swauthkey -a test tester testing``
|
|
||||||
|
|
||||||
8) Ensure it works ``swift -A http://127.0.0.1:8080/auth/v1.0 -U test:tester -K
|
|
||||||
testing stat -v``
|
|
||||||
|
|
||||||
|
|
||||||
Web Admin Install
|
|
||||||
-----------------
|
|
||||||
|
|
||||||
1) If you installed from packages, you'll need to cd to the webadmin directory
|
|
||||||
the package installed. This is ``/usr/share/doc/python-swauth/webadmin``
|
|
||||||
with the Lucid packages. If you installed from source, you'll need to cd to
|
|
||||||
the webadmin directory in the source directory.
|
|
||||||
|
|
||||||
2) Upload the Web Admin files with ``swift -A http://127.0.0.1:8080/auth/v1.0
|
|
||||||
-U .super_admin:.super_admin -K swauthkey upload .webadmin .``
|
|
||||||
|
|
||||||
3) Open ``http://127.0.0.1:8080/auth/`` in your browser.
|
|
||||||
|
|
||||||
|
|
||||||
Swift3 Middleware Compatibility
|
|
||||||
-------------------------------
|
|
||||||
[**Swift3 middleware**](https://github.com/openstack/swift3) can be used with
|
|
||||||
swauth when `auth_type` in swauth is configured to be *Plaintext* (default).
|
|
||||||
|
|
||||||
[pipeline:main]
|
|
||||||
pipeline = catch_errors cache swift3 swauth proxy-server
|
|
||||||
|
|
||||||
It can be used with `auth_type` set to Sha1/Sha512 too but with certain caveats
|
|
||||||
and security concern. Hence, s3 support is disabled by default and you have to
|
|
||||||
explicitly enable it in your configuration.
|
|
||||||
Refer to swift3 compatibility [section](https://swauth.readthedocs.io/en/latest/#swift3-middleware-compatibility)
|
|
||||||
in documentation for further details
|
|
10
README.rst
Normal file
10
README.rst
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
This project is no longer maintained.
|
||||||
|
|
||||||
|
The contents of this repository are still available in the Git
|
||||||
|
source code management system. To see the contents of this
|
||||||
|
repository before it reached its end of life, please check out the
|
||||||
|
previous commit with "git checkout HEAD^1".
|
||||||
|
|
||||||
|
For any further questions, please email
|
||||||
|
openstack-discuss@lists.openstack.org or join #openstack-dev on
|
||||||
|
Freenode.
|
@ -1,72 +0,0 @@
|
|||||||
#!/usr/bin/env python
|
|
||||||
# Copyright (c) 2010-2011 OpenStack, LLC.
|
|
||||||
#
|
|
||||||
# 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.
|
|
||||||
|
|
||||||
from getpass import getpass
|
|
||||||
import gettext
|
|
||||||
from optparse import OptionParser
|
|
||||||
from sys import argv
|
|
||||||
from sys import exit
|
|
||||||
|
|
||||||
from six.moves.urllib.parse import urlparse
|
|
||||||
from swift.common.bufferedhttp import http_connect_raw as http_connect
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
gettext.install('swauth', unicode=1)
|
|
||||||
parser = OptionParser(usage='Usage: %prog [options] <account>')
|
|
||||||
parser.add_option('-s', '--suffix', dest='suffix',
|
|
||||||
default='', help='The suffix to use with the reseller prefix as the '
|
|
||||||
'storage account name (default: <randomly-generated-uuid4>) Note: If '
|
|
||||||
'the account already exists, this will have no effect on existing '
|
|
||||||
'service URLs. Those will need to be updated with '
|
|
||||||
'swauth-set-account-service')
|
|
||||||
parser.add_option('-A', '--admin-url', dest='admin_url',
|
|
||||||
default='http://127.0.0.1:8080/auth/', help='The URL to the auth '
|
|
||||||
'subsystem (default: http://127.0.0.1:8080/auth/)')
|
|
||||||
parser.add_option('-U', '--admin-user', dest='admin_user',
|
|
||||||
default='.super_admin', help='The user with admin rights to add users '
|
|
||||||
'(default: .super_admin).')
|
|
||||||
parser.add_option('-K', '--admin-key', dest='admin_key',
|
|
||||||
help='The key for the user with admin rights to add users.')
|
|
||||||
args = argv[1:]
|
|
||||||
if not args:
|
|
||||||
args.append('-h')
|
|
||||||
(options, args) = parser.parse_args(args)
|
|
||||||
if len(args) != 1:
|
|
||||||
parser.parse_args(['-h'])
|
|
||||||
if not options.admin_key:
|
|
||||||
options.admin_key = getpass()
|
|
||||||
account = args[0]
|
|
||||||
parsed = urlparse(options.admin_url)
|
|
||||||
if parsed.scheme not in ('http', 'https'):
|
|
||||||
raise ValueError('Cannot handle protocol scheme %s for url %s' %
|
|
||||||
(parsed.scheme, repr(options.admin_url)))
|
|
||||||
parsed_path = parsed.path
|
|
||||||
if not parsed_path:
|
|
||||||
parsed_path = '/'
|
|
||||||
elif parsed_path[-1] != '/':
|
|
||||||
parsed_path += '/'
|
|
||||||
path = '%sv2/%s' % (parsed_path, account)
|
|
||||||
headers = {'X-Auth-Admin-User': options.admin_user,
|
|
||||||
'X-Auth-Admin-Key': options.admin_key,
|
|
||||||
'Content-Length': '0'}
|
|
||||||
if options.suffix:
|
|
||||||
headers['X-Account-Suffix'] = options.suffix
|
|
||||||
conn = http_connect(parsed.hostname, parsed.port, 'PUT', path, headers,
|
|
||||||
ssl=(parsed.scheme == 'https'))
|
|
||||||
resp = conn.getresponse()
|
|
||||||
if resp.status // 100 != 2:
|
|
||||||
exit('Account creation failed: %s %s' % (resp.status, resp.reason))
|
|
@ -1,110 +0,0 @@
|
|||||||
#!/usr/bin/env python
|
|
||||||
# Copyright (c) 2010-2011 OpenStack, LLC.
|
|
||||||
#
|
|
||||||
# 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.
|
|
||||||
|
|
||||||
from getpass import getpass
|
|
||||||
import gettext
|
|
||||||
from optparse import OptionParser
|
|
||||||
from sys import argv
|
|
||||||
from sys import exit
|
|
||||||
|
|
||||||
from six.moves.urllib.parse import urlparse
|
|
||||||
from swift.common.bufferedhttp import http_connect_raw as http_connect
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
gettext.install('swauth', unicode=1)
|
|
||||||
parser = OptionParser(
|
|
||||||
usage='Usage: %prog [options] <account> <user> <password>')
|
|
||||||
parser.add_option('-a', '--admin', dest='admin', action='store_true',
|
|
||||||
default=False, help='Give the user administrator access; otherwise '
|
|
||||||
'the user will only have access to containers specifically allowed '
|
|
||||||
'with ACLs.')
|
|
||||||
parser.add_option('-r', '--reseller-admin', dest='reseller_admin',
|
|
||||||
action='store_true', default=False, help='Give the user full reseller '
|
|
||||||
'administrator access, giving them full access to all accounts within '
|
|
||||||
'the reseller, including the ability to create new accounts. Creating '
|
|
||||||
'a new reseller admin requires super_admin rights.')
|
|
||||||
parser.add_option('-s', '--suffix', dest='suffix',
|
|
||||||
default='', help='The suffix to use with the reseller prefix as the '
|
|
||||||
'storage account name (default: <randomly-generated-uuid4>) Note: If '
|
|
||||||
'the account already exists, this will have no effect on existing '
|
|
||||||
'service URLs. Those will need to be updated with '
|
|
||||||
'swauth-set-account-service')
|
|
||||||
parser.add_option('-e', '--hashed', dest='password_hashed',
|
|
||||||
action='store_true', default=False, help='Supplied password is '
|
|
||||||
'already hashed and in format <auth_type>:<hashed_password>')
|
|
||||||
parser.add_option('-A', '--admin-url', dest='admin_url',
|
|
||||||
default='http://127.0.0.1:8080/auth/', help='The URL to the auth '
|
|
||||||
'subsystem (default: http://127.0.0.1:8080/auth/')
|
|
||||||
parser.add_option('-U', '--admin-user', dest='admin_user',
|
|
||||||
default='.super_admin', help='The user with admin rights to add users '
|
|
||||||
'(default: .super_admin).')
|
|
||||||
parser.add_option('-K', '--admin-key', dest='admin_key',
|
|
||||||
help='The key for the user with admin rights to add users.')
|
|
||||||
args = argv[1:]
|
|
||||||
if not args:
|
|
||||||
args.append('-h')
|
|
||||||
(options, args) = parser.parse_args(args)
|
|
||||||
if len(args) != 3:
|
|
||||||
parser.parse_args(['-h'])
|
|
||||||
if not options.admin_key:
|
|
||||||
options.admin_key = getpass()
|
|
||||||
account, user, password = args
|
|
||||||
parsed = urlparse(options.admin_url)
|
|
||||||
if parsed.scheme not in ('http', 'https'):
|
|
||||||
raise ValueError('Cannot handle protocol scheme %s for url %s' %
|
|
||||||
(parsed.scheme, repr(options.admin_url)))
|
|
||||||
parsed_path = parsed.path
|
|
||||||
if not parsed_path:
|
|
||||||
parsed_path = '/'
|
|
||||||
elif parsed_path[-1] != '/':
|
|
||||||
parsed_path += '/'
|
|
||||||
# Ensure the account exists if user is NOT trying to change his password
|
|
||||||
if not options.admin_user == (account + ':' + user):
|
|
||||||
path = '%sv2/%s' % (parsed_path, account)
|
|
||||||
headers = {'X-Auth-Admin-User': options.admin_user,
|
|
||||||
'X-Auth-Admin-Key': options.admin_key}
|
|
||||||
if options.suffix:
|
|
||||||
headers['X-Account-Suffix'] = options.suffix
|
|
||||||
conn = http_connect(parsed.hostname, parsed.port, 'GET', path, headers,
|
|
||||||
ssl=(parsed.scheme == 'https'))
|
|
||||||
resp = conn.getresponse()
|
|
||||||
if resp.status // 100 != 2:
|
|
||||||
headers['Content-Length'] = '0'
|
|
||||||
conn = http_connect(parsed.hostname, parsed.port, 'PUT', path,
|
|
||||||
headers, ssl=(parsed.scheme == 'https'))
|
|
||||||
resp = conn.getresponse()
|
|
||||||
if resp.status // 100 != 2:
|
|
||||||
print('Account creation failed: %s %s' %
|
|
||||||
(resp.status, resp.reason))
|
|
||||||
# Add the user
|
|
||||||
path = '%sv2/%s/%s' % (parsed_path, account, user)
|
|
||||||
headers = {'X-Auth-Admin-User': options.admin_user,
|
|
||||||
'X-Auth-Admin-Key': options.admin_key,
|
|
||||||
'Content-Length': '0'}
|
|
||||||
if options.admin:
|
|
||||||
headers['X-Auth-User-Admin'] = 'true'
|
|
||||||
if options.reseller_admin:
|
|
||||||
headers['X-Auth-User-Reseller-Admin'] = 'true'
|
|
||||||
if options.password_hashed:
|
|
||||||
headers['X-Auth-User-Key-Hash'] = password
|
|
||||||
else:
|
|
||||||
headers['X-Auth-User-Key'] = password
|
|
||||||
conn = http_connect(parsed.hostname, parsed.port, 'PUT', path, headers,
|
|
||||||
ssl=(parsed.scheme == 'https'))
|
|
||||||
resp = conn.getresponse()
|
|
||||||
if resp.status // 100 != 2:
|
|
||||||
exit('User creation failed: %s %s' % (resp.status, resp.reason))
|
|
@ -1,169 +0,0 @@
|
|||||||
#!/usr/bin/env python
|
|
||||||
# Copyright (c) 2010-2011 OpenStack, LLC.
|
|
||||||
#
|
|
||||||
# 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.
|
|
||||||
|
|
||||||
from datetime import datetime
|
|
||||||
from datetime import timedelta
|
|
||||||
from getpass import getpass
|
|
||||||
import gettext
|
|
||||||
import json
|
|
||||||
from optparse import OptionParser
|
|
||||||
import re
|
|
||||||
from sys import argv
|
|
||||||
from sys import exit
|
|
||||||
from time import sleep
|
|
||||||
from time import time
|
|
||||||
|
|
||||||
from swiftclient.client import ClientException
|
|
||||||
from swiftclient.client import Connection
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
gettext.install('swauth', unicode=1)
|
|
||||||
parser = OptionParser(usage='Usage: %prog [options]')
|
|
||||||
parser.add_option('-t', '--token-life', dest='token_life',
|
|
||||||
default='86400', help='The expected life of tokens; token objects '
|
|
||||||
'modified more than this number of seconds ago will be checked for '
|
|
||||||
'expiration (default: 86400).')
|
|
||||||
parser.add_option('-s', '--sleep', dest='sleep',
|
|
||||||
default='0.1', help='The number of seconds to sleep between token '
|
|
||||||
'checks (default: 0.1)')
|
|
||||||
parser.add_option('-v', '--verbose', dest='verbose', action='store_true',
|
|
||||||
default=False, help='Outputs everything done instead of just the '
|
|
||||||
'deletions.')
|
|
||||||
parser.add_option('-A', '--admin-url', dest='admin_url',
|
|
||||||
default='http://127.0.0.1:8080/auth/', help='The URL to the auth '
|
|
||||||
'subsystem (default: http://127.0.0.1:8080/auth/)')
|
|
||||||
parser.add_option('-K', '--admin-key', dest='admin_key',
|
|
||||||
help='The key for .super_admin.')
|
|
||||||
parser.add_option('', '--purge', dest='purge_account', help='Purges all '
|
|
||||||
'tokens for a given account whether the tokens have expired or not.')
|
|
||||||
parser.add_option('', '--purge-all', dest='purge_all', action='store_true',
|
|
||||||
default=False, help='Purges all tokens for all accounts and users '
|
|
||||||
'whether the tokens have expired or not.')
|
|
||||||
args = argv[1:]
|
|
||||||
if not args:
|
|
||||||
args.append('-h')
|
|
||||||
(options, args) = parser.parse_args(args)
|
|
||||||
if len(args) != 0:
|
|
||||||
parser.parse_args(['-h'])
|
|
||||||
if not options.admin_key:
|
|
||||||
options.admin_key = getpass()
|
|
||||||
options.admin_url = options.admin_url.rstrip('/')
|
|
||||||
if not options.admin_url.endswith('/v1.0'):
|
|
||||||
options.admin_url += '/v1.0'
|
|
||||||
options.admin_user = '.super_admin:.super_admin'
|
|
||||||
options.token_life = timedelta(0, float(options.token_life))
|
|
||||||
options.sleep = float(options.sleep)
|
|
||||||
conn = Connection(options.admin_url, options.admin_user, options.admin_key)
|
|
||||||
if options.purge_account:
|
|
||||||
marker = None
|
|
||||||
while True:
|
|
||||||
if options.verbose:
|
|
||||||
print('GET %s?marker=%s' % (options.purge_account, marker))
|
|
||||||
objs = conn.get_container(options.purge_account, marker=marker)[1]
|
|
||||||
if objs:
|
|
||||||
marker = objs[-1]['name']
|
|
||||||
else:
|
|
||||||
if options.verbose:
|
|
||||||
print('No more objects in %s' % options.purge_account)
|
|
||||||
break
|
|
||||||
for obj in objs:
|
|
||||||
if options.verbose:
|
|
||||||
print('HEAD %s/%s' % (options.purge_account, obj['name']))
|
|
||||||
headers = conn.head_object(options.purge_account, obj['name'])
|
|
||||||
if 'x-object-meta-auth-token' in headers:
|
|
||||||
token = headers['x-object-meta-auth-token']
|
|
||||||
container = '.token_%s' % token[-1]
|
|
||||||
if options.verbose:
|
|
||||||
print('%s/%s purge account %r; deleting' %
|
|
||||||
(container, token, options.purge_account))
|
|
||||||
print('DELETE %s/%s' % (container, token))
|
|
||||||
try:
|
|
||||||
conn.delete_object(container, token)
|
|
||||||
except ClientException as err:
|
|
||||||
if err.http_status != 404:
|
|
||||||
raise
|
|
||||||
continue
|
|
||||||
if options.verbose:
|
|
||||||
print('Done.')
|
|
||||||
exit(0)
|
|
||||||
for x in range(16):
|
|
||||||
container = '.token_%x' % x
|
|
||||||
marker = None
|
|
||||||
while True:
|
|
||||||
if options.verbose:
|
|
||||||
print('GET %s?marker=%s' % (container, marker))
|
|
||||||
try:
|
|
||||||
objs = conn.get_container(container, marker=marker)[1]
|
|
||||||
except ClientException as e:
|
|
||||||
if e.http_status == 404:
|
|
||||||
exit('Container %s not found. swauth-prep needs to be '
|
|
||||||
'rerun' % (container))
|
|
||||||
else:
|
|
||||||
exit('Object listing on container %s failed with status '
|
|
||||||
'code %d' % (container, e.http_status))
|
|
||||||
if objs:
|
|
||||||
marker = objs[-1]['name']
|
|
||||||
else:
|
|
||||||
if options.verbose:
|
|
||||||
print('No more objects in %s' % container)
|
|
||||||
break
|
|
||||||
for obj in objs:
|
|
||||||
if options.purge_all:
|
|
||||||
if options.verbose:
|
|
||||||
print('%s/%s purge all; deleting' %
|
|
||||||
(container, obj['name']))
|
|
||||||
print('DELETE %s/%s' % (container, obj['name']))
|
|
||||||
try:
|
|
||||||
conn.delete_object(container, obj['name'])
|
|
||||||
except ClientException as err:
|
|
||||||
if err.http_status != 404:
|
|
||||||
raise
|
|
||||||
continue
|
|
||||||
last_modified = datetime(*map(int, re.split(r'[^\d]',
|
|
||||||
obj['last_modified'])[:-1]))
|
|
||||||
ago = datetime.utcnow() - last_modified
|
|
||||||
if ago > options.token_life:
|
|
||||||
if options.verbose:
|
|
||||||
print('%s/%s last modified %ss ago; investigating' %
|
|
||||||
(container, obj['name'],
|
|
||||||
ago.days * 86400 + ago.seconds))
|
|
||||||
print('GET %s/%s' % (container, obj['name']))
|
|
||||||
detail = conn.get_object(container, obj['name'])[1]
|
|
||||||
detail = json.loads(detail)
|
|
||||||
if detail['expires'] < time():
|
|
||||||
if options.verbose:
|
|
||||||
print('%s/%s expired %ds ago; deleting' %
|
|
||||||
(container, obj['name'],
|
|
||||||
time() - detail['expires']))
|
|
||||||
print('DELETE %s/%s' % (container, obj['name']))
|
|
||||||
try:
|
|
||||||
conn.delete_object(container, obj['name'])
|
|
||||||
except ClientException as e:
|
|
||||||
if e.http_status != 404:
|
|
||||||
print('DELETE of %s/%s failed with status '
|
|
||||||
'code %d' % (container, obj['name'],
|
|
||||||
e.http_status))
|
|
||||||
elif options.verbose:
|
|
||||||
print("%s/%s won't expire for %ds; skipping" %
|
|
||||||
(container, obj['name'],
|
|
||||||
detail['expires'] - time()))
|
|
||||||
elif options.verbose:
|
|
||||||
print('%s/%s last modified %ss ago; skipping' %
|
|
||||||
(container, obj['name'],
|
|
||||||
ago.days * 86400 + ago.seconds))
|
|
||||||
sleep(options.sleep)
|
|
||||||
if options.verbose:
|
|
||||||
print('Done.')
|
|
@ -1,63 +0,0 @@
|
|||||||
#!/usr/bin/env python
|
|
||||||
# Copyright (c) 2010-2011 OpenStack, LLC.
|
|
||||||
#
|
|
||||||
# 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.
|
|
||||||
|
|
||||||
from getpass import getpass
|
|
||||||
import gettext
|
|
||||||
from optparse import OptionParser
|
|
||||||
from sys import argv
|
|
||||||
from sys import exit
|
|
||||||
|
|
||||||
from six.moves.urllib.parse import urlparse
|
|
||||||
from swift.common.bufferedhttp import http_connect_raw as http_connect
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
gettext.install('swauth', unicode=1)
|
|
||||||
parser = OptionParser(usage='Usage: %prog [options] <account>')
|
|
||||||
parser.add_option('-A', '--admin-url', dest='admin_url',
|
|
||||||
default='http://127.0.0.1:8080/auth/', help='The URL to the auth '
|
|
||||||
'subsystem (default: http://127.0.0.1:8080/auth/')
|
|
||||||
parser.add_option('-U', '--admin-user', dest='admin_user',
|
|
||||||
default='.super_admin', help='The user with admin rights to add users '
|
|
||||||
'(default: .super_admin).')
|
|
||||||
parser.add_option('-K', '--admin-key', dest='admin_key',
|
|
||||||
help='The key for the user with admin rights to add users.')
|
|
||||||
args = argv[1:]
|
|
||||||
if not args:
|
|
||||||
args.append('-h')
|
|
||||||
(options, args) = parser.parse_args(args)
|
|
||||||
if len(args) != 1:
|
|
||||||
parser.parse_args(['-h'])
|
|
||||||
if not options.admin_key:
|
|
||||||
options.admin_key = getpass()
|
|
||||||
account = args[0]
|
|
||||||
parsed = urlparse(options.admin_url)
|
|
||||||
if parsed.scheme not in ('http', 'https'):
|
|
||||||
raise ValueError('Cannot handle protocol scheme %s for url %s' %
|
|
||||||
(parsed.scheme, repr(options.admin_url)))
|
|
||||||
parsed_path = parsed.path
|
|
||||||
if not parsed_path:
|
|
||||||
parsed_path = '/'
|
|
||||||
elif parsed_path[-1] != '/':
|
|
||||||
parsed_path += '/'
|
|
||||||
path = '%sv2/%s' % (parsed_path, account)
|
|
||||||
headers = {'X-Auth-Admin-User': options.admin_user,
|
|
||||||
'X-Auth-Admin-Key': options.admin_key}
|
|
||||||
conn = http_connect(parsed.hostname, parsed.port, 'DELETE', path, headers,
|
|
||||||
ssl=(parsed.scheme == 'https'))
|
|
||||||
resp = conn.getresponse()
|
|
||||||
if resp.status // 100 != 2:
|
|
||||||
exit('Account deletion failed: %s %s' % (resp.status, resp.reason))
|
|
@ -1,63 +0,0 @@
|
|||||||
#!/usr/bin/env python
|
|
||||||
# Copyright (c) 2010-2011 OpenStack, LLC.
|
|
||||||
#
|
|
||||||
# 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.
|
|
||||||
|
|
||||||
from getpass import getpass
|
|
||||||
import gettext
|
|
||||||
from optparse import OptionParser
|
|
||||||
from sys import argv
|
|
||||||
from sys import exit
|
|
||||||
|
|
||||||
from six.moves.urllib.parse import urlparse
|
|
||||||
from swift.common.bufferedhttp import http_connect_raw as http_connect
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
gettext.install('swauth', unicode=1)
|
|
||||||
parser = OptionParser(usage='Usage: %prog [options] <account> <user>')
|
|
||||||
parser.add_option('-A', '--admin-url', dest='admin_url',
|
|
||||||
default='http://127.0.0.1:8080/auth/', help='The URL to the auth '
|
|
||||||
'subsystem (default: http://127.0.0.1:8080/auth/')
|
|
||||||
parser.add_option('-U', '--admin-user', dest='admin_user',
|
|
||||||
default='.super_admin', help='The user with admin rights to add users '
|
|
||||||
'(default: .super_admin).')
|
|
||||||
parser.add_option('-K', '--admin-key', dest='admin_key',
|
|
||||||
help='The key for the user with admin rights to add users.')
|
|
||||||
args = argv[1:]
|
|
||||||
if not args:
|
|
||||||
args.append('-h')
|
|
||||||
(options, args) = parser.parse_args(args)
|
|
||||||
if len(args) != 2:
|
|
||||||
parser.parse_args(['-h'])
|
|
||||||
if not options.admin_key:
|
|
||||||
options.admin_key = getpass()
|
|
||||||
account, user = args
|
|
||||||
parsed = urlparse(options.admin_url)
|
|
||||||
if parsed.scheme not in ('http', 'https'):
|
|
||||||
raise ValueError('Cannot handle protocol scheme %s for url %s' %
|
|
||||||
(parsed.scheme, repr(options.admin_url)))
|
|
||||||
parsed_path = parsed.path
|
|
||||||
if not parsed_path:
|
|
||||||
parsed_path = '/'
|
|
||||||
elif parsed_path[-1] != '/':
|
|
||||||
parsed_path += '/'
|
|
||||||
path = '%sv2/%s/%s' % (parsed_path, account, user)
|
|
||||||
headers = {'X-Auth-Admin-User': options.admin_user,
|
|
||||||
'X-Auth-Admin-Key': options.admin_key}
|
|
||||||
conn = http_connect(parsed.hostname, parsed.port, 'DELETE', path, headers,
|
|
||||||
ssl=(parsed.scheme == 'https'))
|
|
||||||
resp = conn.getresponse()
|
|
||||||
if resp.status // 100 != 2:
|
|
||||||
exit('User deletion failed: %s %s' % (resp.status, resp.reason))
|
|
@ -1,86 +0,0 @@
|
|||||||
#!/usr/bin/env python
|
|
||||||
# Copyright (c) 2010-2011 OpenStack, LLC.
|
|
||||||
#
|
|
||||||
# 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.
|
|
||||||
|
|
||||||
from getpass import getpass
|
|
||||||
import gettext
|
|
||||||
import json
|
|
||||||
from optparse import OptionParser
|
|
||||||
from sys import argv
|
|
||||||
from sys import exit
|
|
||||||
|
|
||||||
from six.moves.urllib.parse import urlparse
|
|
||||||
from swift.common.bufferedhttp import http_connect_raw as http_connect
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
gettext.install('swauth', unicode=1)
|
|
||||||
parser = OptionParser(usage='''
|
|
||||||
Usage: %prog [options] [account] [user]
|
|
||||||
|
|
||||||
If [account] and [user] are omitted, a list of accounts will be output.
|
|
||||||
|
|
||||||
If [account] is included but not [user], an account's information will be
|
|
||||||
output, including a list of users within the account.
|
|
||||||
|
|
||||||
If [account] and [user] are included, the user's information will be output,
|
|
||||||
including a list of groups the user belongs to.
|
|
||||||
|
|
||||||
If the [user] is '.groups', the active groups for the account will be listed.
|
|
||||||
'''.strip())
|
|
||||||
parser.add_option('-p', '--plain-text', dest='plain_text',
|
|
||||||
action='store_true', default=False, help='Changes the output from '
|
|
||||||
'JSON to plain text. This will cause an account to list only the '
|
|
||||||
'users and a user to list only the groups.')
|
|
||||||
parser.add_option('-A', '--admin-url', dest='admin_url',
|
|
||||||
default='http://127.0.0.1:8080/auth/', help='The URL to the auth '
|
|
||||||
'subsystem (default: http://127.0.0.1:8080/auth/')
|
|
||||||
parser.add_option('-U', '--admin-user', dest='admin_user',
|
|
||||||
default='.super_admin', help='The user with admin rights to add users '
|
|
||||||
'(default: .super_admin).')
|
|
||||||
parser.add_option('-K', '--admin-key', dest='admin_key',
|
|
||||||
help='The key for the user with admin rights to add users.')
|
|
||||||
args = argv[1:]
|
|
||||||
if not args:
|
|
||||||
args.append('-h')
|
|
||||||
(options, args) = parser.parse_args(args)
|
|
||||||
if len(args) > 2:
|
|
||||||
parser.parse_args(['-h'])
|
|
||||||
if not options.admin_key:
|
|
||||||
options.admin_key = getpass()
|
|
||||||
parsed = urlparse(options.admin_url)
|
|
||||||
if parsed.scheme not in ('http', 'https'):
|
|
||||||
raise ValueError('Cannot handle protocol scheme %s for url %s' %
|
|
||||||
(parsed.scheme, repr(options.admin_url)))
|
|
||||||
parsed_path = parsed.path
|
|
||||||
if not parsed_path:
|
|
||||||
parsed_path = '/'
|
|
||||||
elif parsed_path[-1] != '/':
|
|
||||||
parsed_path += '/'
|
|
||||||
path = '%sv2/%s' % (parsed_path, '/'.join(args))
|
|
||||||
headers = {'X-Auth-Admin-User': options.admin_user,
|
|
||||||
'X-Auth-Admin-Key': options.admin_key}
|
|
||||||
conn = http_connect(parsed.hostname, parsed.port, 'GET', path, headers,
|
|
||||||
ssl=(parsed.scheme == 'https'))
|
|
||||||
resp = conn.getresponse()
|
|
||||||
body = resp.read()
|
|
||||||
if resp.status // 100 != 2:
|
|
||||||
exit('List failed: %s %s' % (resp.status, resp.reason))
|
|
||||||
if options.plain_text:
|
|
||||||
info = json.loads(body)
|
|
||||||
for group in info[['accounts', 'users', 'groups'][len(args)]]:
|
|
||||||
print(group['name'])
|
|
||||||
else:
|
|
||||||
print(body)
|
|
@ -1,62 +0,0 @@
|
|||||||
#!/usr/bin/env python
|
|
||||||
# Copyright (c) 2010-2011 OpenStack, LLC.
|
|
||||||
#
|
|
||||||
# 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.
|
|
||||||
|
|
||||||
from getpass import getpass
|
|
||||||
import gettext
|
|
||||||
from optparse import OptionParser
|
|
||||||
from sys import argv
|
|
||||||
from sys import exit
|
|
||||||
|
|
||||||
from six.moves.urllib.parse import urlparse
|
|
||||||
from swift.common.bufferedhttp import http_connect_raw as http_connect
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
gettext.install('swauth', unicode=1)
|
|
||||||
parser = OptionParser(usage='Usage: %prog [options]')
|
|
||||||
parser.add_option('-A', '--admin-url', dest='admin_url',
|
|
||||||
default='http://127.0.0.1:8080/auth/', help='The URL to the auth '
|
|
||||||
'subsystem (default: http://127.0.0.1:8080/auth/')
|
|
||||||
parser.add_option('-U', '--admin-user', dest='admin_user',
|
|
||||||
default='.super_admin', help='The user with admin rights to add users '
|
|
||||||
'(default: .super_admin).')
|
|
||||||
parser.add_option('-K', '--admin-key', dest='admin_key',
|
|
||||||
help='The key for the user with admin rights to add users.')
|
|
||||||
args = argv[1:]
|
|
||||||
if not args:
|
|
||||||
args.append('-h')
|
|
||||||
(options, args) = parser.parse_args(args)
|
|
||||||
if args:
|
|
||||||
parser.parse_args(['-h'])
|
|
||||||
if not options.admin_key:
|
|
||||||
options.admin_key = getpass()
|
|
||||||
parsed = urlparse(options.admin_url)
|
|
||||||
if parsed.scheme not in ('http', 'https'):
|
|
||||||
raise ValueError('Cannot handle protocol scheme %s for url %s' %
|
|
||||||
(parsed.scheme, repr(options.admin_url)))
|
|
||||||
parsed_path = parsed.path
|
|
||||||
if not parsed_path:
|
|
||||||
parsed_path = '/'
|
|
||||||
elif parsed_path[-1] != '/':
|
|
||||||
parsed_path += '/'
|
|
||||||
path = '%sv2/.prep' % parsed_path
|
|
||||||
headers = {'X-Auth-Admin-User': options.admin_user,
|
|
||||||
'X-Auth-Admin-Key': options.admin_key}
|
|
||||||
conn = http_connect(parsed.hostname, parsed.port, 'POST', path, headers,
|
|
||||||
ssl=(parsed.scheme == 'https'))
|
|
||||||
resp = conn.getresponse()
|
|
||||||
if resp.status // 100 != 2:
|
|
||||||
exit('Auth subsystem prep failed: %s %s' % (resp.status, resp.reason))
|
|
@ -1,74 +0,0 @@
|
|||||||
#!/usr/bin/env python
|
|
||||||
# Copyright (c) 2010-2011 OpenStack, LLC.
|
|
||||||
#
|
|
||||||
# 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.
|
|
||||||
|
|
||||||
from getpass import getpass
|
|
||||||
import gettext
|
|
||||||
import json
|
|
||||||
from optparse import OptionParser
|
|
||||||
from sys import argv
|
|
||||||
from sys import exit
|
|
||||||
|
|
||||||
from six.moves.urllib.parse import urlparse
|
|
||||||
from swift.common.bufferedhttp import http_connect_raw as http_connect
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
gettext.install('swauth', unicode=1)
|
|
||||||
parser = OptionParser(usage='''
|
|
||||||
Usage: %prog [options] <account> <service> <name> <value>
|
|
||||||
|
|
||||||
Sets a service URL for an account. Can only be set by a reseller admin.
|
|
||||||
|
|
||||||
Example: %prog -K swauthkey test storage local
|
|
||||||
http://127.0.0.1:8080/v1/AUTH_018c3946-23f8-4efb-a8fb-b67aae8e4162
|
|
||||||
'''.strip())
|
|
||||||
parser.add_option('-A', '--admin-url', dest='admin_url',
|
|
||||||
default='http://127.0.0.1:8080/auth/', help='The URL to the auth '
|
|
||||||
'subsystem (default: http://127.0.0.1:8080/auth/)')
|
|
||||||
parser.add_option('-U', '--admin-user', dest='admin_user',
|
|
||||||
default='.super_admin', help='The user with admin rights to add users '
|
|
||||||
'(default: .super_admin).')
|
|
||||||
parser.add_option('-K', '--admin-key', dest='admin_key',
|
|
||||||
help='The key for the user with admin rights to add users.')
|
|
||||||
args = argv[1:]
|
|
||||||
if not args:
|
|
||||||
args.append('-h')
|
|
||||||
(options, args) = parser.parse_args(args)
|
|
||||||
if len(args) != 4:
|
|
||||||
parser.parse_args(['-h'])
|
|
||||||
if not options.admin_key:
|
|
||||||
options.admin_key = getpass()
|
|
||||||
account, service, name, url = args
|
|
||||||
parsed = urlparse(options.admin_url)
|
|
||||||
if parsed.scheme not in ('http', 'https'):
|
|
||||||
raise ValueError('Cannot handle protocol scheme %s for url %s' %
|
|
||||||
(parsed.scheme, repr(options.admin_url)))
|
|
||||||
parsed_path = parsed.path
|
|
||||||
if not parsed_path:
|
|
||||||
parsed_path = '/'
|
|
||||||
elif parsed_path[-1] != '/':
|
|
||||||
parsed_path += '/'
|
|
||||||
path = '%sv2/%s/.services' % (parsed_path, account)
|
|
||||||
body = json.dumps({service: {name: url}})
|
|
||||||
headers = {'Content-Length': str(len(body)),
|
|
||||||
'X-Auth-Admin-User': options.admin_user,
|
|
||||||
'X-Auth-Admin-Key': options.admin_key}
|
|
||||||
conn = http_connect(parsed.hostname, parsed.port, 'POST', path, headers,
|
|
||||||
ssl=(parsed.scheme == 'https'))
|
|
||||||
conn.send(body)
|
|
||||||
resp = conn.getresponse()
|
|
||||||
if resp.status // 100 != 2:
|
|
||||||
exit('Service set failed: %s %s' % (resp.status, resp.reason))
|
|
@ -1,5 +0,0 @@
|
|||||||
# This is a cross-platform list tracking distribution packages needed by tests;
|
|
||||||
# see http://docs.openstack.org/infra/bindep/ for additional information.
|
|
||||||
|
|
||||||
liberasurecode-dev [platform:dpkg]
|
|
||||||
liberasurecode-devel [platform:rpm]
|
|
1
doc/build/.gitignore
vendored
1
doc/build/.gitignore
vendored
@ -1 +0,0 @@
|
|||||||
*
|
|
@ -1,9 +0,0 @@
|
|||||||
This document is licensed under Creative Commons Attribution 3.0 License.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
http://creativecommons.org/licenses/by/3.0/legalcode
|
|
||||||
|
|
||||||
|
|
||||||
Rodney Beede
|
|
||||||
http://www.rodneybeede.com/
|
|
Binary file not shown.
@ -1,120 +0,0 @@
|
|||||||
<section xml:id="ch027_storage-idpH123">
|
|
||||||
<title>SWAuth</title>
|
|
||||||
<para>SWAuth is another popular alternative to Keystone.
|
|
||||||
In contrast to Keystone it stores the user accounts,
|
|
||||||
credentials, and metadata in object storage itself. More
|
|
||||||
specifics about where the objects are stored can be found
|
|
||||||
on the SWAuth website at
|
|
||||||
<link xlink:href="http://gholt.github.io/swauth/">http://gholt.github.io/swauth/</link>.</para>
|
|
||||||
<para>SWAuth has these types of roles (or groups) for a
|
|
||||||
user:</para>
|
|
||||||
<informaltable>
|
|
||||||
<tbody>
|
|
||||||
<tr>
|
|
||||||
<td>.super_admin</td>
|
|
||||||
<td>Can perform any action on any
|
|
||||||
OpenStack Account, Container, or Object</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>.reseller_admin</td>
|
|
||||||
<td>Can perform most actions on
|
|
||||||
any OpenStack Account, Container, or Object.
|
|
||||||
Cannot create other reseller admins.
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>.admin</td>
|
|
||||||
<td>Can perform actions limited to
|
|
||||||
the single OpenStack Account it belongs to</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>Regular User</td>
|
|
||||||
<td>Can access containers or
|
|
||||||
objects they have permission to in the
|
|
||||||
OpenStack Account to which they belong
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</informaltable>
|
|
||||||
<para>The following table provides a matrix of what each
|
|
||||||
role/group can do:</para>
|
|
||||||
<figure>
|
|
||||||
<title>Object storage SWAuth role matrix</title>
|
|
||||||
<mediaobject>
|
|
||||||
<imageobject role="html">
|
|
||||||
<imagedata contentdepth="393"
|
|
||||||
contentwidth="1047"
|
|
||||||
fileref="static/swift_swauth_roles_matrix.png"
|
|
||||||
format="PNG" scalefit="1"/>
|
|
||||||
</imageobject>
|
|
||||||
<imageobject role="fo">
|
|
||||||
<imagedata contentdepth="100%"
|
|
||||||
fileref="static/swift_swauth_roles_matrix.png"
|
|
||||||
format="PNG" scalefit="1" width="100%"/>
|
|
||||||
</imageobject>
|
|
||||||
</mediaobject>
|
|
||||||
</figure>
|
|
||||||
<warning><para>The super admin key is stored in
|
|
||||||
<filename>/etc/swift/proxy-server.conf</filename> and MUST
|
|
||||||
be protected! See the File Permissions section for
|
|
||||||
guidance on protecting this file. Frequent changing of
|
|
||||||
this key is recommended.</para></warning>
|
|
||||||
<para>One approach for administration is to create an
|
|
||||||
OpenStack Object Storage Account called "CloudAdmins" and
|
|
||||||
create reseller_admin users in that account. Each user
|
|
||||||
will be able to do administrative functions in all the
|
|
||||||
other accounts. Creating a reseller_admin will require
|
|
||||||
the super admin key.</para>
|
|
||||||
<para>Another useful way to secure the super admin key is
|
|
||||||
to have it exist only on the proxy server and retrieve the
|
|
||||||
key on-demand via ssh or by running the command on the
|
|
||||||
proxy server itself and using a grep to extract the key on
|
|
||||||
the fly.</para>
|
|
||||||
<section xml:id="ch027_storage-idpH1234">
|
|
||||||
<title>Protecting cloud administration</title>
|
|
||||||
<para>When using SWAuth you can actually designate
|
|
||||||
that certain proxy service nodes are to NOT allow
|
|
||||||
administrator API calls. This is useful if you have
|
|
||||||
Proxy service nodes on the public Internet and wish to
|
|
||||||
restrict administration functions to only special
|
|
||||||
Proxy service nodes on a private network. This is
|
|
||||||
done by setting the
|
|
||||||
<code>allow_account_managment</code> to false in your
|
|
||||||
<filename>proxy-server.conf</filename>.</para>
|
|
||||||
<para>Another important consideration is that the
|
|
||||||
SWAuth command line tools expose the user credentials
|
|
||||||
on the command-line. The system from which they are
|
|
||||||
executed must be secure to prevent disclosure in the
|
|
||||||
process list to other uses. Another option is to use
|
|
||||||
the SWAuth admin REST API to implement your own admin
|
|
||||||
CLI tools that don’t expose the key as a command-line
|
|
||||||
option.</para>
|
|
||||||
</section>
|
|
||||||
<section xml:id="ch027_storage-idpH12345">
|
|
||||||
<title>Salting and hashing passwords</title>
|
|
||||||
<para>SWAuth by default stores passwords in
|
|
||||||
clear-text. It also offers a sha1 hashing provider,
|
|
||||||
but the salt used is global. Additionally, no
|
|
||||||
iterations or key stretching is performed. This is a
|
|
||||||
limitation of SWAuth.</para>
|
|
||||||
<para>You may optionally add-in your own hashing code
|
|
||||||
or provider as a hook to SWAuth. See the
|
|
||||||
<link xlink:href="http://gholt.github.io/swauth/dev/authtypes.html">SWAuth code and site</link>
|
|
||||||
for details.</para>
|
|
||||||
<para>If you use the global salt be sure to secure it
|
|
||||||
and back it up. If you have multiple proxy nodes each
|
|
||||||
one has to have a copy so that may be good enough for
|
|
||||||
you. If you ever lose it or change it then all
|
|
||||||
existing user passwords will not work and will have to
|
|
||||||
be reset.</para>
|
|
||||||
<para>You should make sure the salt you choose is
|
|
||||||
generated using a cryptographically secure random
|
|
||||||
number generator and of sufficient length. At least
|
|
||||||
20 characters is recommended.</para>
|
|
||||||
<para>The salt is stored in the
|
|
||||||
<filename>/etc/swift/proxy-server.conf</filename>
|
|
||||||
file which must be secured with proper ACLs. See the
|
|
||||||
File Permissions section for guidance.</para>
|
|
||||||
</section>
|
|
||||||
</section><!-- SWAuth -->
|
|
||||||
</section><!-- Object Storage Authentication -->
|
|
Binary file not shown.
Before Width: | Height: | Size: 36 KiB |
@ -1,468 +0,0 @@
|
|||||||
.. _api_top:
|
|
||||||
|
|
||||||
----------
|
|
||||||
Swauth API
|
|
||||||
----------
|
|
||||||
|
|
||||||
Overview
|
|
||||||
========
|
|
||||||
|
|
||||||
Swauth has its own internal versioned REST API for adding, removing,
|
|
||||||
and editing accounts. This document explains the v2 API.
|
|
||||||
|
|
||||||
Authentication
|
|
||||||
--------------
|
|
||||||
|
|
||||||
Each REST request against the swauth API requires the inclusion of a
|
|
||||||
specific authorization user and key to be passed in a specific HTTP
|
|
||||||
header. These headers are defined as ``X-Auth-Admin-User`` and
|
|
||||||
``X-Auth-Admin-Key``.
|
|
||||||
|
|
||||||
Typically, these values are ``.super_admin`` (the site super admin
|
|
||||||
user) with the key being specified in the swauth middleware
|
|
||||||
configuration as ``super_admin_key``.
|
|
||||||
|
|
||||||
This could also be a reseller admin with the appropriate rights to
|
|
||||||
perform actions on reseller accounts.
|
|
||||||
|
|
||||||
Endpoints
|
|
||||||
---------
|
|
||||||
|
|
||||||
The swauth API endpoint is presented on the proxy servers, in the
|
|
||||||
"/auth" namespace. In addition, the API is versioned, and the version
|
|
||||||
documented is version 2. API versions subdivide the auth namespace by
|
|
||||||
version, specified as a version identifier like "v2".
|
|
||||||
|
|
||||||
The auth endpoint described herein is therefore located at "/auth/v2/"
|
|
||||||
as presented by the proxy servers.
|
|
||||||
|
|
||||||
Bear in mind that in order for the auth management API to be
|
|
||||||
presented, it must be enabled in the proxy server config by setting
|
|
||||||
``allow_account_managment`` to ``true`` in the ``[app:proxy-server]``
|
|
||||||
stanza of your proxy-server.conf.
|
|
||||||
|
|
||||||
Responses
|
|
||||||
---------
|
|
||||||
|
|
||||||
Responses from the auth APIs are returned as a JSON structure.
|
|
||||||
Example return values in this document are edited for readability.
|
|
||||||
|
|
||||||
|
|
||||||
Reseller/Admin Services
|
|
||||||
=======================
|
|
||||||
|
|
||||||
Operations can be performed against the endpoint itself to perform
|
|
||||||
general administrative operations. Currently, the only operations
|
|
||||||
that can be performed is a GET operation to get reseller or site admin
|
|
||||||
information.
|
|
||||||
|
|
||||||
Get Admin Info
|
|
||||||
--------------
|
|
||||||
|
|
||||||
A GET request at the swauth endpoint will return reseller information
|
|
||||||
for the account specified in the ``X-Auth-Admin-User`` header.
|
|
||||||
Currently, the information returned is limited to a list of accounts
|
|
||||||
for the reseller or site admin.
|
|
||||||
|
|
||||||
Valid return codes:
|
|
||||||
* 200: Success
|
|
||||||
* 403: Invalid X-Auth-Admin-User/X-Auth-Admin-Key
|
|
||||||
* 5xx: Internal error
|
|
||||||
|
|
||||||
Example Request::
|
|
||||||
|
|
||||||
GET /auth/<api version>/ HTTP/1.1
|
|
||||||
X-Auth-Admin-User: .super_admin
|
|
||||||
X-Auth-Admin-Key: swauthkey
|
|
||||||
|
|
||||||
Example Curl Request::
|
|
||||||
|
|
||||||
curl -D - https://<endpoint>/auth/v2/ \
|
|
||||||
-H "X-Auth-Admin-User: .super_admin" \
|
|
||||||
-H "X-Auth-Admin-Key: swauthkey"
|
|
||||||
|
|
||||||
Example Result::
|
|
||||||
|
|
||||||
HTTP/1.1 200 OK
|
|
||||||
|
|
||||||
{ "accounts":
|
|
||||||
[
|
|
||||||
{ "name": "account1" },
|
|
||||||
{ "name": "account2" },
|
|
||||||
{ "name": "account3" }
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
Account Services
|
|
||||||
================
|
|
||||||
|
|
||||||
There are API request to get account details, create, and delete
|
|
||||||
accounts, mapping logically to the REST verbs GET, PUT, and DELETE.
|
|
||||||
These actions are performed against an account URI, in the following
|
|
||||||
general request structure::
|
|
||||||
|
|
||||||
METHOD /auth/<version>/<account> HTTP/1.1
|
|
||||||
|
|
||||||
The methods that can be used are detailed below.
|
|
||||||
|
|
||||||
Get Account Details
|
|
||||||
-------------------
|
|
||||||
|
|
||||||
Account details can be retrieved by performing a GET request against
|
|
||||||
an account URI. On success, a JSON dictionary will be returned
|
|
||||||
containing the keys `account_id`, `services`, and `users`. The
|
|
||||||
`account_id` is the value used when creating service accounts. The
|
|
||||||
`services` value is a dict that represents valid storage cluster
|
|
||||||
endpoints, and which endpoint is the default. The 'users' value is a
|
|
||||||
list of dicts, each dict representing a user and currently only
|
|
||||||
containing the single key 'name'.
|
|
||||||
|
|
||||||
Valid Responses:
|
|
||||||
* 200: Success
|
|
||||||
* 403: Invalid X-Auth-Admin-User/X-Auth-Admin-Key
|
|
||||||
* 5xx: Internal error
|
|
||||||
|
|
||||||
Example Request::
|
|
||||||
|
|
||||||
GET /auth/<api version>/<account> HTTP/1.1
|
|
||||||
X-Auth-Admin-User: .super_admin
|
|
||||||
X-Auth-Admin-Key: swauthkey
|
|
||||||
|
|
||||||
Example Curl Request::
|
|
||||||
|
|
||||||
curl -D - https://<endpoint>/auth/v2/<account> \
|
|
||||||
-H "X-Auth-Admin-User: .super_admin" \
|
|
||||||
-H "X-Auth-Admin-Key: swauthkey"
|
|
||||||
|
|
||||||
Example Response::
|
|
||||||
|
|
||||||
HTTP/1.1 200 OK
|
|
||||||
|
|
||||||
{ "services":
|
|
||||||
{ "storage":
|
|
||||||
{ "default": "local",
|
|
||||||
"local": "https://<storage endpoint>/v1/<account_id>" }
|
|
||||||
},
|
|
||||||
"account_id": "<account_id>",
|
|
||||||
"users": [ { "name": "user1" },
|
|
||||||
{ "name": "user2" } ]
|
|
||||||
}
|
|
||||||
|
|
||||||
Create Account
|
|
||||||
--------------
|
|
||||||
|
|
||||||
An account can be created with a PUT request against a non-existent
|
|
||||||
account. By default, a newly created UUID4 will be used with the
|
|
||||||
reseller prefix as the account ID used when creating corresponding
|
|
||||||
service accounts. However, you can provide an X-Account-Suffix header
|
|
||||||
to replace the UUDI4 part.
|
|
||||||
|
|
||||||
Valid return codes:
|
|
||||||
* 200: Success
|
|
||||||
* 403: Invalid X-Auth-Admin-User/X-Auth-Admin-Key
|
|
||||||
* 5xx: Internal error
|
|
||||||
|
|
||||||
Example Request::
|
|
||||||
|
|
||||||
PUT /auth/<api version>/<new_account> HTTP/1.1
|
|
||||||
X-Auth-Admin-User: .super_admin
|
|
||||||
X-Auth-Admin-Key: swauthkey
|
|
||||||
|
|
||||||
Example Curl Request::
|
|
||||||
|
|
||||||
curl -XPUT -D - https://<endpoint>/auth/v2/<new_account> \
|
|
||||||
-H "X-Auth-Admin-User: .super_admin" \
|
|
||||||
-H "X-Auth-Admin-Key: swauthkey"
|
|
||||||
|
|
||||||
Example Response::
|
|
||||||
|
|
||||||
HTTP/1.1 201 Created
|
|
||||||
|
|
||||||
|
|
||||||
Delete Account
|
|
||||||
--------------
|
|
||||||
|
|
||||||
An account can be deleted with a DELETE request against an existing
|
|
||||||
account.
|
|
||||||
|
|
||||||
Valid Responses:
|
|
||||||
* 204: Success
|
|
||||||
* 403: Invalid X-Auth-Admin-User/X-Auth-Admin-Key
|
|
||||||
* 404: Account not found
|
|
||||||
* 5xx: Internal error
|
|
||||||
|
|
||||||
Example Request::
|
|
||||||
|
|
||||||
DELETE /auth/<api version>/<account> HTTP/1.1
|
|
||||||
X-Auth-Admin-User: .super_admin
|
|
||||||
X-Auth-Admin-Key: swauthkey
|
|
||||||
|
|
||||||
Example Curl Request::
|
|
||||||
|
|
||||||
curl -XDELETE -D - https://<endpoint>/auth/v2/<account> \
|
|
||||||
-H "X-Auth-Admin-User: .super_admin" \
|
|
||||||
-H "X-Auth-Admin-Key: swauthkey"
|
|
||||||
|
|
||||||
Example Response::
|
|
||||||
|
|
||||||
HTTP/1.1 204 No Content
|
|
||||||
|
|
||||||
|
|
||||||
User Services
|
|
||||||
=============
|
|
||||||
|
|
||||||
Each account in swauth contains zero or more users. These users can
|
|
||||||
be determined with the 'Get Account Details' API request against an
|
|
||||||
account.
|
|
||||||
|
|
||||||
Users in an account can be created, modified, and detailed as
|
|
||||||
described below by apply the appropriate REST verbs to a user URI, in
|
|
||||||
the following general request structure::
|
|
||||||
|
|
||||||
METHOD /auth/<version>/<account>/<user> HTTP/1.1
|
|
||||||
|
|
||||||
The methods that can be used are detailed below.
|
|
||||||
|
|
||||||
Get User Details
|
|
||||||
----------------
|
|
||||||
|
|
||||||
User details can be retrieved by performing a GET request against
|
|
||||||
a user URI. On success, a JSON dictionary will be returned as
|
|
||||||
described::
|
|
||||||
|
|
||||||
{"groups": [ # List of groups the user is a member of
|
|
||||||
{"name": "<act>:<usr>"},
|
|
||||||
# The first group is a unique user identifier
|
|
||||||
{"name": "<account>"},
|
|
||||||
# The second group is the auth account name
|
|
||||||
{"name": "<additional-group>"}
|
|
||||||
# There may be additional groups, .admin being a
|
|
||||||
# special group indicating an account admin and
|
|
||||||
# .reseller_admin indicating a reseller admin.
|
|
||||||
],
|
|
||||||
"auth": "<auth-type>:<key>"
|
|
||||||
# The auth-type and key for the user; currently only
|
|
||||||
# plaintext and sha1 are implemented as auth types.
|
|
||||||
}
|
|
||||||
|
|
||||||
For example::
|
|
||||||
|
|
||||||
{"groups": [{"name": "test:tester"}, {"name": "test"},
|
|
||||||
{"name": ".admin"}],
|
|
||||||
"auth": "plaintext:testing"}
|
|
||||||
|
|
||||||
Valid Responses:
|
|
||||||
* 200: Success
|
|
||||||
* 403: Invalid X-Auth-Admin-User/X-Auth-Admin-Key
|
|
||||||
* 404: Unknown account
|
|
||||||
* 5xx: Internal error
|
|
||||||
|
|
||||||
Example Request::
|
|
||||||
|
|
||||||
GET /auth/<api version>/<account>/<user> HTTP/1.1
|
|
||||||
X-Auth-Admin-User: .super_admin
|
|
||||||
X-Auth-Admin-Key: swauthkey
|
|
||||||
|
|
||||||
Example Curl Request::
|
|
||||||
|
|
||||||
curl -D - https://<endpoint>/auth/v2/<account>/<user> \
|
|
||||||
-H "X-Auth-Admin-User: .super_admin" \
|
|
||||||
-H "X-Auth-Admin-Key: swauthkey"
|
|
||||||
|
|
||||||
Example Response::
|
|
||||||
|
|
||||||
HTTP/1.1 200 Ok
|
|
||||||
|
|
||||||
{ "groups": [ { "name": "<account>:<user>" },
|
|
||||||
{ "name": "<account>" },
|
|
||||||
{ "name": ".admin" } ],
|
|
||||||
"auth" : "plaintext:password" }
|
|
||||||
|
|
||||||
|
|
||||||
Create User
|
|
||||||
-----------
|
|
||||||
|
|
||||||
A user can be created with a PUT request against a non-existent
|
|
||||||
user URI. The new user's password must be set using the
|
|
||||||
``X-Auth-User-Key`` header. The user name MUST NOT start with a
|
|
||||||
period ('.'). This requirement is enforced by the API, and will
|
|
||||||
result in a 400 error. Alternatively you can use
|
|
||||||
``X-Auth-User-Key-Hash`` header for providing already hashed
|
|
||||||
password in format ``<auth_type>:<hashed_password>``.
|
|
||||||
|
|
||||||
Optional Headers:
|
|
||||||
|
|
||||||
* ``X-Auth-User-Admin: true``: create the user as an account admin
|
|
||||||
* ``X-Auth-User-Reseller-Admin: true``: create the user as a reseller
|
|
||||||
admin
|
|
||||||
|
|
||||||
Reseller admin accounts can only be created by the site admin, while
|
|
||||||
regular accounts (or account admin accounts) can be created by an
|
|
||||||
account admin, an appropriate reseller admin, or the site admin.
|
|
||||||
|
|
||||||
Note that PUT requests are idempotent, and the PUT request serves as
|
|
||||||
both a request and modify action.
|
|
||||||
|
|
||||||
Valid Responses:
|
|
||||||
* 200: Success
|
|
||||||
* 400: Invalid request (missing required headers)
|
|
||||||
* 403: Invalid X-Auth-Admin-User/X-Auth-Admin-Key, or insufficient priv
|
|
||||||
* 404: Unknown account
|
|
||||||
* 5xx: Internal error
|
|
||||||
|
|
||||||
Example Request::
|
|
||||||
|
|
||||||
PUT /auth/<api version>/<account>/<user> HTTP/1.1
|
|
||||||
X-Auth-Admin-User: .super_admin
|
|
||||||
X-Auth-Admin-Key: swauthkey
|
|
||||||
X-Auth-User-Admin: true
|
|
||||||
X-Auth-User-Key: secret
|
|
||||||
|
|
||||||
Example Curl Request::
|
|
||||||
|
|
||||||
curl -XPUT -D - https://<endpoint>/auth/v2/<account>/<user> \
|
|
||||||
-H "X-Auth-Admin-User: .super_admin" \
|
|
||||||
-H "X-Auth-Admin-Key: swauthkey" \
|
|
||||||
-H "X-Auth-User-Admin: true" \
|
|
||||||
-H "X-Auth-User-Key: secret"
|
|
||||||
|
|
||||||
Example Response::
|
|
||||||
|
|
||||||
HTTP/1.1 201 Created
|
|
||||||
|
|
||||||
Delete User
|
|
||||||
-----------
|
|
||||||
|
|
||||||
A user can be deleted by performing a DELETE request against a user
|
|
||||||
URI. This action can only be performed by an account admin,
|
|
||||||
appropriate reseller admin, or site admin.
|
|
||||||
|
|
||||||
Valid Responses:
|
|
||||||
* 200: Success
|
|
||||||
* 403: Invalid X-Auth-Admin-User/X-Auth-Admin-Key, or insufficient priv
|
|
||||||
* 404: Unknown account or user
|
|
||||||
* 5xx: Internal error
|
|
||||||
|
|
||||||
Example Request::
|
|
||||||
|
|
||||||
DELETE /auth/<api version>/<account>/<user> HTTP/1.1
|
|
||||||
X-Auth-Admin-User: .super_admin
|
|
||||||
X-Auth-Admin-Key: swauthkey
|
|
||||||
|
|
||||||
Example Curl Request::
|
|
||||||
|
|
||||||
curl -XDELETE -D - https://<endpoint>/auth/v2/<account>/<user> \
|
|
||||||
-H "X-Auth-Admin-User: .super_admin" \
|
|
||||||
-H "X-Auth-Admin-Key: swauthkey"
|
|
||||||
|
|
||||||
Example Response::
|
|
||||||
|
|
||||||
HTTP/1.1 204 No Content
|
|
||||||
|
|
||||||
|
|
||||||
Other Services
|
|
||||||
==============
|
|
||||||
|
|
||||||
There are several other swauth functions that can be performed, mostly
|
|
||||||
done via "pseudo-user" accounts. These are well-known user names that
|
|
||||||
are unable to be actually provisioned. These pseudo-users are
|
|
||||||
described below.
|
|
||||||
|
|
||||||
.. _api_set_service_endpoints:
|
|
||||||
|
|
||||||
Set Service Endpoints
|
|
||||||
---------------------
|
|
||||||
|
|
||||||
Service endpoint information can be retrived using the _`Get Account
|
|
||||||
Details` API method.
|
|
||||||
|
|
||||||
This function allows setting values within this section for
|
|
||||||
the <account>, allowing the addition of new service end points
|
|
||||||
or updating existing ones by performing a POST to the URI
|
|
||||||
corresponding to the pseudo-user ".services".
|
|
||||||
|
|
||||||
The body of the POST request should contain a JSON dict with
|
|
||||||
the following format::
|
|
||||||
|
|
||||||
{"service_name": {"end_point_name": "end_point_value"}}
|
|
||||||
|
|
||||||
There can be multiple services and multiple end points in the
|
|
||||||
same call.
|
|
||||||
|
|
||||||
Any new services or end points will be added to the existing
|
|
||||||
set of services and end points. Any existing services with the
|
|
||||||
same service name will be merged with the new end points. Any
|
|
||||||
existing end points with the same end point name will have
|
|
||||||
their values updated.
|
|
||||||
|
|
||||||
The updated services dictionary will be returned on success.
|
|
||||||
|
|
||||||
Valid Responses:
|
|
||||||
|
|
||||||
* 200: Success
|
|
||||||
* 403: Invalid X-Auth-Admin-User/X-Auth-Admin-Key
|
|
||||||
* 404: Account not found
|
|
||||||
* 5xx: Internal error
|
|
||||||
|
|
||||||
Example Request::
|
|
||||||
|
|
||||||
POST /auth/<api version>/<account>/.services HTTP/1.0
|
|
||||||
X-Auth-Admin-User: .super_admin
|
|
||||||
X-Auth-Admin-Key: swauthkey
|
|
||||||
|
|
||||||
{"storage": { "local": "<new endpoint>" }}
|
|
||||||
|
|
||||||
Example Curl Request::
|
|
||||||
|
|
||||||
curl -XPOST -D - https://<endpoint>/auth/v2/<account>/.services \
|
|
||||||
-H "X-Auth-Admin-User: .super_admin" \
|
|
||||||
-H "X-Auth-Admin-Key: swauthkey" --data-binary \
|
|
||||||
'{ "storage": { "local": "<new endpoint>" }}'
|
|
||||||
|
|
||||||
Example Response::
|
|
||||||
|
|
||||||
HTTP/1.1 200 OK
|
|
||||||
|
|
||||||
{"storage": {"default": "local", "local": "<new endpoint>" }}
|
|
||||||
|
|
||||||
Get Account Groups
|
|
||||||
------------------
|
|
||||||
|
|
||||||
Individual user group information can be retrieved using the `Get User Details`_ API method.
|
|
||||||
|
|
||||||
This function allows retrieving all group information for all users in
|
|
||||||
an existing account. This can be achieved using a GET action against
|
|
||||||
a user URI with the pseudo-user ".groups".
|
|
||||||
|
|
||||||
The JSON dictionary returned will be a "groups" dictionary similar to
|
|
||||||
that documented in the `Get User Details`_ method, but representing
|
|
||||||
the summary of all groups utilized by all active users in the account.
|
|
||||||
|
|
||||||
Valid Responses:
|
|
||||||
* 200: Success
|
|
||||||
* 403: Invalid X-Auth-Admin-User/X-Auth-Admin-Key
|
|
||||||
* 404: Account not found
|
|
||||||
* 5xx: Internal error
|
|
||||||
|
|
||||||
Example Request::
|
|
||||||
|
|
||||||
GET /auth/<api version>/<account>/.groups
|
|
||||||
X-Auth-Admin-User: .super_admin
|
|
||||||
X-Auth-Admin-Key: swauthkey
|
|
||||||
|
|
||||||
Example Curl Request::
|
|
||||||
|
|
||||||
curl -D - https://<endpoint>/auth/v2/<account>/.groups \
|
|
||||||
-H "X-Auth-Admin-User: .super_admin" \
|
|
||||||
-H "X-Auth-Admin-Key: swauthkey"
|
|
||||||
|
|
||||||
Example Response::
|
|
||||||
|
|
||||||
HTTP/1.1 200 OK
|
|
||||||
|
|
||||||
{ "groups": [ { "name": ".admin" },
|
|
||||||
{ "name": "<account>" },
|
|
||||||
{ "name": "<account>:user1" },
|
|
||||||
{ "name": "<account>:user2" } ] }
|
|
||||||
|
|
@ -1,10 +0,0 @@
|
|||||||
.. _swauth_authtypes_module:
|
|
||||||
|
|
||||||
swauth.authtypes
|
|
||||||
=================
|
|
||||||
|
|
||||||
.. automodule:: swauth.authtypes
|
|
||||||
:members:
|
|
||||||
:undoc-members:
|
|
||||||
:show-inheritance:
|
|
||||||
:noindex:
|
|
@ -1,234 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
# Copyright (c) 2010-2011 OpenStack, LLC.
|
|
||||||
#
|
|
||||||
# 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.
|
|
||||||
|
|
||||||
#
|
|
||||||
# Swauth documentation build configuration file, created by
|
|
||||||
# sphinx-quickstart on Mon Feb 14 19:34:51 2011.
|
|
||||||
#
|
|
||||||
# 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 sys, os
|
|
||||||
|
|
||||||
import swauth
|
|
||||||
|
|
||||||
# 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']
|
|
||||||
|
|
||||||
# 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'Swauth'
|
|
||||||
copyright = u'2010-2011, OpenStack, LLC'
|
|
||||||
|
|
||||||
# The version info for the project you're documenting, acts as replacement for
|
|
||||||
# |version| and |release|, also used in various other places throughout the
|
|
||||||
# built documents.
|
|
||||||
#
|
|
||||||
# The short X.Y version.
|
|
||||||
from swauth import __version__
|
|
||||||
version = __version__.rsplit('.', 1)[0]
|
|
||||||
# The full version, including alpha/beta/rc tags.
|
|
||||||
release = swauth.__version__
|
|
||||||
|
|
||||||
# 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 = []
|
|
||||||
|
|
||||||
# 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 = []
|
|
||||||
|
|
||||||
|
|
||||||
# -- 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 = 'default'
|
|
||||||
|
|
||||||
# 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 = ['_static']
|
|
||||||
|
|
||||||
# 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 = 'Swauthdoc'
|
|
||||||
|
|
||||||
|
|
||||||
# -- Options for LaTeX output --------------------------------------------------
|
|
||||||
|
|
||||||
# The paper size ('letter' or 'a4').
|
|
||||||
#latex_paper_size = 'letter'
|
|
||||||
|
|
||||||
# The font size ('10pt', '11pt' or '12pt').
|
|
||||||
#latex_font_size = '10pt'
|
|
||||||
|
|
||||||
# Grouping the document tree into LaTeX files. List of tuples
|
|
||||||
# (source start file, target name, title, author, documentclass [howto/manual]).
|
|
||||||
latex_documents = [
|
|
||||||
('index', 'Swauth.tex', u'Swauth Documentation',
|
|
||||||
u'OpenStack, LLC', 'manual'),
|
|
||||||
]
|
|
||||||
|
|
||||||
# The name of an image file (relative to this directory) to place at the top of
|
|
||||||
# the title page.
|
|
||||||
#latex_logo = None
|
|
||||||
|
|
||||||
# For "manual" documents, if this is true, then toplevel headings are parts,
|
|
||||||
# not chapters.
|
|
||||||
#latex_use_parts = False
|
|
||||||
|
|
||||||
# If true, show page references after internal links.
|
|
||||||
#latex_show_pagerefs = False
|
|
||||||
|
|
||||||
# If true, show URL addresses after external links.
|
|
||||||
#latex_show_urls = False
|
|
||||||
|
|
||||||
# Additional stuff for the LaTeX preamble.
|
|
||||||
#latex_preamble = ''
|
|
||||||
|
|
||||||
# Documents to append as an appendix to all manuals.
|
|
||||||
#latex_appendices = []
|
|
||||||
|
|
||||||
# If false, no module index is generated.
|
|
||||||
#latex_domain_indices = True
|
|
||||||
|
|
||||||
|
|
||||||
# -- Options for manual page output --------------------------------------------
|
|
||||||
|
|
||||||
# One entry per manual page. List of tuples
|
|
||||||
# (source start file, name, description, authors, manual section).
|
|
||||||
man_pages = [
|
|
||||||
('index', 'swauth', u'Swauth Documentation',
|
|
||||||
[u'OpenStack, LLC'], 1)
|
|
||||||
]
|
|
@ -1,159 +0,0 @@
|
|||||||
----------------------
|
|
||||||
Implementation Details
|
|
||||||
----------------------
|
|
||||||
|
|
||||||
The Swauth system is a scalable authentication and authorization system that
|
|
||||||
uses Swift itself as its backing store. This section will describe how it
|
|
||||||
stores its data.
|
|
||||||
|
|
||||||
.. note::
|
|
||||||
|
|
||||||
You can access Swauth's internal .auth account by using the account:user of
|
|
||||||
.super_admin:.super_admin and the super admin key you have set in your
|
|
||||||
configuration. Here's an example using `st` on a standard SAIO: ``st -A
|
|
||||||
http://127.0.0.1:8080/auth/v1.0 -U .super_admin:.super_admin -K swauthkey
|
|
||||||
stat``
|
|
||||||
|
|
||||||
At the topmost level, the auth system has its own Swift account it stores its
|
|
||||||
own account information within. This Swift account is known as
|
|
||||||
self.auth_account in the code and its name is in the format
|
|
||||||
self.reseller_prefix + ".auth". In this text, we'll refer to this account as
|
|
||||||
<auth_account>.
|
|
||||||
|
|
||||||
The containers whose names do not begin with a period represent the accounts
|
|
||||||
within the auth service. For example, the <auth_account>/test container would
|
|
||||||
represent the "test" account.
|
|
||||||
|
|
||||||
The objects within each container represent the users for that auth service
|
|
||||||
account. For example, the <auth_account>/test/bob object would represent the
|
|
||||||
user "bob" within the auth service account of "test". Each of these user
|
|
||||||
objects contain a JSON dictionary of the format::
|
|
||||||
|
|
||||||
{"auth": "<auth_type>:<auth_value>", "groups": <groups_array>}
|
|
||||||
|
|
||||||
The `<auth_type>` specifies how the user key is encoded. The default is `plaintext`,
|
|
||||||
which saves the user's key in plaintext in the `<auth_value>` field.
|
|
||||||
The value `sha1` is supported as well, which stores the user's key as a salted
|
|
||||||
SHA1 hash. Note that using a one-way hash like SHA1 will likely inhibit future use of key-signing request types, assuming such support is added. The `<auth_type>` can be specified in the swauth section of the proxy server's
|
|
||||||
config file, along with the salt value in the following way::
|
|
||||||
|
|
||||||
auth_type = <auth_type>
|
|
||||||
auth_type_salt = <salt-value>
|
|
||||||
|
|
||||||
Both fields are optional. auth_type defaults to `plaintext` and auth_type_salt defaults to "swauthsalt". Additional auth types can be implemented along with existing ones in the authtypes.py module.
|
|
||||||
|
|
||||||
The `<groups_array>` contains at least two groups. The first is a unique group
|
|
||||||
identifying that user and it's name is of the format `<user>:<account>`. The
|
|
||||||
second group is the `<account>` itself. Additional groups of `.admin` for
|
|
||||||
account administrators and `.reseller_admin` for reseller administrators may
|
|
||||||
exist. Here's an example user JSON dictionary::
|
|
||||||
|
|
||||||
{"auth": "plaintext:testing",
|
|
||||||
"groups": [{"name": "test:tester"}, {"name": "test"}, {"name": ".admin"}]}
|
|
||||||
|
|
||||||
To map an auth service account to a Swift storage account, the Service Account
|
|
||||||
Id string is stored in the `X-Container-Meta-Account-Id` header for the
|
|
||||||
<auth_account>/<account> container. To map back the other way, an
|
|
||||||
<auth_account>/.account_id/<account_id> object is created with the contents of
|
|
||||||
the corresponding auth service's account name.
|
|
||||||
|
|
||||||
Also, to support a future where the auth service will support multiple Swift
|
|
||||||
clusters or even multiple services for the same auth service account, an
|
|
||||||
<auth_account>/<account>/.services object is created with its contents having a
|
|
||||||
JSON dictionary of the format::
|
|
||||||
|
|
||||||
{"storage": {"default": "local", "local": <url>}}
|
|
||||||
|
|
||||||
The "default" is always "local" right now, and "local" is always the single
|
|
||||||
Swift cluster URL; but in the future there can be more than one cluster with
|
|
||||||
various names instead of just "local", and the "default" key's value will
|
|
||||||
contain the primary cluster to use for that account. Also, there may be more
|
|
||||||
services in addition to the current "storage" service right now.
|
|
||||||
|
|
||||||
Here's an example .services dictionary at the moment::
|
|
||||||
|
|
||||||
{"storage":
|
|
||||||
{"default": "local",
|
|
||||||
"local": "http://127.0.0.1:8080/v1/AUTH_8980f74b1cda41e483cbe0a925f448a9"}}
|
|
||||||
|
|
||||||
But, here's an example of what the dictionary may look like in the future::
|
|
||||||
|
|
||||||
{"storage":
|
|
||||||
{"default": "dfw",
|
|
||||||
"dfw": "http://dfw.storage.com:8080/v1/AUTH_8980f74b1cda41e483cbe0a925f448a9",
|
|
||||||
"ord": "http://ord.storage.com:8080/v1/AUTH_8980f74b1cda41e483cbe0a925f448a9",
|
|
||||||
"sat": "http://ord.storage.com:8080/v1/AUTH_8980f74b1cda41e483cbe0a925f448a9"},
|
|
||||||
"servers":
|
|
||||||
{"default": "dfw",
|
|
||||||
"dfw": "http://dfw.servers.com:8080/v1/AUTH_8980f74b1cda41e483cbe0a925f448a9",
|
|
||||||
"ord": "http://ord.servers.com:8080/v1/AUTH_8980f74b1cda41e483cbe0a925f448a9",
|
|
||||||
"sat": "http://ord.servers.com:8080/v1/AUTH_8980f74b1cda41e483cbe0a925f448a9"}}
|
|
||||||
|
|
||||||
Lastly, the tokens themselves are stored as objects in the
|
|
||||||
`<auth_account>/.token_[0-f]` containers. The names of the objects are the
|
|
||||||
token strings themselves, such as `AUTH_tked86bbd01864458aa2bd746879438d5a`.
|
|
||||||
The exact `.token_[0-f]` container chosen is based on the final digit of the
|
|
||||||
token name, such as `.token_a` for the token
|
|
||||||
`AUTH_tked86bbd01864458aa2bd746879438d5a`. The contents of the token objects
|
|
||||||
are JSON dictionaries of the format::
|
|
||||||
|
|
||||||
{"account": <account>,
|
|
||||||
"user": <user>,
|
|
||||||
"account_id": <account_id>,
|
|
||||||
"groups": <groups_array>,
|
|
||||||
"expires": <time.time() value>}
|
|
||||||
|
|
||||||
The `<account>` is the auth service account's name for that token. The `<user>`
|
|
||||||
is the user within the account for that token. The `<account_id>` is the
|
|
||||||
same as the `X-Container-Meta-Account-Id` for the auth service's account,
|
|
||||||
as described above. The `<groups_array>` is the user's groups, as described
|
|
||||||
above with the user object. The "expires" value indicates when the token is no
|
|
||||||
longer valid, as compared to Python's time.time() value.
|
|
||||||
|
|
||||||
Here's an example token object's JSON dictionary::
|
|
||||||
|
|
||||||
{"account": "test",
|
|
||||||
"user": "tester",
|
|
||||||
"account_id": "AUTH_8980f74b1cda41e483cbe0a925f448a9",
|
|
||||||
"groups": [{"name": "test:tester"}, {"name": "test"}, {"name": ".admin"}],
|
|
||||||
"expires": 1291273147.1624689}
|
|
||||||
|
|
||||||
To easily map a user to an already issued token, the token name is stored in
|
|
||||||
the user object's `X-Object-Meta-Auth-Token` header.
|
|
||||||
|
|
||||||
Here is an example full listing of an <auth_account>::
|
|
||||||
|
|
||||||
.account_id
|
|
||||||
AUTH_2282f516-559f-4966-b239-b5c88829e927
|
|
||||||
AUTH_f6f57a3c-33b5-4e85-95a5-a801e67505c8
|
|
||||||
AUTH_fea96a36-c177-4ca4-8c7e-b8c715d9d37b
|
|
||||||
.token_0
|
|
||||||
.token_1
|
|
||||||
.token_2
|
|
||||||
.token_3
|
|
||||||
.token_4
|
|
||||||
.token_5
|
|
||||||
.token_6
|
|
||||||
AUTH_tk9d2941b13d524b268367116ef956dee6
|
|
||||||
.token_7
|
|
||||||
.token_8
|
|
||||||
AUTH_tk93627c6324c64f78be746f1e6a4e3f98
|
|
||||||
.token_9
|
|
||||||
.token_a
|
|
||||||
.token_b
|
|
||||||
.token_c
|
|
||||||
.token_d
|
|
||||||
.token_e
|
|
||||||
AUTH_tk0d37d286af2c43ffad06e99112b3ec4e
|
|
||||||
.token_f
|
|
||||||
AUTH_tk766bbde93771489982d8dc76979d11cf
|
|
||||||
reseller
|
|
||||||
.services
|
|
||||||
reseller
|
|
||||||
test
|
|
||||||
.services
|
|
||||||
tester
|
|
||||||
tester3
|
|
||||||
test2
|
|
||||||
.services
|
|
||||||
tester2
|
|
@ -1,188 +0,0 @@
|
|||||||
.. Swauth documentation master file, created by
|
|
||||||
sphinx-quickstart on Mon Feb 14 19:34:51 2011.
|
|
||||||
You can adapt this file completely to your liking, but it should at least
|
|
||||||
contain the root `toctree` directive.
|
|
||||||
|
|
||||||
Swauth
|
|
||||||
======
|
|
||||||
|
|
||||||
Copyright (c) 2010-2012 OpenStack, LLC
|
|
||||||
|
|
||||||
An Auth Service for Swift as WSGI Middleware that uses Swift itself as a
|
|
||||||
backing store. Docs at: https://swauth.readthedocs.io/ or ask in
|
|
||||||
#openstack-swauth on freenode IRC (archive: http://eavesdrop.openstack.org/irclogs/%23openstack-swauth/).
|
|
||||||
|
|
||||||
Source available at: https://github.com/openstack/swauth
|
|
||||||
|
|
||||||
See also https://github.com/openstack/keystone for the standard OpenStack
|
|
||||||
auth service.
|
|
||||||
|
|
||||||
Overview
|
|
||||||
--------
|
|
||||||
|
|
||||||
Before discussing how to install Swauth within a Swift system, it might help to understand how Swauth does it work first.
|
|
||||||
|
|
||||||
1. Swauth is middleware installed in the Swift Proxy's WSGI pipeline.
|
|
||||||
|
|
||||||
2. It intercepts requests to ``/auth/`` (by default).
|
|
||||||
|
|
||||||
3. It also uses Swift's `authorize callback <https://docs.openstack.org/swift/latest/development_auth.html>`_ and `acl callback <https://docs.openstack.org/swift/latest/misc.html#acls>`_ features to authorize Swift requests.
|
|
||||||
|
|
||||||
4. Swauth will also make various internal calls to the Swift WSGI pipeline it's installed in to manipulate containers and objects within an ``AUTH_.auth`` (by default) Swift account. These containers and objects are what store account and user information.
|
|
||||||
|
|
||||||
5. Instead of #4, Swauth can be configured to call out to another remote Swauth to perform #4 on its behalf (using the swauth_remote config value).
|
|
||||||
|
|
||||||
6. When managing accounts and users with the various ``swauth-`` command line tools, these tools are actually just performing HTTP requests against the ``/auth/`` end point referenced in #2. You can make your own tools that use the same :ref:`API <api_top>`.
|
|
||||||
|
|
||||||
7. In the special case of creating a new account, Swauth will do its usual WSGI-internal requests as per #4 but will also call out to the Swift cluster to create the actual Swift account.
|
|
||||||
|
|
||||||
a. This Swift cluster callout is an account PUT request to the URL defined by the ``swift_default_cluster`` config value.
|
|
||||||
|
|
||||||
b. This callout end point is also saved when the account is created so that it can be given to the users of that account in the future.
|
|
||||||
|
|
||||||
c. Sometimes, due to public/private network routing or firewalling, the URL Swauth should use should be different than the URL Swauth should give the users later. That is why the ``default_swift_cluster`` config value can accept two URLs (first is the one for users, second is the one for Swauth).
|
|
||||||
|
|
||||||
d. Once an account is created, the URL given to users for that account will not change, even if the ``default_swift_cluster`` config value changes. This is so that you can use multiple clusters with the same Swauth system; ``default_swift_cluster`` just points to the one where you want new users to go.
|
|
||||||
|
|
||||||
e. You can change the stored URL for an account if need be with the ``swauth-set-account-service`` command line tool or a POST request (see :ref:`API <api_set_service_endpoints>`).
|
|
||||||
|
|
||||||
|
|
||||||
Install
|
|
||||||
-------
|
|
||||||
|
|
||||||
1) Install Swauth with ``sudo python setup.py install`` or ``sudo python
|
|
||||||
setup.py develop`` or via whatever packaging system you may be using.
|
|
||||||
|
|
||||||
2) Alter your ``proxy-server.conf`` pipeline to have ``swauth`` instead of ``tempauth``:
|
|
||||||
|
|
||||||
Was::
|
|
||||||
|
|
||||||
[pipeline:main]
|
|
||||||
pipeline = catch_errors cache tempauth proxy-server
|
|
||||||
|
|
||||||
Change To::
|
|
||||||
|
|
||||||
[pipeline:main]
|
|
||||||
pipeline = catch_errors cache swauth proxy-server
|
|
||||||
|
|
||||||
3) Add to your ``proxy-server.conf`` the section for the Swauth WSGI filter::
|
|
||||||
|
|
||||||
[filter:swauth]
|
|
||||||
use = egg:swauth#swauth
|
|
||||||
set log_name = swauth
|
|
||||||
super_admin_key = swauthkey
|
|
||||||
default_swift_cluster = <your setting as discussed below>
|
|
||||||
|
|
||||||
The ``default_swift_cluster`` setting can be confusing.
|
|
||||||
|
|
||||||
a. If you're using an all-in-one type configuration where everything will be run on the local host on port 8080, you can omit the ``default_swift_cluster`` completely and it will default to ``local#http://127.0.0.1:8080/v1``.
|
|
||||||
|
|
||||||
b. If you're using a single Swift proxy you can just set the ``default_swift_cluster = cluster_name#https://<public_ip>:<port>/v1`` and that URL will be given to users as well as used by Swauth internally. (Quick note: be sure the ``http`` vs. ``https`` is set right depending on if you're using SSL.)
|
|
||||||
|
|
||||||
c. If you're using multiple Swift proxies behind a load balancer, you'll probably want ``default_swift_cluster = cluster_name#https://<load_balancer_ip>:<port>/v1#http://127.0.0.1:<port>/v1`` so that Swauth gives out the first URL but uses the second URL internally. Remember to double-check the ``http`` vs. ``https`` settings for each of the URLs; they might be different if you're terminating SSL at the load balancer.
|
|
||||||
|
|
||||||
Also see the ``proxy-server.conf-sample`` for more config options, such as the ability to have a remote Swauth in a multiple Swift cluster configuration.
|
|
||||||
|
|
||||||
4) Be sure your Swift proxy allows account management in the ``proxy-server.conf``::
|
|
||||||
|
|
||||||
[app:proxy-server]
|
|
||||||
...
|
|
||||||
allow_account_management = true
|
|
||||||
|
|
||||||
For greater security, you can leave this off any public proxies and just have one or two private proxies with it turned on.
|
|
||||||
|
|
||||||
5) Restart your proxy server ``swift-init proxy reload``
|
|
||||||
|
|
||||||
6) Initialize the Swauth backing store in Swift ``swauth-prep -K swauthkey``
|
|
||||||
|
|
||||||
7) Add an account/user ``swauth-add-user -A http[s]://<host>:<port>/auth/ -K
|
|
||||||
swauthkey -a test tester testing``
|
|
||||||
|
|
||||||
8) Ensure it works ``swift -A http[s]://<host>:<port>/auth/v1.0 -U test:tester -K testing stat -v``
|
|
||||||
|
|
||||||
|
|
||||||
If anything goes wrong, it's best to start checking the proxy server logs. The client command line utilities often don't get enough information to help. I will often just ``tail -F`` the appropriate proxy log (``/var/log/syslog`` or however you have it configured) and then run the Swauth command to see exactly what requests are happening to try to determine where things fail.
|
|
||||||
|
|
||||||
General note, I find I occasionally just forget to reload the proxies after a config change; so that's the first thing you might try. Or, if you suspect the proxies aren't reloading properly, you might try ``swift-init proxy stop``, ensure all the processes died, then ``swift-init proxy start``.
|
|
||||||
|
|
||||||
Also, it's quite common to get the ``/auth/v1.0`` vs. just ``/auth/`` URL paths confused. Usual rule is: Swauth tools use just ``/auth/`` and Swift tools use ``/auth/v1.0``.
|
|
||||||
|
|
||||||
|
|
||||||
Web Admin Install
|
|
||||||
-----------------
|
|
||||||
|
|
||||||
1) If you installed from packages, you'll need to cd to the webadmin directory
|
|
||||||
the package installed. This is ``/usr/share/doc/python-swauth/webadmin``
|
|
||||||
with the Lucid packages. If you installed from source, you'll need to cd to
|
|
||||||
the webadmin directory in the source directory.
|
|
||||||
|
|
||||||
2) Upload the Web Admin files with ``swift -A http[s]://<host>:<port>/auth/v1.0
|
|
||||||
-U .super_admin:.super_admin -K swauthkey upload .webadmin .``
|
|
||||||
|
|
||||||
3) Open ``http[s]://<host>:<port>/auth/`` in your browser.
|
|
||||||
|
|
||||||
|
|
||||||
Swift3 Middleware Compatibility
|
|
||||||
-------------------------------
|
|
||||||
|
|
||||||
|
|
||||||
`Swift3 middleware <https://github.com/openstack/swift3>`_ support has to be
|
|
||||||
explicitly turned on in conf file using `s3_support` config option. It can
|
|
||||||
easily be used with swauth when `auth_type` in swauth is configured to be
|
|
||||||
*Plaintext* (default)::
|
|
||||||
|
|
||||||
[pipeline:main]
|
|
||||||
pipeline = catch_errors cache swift3 swauth proxy-server
|
|
||||||
|
|
||||||
[filter:swauth]
|
|
||||||
use = egg:swauth#swauth
|
|
||||||
super_admin_key = swauthkey
|
|
||||||
s3_support = on
|
|
||||||
|
|
||||||
The AWS S3 client uses password in plaintext to
|
|
||||||
`compute HMAC signature <https://docs.aws.amazon.com/AmazonS3/latest/dev/RESTAuthentication.html>`_
|
|
||||||
When `auth_type` in swauth is configured to be *Sha1* or *Sha512*, swauth
|
|
||||||
can only use the stored hashed password to compute HMAC signature. This results
|
|
||||||
in signature mismatch although the user credentials are correct.
|
|
||||||
|
|
||||||
When `auth_type` is **not** *Plaintext*, the only way for S3 clients to
|
|
||||||
authenticate is by giving SHA1/SHA512 of password as input to it's HMAC
|
|
||||||
function. In this case, the S3 clients will have to know `auth_type` and
|
|
||||||
`auth_type_salt` beforehand. Here is a sample configuration::
|
|
||||||
|
|
||||||
[pipeline:main]
|
|
||||||
pipeline = catch_errors cache swift3 swauth proxy-server
|
|
||||||
|
|
||||||
[filter:swauth]
|
|
||||||
use = egg:swauth#swauth
|
|
||||||
super_admin_key = swauthkey
|
|
||||||
s3_support = on
|
|
||||||
auth_type = Sha512
|
|
||||||
auth_type_salt = mysalt
|
|
||||||
|
|
||||||
**Security Concern**: Swauth stores user information (username, password hash,
|
|
||||||
salt etc) as objects in the Swift cluster. If these backend objects which
|
|
||||||
contain password hashes gets stolen, the intruder will be able to authenticate
|
|
||||||
using the hash directly when S3 API is used.
|
|
||||||
|
|
||||||
|
|
||||||
Contents
|
|
||||||
--------
|
|
||||||
|
|
||||||
.. toctree::
|
|
||||||
:maxdepth: 2
|
|
||||||
|
|
||||||
license
|
|
||||||
details
|
|
||||||
swauth
|
|
||||||
middleware
|
|
||||||
api
|
|
||||||
authtypes
|
|
||||||
|
|
||||||
|
|
||||||
Indices and tables
|
|
||||||
------------------
|
|
||||||
|
|
||||||
* :ref:`genindex`
|
|
||||||
* :ref:`modindex`
|
|
||||||
* :ref:`search`
|
|
@ -1,225 +0,0 @@
|
|||||||
.. _license:
|
|
||||||
|
|
||||||
*******
|
|
||||||
LICENSE
|
|
||||||
*******
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
Copyright (c) 2010-2011 OpenStack, LLC
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
END OF TERMS AND CONDITIONS
|
|
||||||
|
|
||||||
APPENDIX: How to apply the Apache License to your work.
|
|
||||||
|
|
||||||
To apply the Apache License to your work, attach the following
|
|
||||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
|
||||||
replaced with your own identifying information. (Don't include
|
|
||||||
the brackets!) The text should be enclosed in the appropriate
|
|
||||||
comment syntax for the file format. We also recommend that a
|
|
||||||
file or class name and description of purpose be included on the
|
|
||||||
same "printed page" as the copyright notice for easier
|
|
||||||
identification within third-party archives.
|
|
||||||
|
|
||||||
Copyright [yyyy] [name of copyright owner]
|
|
||||||
|
|
||||||
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.
|
|
@ -1,9 +0,0 @@
|
|||||||
.. _swauth_middleware_module:
|
|
||||||
|
|
||||||
swauth.middleware
|
|
||||||
=================
|
|
||||||
|
|
||||||
.. automodule:: swauth.middleware
|
|
||||||
:members:
|
|
||||||
:undoc-members:
|
|
||||||
:show-inheritance:
|
|
@ -1,9 +0,0 @@
|
|||||||
.. _swauth_module:
|
|
||||||
|
|
||||||
swauth
|
|
||||||
======
|
|
||||||
|
|
||||||
.. automodule:: swauth
|
|
||||||
:members:
|
|
||||||
:undoc-members:
|
|
||||||
:show-inheritance:
|
|
@ -1,86 +0,0 @@
|
|||||||
[DEFAULT]
|
|
||||||
# Standard from Swift
|
|
||||||
|
|
||||||
[pipeline:main]
|
|
||||||
# Standard from Swift, this is just an example of where to put swauth
|
|
||||||
pipeline = catch_errors healthcheck cache ratelimit swauth proxy-server
|
|
||||||
|
|
||||||
[app:proxy-server]
|
|
||||||
# Standard from Swift, main point to note is the inclusion of
|
|
||||||
# allow_account_management = true (only for the proxy servers where you want to
|
|
||||||
# be able to create/delete accounts).
|
|
||||||
use = egg:swift#proxy
|
|
||||||
allow_account_management = true
|
|
||||||
|
|
||||||
[filter:swauth]
|
|
||||||
use = egg:swauth#swauth
|
|
||||||
# You can override the default log routing for this filter here:
|
|
||||||
# set log_name = swauth
|
|
||||||
# set log_facility = LOG_LOCAL0
|
|
||||||
# set log_level = INFO
|
|
||||||
# set log_headers = False
|
|
||||||
# set log_address = /dev/log
|
|
||||||
# The reseller prefix will verify a token begins with this prefix before even
|
|
||||||
# attempting to validate it. Also, with authorization, only Swift storage
|
|
||||||
# accounts with this prefix will be authorized by this middleware. Useful if
|
|
||||||
# multiple auth systems are in use for one Swift cluster.
|
|
||||||
# reseller_prefix = AUTH
|
|
||||||
# If you wish to use a Swauth service on a remote cluster with this cluster:
|
|
||||||
# swauth_remote = http://remotehost:port/auth
|
|
||||||
# swauth_remote_timeout = 10
|
|
||||||
# When using swauth_remote, the rest of these settings have no effect.
|
|
||||||
#
|
|
||||||
# The auth prefix will cause requests beginning with this prefix to be routed
|
|
||||||
# to the auth subsystem, for granting tokens, creating accounts, users, etc.
|
|
||||||
# auth_prefix = /auth/
|
|
||||||
# Cluster strings are of the format name#url where name is a short name for the
|
|
||||||
# Swift cluster and url is the url to the proxy server(s) for the cluster.
|
|
||||||
# default_swift_cluster = local#http://127.0.0.1:8080/v1
|
|
||||||
# You may also use the format name#url#url where the first url is the one
|
|
||||||
# given to users to access their account (public url) and the second is the one
|
|
||||||
# used by swauth itself to create and delete accounts (private url). This is
|
|
||||||
# useful when a load balancer url should be used by users, but swauth itself is
|
|
||||||
# behind the load balancer. Example:
|
|
||||||
# default_swift_cluster = local#https://public.com:8080/v1#http://private.com:8080/v1
|
|
||||||
# Number of seconds a newly issued token should be valid for, by default.
|
|
||||||
# token_life = 86400
|
|
||||||
# Maximum number of seconds a newly issued token can be valid for.
|
|
||||||
# max_token_life = <same as token_life>
|
|
||||||
# Specifies how the user key is stored. The default is 'plaintext', leaving the
|
|
||||||
# key unsecured but available for key-signing features if such are ever added.
|
|
||||||
# An alternative is 'sha512' which stores only a one-way hash of the key leaving
|
|
||||||
# it secure but unavailable for key-signing.
|
|
||||||
# auth_type = plaintext
|
|
||||||
# Used if the auth_type is sha1 or sha512. Salt is data(text) that is used as
|
|
||||||
# an additional input to the one-way encoding function. If not set, a random
|
|
||||||
# salt will be generated for each password.
|
|
||||||
# auth_type_salt =
|
|
||||||
# This allows middleware higher in the WSGI pipeline to override auth
|
|
||||||
# processing, useful for middleware such as tempurl and formpost. If you know
|
|
||||||
# you're not going to use such middleware and you want a bit of extra security,
|
|
||||||
# you can set this to false.
|
|
||||||
# allow_overrides = true
|
|
||||||
# This allows swauth to PUT authentication related objects over a specific
|
|
||||||
# storage policy instead of the default one. When this is set, all requests
|
|
||||||
# sent by swauth will contain X-Storage-Policy header with its value set
|
|
||||||
# to the value specified here.
|
|
||||||
# default_storage_policy =
|
|
||||||
# Highly recommended to change this. If you comment this out, the Swauth
|
|
||||||
# administration features will be disabled for this proxy.
|
|
||||||
super_admin_key = swauthkey
|
|
||||||
|
|
||||||
[filter:ratelimit]
|
|
||||||
# Standard from Swift
|
|
||||||
use = egg:swift#ratelimit
|
|
||||||
|
|
||||||
[filter:cache]
|
|
||||||
# Standard from Swift
|
|
||||||
use = egg:swift#memcache
|
|
||||||
|
|
||||||
[filter:healthcheck]
|
|
||||||
# Standard from Swift
|
|
||||||
use = egg:swift#healthcheck
|
|
||||||
|
|
||||||
[filter:catch_errors]
|
|
||||||
# Standard from Swift
|
|
||||||
use = egg:swift#catch_errors
|
|
@ -1,7 +0,0 @@
|
|||||||
# The order of packages is significant, because pip processes them in the order
|
|
||||||
# of appearance. Changing the order has an impact on the overall integration
|
|
||||||
# process, which may cause wedges in the gate later.
|
|
||||||
|
|
||||||
eventlet!=0.18.3,!=0.20.1,>=0.18.2 # MIT
|
|
||||||
python-swiftclient>=3.2.0 # Apache-2.0
|
|
||||||
six>=1.10.0 # MIT
|
|
63
setup.cfg
63
setup.cfg
@ -1,63 +0,0 @@
|
|||||||
[metadata]
|
|
||||||
name = swauth
|
|
||||||
summary = An alternative authentication system for Swift
|
|
||||||
description-file =
|
|
||||||
README.md
|
|
||||||
author = OpenStack
|
|
||||||
author-email = openstack-discuss@lists.openstack.org
|
|
||||||
home-page = https://github.com/openstack/swauth
|
|
||||||
classifier =
|
|
||||||
Development Status :: 5 - Production/Stable
|
|
||||||
Environment :: OpenStack
|
|
||||||
Intended Audience :: Information Technology
|
|
||||||
Intended Audience :: System Administrators
|
|
||||||
License :: OSI Approved :: Apache Software License
|
|
||||||
Operating System :: POSIX :: Linux
|
|
||||||
Programming Language :: Python
|
|
||||||
Programming Language :: Python :: 2
|
|
||||||
Programming Language :: Python :: 2.7
|
|
||||||
|
|
||||||
[pbr]
|
|
||||||
skip_authors = True
|
|
||||||
skip_changelog = True
|
|
||||||
|
|
||||||
[files]
|
|
||||||
packages =
|
|
||||||
swauth
|
|
||||||
scripts =
|
|
||||||
bin/swauth-add-account
|
|
||||||
bin/swauth-add-user
|
|
||||||
bin/swauth-cleanup-tokens
|
|
||||||
bin/swauth-delete-account
|
|
||||||
bin/swauth-delete-user
|
|
||||||
bin/swauth-list
|
|
||||||
bin/swauth-prep
|
|
||||||
bin/swauth-set-account-service
|
|
||||||
|
|
||||||
[entry_points]
|
|
||||||
paste.filter_factory =
|
|
||||||
swauth = swauth.middleware:filter_factory
|
|
||||||
|
|
||||||
[build_sphinx]
|
|
||||||
all_files = 1
|
|
||||||
build-dir = doc/build
|
|
||||||
source-dir = doc/source
|
|
||||||
|
|
||||||
[egg_info]
|
|
||||||
tag_build =
|
|
||||||
tag_date = 0
|
|
||||||
tag_svn_revision = 0
|
|
||||||
|
|
||||||
[compile_catalog]
|
|
||||||
directory = swauth/locale
|
|
||||||
domain = swauth
|
|
||||||
|
|
||||||
[update_catalog]
|
|
||||||
domain = swauth
|
|
||||||
output_dir = swauth/locale
|
|
||||||
input_file = swauth/locale/swauth.pot
|
|
||||||
|
|
||||||
[extract_messages]
|
|
||||||
keywords = _ l_ lazy_gettext
|
|
||||||
mapping_file = babel.cfg
|
|
||||||
output_file = swauth/locale/swauth.pot
|
|
29
setup.py
29
setup.py
@ -1,29 +0,0 @@
|
|||||||
# Copyright (c) 2013 Hewlett-Packard Development Company, L.P.
|
|
||||||
#
|
|
||||||
# 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.
|
|
||||||
|
|
||||||
# THIS FILE IS MANAGED BY THE GLOBAL REQUIREMENTS REPO - DO NOT EDIT
|
|
||||||
import setuptools
|
|
||||||
|
|
||||||
# In python < 2.7.4, a lazy loading of package `pbr` will break
|
|
||||||
# setuptools if some other modules registered functions in `atexit`.
|
|
||||||
# solution from: http://bugs.python.org/issue15881#msg170215
|
|
||||||
try:
|
|
||||||
import multiprocessing # noqa
|
|
||||||
except ImportError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
setuptools.setup(
|
|
||||||
setup_requires=['pbr>=2.0.0'],
|
|
||||||
pbr=True)
|
|
@ -1,34 +0,0 @@
|
|||||||
# Copyright (c) 2010-2013 OpenStack, LLC.
|
|
||||||
#
|
|
||||||
# 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 gettext
|
|
||||||
import pkg_resources
|
|
||||||
|
|
||||||
|
|
||||||
try:
|
|
||||||
# First, try to get our version out of PKG-INFO. If we're installed,
|
|
||||||
# this'll let us find our version without pulling in pbr. After all, if
|
|
||||||
# we're installed on a system, we're not in a Git-managed source tree, so
|
|
||||||
# pbr doesn't really buy us anything.
|
|
||||||
__version__ = pkg_resources.get_provider(
|
|
||||||
pkg_resources.Requirement.parse('swauth')).version
|
|
||||||
except pkg_resources.DistributionNotFound:
|
|
||||||
# No PKG-INFO? We're probably running from a checkout, then. Let pbr do
|
|
||||||
# its thing to figure out a version number.
|
|
||||||
import pbr.version
|
|
||||||
__version__ = pbr.version.VersionInfo(
|
|
||||||
'swauth').version_string()
|
|
||||||
|
|
||||||
gettext.install('swauth')
|
|
@ -1,238 +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.
|
|
||||||
#
|
|
||||||
# Pablo Llopis 2011
|
|
||||||
|
|
||||||
|
|
||||||
"""This module hosts available auth types for encoding and matching user keys.
|
|
||||||
For adding a new auth type, simply write a class that satisfies the following
|
|
||||||
conditions:
|
|
||||||
|
|
||||||
- For the class name, capitalize first letter only. This makes sure the user
|
|
||||||
can specify an all-lowercase config option such as "plaintext" or "sha1".
|
|
||||||
Swauth takes care of capitalizing the first letter before instantiating it.
|
|
||||||
- Write an encode(key) method that will take a single argument, the user's key,
|
|
||||||
and returns the encoded string. For plaintext, this would be
|
|
||||||
"plaintext:<key>"
|
|
||||||
- Write a match(key, creds) method that will take two arguments: the user's
|
|
||||||
key, and the user's retrieved credentials. Return a boolean value that
|
|
||||||
indicates whether the match is True or False.
|
|
||||||
"""
|
|
||||||
|
|
||||||
import hashlib
|
|
||||||
import os
|
|
||||||
import string
|
|
||||||
import sys
|
|
||||||
|
|
||||||
|
|
||||||
#: Maximum length any valid token should ever be.
|
|
||||||
MAX_TOKEN_LENGTH = 5000
|
|
||||||
|
|
||||||
|
|
||||||
def validate_creds(creds):
|
|
||||||
"""Parse and validate user credentials whether format is right
|
|
||||||
|
|
||||||
:param creds: User credentials
|
|
||||||
:returns: Auth_type class instance and parsed user credentials in dict
|
|
||||||
:raises ValueError: If credential format is wrong (eg: bad auth_type)
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
auth_type, auth_rest = creds.split(':', 1)
|
|
||||||
except ValueError:
|
|
||||||
raise ValueError("Missing ':' in %s" % creds)
|
|
||||||
authtypes = sys.modules[__name__]
|
|
||||||
auth_encoder = getattr(authtypes, auth_type.title(), None)
|
|
||||||
if auth_encoder is None:
|
|
||||||
raise ValueError('Invalid auth_type: %s' % auth_type)
|
|
||||||
auth_encoder = auth_encoder()
|
|
||||||
parsed_creds = dict(type=auth_type, salt=None, hash=None)
|
|
||||||
parsed_creds.update(auth_encoder.validate(auth_rest))
|
|
||||||
return auth_encoder, parsed_creds
|
|
||||||
|
|
||||||
|
|
||||||
class Plaintext(object):
|
|
||||||
"""Provides a particular auth type for encoding format for encoding and
|
|
||||||
matching user keys.
|
|
||||||
|
|
||||||
This class must be all lowercase except for the first character, which
|
|
||||||
must be capitalized. encode and match methods must be provided and are
|
|
||||||
the only ones that will be used by swauth.
|
|
||||||
"""
|
|
||||||
def encode(self, key):
|
|
||||||
"""Encodes a user key into a particular format. The result of this method
|
|
||||||
will be used by swauth for storing user credentials.
|
|
||||||
|
|
||||||
:param key: User's secret key
|
|
||||||
:returns: A string representing user credentials
|
|
||||||
"""
|
|
||||||
return "plaintext:%s" % key
|
|
||||||
|
|
||||||
def match(self, key, creds, **kwargs):
|
|
||||||
"""Checks whether the user-provided key matches the user's credentials
|
|
||||||
|
|
||||||
:param key: User-supplied key
|
|
||||||
:param creds: User's stored credentials
|
|
||||||
:param kwargs: Extra keyword args for compatibility reason with
|
|
||||||
other auth_type classes
|
|
||||||
:returns: True if the supplied key is valid, False otherwise
|
|
||||||
"""
|
|
||||||
return self.encode(key) == creds
|
|
||||||
|
|
||||||
def validate(self, auth_rest):
|
|
||||||
"""Validate user credentials whether format is right for Plaintext
|
|
||||||
|
|
||||||
:param auth_rest: User credentials' part without auth_type
|
|
||||||
:return: Dict with a hash part of user credentials
|
|
||||||
:raises ValueError: If credentials' part has zero length
|
|
||||||
"""
|
|
||||||
if len(auth_rest) == 0:
|
|
||||||
raise ValueError("Key must have non-zero length!")
|
|
||||||
return dict(hash=auth_rest)
|
|
||||||
|
|
||||||
|
|
||||||
class Sha1(object):
|
|
||||||
"""Provides a particular auth type for encoding format for encoding and
|
|
||||||
matching user keys.
|
|
||||||
|
|
||||||
This class must be all lowercase except for the first character, which
|
|
||||||
must be capitalized. encode and match methods must be provided and are
|
|
||||||
the only ones that will be used by swauth.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def encode_w_salt(self, salt, key):
|
|
||||||
"""Encodes a user key with salt into a particular format. The result of
|
|
||||||
this method will be used internally.
|
|
||||||
|
|
||||||
:param salt: Salt for hashing
|
|
||||||
:param key: User's secret key
|
|
||||||
:returns: A string representing user credentials
|
|
||||||
"""
|
|
||||||
enc_key = '%s%s' % (salt, key)
|
|
||||||
enc_val = hashlib.sha1(enc_key).hexdigest()
|
|
||||||
return "sha1:%s$%s" % (salt, enc_val)
|
|
||||||
|
|
||||||
def encode(self, key):
|
|
||||||
"""Encodes a user key into a particular format. The result of this method
|
|
||||||
will be used by swauth for storing user credentials.
|
|
||||||
|
|
||||||
If salt is not manually set in conf file, a random salt will be
|
|
||||||
generated and used.
|
|
||||||
|
|
||||||
:param key: User's secret key
|
|
||||||
:returns: A string representing user credentials
|
|
||||||
"""
|
|
||||||
salt = self.salt or os.urandom(32).encode('base64').rstrip()
|
|
||||||
return self.encode_w_salt(salt, key)
|
|
||||||
|
|
||||||
def match(self, key, creds, salt, **kwargs):
|
|
||||||
"""Checks whether the user-provided key matches the user's credentials
|
|
||||||
|
|
||||||
:param key: User-supplied key
|
|
||||||
:param creds: User's stored credentials
|
|
||||||
:param salt: Salt for hashing
|
|
||||||
:param kwargs: Extra keyword args for compatibility reason with
|
|
||||||
other auth_type classes
|
|
||||||
:returns: True if the supplied key is valid, False otherwise
|
|
||||||
"""
|
|
||||||
return self.encode_w_salt(salt, key) == creds
|
|
||||||
|
|
||||||
def validate(self, auth_rest):
|
|
||||||
"""Validate user credentials whether format is right for Sha1
|
|
||||||
|
|
||||||
:param auth_rest: User credentials' part without auth_type
|
|
||||||
:return: Dict with a hash and a salt part of user credentials
|
|
||||||
:raises ValueError: If credentials' part doesn't contain delimiter
|
|
||||||
between a salt and a hash.
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
auth_salt, auth_hash = auth_rest.split('$')
|
|
||||||
except ValueError:
|
|
||||||
raise ValueError("Missing '$' in %s" % auth_rest)
|
|
||||||
|
|
||||||
if len(auth_salt) == 0:
|
|
||||||
raise ValueError("Salt must have non-zero length!")
|
|
||||||
if len(auth_hash) != 40:
|
|
||||||
raise ValueError("Hash must have 40 chars!")
|
|
||||||
if not all(c in string.hexdigits for c in auth_hash):
|
|
||||||
raise ValueError("Hash must be hexadecimal!")
|
|
||||||
|
|
||||||
return dict(salt=auth_salt, hash=auth_hash)
|
|
||||||
|
|
||||||
|
|
||||||
class Sha512(object):
|
|
||||||
"""Provides a particular auth type for encoding format for encoding and
|
|
||||||
matching user keys.
|
|
||||||
|
|
||||||
This class must be all lowercase except for the first character, which
|
|
||||||
must be capitalized. encode and match methods must be provided and are
|
|
||||||
the only ones that will be used by swauth.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def encode_w_salt(self, salt, key):
|
|
||||||
"""Encodes a user key with salt into a particular format. The result of
|
|
||||||
this method will be used internal.
|
|
||||||
|
|
||||||
:param salt: Salt for hashing
|
|
||||||
:param key: User's secret key
|
|
||||||
:returns: A string representing user credentials
|
|
||||||
"""
|
|
||||||
enc_key = '%s%s' % (salt, key)
|
|
||||||
enc_val = hashlib.sha512(enc_key).hexdigest()
|
|
||||||
return "sha512:%s$%s" % (salt, enc_val)
|
|
||||||
|
|
||||||
def encode(self, key):
|
|
||||||
"""Encodes a user key into a particular format. The result of this method
|
|
||||||
will be used by swauth for storing user credentials.
|
|
||||||
|
|
||||||
If salt is not manually set in conf file, a random salt will be
|
|
||||||
generated and used.
|
|
||||||
|
|
||||||
:param key: User's secret key
|
|
||||||
:returns: A string representing user credentials
|
|
||||||
"""
|
|
||||||
salt = self.salt or os.urandom(32).encode('base64').rstrip()
|
|
||||||
return self.encode_w_salt(salt, key)
|
|
||||||
|
|
||||||
def match(self, key, creds, salt, **kwargs):
|
|
||||||
"""Checks whether the user-provided key matches the user's credentials
|
|
||||||
|
|
||||||
:param key: User-supplied key
|
|
||||||
:param creds: User's stored credentials
|
|
||||||
:param salt: Salt for hashing
|
|
||||||
:param kwargs: Extra keyword args for compatibility reason with
|
|
||||||
other auth_type classes
|
|
||||||
:returns: True if the supplied key is valid, False otherwise
|
|
||||||
"""
|
|
||||||
return self.encode_w_salt(salt, key) == creds
|
|
||||||
|
|
||||||
def validate(self, auth_rest):
|
|
||||||
"""Validate user credentials whether format is right for Sha512
|
|
||||||
|
|
||||||
:param auth_rest: User credentials' part without auth_type
|
|
||||||
:return: Dict with a hash and a salt part of user credentials
|
|
||||||
:raises ValueError: If credentials' part doesn't contain delimiter
|
|
||||||
between a salt and a hash.
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
auth_salt, auth_hash = auth_rest.split('$')
|
|
||||||
except ValueError:
|
|
||||||
raise ValueError("Missing '$' in %s" % auth_rest)
|
|
||||||
|
|
||||||
if len(auth_salt) == 0:
|
|
||||||
raise ValueError("Salt must have non-zero length!")
|
|
||||||
if len(auth_hash) != 128:
|
|
||||||
raise ValueError("Hash must have 128 chars!")
|
|
||||||
if not all(c in string.hexdigits for c in auth_hash):
|
|
||||||
raise ValueError("Hash must be hexadecimal!")
|
|
||||||
|
|
||||||
return dict(salt=auth_salt, hash=auth_hash)
|
|
1709
swauth/middleware.py
1709
swauth/middleware.py
File diff suppressed because it is too large
Load Diff
@ -1,82 +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 swift
|
|
||||||
|
|
||||||
|
|
||||||
MAJOR = None
|
|
||||||
MINOR = None
|
|
||||||
REVISION = None
|
|
||||||
FINAL = None
|
|
||||||
|
|
||||||
|
|
||||||
def parse(value):
|
|
||||||
parts = value.split('.')
|
|
||||||
if parts[-1].endswith('-dev'):
|
|
||||||
final = False
|
|
||||||
parts[-1] = parts[-1][:-4]
|
|
||||||
else:
|
|
||||||
final = True
|
|
||||||
major = int(parts.pop(0))
|
|
||||||
minor = int(parts.pop(0))
|
|
||||||
if parts:
|
|
||||||
revision = int(parts.pop(0).split('-', 1)[0])
|
|
||||||
else:
|
|
||||||
revision = 0
|
|
||||||
return major, minor, revision, final
|
|
||||||
|
|
||||||
|
|
||||||
def newer_than(value):
|
|
||||||
global MAJOR, MINOR, REVISION, FINAL
|
|
||||||
try:
|
|
||||||
major, minor, revision, final = parse(value)
|
|
||||||
if MAJOR is None:
|
|
||||||
MAJOR, MINOR, REVISION, FINAL = parse(swift.__version__)
|
|
||||||
if MAJOR < major:
|
|
||||||
return False
|
|
||||||
elif MAJOR == major:
|
|
||||||
if MINOR < minor:
|
|
||||||
return False
|
|
||||||
elif MINOR == minor:
|
|
||||||
if REVISION < revision:
|
|
||||||
return False
|
|
||||||
elif REVISION == revision:
|
|
||||||
if not FINAL or final:
|
|
||||||
return False
|
|
||||||
except Exception:
|
|
||||||
# Unable to detect if it's newer, better to fail
|
|
||||||
return False
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
def at_least(value):
|
|
||||||
global MAJOR, MINOR, REVISION, FINAL
|
|
||||||
try:
|
|
||||||
major, minor, revision, final = parse(value)
|
|
||||||
if MAJOR is None:
|
|
||||||
MAJOR, MINOR, REVISION, FINAL = parse(swift.__version__)
|
|
||||||
if MAJOR < major:
|
|
||||||
return False
|
|
||||||
elif MAJOR == major:
|
|
||||||
if MINOR < minor:
|
|
||||||
return False
|
|
||||||
elif MINOR == minor:
|
|
||||||
if REVISION < revision:
|
|
||||||
return False
|
|
||||||
elif REVISION == revision:
|
|
||||||
if not FINAL and final:
|
|
||||||
return False
|
|
||||||
except Exception:
|
|
||||||
# Unable to detect if it's newer, better to fail
|
|
||||||
return False
|
|
||||||
return True
|
|
@ -1,13 +0,0 @@
|
|||||||
# The order of packages is significant, because pip processes them in the order
|
|
||||||
# of appearance. Changing the order has an impact on the overall integration
|
|
||||||
# process, which may cause wedges in the gate later.
|
|
||||||
hacking<0.11,>=0.10.0
|
|
||||||
|
|
||||||
flake8<2.6.0,>=2.5.4 # MIT
|
|
||||||
mock>=2.0.0 # BSD
|
|
||||||
nose>=1.3.7 # LGPL
|
|
||||||
coverage!=4.4,>=4.0 # Apache-2.0
|
|
||||||
#discover
|
|
||||||
#python-subunit>=0.0.18
|
|
||||||
sphinx>=1.6.2 # BSD
|
|
||||||
bandit>=1.1.0 # Apache-2.0
|
|
@ -1,6 +0,0 @@
|
|||||||
# See http://code.google.com/p/python-nose/issues/detail?id=373
|
|
||||||
# The code below enables nosetests to work with i18n _() blocks
|
|
||||||
|
|
||||||
import six.moves.builtins as __builtin__
|
|
||||||
|
|
||||||
setattr(__builtin__, '_', lambda x: x)
|
|
@ -1,207 +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.
|
|
||||||
#
|
|
||||||
# Pablo Llopis 2011
|
|
||||||
|
|
||||||
import mock
|
|
||||||
from swauth import authtypes
|
|
||||||
import unittest
|
|
||||||
|
|
||||||
|
|
||||||
class TestValidation(unittest.TestCase):
|
|
||||||
def test_validate_creds(self):
|
|
||||||
creds = 'plaintext:keystring'
|
|
||||||
creds_dict = dict(type='plaintext', salt=None, hash='keystring')
|
|
||||||
auth_encoder, parsed_creds = authtypes.validate_creds(creds)
|
|
||||||
self.assertEqual(parsed_creds, creds_dict)
|
|
||||||
self.assertTrue(isinstance(auth_encoder, authtypes.Plaintext))
|
|
||||||
|
|
||||||
creds = 'sha1:salt$d50dc700c296e23ce5b41f7431a0e01f69010f06'
|
|
||||||
creds_dict = dict(type='sha1', salt='salt',
|
|
||||||
hash='d50dc700c296e23ce5b41f7431a0e01f69010f06')
|
|
||||||
auth_encoder, parsed_creds = authtypes.validate_creds(creds)
|
|
||||||
self.assertEqual(parsed_creds, creds_dict)
|
|
||||||
self.assertTrue(isinstance(auth_encoder, authtypes.Sha1))
|
|
||||||
|
|
||||||
creds = ('sha512:salt$482e73705fac6909e2d78e8bbaf65ac3ca1473'
|
|
||||||
'8f445cc2367b7daa3f0e8f3dcfe798e426b9e332776c8da59c'
|
|
||||||
'0c11d4832931d1bf48830f670ecc6ceb04fbad0f')
|
|
||||||
creds_dict = dict(type='sha512', salt='salt',
|
|
||||||
hash='482e73705fac6909e2d78e8bbaf65ac3ca1473'
|
|
||||||
'8f445cc2367b7daa3f0e8f3dcfe798e426b9e3'
|
|
||||||
'32776c8da59c0c11d4832931d1bf48830f670e'
|
|
||||||
'cc6ceb04fbad0f')
|
|
||||||
auth_encoder, parsed_creds = authtypes.validate_creds(creds)
|
|
||||||
self.assertEqual(parsed_creds, creds_dict)
|
|
||||||
self.assertTrue(isinstance(auth_encoder, authtypes.Sha512))
|
|
||||||
|
|
||||||
def test_validate_creds_fail(self):
|
|
||||||
# wrong format, missing `:`
|
|
||||||
creds = 'unknown;keystring'
|
|
||||||
self.assertRaisesRegexp(ValueError, "Missing ':' in .*",
|
|
||||||
authtypes.validate_creds, creds)
|
|
||||||
# unknown auth_type
|
|
||||||
creds = 'unknown:keystring'
|
|
||||||
self.assertRaisesRegexp(ValueError, "Invalid auth_type: .*",
|
|
||||||
authtypes.validate_creds, creds)
|
|
||||||
# wrong plaintext keystring
|
|
||||||
creds = 'plaintext:'
|
|
||||||
self.assertRaisesRegexp(ValueError, "Key must have non-zero length!",
|
|
||||||
authtypes.validate_creds, creds)
|
|
||||||
# wrong sha1 format, missing `$`
|
|
||||||
creds = 'sha1:saltkeystring'
|
|
||||||
self.assertRaisesRegexp(ValueError, "Missing '\$' in .*",
|
|
||||||
authtypes.validate_creds, creds)
|
|
||||||
# wrong sha1 format, missing salt
|
|
||||||
creds = 'sha1:$hash'
|
|
||||||
self.assertRaisesRegexp(ValueError, "Salt must have non-zero length!",
|
|
||||||
authtypes.validate_creds, creds)
|
|
||||||
# wrong sha1 format, missing hash
|
|
||||||
creds = 'sha1:salt$'
|
|
||||||
self.assertRaisesRegexp(ValueError, "Hash must have 40 chars!",
|
|
||||||
authtypes.validate_creds, creds)
|
|
||||||
# wrong sha1 format, short hash
|
|
||||||
creds = 'sha1:salt$short_hash'
|
|
||||||
self.assertRaisesRegexp(ValueError, "Hash must have 40 chars!",
|
|
||||||
authtypes.validate_creds, creds)
|
|
||||||
# wrong sha1 format, wrong format
|
|
||||||
creds = 'sha1:salt$' + "z" * 40
|
|
||||||
self.assertRaisesRegexp(ValueError, "Hash must be hexadecimal!",
|
|
||||||
authtypes.validate_creds, creds)
|
|
||||||
# wrong sha512 format, missing `$`
|
|
||||||
creds = 'sha512:saltkeystring'
|
|
||||||
self.assertRaisesRegexp(ValueError, "Missing '\$' in .*",
|
|
||||||
authtypes.validate_creds, creds)
|
|
||||||
# wrong sha512 format, missing salt
|
|
||||||
creds = 'sha512:$hash'
|
|
||||||
self.assertRaisesRegexp(ValueError, "Salt must have non-zero length!",
|
|
||||||
authtypes.validate_creds, creds)
|
|
||||||
# wrong sha512 format, missing hash
|
|
||||||
creds = 'sha512:salt$'
|
|
||||||
self.assertRaisesRegexp(ValueError, "Hash must have 128 chars!",
|
|
||||||
authtypes.validate_creds, creds)
|
|
||||||
# wrong sha512 format, short hash
|
|
||||||
creds = 'sha512:salt$short_hash'
|
|
||||||
self.assertRaisesRegexp(ValueError, "Hash must have 128 chars!",
|
|
||||||
authtypes.validate_creds, creds)
|
|
||||||
# wrong sha1 format, wrong format
|
|
||||||
creds = 'sha512:salt$' + "z" * 128
|
|
||||||
self.assertRaisesRegexp(ValueError, "Hash must be hexadecimal!",
|
|
||||||
authtypes.validate_creds, creds)
|
|
||||||
|
|
||||||
|
|
||||||
class TestPlaintext(unittest.TestCase):
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
self.auth_encoder = authtypes.Plaintext()
|
|
||||||
|
|
||||||
def test_plaintext_encode(self):
|
|
||||||
enc_key = self.auth_encoder.encode('keystring')
|
|
||||||
self.assertEqual('plaintext:keystring', enc_key)
|
|
||||||
|
|
||||||
def test_plaintext_valid_match(self):
|
|
||||||
creds = 'plaintext:keystring'
|
|
||||||
match = self.auth_encoder.match('keystring', creds)
|
|
||||||
self.assertEqual(match, True)
|
|
||||||
|
|
||||||
def test_plaintext_invalid_match(self):
|
|
||||||
creds = 'plaintext:other-keystring'
|
|
||||||
match = self.auth_encoder.match('keystring', creds)
|
|
||||||
self.assertEqual(match, False)
|
|
||||||
|
|
||||||
|
|
||||||
class TestSha1(unittest.TestCase):
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
self.auth_encoder = authtypes.Sha1()
|
|
||||||
self.auth_encoder.salt = 'salt'
|
|
||||||
|
|
||||||
@mock.patch('swauth.authtypes.os')
|
|
||||||
def test_sha1_encode(self, os):
|
|
||||||
os.urandom.return_value.encode.return_value.rstrip \
|
|
||||||
.return_value = 'salt'
|
|
||||||
enc_key = self.auth_encoder.encode('keystring')
|
|
||||||
self.assertEqual('sha1:salt$d50dc700c296e23ce5b41f7431a0e01f69010f06',
|
|
||||||
enc_key)
|
|
||||||
|
|
||||||
def test_sha1_valid_match(self):
|
|
||||||
creds = 'sha1:salt$d50dc700c296e23ce5b41f7431a0e01f69010f06'
|
|
||||||
creds_dict = dict(type='sha1', salt='salt',
|
|
||||||
hash='d50dc700c296e23ce5b41f7431a0e01f69010f06')
|
|
||||||
match = self.auth_encoder.match('keystring', creds, **creds_dict)
|
|
||||||
self.assertEqual(match, True)
|
|
||||||
|
|
||||||
def test_sha1_invalid_match(self):
|
|
||||||
creds = 'sha1:salt$deadbabedeadbabedeadbabec0ffeebadc0ffeee'
|
|
||||||
creds_dict = dict(type='sha1', salt='salt',
|
|
||||||
hash='deadbabedeadbabedeadbabec0ffeebadc0ffeee')
|
|
||||||
match = self.auth_encoder.match('keystring', creds, **creds_dict)
|
|
||||||
self.assertEqual(match, False)
|
|
||||||
|
|
||||||
creds = 'sha1:salt$d50dc700c296e23ce5b41f7431a0e01f69010f06'
|
|
||||||
creds_dict = dict(type='sha1', salt='salt',
|
|
||||||
hash='d50dc700c296e23ce5b41f7431a0e01f69010f06')
|
|
||||||
match = self.auth_encoder.match('keystring2', creds, **creds_dict)
|
|
||||||
self.assertEqual(match, False)
|
|
||||||
|
|
||||||
|
|
||||||
class TestSha512(unittest.TestCase):
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
self.auth_encoder = authtypes.Sha512()
|
|
||||||
self.auth_encoder.salt = 'salt'
|
|
||||||
|
|
||||||
@mock.patch('swauth.authtypes.os')
|
|
||||||
def test_sha512_encode(self, os):
|
|
||||||
os.urandom.return_value.encode.return_value.rstrip \
|
|
||||||
.return_value = 'salt'
|
|
||||||
enc_key = self.auth_encoder.encode('keystring')
|
|
||||||
self.assertEqual('sha512:salt$482e73705fac6909e2d78e8bbaf65ac3ca1473'
|
|
||||||
'8f445cc2367b7daa3f0e8f3dcfe798e426b9e332776c8da59c'
|
|
||||||
'0c11d4832931d1bf48830f670ecc6ceb04fbad0f', enc_key)
|
|
||||||
|
|
||||||
def test_sha512_valid_match(self):
|
|
||||||
creds = ('sha512:salt$482e73705fac6909e2d78e8bbaf65ac3ca14738f445cc2'
|
|
||||||
'367b7daa3f0e8f3dcfe798e426b9e332776c8da59c0c11d4832931d1bf'
|
|
||||||
'48830f670ecc6ceb04fbad0f')
|
|
||||||
creds_dict = dict(type='sha512', salt='salt',
|
|
||||||
hash='482e73705fac6909e2d78e8bbaf65ac3ca14738f445cc2'
|
|
||||||
'367b7daa3f0e8f3dcfe798e426b9e332776c8da59c0c11'
|
|
||||||
'd4832931d1bf48830f670ecc6ceb04fbad0f')
|
|
||||||
match = self.auth_encoder.match('keystring', creds, **creds_dict)
|
|
||||||
self.assertEqual(match, True)
|
|
||||||
|
|
||||||
def test_sha512_invalid_match(self):
|
|
||||||
creds = ('sha512:salt$deadbabedeadbabedeadbabedeadbabedeadbabedeadba'
|
|
||||||
'bedeadbabedeadbabedeadbabedeadbabedeadbabedeadbabedeadbabe'
|
|
||||||
'c0ffeebadc0ffeeec0ffeeba')
|
|
||||||
creds_dict = dict(type='sha512', salt='salt',
|
|
||||||
hash='deadbabedeadbabedeadbabedeadbabedeadbabedeadba'
|
|
||||||
'bedeadbabedeadbabedeadbabedeadbabedeadbabedead'
|
|
||||||
'babedeadbabec0ffeebadc0ffeeec0ffeeba')
|
|
||||||
match = self.auth_encoder.match('keystring', creds, **creds_dict)
|
|
||||||
self.assertEqual(match, False)
|
|
||||||
|
|
||||||
creds = ('sha512:salt$482e73705fac6909e2d78e8bbaf65ac3ca14738f445cc2'
|
|
||||||
'367b7daa3f0e8f3dcfe798e426b9e332776c8da59c0c11d4832931d1bf'
|
|
||||||
'48830f670ecc6ceb04fbad0f')
|
|
||||||
creds_dict = dict(type='sha512', salt='salt',
|
|
||||||
hash='482e73705fac6909e2d78e8bbaf65ac3ca14738f445cc2'
|
|
||||||
'367b7daa3f0e8f3dcfe798e426b9e332776c8da59c0c11'
|
|
||||||
'd4832931d1bf48830f670ecc6ceb04fbad0f')
|
|
||||||
match = self.auth_encoder.match('keystring2', creds, **creds_dict)
|
|
||||||
self.assertEqual(match, False)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
unittest.main()
|
|
File diff suppressed because it is too large
Load Diff
@ -1,184 +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 unittest
|
|
||||||
|
|
||||||
from swauth import swift_version as ver
|
|
||||||
import swift
|
|
||||||
|
|
||||||
|
|
||||||
class TestSwiftVersion(unittest.TestCase):
|
|
||||||
def test_parse(self):
|
|
||||||
tests = {
|
|
||||||
"1.2": (1, 2, 0, True),
|
|
||||||
"1.2.3": (1, 2, 3, True),
|
|
||||||
"1.2.3-dev": (1, 2, 3, False)
|
|
||||||
}
|
|
||||||
|
|
||||||
for (input, ref_out) in tests.items():
|
|
||||||
out = ver.parse(input)
|
|
||||||
self.assertEqual(ref_out, out)
|
|
||||||
|
|
||||||
def test_newer_than(self):
|
|
||||||
orig_version = swift.__version__
|
|
||||||
|
|
||||||
swift.__version__ = '1.3'
|
|
||||||
ver.MAJOR = None
|
|
||||||
self.assertTrue(ver.newer_than('1.2'))
|
|
||||||
self.assertTrue(ver.newer_than('1.2.9'))
|
|
||||||
self.assertTrue(ver.newer_than('1.3-dev'))
|
|
||||||
self.assertTrue(ver.newer_than('1.3.0-dev'))
|
|
||||||
self.assertFalse(ver.newer_than('1.3'))
|
|
||||||
self.assertFalse(ver.newer_than('1.3.0'))
|
|
||||||
self.assertFalse(ver.newer_than('1.3.1-dev'))
|
|
||||||
self.assertFalse(ver.newer_than('1.3.1'))
|
|
||||||
self.assertFalse(ver.newer_than('1.4-dev'))
|
|
||||||
self.assertFalse(ver.newer_than('1.4'))
|
|
||||||
self.assertFalse(ver.newer_than('2.0-dev'))
|
|
||||||
self.assertFalse(ver.newer_than('2.0'))
|
|
||||||
|
|
||||||
swift.__version__ = '1.3-dev'
|
|
||||||
ver.MAJOR = None
|
|
||||||
self.assertTrue(ver.newer_than('1.2'))
|
|
||||||
self.assertTrue(ver.newer_than('1.2.9'))
|
|
||||||
self.assertFalse(ver.newer_than('1.3-dev'))
|
|
||||||
self.assertFalse(ver.newer_than('1.3.0-dev'))
|
|
||||||
self.assertFalse(ver.newer_than('1.3'))
|
|
||||||
self.assertFalse(ver.newer_than('1.3.0'))
|
|
||||||
self.assertFalse(ver.newer_than('1.3.1-dev'))
|
|
||||||
self.assertFalse(ver.newer_than('1.3.1'))
|
|
||||||
self.assertFalse(ver.newer_than('1.4-dev'))
|
|
||||||
self.assertFalse(ver.newer_than('1.4'))
|
|
||||||
self.assertFalse(ver.newer_than('2.0-dev'))
|
|
||||||
self.assertFalse(ver.newer_than('2.0'))
|
|
||||||
|
|
||||||
swift.__version__ = '1.5.6'
|
|
||||||
ver.MAJOR = None
|
|
||||||
self.assertTrue(ver.newer_than('1.4'))
|
|
||||||
self.assertTrue(ver.newer_than('1.5'))
|
|
||||||
self.assertTrue(ver.newer_than('1.5.5-dev'))
|
|
||||||
self.assertTrue(ver.newer_than('1.5.5'))
|
|
||||||
self.assertTrue(ver.newer_than('1.5.6-dev'))
|
|
||||||
self.assertFalse(ver.newer_than('1.5.6'))
|
|
||||||
self.assertFalse(ver.newer_than('1.5.7-dev'))
|
|
||||||
self.assertFalse(ver.newer_than('1.5.7'))
|
|
||||||
self.assertFalse(ver.newer_than('1.6-dev'))
|
|
||||||
self.assertFalse(ver.newer_than('1.6'))
|
|
||||||
self.assertFalse(ver.newer_than('2.0-dev'))
|
|
||||||
self.assertFalse(ver.newer_than('2.0'))
|
|
||||||
|
|
||||||
swift.__version__ = '1.5.6-dev'
|
|
||||||
ver.MAJOR = None
|
|
||||||
self.assertTrue(ver.newer_than('1.4'))
|
|
||||||
self.assertTrue(ver.newer_than('1.5'))
|
|
||||||
self.assertTrue(ver.newer_than('1.5.5-dev'))
|
|
||||||
self.assertTrue(ver.newer_than('1.5.5'))
|
|
||||||
self.assertFalse(ver.newer_than('1.5.6-dev'))
|
|
||||||
self.assertFalse(ver.newer_than('1.5.6'))
|
|
||||||
self.assertFalse(ver.newer_than('1.5.7-dev'))
|
|
||||||
self.assertFalse(ver.newer_than('1.5.7'))
|
|
||||||
self.assertFalse(ver.newer_than('1.6-dev'))
|
|
||||||
self.assertFalse(ver.newer_than('1.6'))
|
|
||||||
self.assertFalse(ver.newer_than('2.0-dev'))
|
|
||||||
self.assertFalse(ver.newer_than('2.0'))
|
|
||||||
|
|
||||||
swift.__version__ = '1.10.0-2.el6'
|
|
||||||
ver.MAJOR = None
|
|
||||||
self.assertTrue(ver.newer_than('1.9'))
|
|
||||||
self.assertTrue(ver.newer_than('1.10.0-dev'))
|
|
||||||
self.assertFalse(ver.newer_than('1.10.0'))
|
|
||||||
self.assertFalse(ver.newer_than('1.11'))
|
|
||||||
self.assertFalse(ver.newer_than('2.0'))
|
|
||||||
|
|
||||||
swift.__version__ = 'garbage'
|
|
||||||
ver.MAJOR = None
|
|
||||||
self.assertFalse(ver.newer_than('2.0'))
|
|
||||||
|
|
||||||
swift.__version__ = orig_version
|
|
||||||
|
|
||||||
def test_at_least(self):
|
|
||||||
orig_version = swift.__version__
|
|
||||||
|
|
||||||
swift.__version__ = '1.3'
|
|
||||||
ver.MAJOR = None
|
|
||||||
self.assertTrue(ver.at_least('1.2'))
|
|
||||||
self.assertTrue(ver.at_least('1.2.9'))
|
|
||||||
self.assertTrue(ver.at_least('1.3-dev'))
|
|
||||||
self.assertTrue(ver.at_least('1.3.0-dev'))
|
|
||||||
self.assertTrue(ver.at_least('1.3'))
|
|
||||||
self.assertTrue(ver.at_least('1.3.0'))
|
|
||||||
self.assertFalse(ver.at_least('1.3.1-dev'))
|
|
||||||
self.assertFalse(ver.at_least('1.3.1'))
|
|
||||||
self.assertFalse(ver.at_least('1.4-dev'))
|
|
||||||
self.assertFalse(ver.at_least('1.4'))
|
|
||||||
self.assertFalse(ver.at_least('2.0-dev'))
|
|
||||||
self.assertFalse(ver.at_least('2.0'))
|
|
||||||
|
|
||||||
swift.__version__ = '1.3-dev'
|
|
||||||
ver.MAJOR = None
|
|
||||||
self.assertTrue(ver.at_least('1.2'))
|
|
||||||
self.assertTrue(ver.at_least('1.2.9'))
|
|
||||||
self.assertTrue(ver.at_least('1.3-dev'))
|
|
||||||
self.assertTrue(ver.at_least('1.3.0-dev'))
|
|
||||||
self.assertFalse(ver.at_least('1.3'))
|
|
||||||
self.assertFalse(ver.at_least('1.3.0'))
|
|
||||||
self.assertFalse(ver.at_least('1.3.1-dev'))
|
|
||||||
self.assertFalse(ver.at_least('1.3.1'))
|
|
||||||
self.assertFalse(ver.at_least('1.4-dev'))
|
|
||||||
self.assertFalse(ver.at_least('1.4'))
|
|
||||||
self.assertFalse(ver.at_least('2.0-dev'))
|
|
||||||
self.assertFalse(ver.at_least('2.0'))
|
|
||||||
|
|
||||||
swift.__version__ = '1.5.6'
|
|
||||||
ver.MAJOR = None
|
|
||||||
self.assertTrue(ver.at_least('1.4'))
|
|
||||||
self.assertTrue(ver.at_least('1.5'))
|
|
||||||
self.assertTrue(ver.at_least('1.5.5-dev'))
|
|
||||||
self.assertTrue(ver.at_least('1.5.5'))
|
|
||||||
self.assertTrue(ver.at_least('1.5.6-dev'))
|
|
||||||
self.assertTrue(ver.at_least('1.5.6'))
|
|
||||||
self.assertFalse(ver.at_least('1.5.7-dev'))
|
|
||||||
self.assertFalse(ver.at_least('1.5.7'))
|
|
||||||
self.assertFalse(ver.at_least('1.6-dev'))
|
|
||||||
self.assertFalse(ver.at_least('1.6'))
|
|
||||||
self.assertFalse(ver.at_least('2.0-dev'))
|
|
||||||
self.assertFalse(ver.at_least('2.0'))
|
|
||||||
|
|
||||||
swift.__version__ = '1.5.6-dev'
|
|
||||||
ver.MAJOR = None
|
|
||||||
self.assertTrue(ver.at_least('1.4'))
|
|
||||||
self.assertTrue(ver.at_least('1.5'))
|
|
||||||
self.assertTrue(ver.at_least('1.5.5-dev'))
|
|
||||||
self.assertTrue(ver.at_least('1.5.5'))
|
|
||||||
self.assertTrue(ver.at_least('1.5.6-dev'))
|
|
||||||
self.assertFalse(ver.at_least('1.5.6'))
|
|
||||||
self.assertFalse(ver.at_least('1.5.7-dev'))
|
|
||||||
self.assertFalse(ver.at_least('1.5.7'))
|
|
||||||
self.assertFalse(ver.at_least('1.6-dev'))
|
|
||||||
self.assertFalse(ver.at_least('1.6'))
|
|
||||||
self.assertFalse(ver.at_least('2.0-dev'))
|
|
||||||
self.assertFalse(ver.at_least('2.0'))
|
|
||||||
|
|
||||||
swift.__version__ = '1.10.0-2.el6'
|
|
||||||
ver.MAJOR = None
|
|
||||||
self.assertTrue(ver.at_least('1.9'))
|
|
||||||
self.assertTrue(ver.at_least('1.10.0-dev'))
|
|
||||||
self.assertTrue(ver.at_least('1.10.0'))
|
|
||||||
self.assertFalse(ver.at_least('1.11'))
|
|
||||||
self.assertFalse(ver.at_least('2.0'))
|
|
||||||
|
|
||||||
swift.__version__ = 'garbage'
|
|
||||||
ver.MAJOR = None
|
|
||||||
self.assertFalse(ver.at_least('2.0'))
|
|
||||||
|
|
||||||
swift.__version__ = orig_version
|
|
55
tox.ini
55
tox.ini
@ -1,55 +0,0 @@
|
|||||||
[tox]
|
|
||||||
minversion = 1.6
|
|
||||||
envlist = py27,pep8,cover
|
|
||||||
skipsdist = True
|
|
||||||
|
|
||||||
[testenv]
|
|
||||||
basepython = python2.7
|
|
||||||
usedevelop = True
|
|
||||||
install_command = pip install -c{env:UPPER_CONSTRAINTS_FILE:https://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt} {opts} {packages}
|
|
||||||
setenv = VIRTUAL_ENV={envdir}
|
|
||||||
NOSE_WITH_COVERAGE=1
|
|
||||||
NOSE_COVER_BRANCHES=1
|
|
||||||
NOSE_COVER_ERASE=1
|
|
||||||
deps =
|
|
||||||
-r{toxinidir}/test-requirements.txt
|
|
||||||
https://tarballs.openstack.org/swift/swift-2.15.1.tar.gz
|
|
||||||
commands = nosetests {posargs:test/unit}
|
|
||||||
|
|
||||||
[testenv:cover]
|
|
||||||
setenv = VIRTUAL_ENV={envdir}
|
|
||||||
NOSE_WITH_COVERAGE=1
|
|
||||||
NOSE_COVER_BRANCHES=1
|
|
||||||
NOSE_COVER_HTML=1
|
|
||||||
NOSE_COVER_HTML_DIR={toxinidir}/cover
|
|
||||||
NOSE_COVER_MIN_PERCENTAGE=89
|
|
||||||
NOSE_COVER_ERASE=1
|
|
||||||
|
|
||||||
[testenv:pep8]
|
|
||||||
commands =
|
|
||||||
flake8 swauth test
|
|
||||||
flake8 --filename=swauth* bin
|
|
||||||
bandit -r swauth -s B303,B309
|
|
||||||
|
|
||||||
[testenv:bandit]
|
|
||||||
# B303 Use of insecure hash function
|
|
||||||
# B309 Use of HTTPSConnection
|
|
||||||
commands = bandit -r swauth -s B303,B309
|
|
||||||
|
|
||||||
[testenv:venv]
|
|
||||||
commands = {posargs}
|
|
||||||
|
|
||||||
[testenv:docs]
|
|
||||||
commands = python setup.py build_sphinx
|
|
||||||
|
|
||||||
[flake8]
|
|
||||||
# E123 skipped as they are invalid PEP-8.
|
|
||||||
# will be removed later
|
|
||||||
# H405 multi line docstring summary not separated with an empty line
|
|
||||||
# E128 continuation line under-indented for visual indent
|
|
||||||
# E121 continuation line under-indented for hanging indent
|
|
||||||
|
|
||||||
show-source = True
|
|
||||||
ignore = E123,H405,E128,E121
|
|
||||||
builtins = _
|
|
||||||
exclude=.venv,.git,.tox,dist,doc,*egg,build
|
|
@ -1,575 +0,0 @@
|
|||||||
<html>
|
|
||||||
<head>
|
|
||||||
<style type="text/css">
|
|
||||||
body {font-family: sans-serif}
|
|
||||||
table {border-collapse: collapse}
|
|
||||||
td {padding-left: 1ex; padding-right: 1ex}
|
|
||||||
.account {color: #0000ff; padding-left: 3ex; cursor: pointer}
|
|
||||||
.add_account_heading {text-align: right; padding-right: 0}
|
|
||||||
.service {padding-left: 3ex; vertical-align: top}
|
|
||||||
.service_detail {padding-left: 0}
|
|
||||||
.user {color: #0000ff; padding-left: 3ex; cursor: pointer}
|
|
||||||
.group {padding-left: 3ex}
|
|
||||||
.add_user_heading {text-align: right; padding-right: 0}
|
|
||||||
.shadow_delement {color: #0000ff; cursor: pointer}
|
|
||||||
.shadow_felement {display: none}
|
|
||||||
#swauth {font-size: 200%; font-weight: bold; font-style: italic; margin: 0px; padding: 0px}
|
|
||||||
#creds_area {float: right}
|
|
||||||
#logout {color: #0000ff; padding-left: 3ex; cursor: pointer}
|
|
||||||
#refresh_accounts {color: #0000ff; padding-left: 1ex; cursor: pointer}
|
|
||||||
#add_account {color: #0000ff; padding-left: 1ex; padding-right: 1ex; cursor: pointer}
|
|
||||||
#add_account_title {padding-top: 1ex; padding-bottom: 1ex}
|
|
||||||
#add_account_cancel {color: #0000ff; padding-top: 1ex; padding-left: 3ex; cursor: pointer}
|
|
||||||
#add_account_save {color: #0000ff; text-align: right; padding-top: 1ex; padding-right: 3ex; cursor: pointer}
|
|
||||||
#account_area {background: #ddeeff}
|
|
||||||
#add_user {color: #0000ff; padding-left: 1ex; padding-right: 1ex; cursor: pointer}
|
|
||||||
#add_user_title {padding-top: 1ex; padding-bottom: 1ex}
|
|
||||||
#add_user_cancel {color: #0000ff; padding-top: 1ex; padding-left: 3ex; cursor: pointer}
|
|
||||||
#add_user_save {color: #0000ff; text-align: right; padding-top: 1ex; padding-right: 3ex; cursor: pointer}
|
|
||||||
#delete_account {color: #0000ff; text-align: right; margin-left: 45ex; padding-right: 1ex; cursor: pointer}
|
|
||||||
#user_area {background: #aaccff}
|
|
||||||
#delete_user {color: #0000ff; text-align: right; margin-left: 45ex; padding-right: 1ex; cursor: pointer}
|
|
||||||
#auth_view {display: none}
|
|
||||||
#auth_toggler {color: #0000ff; cursor: pointer}
|
|
||||||
#auth_update {color: #0000ff; padding-left: 1ex; cursor: pointer}
|
|
||||||
#auth_update_field {display: none}
|
|
||||||
</style>
|
|
||||||
<script type="text/javascript">
|
|
||||||
var request = null;
|
|
||||||
var creds_user = '';
|
|
||||||
var creds_key = '';
|
|
||||||
var creds_logged_in = true;
|
|
||||||
var account = '';
|
|
||||||
var user = '';
|
|
||||||
var account_selection = -1;
|
|
||||||
var user_selection = -1;
|
|
||||||
var swauth_area_selected_background = '#ddeeff';
|
|
||||||
var account_area_selected_background = '#aaccff';
|
|
||||||
var endpoints;
|
|
||||||
|
|
||||||
function msg_http_error(request) {
|
|
||||||
return 'Server returned status:\n' +
|
|
||||||
request.status + ' ' + request.statusText +
|
|
||||||
'\n\nDetail:\n' +
|
|
||||||
request.responseText;
|
|
||||||
}
|
|
||||||
|
|
||||||
function get_bounds(element) {
|
|
||||||
bounds = {};
|
|
||||||
bounds.top = 0;
|
|
||||||
bounds.left = 0;
|
|
||||||
bounds.width = element.offsetWidth;
|
|
||||||
bounds.height = element.offsetHeight;
|
|
||||||
if (element.offsetParent) {
|
|
||||||
do {
|
|
||||||
bounds.top += element.offsetTop;
|
|
||||||
bounds.left += element.offsetLeft;
|
|
||||||
} while (element = element.offsetParent);
|
|
||||||
}
|
|
||||||
return bounds;
|
|
||||||
}
|
|
||||||
|
|
||||||
function shadow_edit(delement) {
|
|
||||||
felement = document.getElementById('f' + delement.id.substring(1));
|
|
||||||
felement.value = delement.innerHTML;
|
|
||||||
delement.style.display = 'none';
|
|
||||||
felement.style.display = 'inline';
|
|
||||||
felement.focus();
|
|
||||||
}
|
|
||||||
|
|
||||||
function shadow_submitter(felement, evnt, func) {
|
|
||||||
keycode = 0;
|
|
||||||
if (window.event) {
|
|
||||||
keycode = window.event.keyCode;
|
|
||||||
} else if (evnt) {
|
|
||||||
keycode = evnt.which;
|
|
||||||
}
|
|
||||||
if (keycode == 13) {
|
|
||||||
func(felement);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
function shadow_escaper(felement, evnt) {
|
|
||||||
keycode = 0;
|
|
||||||
if (window.event) {
|
|
||||||
keycode = window.event.keyCode;
|
|
||||||
} else if (evnt) {
|
|
||||||
keycode = evnt.which;
|
|
||||||
}
|
|
||||||
if (keycode == 27) {
|
|
||||||
felement.style.display = 'none';
|
|
||||||
document.getElementById('d' + felement.id.substring(1)).style.display = 'inline';
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
function creds_clicked() {
|
|
||||||
creds_area = document.getElementById('creds_area');
|
|
||||||
if (creds_logged_in) {
|
|
||||||
creds_area.innerHTML = 'User: <input id="creds_user" type="text" size="10" /> Key: <input id="creds_key" type="password" size="10" onkeypress="return creds_submitter(event)" /> <input type="button" onclick="return creds_clicked();" value="Login" />';
|
|
||||||
document.getElementById('creds_user').value = creds_user;
|
|
||||||
swauth_area_reset();
|
|
||||||
|
|
||||||
creds_logged_in = false;
|
|
||||||
creds_user = '';
|
|
||||||
creds_key = '';
|
|
||||||
document.getElementById("creds_user").focus();
|
|
||||||
} else {
|
|
||||||
creds_user = document.getElementById('creds_user').value;
|
|
||||||
creds_key = document.getElementById('creds_key').value;
|
|
||||||
if (!creds_user) {
|
|
||||||
alert('Please fill user');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!creds_key) {
|
|
||||||
alert('Please fill key');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
creds_area.innerHTML = '<div>Logged in as ' + creds_user + ' <span id="logout" onclick="creds_clicked()">Logout</span></div>';
|
|
||||||
creds_logged_in = true;
|
|
||||||
swauth_area_load();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function creds_submitter(e) {
|
|
||||||
keycode = 0;
|
|
||||||
if (window.event) {
|
|
||||||
keycode = window.event.keyCode;
|
|
||||||
} else if (e) {
|
|
||||||
keycode = e.which;
|
|
||||||
}
|
|
||||||
if (keycode == 13) {
|
|
||||||
creds_clicked();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
function swauth_area_reset() {
|
|
||||||
account_area_reset();
|
|
||||||
document.getElementById('swauth_area').innerHTML = '';
|
|
||||||
}
|
|
||||||
|
|
||||||
function account_area_reset() {
|
|
||||||
user_area_reset();
|
|
||||||
element = document.getElementById('add_account')
|
|
||||||
if (element) {
|
|
||||||
element.style.background = 'none';
|
|
||||||
}
|
|
||||||
if (account_selection != -1) {
|
|
||||||
document.getElementById('account_' + account_selection).style.background = 'none';
|
|
||||||
}
|
|
||||||
account = '';
|
|
||||||
account_selection = -1;
|
|
||||||
document.getElementById('account_area').innerHTML = '';
|
|
||||||
}
|
|
||||||
|
|
||||||
function user_area_reset() {
|
|
||||||
element = document.getElementById('add_user')
|
|
||||||
if (element) {
|
|
||||||
element.style.background = 'none';
|
|
||||||
}
|
|
||||||
if (user_selection != -1) {
|
|
||||||
document.getElementById('user_' + user_selection).style.background = 'none';
|
|
||||||
}
|
|
||||||
user = '';
|
|
||||||
user_selection = -1;
|
|
||||||
document.getElementById('user_area').innerHTML = '';
|
|
||||||
}
|
|
||||||
|
|
||||||
function swauth_area_load() {
|
|
||||||
swauth_area_reset();
|
|
||||||
request = new XMLHttpRequest();
|
|
||||||
request.onreadystatechange = swauth_area_load2;
|
|
||||||
request.open('GET', './v2/', true);
|
|
||||||
request.setRequestHeader('X-Auth-Admin-User', creds_user);
|
|
||||||
request.setRequestHeader('X-Auth-Admin-Key', creds_key);
|
|
||||||
request.send();
|
|
||||||
}
|
|
||||||
|
|
||||||
function swauth_area_load2() {
|
|
||||||
if (request.readyState == 4) {
|
|
||||||
swauth_area = document.getElementById('swauth_area');
|
|
||||||
if (request.status >= 200 && request.status <= 299) {
|
|
||||||
data = JSON.parse(request.responseText);
|
|
||||||
content = '<table><tr><td>Accounts <span id="refresh_accounts" onclick="swauth_area_load()">Refresh</span> <span id="add_account" onclick="add_account()">Add</span></td></tr>';
|
|
||||||
for (ix = 0; ix < data.accounts.length; ix++) {
|
|
||||||
content += '<tr><td id="account_' + ix + '" onclick="account_area_load(' + ix + ')" class="account">' + data.accounts[ix].name + '</td></tr>';
|
|
||||||
}
|
|
||||||
content += '</table>';
|
|
||||||
swauth_area.innerHTML = content;
|
|
||||||
} else {
|
|
||||||
swauth_area.innerHTML = msg_http_error(request);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function add_account() {
|
|
||||||
account_area_reset();
|
|
||||||
document.getElementById('add_account').style.background = swauth_area_selected_background;
|
|
||||||
account_area = document.getElementById('account_area');
|
|
||||||
account_area.innerHTML = '<table><tr><td id="add_account_title" colspan="2">New Account</td></tr><tr><td class="add_account_heading">Name</td><td><input id="add_account_name" type="text" size="20" /></td></tr><tr><td class="add_account_heading">Suffix</td><td><input id="add_account_suffix" type="text" size="20" /> (Optional)</td></tr><tr><td id="add_account_cancel" onclick="swauth_area_load()">Cancel</td><td id="add_account_save" onclick="add_account_save()">Add</td></tr></table>';
|
|
||||||
bounds = get_bounds(document.getElementById('add_account'));
|
|
||||||
account_area.style.position = 'absolute';
|
|
||||||
account_area.style.top = bounds.top;
|
|
||||||
account_area.style.left = bounds.left + bounds.width;
|
|
||||||
document.getElementById("add_account_name").focus();
|
|
||||||
}
|
|
||||||
|
|
||||||
function add_account_save() {
|
|
||||||
request = new XMLHttpRequest();
|
|
||||||
request.onreadystatechange = add_account_save2;
|
|
||||||
request.open('PUT', './v2/' + document.getElementById('add_account_name').value, true);
|
|
||||||
request.setRequestHeader('X-Auth-Admin-User', creds_user);
|
|
||||||
request.setRequestHeader('X-Auth-Admin-Key', creds_key);
|
|
||||||
request.setRequestHeader('X-Account-Suffix', document.getElementById('add_account_suffix').value);
|
|
||||||
request.send();
|
|
||||||
}
|
|
||||||
|
|
||||||
function add_account_save2() {
|
|
||||||
if (request.readyState == 4) {
|
|
||||||
if (request.status >= 200 && request.status <= 299) {
|
|
||||||
swauth_area_load();
|
|
||||||
} else {
|
|
||||||
alert(msg_http_error(request));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function account_area_load(account_index) {
|
|
||||||
account_area_reset();
|
|
||||||
account_element = document.getElementById('account_' + account_index);
|
|
||||||
account_element.style.background = swauth_area_selected_background;
|
|
||||||
account_selection = account_index;
|
|
||||||
account = account_element.innerHTML;
|
|
||||||
request = new XMLHttpRequest();
|
|
||||||
request.onreadystatechange = account_area_load2;
|
|
||||||
request.open('GET', './v2/' + account, true);
|
|
||||||
request.setRequestHeader('X-Auth-Admin-User', creds_user);
|
|
||||||
request.setRequestHeader('X-Auth-Admin-Key', creds_key);
|
|
||||||
request.send();
|
|
||||||
}
|
|
||||||
|
|
||||||
function account_area_load2() {
|
|
||||||
account_area = document.getElementById('account_area');
|
|
||||||
if (request.readyState == 4) {
|
|
||||||
if (request.status >= 200 && request.status <= 299) {
|
|
||||||
data = JSON.parse(request.responseText);
|
|
||||||
content = '<div id="delete_account" onclick="delete_account()">Delete</div><table><tr><td>Account Id</td><td>' + data.account_id + '</td></tr></table><table><tr><td>Services</td></tr>';
|
|
||||||
services = [];
|
|
||||||
for (service in data.services) {
|
|
||||||
services.push(service);
|
|
||||||
}
|
|
||||||
services.sort();
|
|
||||||
for (ix = 0; ix < services.length; ix++) {
|
|
||||||
content += '<tr><td class="service">' + services[ix] + '</td><td class="service_detail"><table>';
|
|
||||||
if (data.services[services[ix]]['default']) {
|
|
||||||
content += '<tr><td>default</td><td><span id="d-' + services[ix] + '" class="shadow_delement" onclick="shadow_edit(this)">' + data.services[services[ix]]['default'] + '</span><input id="f-' + services[ix] + '" class="shadow_felement" type="text" size="40" onkeypress="return shadow_submitter(this, event, endpoint_save)" onkeydown="return shadow_escaper(this, event)" /></td></tr>';
|
|
||||||
}
|
|
||||||
endpoints = [];
|
|
||||||
for (name in data.services[services[ix]]) {
|
|
||||||
if (name != 'default') {
|
|
||||||
endpoints.push(name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
endpoints.sort();
|
|
||||||
for (iy = 0; iy < endpoints.length; iy++) {
|
|
||||||
content += '<tr><td>' + endpoints[iy] + '</td><td><span id="d' + iy + '-' + services[ix] + '" class="shadow_delement" onclick="shadow_edit(this)">' + data.services[services[ix]][endpoints[iy]] + '</span><input id="f' + iy + '-' + services[ix] + '" class="shadow_felement" type="text" size="40" onkeypress="return shadow_submitter(this, event, endpoint_save)" onkeydown="return shadow_escaper(this, event)" /></td></tr>';
|
|
||||||
}
|
|
||||||
content += '</table></td></tr>';
|
|
||||||
}
|
|
||||||
content += '</table><table><tr><td>Users <span id="add_user" onclick="add_user()">Add</span></td></tr>';
|
|
||||||
for (ix = 0; ix < data.users.length; ix++) {
|
|
||||||
content += '<tr><td id="user_' + ix + '" onclick="user_area_load(' + ix + ')" class="user">' + data.users[ix].name + '</td></tr>';
|
|
||||||
}
|
|
||||||
content += '</table>';
|
|
||||||
account_area.innerHTML = content;
|
|
||||||
} else {
|
|
||||||
account_area.innerHTML = msg_http_error(request);
|
|
||||||
}
|
|
||||||
bounds = get_bounds(document.getElementById('account_' + account_selection));
|
|
||||||
account_area.style.position = 'absolute';
|
|
||||||
account_area.style.top = bounds.top;
|
|
||||||
account_area.style.left = bounds.left + bounds.width;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function endpoint_save(field) {
|
|
||||||
service = field.id.substring(field.id.indexOf('-') + 1)
|
|
||||||
index = field.id.substring(1, field.id.indexOf('-'))
|
|
||||||
if (index) {
|
|
||||||
endpoint = endpoints[index];
|
|
||||||
} else {
|
|
||||||
endpoint = 'default';
|
|
||||||
}
|
|
||||||
services = {};
|
|
||||||
services[service] = {};
|
|
||||||
services[service][endpoint] = field.value;
|
|
||||||
request = new XMLHttpRequest();
|
|
||||||
request.onreadystatechange = endpoint_save2;
|
|
||||||
request.open('POST', './v2/' + account + '/.services', true);
|
|
||||||
request.setRequestHeader('X-Auth-Admin-User', creds_user);
|
|
||||||
request.setRequestHeader('X-Auth-Admin-Key', creds_key);
|
|
||||||
request.send(JSON.stringify(services));
|
|
||||||
}
|
|
||||||
|
|
||||||
function endpoint_save2() {
|
|
||||||
if (request.readyState == 4) {
|
|
||||||
if (request.status >= 200 && request.status <= 299) {
|
|
||||||
account_area_load(account_selection);
|
|
||||||
} else {
|
|
||||||
alert(msg_http_error(request));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function add_user() {
|
|
||||||
user_area_reset();
|
|
||||||
document.getElementById('add_user').style.background = account_area_selected_background;
|
|
||||||
user_area = document.getElementById('user_area');
|
|
||||||
user_area.innerHTML = '<table><tr><td id="add_user_title" colspan="2">New User</td></tr><tr><td class="add_user_heading">Name</td><td><input id="add_user_name" type="text" size="20" /></td></tr><tr><td class="add_user_heading">Auth Key</td><td><input id="add_user_key" type="password" size="20" /></td></tr><tr><td class="add_user_heading">Account Admin</td><td><input id="add_user_admin" type="checkbox" /></td></tr><tr><td class="add_user_heading">Reseller Admin</td><td><input id="add_user_reseller_admin" type="checkbox" /></td></tr><tr><td id="add_user_cancel" onclick="add_user_cancel()">Cancel</td><td id="add_user_save" onclick="add_user_save()">Add</td></tr></table>';
|
|
||||||
bounds = get_bounds(document.getElementById('add_user'));
|
|
||||||
user_area.style.position = 'absolute';
|
|
||||||
user_area.style.top = bounds.top;
|
|
||||||
user_area.style.left = bounds.left + bounds.width;
|
|
||||||
document.getElementById("add_user_name").focus();
|
|
||||||
}
|
|
||||||
|
|
||||||
function add_user_cancel() {
|
|
||||||
document.getElementById('add_user').style.background = 'none';
|
|
||||||
document.getElementById('user_area').innerHTML = '';
|
|
||||||
}
|
|
||||||
|
|
||||||
function add_user_save() {
|
|
||||||
request = new XMLHttpRequest();
|
|
||||||
request.onreadystatechange = add_user_save2;
|
|
||||||
request.open('PUT', './v2/' + account + '/' + document.getElementById('add_user_name').value, true);
|
|
||||||
request.setRequestHeader('X-Auth-Admin-User', creds_user);
|
|
||||||
request.setRequestHeader('X-Auth-Admin-Key', creds_key);
|
|
||||||
request.setRequestHeader('X-Auth-User-Key', document.getElementById('add_user_key').value);
|
|
||||||
if (document.getElementById('add_user_admin').checked) {
|
|
||||||
request.setRequestHeader('X-Auth-User-Admin', 'true');
|
|
||||||
}
|
|
||||||
if (document.getElementById('add_user_reseller_admin').checked) {
|
|
||||||
request.setRequestHeader('X-Auth-User-Reseller-Admin', 'true');
|
|
||||||
}
|
|
||||||
request.send();
|
|
||||||
}
|
|
||||||
|
|
||||||
function add_user_save2() {
|
|
||||||
if (request.readyState == 4) {
|
|
||||||
if (request.status >= 200 && request.status <= 299) {
|
|
||||||
account_area_load(account_selection);
|
|
||||||
} else {
|
|
||||||
alert(msg_http_error(request));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function delete_account() {
|
|
||||||
var user = document.getElementById('user_0');
|
|
||||||
if (user) {
|
|
||||||
alert('Remove all users first');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
request = new XMLHttpRequest();
|
|
||||||
request.onreadystatechange = delete_account2;
|
|
||||||
request.open('DELETE', './v2/' + account, true);
|
|
||||||
request.setRequestHeader('X-Auth-Admin-User', creds_user);
|
|
||||||
request.setRequestHeader('X-Auth-Admin-Key', creds_key);
|
|
||||||
request.send();
|
|
||||||
}
|
|
||||||
|
|
||||||
function delete_account2() {
|
|
||||||
if (request.readyState == 4) {
|
|
||||||
if (request.status >= 200 && request.status <= 299) {
|
|
||||||
swauth_area_load();
|
|
||||||
} else {
|
|
||||||
alert(msg_http_error(request));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function user_area_load(account_area_user_index) {
|
|
||||||
user_area_reset();
|
|
||||||
user_element = document.getElementById('user_' + account_area_user_index);
|
|
||||||
user_element.style.background = account_area_selected_background;
|
|
||||||
user_selection = account_area_user_index;
|
|
||||||
user = user_element.innerHTML;
|
|
||||||
request = new XMLHttpRequest();
|
|
||||||
request.onreadystatechange = user_area_load2;
|
|
||||||
request.open('GET', './v2/' + account + '/' + user, true);
|
|
||||||
request.setRequestHeader('X-Auth-Admin-User', creds_user);
|
|
||||||
request.setRequestHeader('X-Auth-Admin-Key', creds_key);
|
|
||||||
request.send();
|
|
||||||
}
|
|
||||||
|
|
||||||
function user_area_load2() {
|
|
||||||
user_area = document.getElementById('user_area');
|
|
||||||
if (request.readyState == 4) {
|
|
||||||
if (request.status >= 200 && request.status <= 299) {
|
|
||||||
data = JSON.parse(request.responseText);
|
|
||||||
content = '<div id="delete_user" onclick="delete_user()">Delete</div><table><tr><td>Auth</td><td><span id="auth_toggler" onclick="auth_toggle()">Show</span> <span id="auth_view">' + data.auth + '</span></td><td><input id="auth_update_field" type="password" size="20" onkeypress="return auth_submitter(event)" onkeydown="return auth_escaper(event)" /> <span id="auth_update" onclick="auth_update()">Update</span></td></tr></table><table><tr><td>Groups</td></tr>';
|
|
||||||
groups = [];
|
|
||||||
for (ix = 0; ix < data.groups.length; ix++) {
|
|
||||||
groups.push(data.groups[ix].name);
|
|
||||||
}
|
|
||||||
groups.sort();
|
|
||||||
for (ix = 0; ix < groups.length; ix++) {
|
|
||||||
content += '<tr><td class="group">' + groups[ix] + '</td></tr>';
|
|
||||||
}
|
|
||||||
content += '</table>';
|
|
||||||
user_area.innerHTML = content;
|
|
||||||
} else {
|
|
||||||
user_area.innerHTML = msg_http_error(request);
|
|
||||||
}
|
|
||||||
bounds = get_bounds(document.getElementById('user_' + user_selection));
|
|
||||||
user_area.style.position = 'absolute';
|
|
||||||
user_area.style.top = bounds.top;
|
|
||||||
user_area.style.left = bounds.left + bounds.width;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function delete_user() {
|
|
||||||
request = new XMLHttpRequest();
|
|
||||||
request.onreadystatechange = delete_user2;
|
|
||||||
request.open('DELETE', './v2/' + account + '/' + user, true);
|
|
||||||
request.setRequestHeader('X-Auth-Admin-User', creds_user);
|
|
||||||
request.setRequestHeader('X-Auth-Admin-Key', creds_key);
|
|
||||||
request.send();
|
|
||||||
}
|
|
||||||
|
|
||||||
function delete_user2() {
|
|
||||||
if (request.readyState == 4) {
|
|
||||||
if (request.status >= 200 && request.status <= 299) {
|
|
||||||
account_area_load(account_selection);
|
|
||||||
} else {
|
|
||||||
alert(msg_http_error(request));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function auth_toggle() {
|
|
||||||
to_toggle = document.getElementById('auth_view');
|
|
||||||
toggler = document.getElementById('auth_toggler');
|
|
||||||
if (to_toggle.style.display && to_toggle.style.display != 'none') {
|
|
||||||
toggler.innerHTML = 'Show';
|
|
||||||
to_toggle.style.display = 'none';
|
|
||||||
} else {
|
|
||||||
toggler.innerHTML = 'Hide';
|
|
||||||
to_toggle.style.display = 'inline';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function auth_update() {
|
|
||||||
field = document.getElementById('auth_update_field');
|
|
||||||
trigger = document.getElementById('auth_update');
|
|
||||||
if (field.style.display && field.style.display != 'none') {
|
|
||||||
auth_save();
|
|
||||||
} else {
|
|
||||||
field.style.display = 'inline';
|
|
||||||
trigger.style.display = 'none';
|
|
||||||
field.focus();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function auth_submitter(e) {
|
|
||||||
keycode = 0;
|
|
||||||
if (window.event) {
|
|
||||||
keycode = window.event.keyCode;
|
|
||||||
} else if (e) {
|
|
||||||
keycode = e.which;
|
|
||||||
}
|
|
||||||
if (keycode == 13) {
|
|
||||||
auth_save();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
function auth_escaper(e) {
|
|
||||||
keycode = 0;
|
|
||||||
if (window.event) {
|
|
||||||
keycode = window.event.keyCode;
|
|
||||||
} else if (e) {
|
|
||||||
keycode = e.which;
|
|
||||||
}
|
|
||||||
if (keycode == 27) {
|
|
||||||
field = document.getElementById('auth_update_field');
|
|
||||||
field.value = '';
|
|
||||||
field.style.display ='none';
|
|
||||||
document.getElementById('auth_update').style.display ='inline';
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
function auth_save() {
|
|
||||||
document.getElementById('auth_update_field').style.display ='none';
|
|
||||||
if (document.getElementById('auth_update_field').value) {
|
|
||||||
request = new XMLHttpRequest();
|
|
||||||
request.onreadystatechange = auth_save2;
|
|
||||||
request.open('GET', './v2/' + account + '/' + user, true);
|
|
||||||
request.setRequestHeader('X-Auth-Admin-User', creds_user);
|
|
||||||
request.setRequestHeader('X-Auth-Admin-Key', creds_key);
|
|
||||||
request.send();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function auth_save2() {
|
|
||||||
if (request.readyState == 4) {
|
|
||||||
if (request.status >= 200 && request.status <= 299) {
|
|
||||||
data = JSON.parse(request.responseText);
|
|
||||||
request = new XMLHttpRequest();
|
|
||||||
request.onreadystatechange = auth_save3;
|
|
||||||
request.open('PUT', './v2/' + account_element.innerHTML + '/' + user_element.innerHTML, true);
|
|
||||||
request.setRequestHeader('X-Auth-Admin-User', creds_user);
|
|
||||||
request.setRequestHeader('X-Auth-Admin-Key', creds_key);
|
|
||||||
request.setRequestHeader('X-Auth-User-Key', document.getElementById('auth_update_field').value);
|
|
||||||
admin = false;
|
|
||||||
reseller_admin = false;
|
|
||||||
for (ix = 0; ix < data.groups.length; ix++) {
|
|
||||||
if (data.groups[ix].name == '.admin') {
|
|
||||||
admin = true;
|
|
||||||
} else if (data.groups[ix].name == '.reseller_admin') {
|
|
||||||
reseller_admin = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (admin) {
|
|
||||||
request.setRequestHeader('X-Auth-User-Admin', 'true');
|
|
||||||
}
|
|
||||||
if (reseller_admin) {
|
|
||||||
request.setRequestHeader('X-Auth-User-Reseller-Admin', 'true');
|
|
||||||
}
|
|
||||||
request.send();
|
|
||||||
} else {
|
|
||||||
alert(msg_http_error(request));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function auth_save3() {
|
|
||||||
if (request.readyState == 4) {
|
|
||||||
if (request.status >= 200 && request.status <= 299) {
|
|
||||||
user_area_load(user_selection);
|
|
||||||
} else {
|
|
||||||
alert(msg_http_error(request));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
</head>
|
|
||||||
<body onload="creds_clicked()">
|
|
||||||
<form onsubmit="return false">
|
|
||||||
<div id="creds_area"></div>
|
|
||||||
<div id="swauth">Swauth</div>
|
|
||||||
<div id="swauth_area"></div>
|
|
||||||
<div id="account_area"></div>
|
|
||||||
<div id="user_area"></div>
|
|
||||||
</form>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
Loading…
x
Reference in New Issue
Block a user