diff --git a/.coveragerc b/.coveragerc deleted file mode 100644 index 52c7ed7..0000000 --- a/.coveragerc +++ /dev/null @@ -1,6 +0,0 @@ -[run] -branch = True -omit = /usr*,setup.py,*egg*,.venv/*,.tox/*,test/* - -[report] -ignore_errors = True diff --git a/.gitignore b/.gitignore deleted file mode 100644 index f6c38b3..0000000 --- a/.gitignore +++ /dev/null @@ -1,6 +0,0 @@ -*.egg-info -*.py[co] -.DS_Store -.coverage -.tox -cover diff --git a/.mailmap b/.mailmap deleted file mode 100644 index 35232e2..0000000 --- a/.mailmap +++ /dev/null @@ -1,12 +0,0 @@ -Greg Holt -Greg Holt -Greg Holt -Greg Holt -Greg Holt -Greg Holt -Greg Holt -Greg Holt -Christian Schwede -Christian Schwede -Ondřej Nový -Ron Pedde diff --git a/.unittests b/.unittests deleted file mode 100755 index ccc1339..0000000 --- a/.unittests +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/bash - -nosetests test/unit --exe --with-coverage --cover-package swauth --cover-erase -rm -f .coverage diff --git a/AUTHORS b/AUTHORS deleted file mode 100644 index 5fd7db7..0000000 --- a/AUTHORS +++ /dev/null @@ -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 diff --git a/CHANGELOG b/CHANGELOG deleted file mode 100644 index 2eb2468..0000000 --- a/CHANGELOG +++ /dev/null @@ -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. diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst deleted file mode 100644 index 6cce2f4..0000000 --- a/CONTRIBUTING.rst +++ /dev/null @@ -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 diff --git a/LICENSE b/LICENSE deleted file mode 100644 index 75b5248..0000000 --- a/LICENSE +++ /dev/null @@ -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. diff --git a/README.md b/README.md deleted file mode 100644 index c08f62d..0000000 --- a/README.md +++ /dev/null @@ -1,87 +0,0 @@ -Swauth ------- - -An Auth Service for Swift as WSGI Middleware that uses Swift itself as a -backing store. Docs at: or ask in #openstack-swauth on -freenode [IRC](http://eavesdrop.openstack.org/irclogs/%23openstack-swauth/). - -See also for the standard OpenStack -auth service. - - -NOTE ----- - -**Be sure to review the docs at: -** - - -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 diff --git a/README.rst b/README.rst new file mode 100644 index 0000000..86e34d6 --- /dev/null +++ b/README.rst @@ -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. diff --git a/babel.cfg b/babel.cfg deleted file mode 100644 index 15cd6cb..0000000 --- a/babel.cfg +++ /dev/null @@ -1,2 +0,0 @@ -[python: **.py] - diff --git a/bin/swauth-add-account b/bin/swauth-add-account deleted file mode 100755 index f33e491..0000000 --- a/bin/swauth-add-account +++ /dev/null @@ -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] ') - parser.add_option('-s', '--suffix', dest='suffix', - default='', help='The suffix to use with the reseller prefix as the ' - 'storage account name (default: ) 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)) diff --git a/bin/swauth-add-user b/bin/swauth-add-user deleted file mode 100755 index f0c79be..0000000 --- a/bin/swauth-add-user +++ /dev/null @@ -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] ') - 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: ) 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 :') - 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)) diff --git a/bin/swauth-cleanup-tokens b/bin/swauth-cleanup-tokens deleted file mode 100755 index 60e6bd8..0000000 --- a/bin/swauth-cleanup-tokens +++ /dev/null @@ -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.') diff --git a/bin/swauth-delete-account b/bin/swauth-delete-account deleted file mode 100755 index 724597d..0000000 --- a/bin/swauth-delete-account +++ /dev/null @@ -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] ') - 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)) diff --git a/bin/swauth-delete-user b/bin/swauth-delete-user deleted file mode 100755 index c1cb209..0000000 --- a/bin/swauth-delete-user +++ /dev/null @@ -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] ') - 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)) diff --git a/bin/swauth-list b/bin/swauth-list deleted file mode 100755 index 53916e2..0000000 --- a/bin/swauth-list +++ /dev/null @@ -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) diff --git a/bin/swauth-prep b/bin/swauth-prep deleted file mode 100755 index 70fc1f0..0000000 --- a/bin/swauth-prep +++ /dev/null @@ -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)) diff --git a/bin/swauth-set-account-service b/bin/swauth-set-account-service deleted file mode 100755 index ff45e6f..0000000 --- a/bin/swauth-set-account-service +++ /dev/null @@ -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] - -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)) diff --git a/bindep.txt b/bindep.txt deleted file mode 100644 index 4f5e3c1..0000000 --- a/bindep.txt +++ /dev/null @@ -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] diff --git a/doc/build/.gitignore b/doc/build/.gitignore deleted file mode 100644 index 72e8ffc..0000000 --- a/doc/build/.gitignore +++ /dev/null @@ -1 +0,0 @@ -* diff --git a/doc/source/Draft Security Guide/Documentation LICENSE.txt b/doc/source/Draft Security Guide/Documentation LICENSE.txt deleted file mode 100644 index 242e8a3..0000000 --- a/doc/source/Draft Security Guide/Documentation LICENSE.txt +++ /dev/null @@ -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/ \ No newline at end of file diff --git a/doc/source/Draft Security Guide/SWAuth Security Explained, Rodney Beede, 2013-12-13.pdf b/doc/source/Draft Security Guide/SWAuth Security Explained, Rodney Beede, 2013-12-13.pdf deleted file mode 100644 index 06114d1..0000000 Binary files a/doc/source/Draft Security Guide/SWAuth Security Explained, Rodney Beede, 2013-12-13.pdf and /dev/null differ diff --git a/doc/source/Draft Security Guide/docbook SWAuth snippet, Rodney Beede, 2013-12-13.xml b/doc/source/Draft Security Guide/docbook SWAuth snippet, Rodney Beede, 2013-12-13.xml deleted file mode 100644 index 60841e1..0000000 --- a/doc/source/Draft Security Guide/docbook SWAuth snippet, Rodney Beede, 2013-12-13.xml +++ /dev/null @@ -1,120 +0,0 @@ -
- SWAuth - 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 - http://gholt.github.io/swauth/. - SWAuth has these types of roles (or groups) for a - user: - - - - .super_admin - Can perform any action on any - OpenStack Account, Container, or Object - - - .reseller_admin - Can perform most actions on - any OpenStack Account, Container, or Object. - Cannot create other reseller admins. - - - - .admin - Can perform actions limited to - the single OpenStack Account it belongs to - - - Regular User - Can access containers or - objects they have permission to in the - OpenStack Account to which they belong - - - - - The following table provides a matrix of what each - role/group can do: -
- Object storage SWAuth role matrix - - - - - - - - -
- The super admin key is stored in - /etc/swift/proxy-server.conf and MUST - be protected! See the File Permissions section for - guidance on protecting this file. Frequent changing of - this key is recommended. - 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. - 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. -
- Protecting cloud administration - 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 - allow_account_managment to false in your - proxy-server.conf. - 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. -
-
- Salting and hashing passwords - 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. - You may optionally add-in your own hashing code - or provider as a hook to SWAuth. See the - SWAuth code and site - for details. - 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. - 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. - The salt is stored in the - /etc/swift/proxy-server.conf - file which must be secured with proper ACLs. See the - File Permissions section for guidance. -
-
- \ No newline at end of file diff --git a/doc/source/Draft Security Guide/swift_swauth_roles_matrix.png b/doc/source/Draft Security Guide/swift_swauth_roles_matrix.png deleted file mode 100644 index 9401c4a..0000000 Binary files a/doc/source/Draft Security Guide/swift_swauth_roles_matrix.png and /dev/null differ diff --git a/doc/source/_static/.empty b/doc/source/_static/.empty deleted file mode 100644 index e69de29..0000000 diff --git a/doc/source/_templates/.empty b/doc/source/_templates/.empty deleted file mode 100644 index e69de29..0000000 diff --git a/doc/source/api.rst b/doc/source/api.rst deleted file mode 100644 index e5effe4..0000000 --- a/doc/source/api.rst +++ /dev/null @@ -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// HTTP/1.1 - X-Auth-Admin-User: .super_admin - X-Auth-Admin-Key: swauthkey - -Example Curl Request:: - - curl -D - https:///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// 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// HTTP/1.1 - X-Auth-Admin-User: .super_admin - X-Auth-Admin-Key: swauthkey - -Example Curl Request:: - - curl -D - https:///auth/v2/ \ - -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:///v1/" } - }, - "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// HTTP/1.1 - X-Auth-Admin-User: .super_admin - X-Auth-Admin-Key: swauthkey - -Example Curl Request:: - - curl -XPUT -D - https:///auth/v2/ \ - -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// HTTP/1.1 - X-Auth-Admin-User: .super_admin - X-Auth-Admin-Key: swauthkey - -Example Curl Request:: - - curl -XDELETE -D - https:///auth/v2/ \ - -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/// 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": ":"}, - # The first group is a unique user identifier - {"name": ""}, - # The second group is the auth account name - {"name": ""} - # There may be additional groups, .admin being a - # special group indicating an account admin and - # .reseller_admin indicating a reseller admin. - ], - "auth": ":" - # 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/// HTTP/1.1 - X-Auth-Admin-User: .super_admin - X-Auth-Admin-Key: swauthkey - -Example Curl Request:: - - curl -D - https:///auth/v2// \ - -H "X-Auth-Admin-User: .super_admin" \ - -H "X-Auth-Admin-Key: swauthkey" - -Example Response:: - - HTTP/1.1 200 Ok - - { "groups": [ { "name": ":" }, - { "name": "" }, - { "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 ``:``. - -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/// 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:///auth/v2// \ - -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/// HTTP/1.1 - X-Auth-Admin-User: .super_admin - X-Auth-Admin-Key: swauthkey - -Example Curl Request:: - - curl -XDELETE -D - https:///auth/v2// \ - -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 , 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///.services HTTP/1.0 - X-Auth-Admin-User: .super_admin - X-Auth-Admin-Key: swauthkey - - {"storage": { "local": "" }} - -Example Curl Request:: - - curl -XPOST -D - https:///auth/v2//.services \ - -H "X-Auth-Admin-User: .super_admin" \ - -H "X-Auth-Admin-Key: swauthkey" --data-binary \ - '{ "storage": { "local": "" }}' - -Example Response:: - - HTTP/1.1 200 OK - - {"storage": {"default": "local", "local": "" }} - -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///.groups - X-Auth-Admin-User: .super_admin - X-Auth-Admin-Key: swauthkey - -Example Curl Request:: - - curl -D - https:///auth/v2//.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": "" }, - { "name": ":user1" }, - { "name": ":user2" } ] } - diff --git a/doc/source/authtypes.rst b/doc/source/authtypes.rst deleted file mode 100644 index a19ee22..0000000 --- a/doc/source/authtypes.rst +++ /dev/null @@ -1,10 +0,0 @@ -.. _swauth_authtypes_module: - -swauth.authtypes -================= - -.. automodule:: swauth.authtypes - :members: - :undoc-members: - :show-inheritance: - :noindex: diff --git a/doc/source/conf.py b/doc/source/conf.py deleted file mode 100644 index 3c31ec1..0000000 --- a/doc/source/conf.py +++ /dev/null @@ -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 -# " v 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 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) -] diff --git a/doc/source/details.rst b/doc/source/details.rst deleted file mode 100644 index 8dd057b..0000000 --- a/doc/source/details.rst +++ /dev/null @@ -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 -. - -The containers whose names do not begin with a period represent the accounts -within the auth service. For example, the /test container would -represent the "test" account. - -The objects within each container represent the users for that auth service -account. For example, the /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": ":", "groups": } - -The `` specifies how the user key is encoded. The default is `plaintext`, -which saves the user's key in plaintext in the `` 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 `` 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_salt = - -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 `` contains at least two groups. The first is a unique group -identifying that user and it's name is of the format `:`. The -second group is the `` 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 -/ container. To map back the other way, an -/.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 -//.services object is created with its contents having a -JSON dictionary of the format:: - - {"storage": {"default": "local", "local": }} - -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 -`/.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": , - "user": , - "account_id": , - "groups": , - "expires": } - -The `` is the auth service account's name for that token. The `` -is the user within the account for that token. The `` is the -same as the `X-Container-Meta-Account-Id` for the auth service's account, -as described above. The `` 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 :: - - .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 diff --git a/doc/source/index.rst b/doc/source/index.rst deleted file mode 100644 index dca14e1..0000000 --- a/doc/source/index.rst +++ /dev/null @@ -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 `_ and `acl callback `_ 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 `. - -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 `). - - -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 = - - 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://:/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://:/v1#http://127.0.0.1:/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]://:/auth/ -K - swauthkey -a test tester testing`` - -8) Ensure it works ``swift -A http[s]://:/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]://:/auth/v1.0 - -U .super_admin:.super_admin -K swauthkey upload .webadmin .`` - -3) Open ``http[s]://:/auth/`` in your browser. - - -Swift3 Middleware Compatibility -------------------------------- - - -`Swift3 middleware `_ 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 `_ -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` diff --git a/doc/source/license.rst b/doc/source/license.rst deleted file mode 100644 index 590a9b4..0000000 --- a/doc/source/license.rst +++ /dev/null @@ -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. diff --git a/doc/source/middleware.rst b/doc/source/middleware.rst deleted file mode 100644 index a25acd4..0000000 --- a/doc/source/middleware.rst +++ /dev/null @@ -1,9 +0,0 @@ -.. _swauth_middleware_module: - -swauth.middleware -================= - -.. automodule:: swauth.middleware - :members: - :undoc-members: - :show-inheritance: diff --git a/doc/source/swauth.rst b/doc/source/swauth.rst deleted file mode 100644 index c50c350..0000000 --- a/doc/source/swauth.rst +++ /dev/null @@ -1,9 +0,0 @@ -.. _swauth_module: - -swauth -====== - -.. automodule:: swauth - :members: - :undoc-members: - :show-inheritance: diff --git a/etc/proxy-server.conf-sample b/etc/proxy-server.conf-sample deleted file mode 100644 index db44bcd..0000000 --- a/etc/proxy-server.conf-sample +++ /dev/null @@ -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 = -# 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 diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index dead155..0000000 --- a/requirements.txt +++ /dev/null @@ -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 diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index 004a22e..0000000 --- a/setup.cfg +++ /dev/null @@ -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 diff --git a/setup.py b/setup.py deleted file mode 100644 index 566d844..0000000 --- a/setup.py +++ /dev/null @@ -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) diff --git a/swauth/__init__.py b/swauth/__init__.py deleted file mode 100644 index 47f805b..0000000 --- a/swauth/__init__.py +++ /dev/null @@ -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') diff --git a/swauth/authtypes.py b/swauth/authtypes.py deleted file mode 100644 index 7dbc175..0000000 --- a/swauth/authtypes.py +++ /dev/null @@ -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:" -- 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) diff --git a/swauth/middleware.py b/swauth/middleware.py deleted file mode 100644 index d0f97ac..0000000 --- a/swauth/middleware.py +++ /dev/null @@ -1,1709 +0,0 @@ -# Copyright (c) 2010-2012 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 base64 -from hashlib import sha1 -from hashlib import sha512 -import hmac -from httplib import HTTPConnection -from httplib import HTTPSConnection -import json -import six -from six.moves.urllib.parse import urlparse -import swift -from time import gmtime -from time import strftime -from time import time -from traceback import format_exc -from urllib import quote -from urllib import unquote -from uuid import uuid4 - -from eventlet.timeout import Timeout -from eventlet import TimeoutError -from swift.common.swob import HTTPAccepted -from swift.common.swob import HTTPBadRequest -from swift.common.swob import HTTPConflict -from swift.common.swob import HTTPCreated -from swift.common.swob import HTTPForbidden -from swift.common.swob import HTTPMethodNotAllowed -from swift.common.swob import HTTPMovedPermanently -from swift.common.swob import HTTPNoContent -from swift.common.swob import HTTPNotFound -from swift.common.swob import HTTPUnauthorized -from swift.common.swob import Request -from swift.common.swob import Response - -from swift.common.bufferedhttp import http_connect_raw as http_connect -from swift.common.middleware.acl import clean_acl -from swift.common.middleware.acl import parse_acl -from swift.common.middleware.acl import referrer_allowed -from swift.common.utils import cache_from_env -from swift.common.utils import get_logger -from swift.common.utils import get_remote_client -from swift.common.utils import HASH_PATH_PREFIX -from swift.common.utils import HASH_PATH_SUFFIX -from swift.common.utils import split_path -from swift.common.utils import TRUE_VALUES -import swift.common.wsgi - -import swauth.authtypes -from swauth import swift_version - - -SWIFT_MIN_VERSION = "2.2.0" -CONTENT_TYPE_JSON = 'application/json' - - -class Swauth(object): - """Scalable authentication and authorization system that uses Swift as its - backing store. - - :param app: The next WSGI app in the pipeline - :param conf: The dict of configuration values - """ - - def __init__(self, app, conf): - self.app = app - self.conf = conf - self.logger = get_logger(conf, log_route='swauth') - if not swift_version.at_least(SWIFT_MIN_VERSION): - msg = ("Your Swift installation is too old (%s). You need at " - "least %s." % (swift.__version__, SWIFT_MIN_VERSION)) - self.logger.critical(msg) - raise ValueError(msg) - self.log_headers = conf.get('log_headers', 'no').lower() in TRUE_VALUES - self.reseller_prefix = conf.get('reseller_prefix', 'AUTH').strip() - if self.reseller_prefix and self.reseller_prefix[-1] != '_': - self.reseller_prefix += '_' - self.auth_prefix = conf.get('auth_prefix', '/auth/') - if not self.auth_prefix: - self.auth_prefix = '/auth/' - if self.auth_prefix[0] != '/': - self.auth_prefix = '/' + self.auth_prefix - if self.auth_prefix[-1] != '/': - self.auth_prefix += '/' - self.swauth_remote = conf.get('swauth_remote') - if self.swauth_remote: - self.swauth_remote = self.swauth_remote.rstrip('/') - if not self.swauth_remote: - msg = _('Invalid swauth_remote set in conf file! Exiting.') - self.logger.critical(msg) - raise ValueError(msg) - self.swauth_remote_parsed = urlparse(self.swauth_remote) - if self.swauth_remote_parsed.scheme not in ('http', 'https'): - msg = _('Cannot handle protocol scheme %(schema)s ' - 'for url %(url)s!') % \ - (self.swauth_remote_parsed.scheme, repr(self.swauth_remote)) - self.logger.critical(msg) - raise ValueError(msg) - self.swauth_remote_timeout = int(conf.get('swauth_remote_timeout', 10)) - self.auth_account = '%s.auth' % self.reseller_prefix - self.default_swift_cluster = conf.get('default_swift_cluster', - 'local#http://127.0.0.1:8080/v1') - # This setting is a little messy because of the options it has to - # provide. The basic format is cluster_name#url, such as the default - # value of local#http://127.0.0.1:8080/v1. - # If the URL given to the user needs to differ from the url used by - # Swauth to create/delete accounts, there's a more complex format: - # cluster_name#url#url, such as - # local#https://public.com:8080/v1#http://private.com:8080/v1. - cluster_parts = self.default_swift_cluster.split('#', 2) - self.dsc_name = cluster_parts[0] - if len(cluster_parts) == 3: - self.dsc_url = cluster_parts[1].rstrip('/') - self.dsc_url2 = cluster_parts[2].rstrip('/') - elif len(cluster_parts) == 2: - self.dsc_url = self.dsc_url2 = cluster_parts[1].rstrip('/') - else: - raise ValueError('Invalid cluster format') - self.dsc_parsed = urlparse(self.dsc_url) - if self.dsc_parsed.scheme not in ('http', 'https'): - raise ValueError('Cannot handle protocol scheme %s for url %s' % - (self.dsc_parsed.scheme, repr(self.dsc_url))) - self.dsc_parsed2 = urlparse(self.dsc_url2) - if self.dsc_parsed2.scheme not in ('http', 'https'): - raise ValueError('Cannot handle protocol scheme %s for url %s' % - (self.dsc_parsed2.scheme, repr(self.dsc_url2))) - self.super_admin_key = conf.get('super_admin_key') - if not self.super_admin_key and not self.swauth_remote: - msg = _('No super_admin_key set in conf file; Swauth ' - 'administration features will be disabled.') - self.logger.warning(msg) - self.token_life = int(conf.get('token_life', 86400)) - self.max_token_life = int(conf.get('max_token_life', self.token_life)) - self.timeout = int(conf.get('node_timeout', 10)) - self.itoken = None - self.itoken_expires = None - self.allowed_sync_hosts = [h.strip() - for h in conf.get('allowed_sync_hosts', '127.0.0.1').split(',') - if h.strip()] - # Get an instance of our auth_type encoder for saving and checking the - # user's key - self.auth_type = conf.get('auth_type', 'Plaintext').title() - self.auth_encoder = getattr(swauth.authtypes, self.auth_type, None) - if self.auth_encoder is None: - raise ValueError('Invalid auth_type in config file: %s' - % self.auth_type) - # If auth_type_salt is not set in conf file, a random salt will be - # generated for each new password to be encoded. - self.auth_encoder.salt = conf.get('auth_type_salt', None) - - # Due to security concerns, S3 support is disabled by default. - self.s3_support = conf.get('s3_support', 'off').lower() in TRUE_VALUES - if self.s3_support and self.auth_type != 'Plaintext' \ - and not self.auth_encoder.salt: - msg = _('S3 support requires salt to be manually set in conf ' - 'file using auth_type_salt config option.') - self.logger.warning(msg) - self.s3_support = False - - self.allow_overrides = \ - conf.get('allow_overrides', 't').lower() in TRUE_VALUES - self.agent = '%(orig)s Swauth' - self.swift_source = 'SWTH' - self.default_storage_policy = conf.get('default_storage_policy', None) - - def make_pre_authed_request(self, env, method=None, path=None, body=None, - headers=None): - """Nearly the same as swift.common.wsgi.make_pre_authed_request - except that this also always sets the 'swift.source' and user - agent. - - Newer Swift code will support swift_source as a kwarg, but we - do it this way so we don't have to have a newer Swift. - - Since we're doing this anyway, we may as well set the user - agent too since we always do that. - """ - if self.default_storage_policy: - sp = self.default_storage_policy - if headers: - headers.update({'X-Storage-Policy': sp}) - else: - headers = {'X-Storage-Policy': sp} - subreq = swift.common.wsgi.make_pre_authed_request( - env, method=method, path=path, body=body, headers=headers, - agent=self.agent) - subreq.environ['swift.source'] = self.swift_source - return subreq - - def __call__(self, env, start_response): - """Accepts a standard WSGI application call, authenticating the request - and installing callback hooks for authorization and ACL header - validation. For an authenticated request, REMOTE_USER will be set to a - comma separated list of the user's groups. - - With a non-empty reseller prefix, acts as the definitive auth service - for just tokens and accounts that begin with that prefix, but will deny - requests outside this prefix if no other auth middleware overrides it. - - With an empty reseller prefix, acts as the definitive auth service only - for tokens that validate to a non-empty set of groups. For all other - requests, acts as the fallback auth service when no other auth - middleware overrides it. - - Alternatively, if the request matches the self.auth_prefix, the request - will be routed through the internal auth request handler (self.handle). - This is to handle creating users, accounts, granting tokens, etc. - """ - if 'keystone.identity' in env: - return self.app(env, start_response) - # We're going to consider OPTIONS requests harmless and the CORS - # support in the Swift proxy needs to get them. - if env.get('REQUEST_METHOD') == 'OPTIONS': - return self.app(env, start_response) - if self.allow_overrides and env.get('swift.authorize_override', False): - return self.app(env, start_response) - if not self.swauth_remote: - if env.get('PATH_INFO', '') == self.auth_prefix[:-1]: - return HTTPMovedPermanently(add_slash=True)(env, - start_response) - elif env.get('PATH_INFO', '').startswith(self.auth_prefix): - return self.handle(env, start_response) - s3 = env.get('swift3.auth_details') - if s3 and not self.s3_support: - msg = 'S3 support is disabled in swauth.' - return HTTPBadRequest(body=msg)(env, start_response) - token = env.get('HTTP_X_AUTH_TOKEN', env.get('HTTP_X_STORAGE_TOKEN')) - if token and len(token) > swauth.authtypes.MAX_TOKEN_LENGTH: - return HTTPBadRequest(body='Token exceeds maximum length.')(env, - start_response) - if s3 or (token and token.startswith(self.reseller_prefix)): - # Note: Empty reseller_prefix will match all tokens. - groups = self.get_groups(env, token) - if groups: - env['REMOTE_USER'] = groups - user = groups and groups.split(',', 1)[0] or '' - # We know the proxy logs the token, so we augment it just a bit - # to also log the authenticated user. - env['HTTP_X_AUTH_TOKEN'] = \ - '%s,%s' % (user, 's3' if s3 else token) - env['swift.authorize'] = self.authorize - env['swift.clean_acl'] = clean_acl - if '.reseller_admin' in groups: - env['reseller_request'] = True - else: - # Unauthorized token - if self.reseller_prefix and token and \ - token.startswith(self.reseller_prefix): - # Because I know I'm the definitive auth for this token, I - # can deny it outright. - return HTTPUnauthorized()(env, start_response) - # Because I'm not certain if I'm the definitive auth, I won't - # overwrite swift.authorize and I'll just set a delayed denial - # if nothing else overrides me. - elif 'swift.authorize' not in env: - env['swift.authorize'] = self.denied_response - else: - if self.reseller_prefix: - # With a non-empty reseller_prefix, I would like to be called - # back for anonymous access to accounts I know I'm the - # definitive auth for. - try: - version, rest = split_path(env.get('PATH_INFO', ''), - 1, 2, True) - except ValueError: - rest = None - if rest and rest.startswith(self.reseller_prefix): - # Handle anonymous access to accounts I'm the definitive - # auth for. - env['swift.authorize'] = self.authorize - env['swift.clean_acl'] = clean_acl - # Not my token, not my account, I can't authorize this request, - # deny all is a good idea if not already set... - elif 'swift.authorize' not in env: - env['swift.authorize'] = self.denied_response - # Because I'm not certain if I'm the definitive auth for empty - # reseller_prefixed accounts, I won't overwrite swift.authorize. - elif 'swift.authorize' not in env: - env['swift.authorize'] = self.authorize - env['swift.clean_acl'] = clean_acl - return self.app(env, start_response) - - def _get_concealed_token(self, token): - """Returns hashed token to be used as object name in Swift. - - Tokens are stored in auth account but object names are visible in Swift - logs. Object names are hashed from token. - """ - enc_key = "%s:%s:%s" % (HASH_PATH_PREFIX, token, HASH_PATH_SUFFIX) - return sha512(enc_key).hexdigest() - - def get_groups(self, env, token): - """Get groups for the given token. - - :param env: The current WSGI environment dictionary. - :param token: Token to validate and return a group string for. - - :returns: None if the token is invalid or a string containing a comma - separated list of groups the authenticated user is a member - of. The first group in the list is also considered a unique - identifier for that user. - """ - groups = None - memcache_client = cache_from_env(env) - if memcache_client: - memcache_key = '%s/auth/%s' % (self.reseller_prefix, token) - cached_auth_data = memcache_client.get(memcache_key) - if cached_auth_data: - expires, groups = cached_auth_data - if expires < time(): - groups = None - - s3_auth_details = env.get('swift3.auth_details') - if s3_auth_details: - if not self.s3_support: - self.logger.warning('S3 support is disabled in swauth.') - return None - if self.swauth_remote: - # TODO(gholt): Support S3-style authorization with - # swauth_remote mode - self.logger.warning('S3-style authorization not supported yet ' - 'with swauth_remote mode.') - return None - try: - account, user = s3_auth_details['access_key'].split(':', 1) - signature_from_user = s3_auth_details['signature'] - msg = s3_auth_details['string_to_sign'] - except Exception: - self.logger.debug( - 'Swauth cannot parse swift3.auth_details value %r' % - (s3_auth_details, )) - return None - path = quote('/v1/%s/%s/%s' % (self.auth_account, account, user)) - resp = self.make_pre_authed_request( - env, 'GET', path).get_response(self.app) - if resp.status_int // 100 != 2: - return None - - if 'x-object-meta-account-id' in resp.headers: - account_id = resp.headers['x-object-meta-account-id'] - else: - path = quote('/v1/%s/%s' % (self.auth_account, account)) - resp2 = self.make_pre_authed_request( - env, 'HEAD', path).get_response(self.app) - if resp2.status_int // 100 != 2: - return None - account_id = resp2.headers['x-container-meta-account-id'] - - path = env['PATH_INFO'] - env['PATH_INFO'] = path.replace("%s:%s" % (account, user), - account_id, 1) - detail = json.loads(resp.body) - if detail: - creds = detail.get('auth') - try: - auth_encoder, creds_dict = \ - swauth.authtypes.validate_creds(creds) - except ValueError as e: - self.logger.error('%s' % e.args[0]) - return None - - password = creds_dict['hash'] - - # https://bugs.python.org/issue5285 - if isinstance(password, six.text_type): - password = password.encode('utf-8') - if isinstance(msg, six.text_type): - msg = msg.encode('utf-8') - - valid_signature = base64.encodestring(hmac.new( - password, msg, sha1).digest()).strip() - if signature_from_user != valid_signature: - return None - groups = [g['name'] for g in detail['groups']] - if '.admin' in groups: - groups.remove('.admin') - groups.append(account_id) - groups = ','.join(groups) - return groups - - if not groups: - if self.swauth_remote: - with Timeout(self.swauth_remote_timeout): - conn = http_connect(self.swauth_remote_parsed.hostname, - self.swauth_remote_parsed.port, 'GET', - '%s/v2/.token/%s' % (self.swauth_remote_parsed.path, - quote(token)), - ssl=(self.swauth_remote_parsed.scheme == 'https')) - resp = conn.getresponse() - resp.read() - conn.close() - if resp.status // 100 != 2: - return None - expires_from_now = float(resp.getheader('x-auth-ttl')) - groups = resp.getheader('x-auth-groups') - if memcache_client: - memcache_client.set( - memcache_key, (time() + expires_from_now, groups), - time=expires_from_now) - else: - object_name = self._get_concealed_token(token) - path = quote('/v1/%s/.token_%s/%s' % - (self.auth_account, object_name[-1], object_name)) - resp = self.make_pre_authed_request( - env, 'GET', path).get_response(self.app) - if resp.status_int // 100 != 2: - return None - detail = json.loads(resp.body) - if detail['expires'] < time(): - self.make_pre_authed_request( - env, 'DELETE', path).get_response(self.app) - return None - groups = [g['name'] for g in detail['groups']] - if '.admin' in groups: - groups.remove('.admin') - groups.append(detail['account_id']) - groups = ','.join(groups) - if memcache_client: - memcache_client.set( - memcache_key, - (detail['expires'], groups), - time=float(detail['expires'] - time())) - return groups - - def authorize(self, req): - """Returns None if the request is authorized to continue or a standard - WSGI response callable if not. - """ - try: - version, account, container, obj = split_path(req.path, 1, 4, True) - except ValueError: - return HTTPNotFound(request=req) - if not account or not account.startswith(self.reseller_prefix): - return self.denied_response(req) - user_groups = (req.remote_user or '').split(',') - if '.reseller_admin' in user_groups and \ - account != self.reseller_prefix and \ - account[len(self.reseller_prefix)] != '.': - req.environ['swift_owner'] = True - return None - if account in user_groups and \ - (req.method not in ('DELETE', 'PUT') or container): - # If the user is admin for the account and is not trying to do an - # account DELETE or PUT... - req.environ['swift_owner'] = True - return None - if (req.environ.get('swift_sync_key') and - req.environ['swift_sync_key'] == - req.headers.get('x-container-sync-key', None) and - 'x-timestamp' in req.headers and - (req.remote_addr in self.allowed_sync_hosts or - get_remote_client(req) in self.allowed_sync_hosts)): - return None - referrers, groups = parse_acl(getattr(req, 'acl', None)) - if referrer_allowed(req.referer, referrers): - if obj or '.rlistings' in groups: - return None - return self.denied_response(req) - if not req.remote_user: - return self.denied_response(req) - for user_group in user_groups: - if user_group in groups: - return None - return self.denied_response(req) - - def denied_response(self, req): - """Returns a standard WSGI response callable with the status of 403 or 401 - depending on whether the REMOTE_USER is set or not. - """ - if not hasattr(req, 'credentials_valid'): - req.credentials_valid = None - if req.remote_user or req.credentials_valid: - return HTTPForbidden(request=req) - else: - return HTTPUnauthorized(request=req) - - def handle(self, env, start_response): - """WSGI entry point for auth requests (ones that match the - self.auth_prefix). - Wraps env in swob.Request object and passes it down. - - :param env: WSGI environment dictionary - :param start_response: WSGI callable - """ - try: - req = Request(env) - if self.auth_prefix: - req.path_info_pop() - req.bytes_transferred = '-' - req.client_disconnect = False - if 'x-storage-token' in req.headers and \ - 'x-auth-token' not in req.headers: - req.headers['x-auth-token'] = req.headers['x-storage-token'] - if 'eventlet.posthooks' in env: - env['eventlet.posthooks'].append( - (self.posthooklogger, (req,), {})) - return self.handle_request(req)(env, start_response) - else: - # Lack of posthook support means that we have to log on the - # start of the response, rather than after all the data has - # been sent. This prevents logging client disconnects - # differently than full transmissions. - response = self.handle_request(req)(env, start_response) - self.posthooklogger(env, req) - return response - except (Exception, TimeoutError): - print("EXCEPTION IN handle: %s: %s" % (format_exc(), env)) - start_response('500 Server Error', - [('Content-Type', 'text/plain')]) - return ['Internal server error.\n'] - - def handle_request(self, req): - """Entry point for auth requests (ones that match the self.auth_prefix). - Should return a WSGI-style callable (such as swob.Response). - - :param req: swob.Request object - """ - req.start_time = time() - handler = None - try: - version, account, user, _junk = split_path(req.path_info, - minsegs=0, maxsegs=4, rest_with_last=True) - except ValueError: - return HTTPNotFound(request=req) - if version in ('v1', 'v1.0', 'auth'): - if req.method == 'GET': - handler = self.handle_get_token - elif version == 'v2': - if not self.super_admin_key: - return HTTPNotFound(request=req) - req.path_info_pop() - if req.method == 'GET': - if not account and not user: - handler = self.handle_get_reseller - elif account: - if not user: - handler = self.handle_get_account - elif account == '.token': - req.path_info_pop() - handler = self.handle_validate_token - else: - handler = self.handle_get_user - elif req.method == 'PUT': - if not user: - handler = self.handle_put_account - else: - handler = self.handle_put_user - elif req.method == 'DELETE': - if not user: - handler = self.handle_delete_account - else: - handler = self.handle_delete_user - elif req.method == 'POST': - if account == '.prep': - handler = self.handle_prep - elif user == '.services': - handler = self.handle_set_services - else: - handler = self.handle_webadmin - if not handler: - req.response = HTTPBadRequest(request=req) - else: - req.response = handler(req) - return req.response - - def handle_webadmin(self, req): - if req.method not in ('GET', 'HEAD'): - return HTTPMethodNotAllowed(request=req) - subpath = req.path[len(self.auth_prefix):] or 'index.html' - path = quote('/v1/%s/.webadmin/%s' % (self.auth_account, subpath)) - req.response = self.make_pre_authed_request( - req.environ, req.method, path).get_response(self.app) - return req.response - - def handle_prep(self, req): - """Handles the POST v2/.prep call for preparing the backing store Swift - cluster for use with the auth subsystem. Can only be called by - .super_admin. - - :param req: The swob.Request to process. - :returns: swob.Response, 204 on success - """ - if not self.is_super_admin(req): - return self.denied_response(req) - path = quote('/v1/%s' % self.auth_account) - resp = self.make_pre_authed_request( - req.environ, 'PUT', path).get_response(self.app) - if resp.status_int // 100 != 2: - raise Exception('Could not create the main auth account: %s %s' % - (path, resp.status)) - path = quote('/v1/%s/.account_id' % self.auth_account) - resp = self.make_pre_authed_request( - req.environ, 'PUT', path).get_response(self.app) - if resp.status_int // 100 != 2: - raise Exception('Could not create container: %s %s' % - (path, resp.status)) - for container in xrange(16): - path = quote('/v1/%s/.token_%x' % (self.auth_account, container)) - resp = self.make_pre_authed_request( - req.environ, 'PUT', path).get_response(self.app) - if resp.status_int // 100 != 2: - raise Exception('Could not create container: %s %s' % - (path, resp.status)) - return HTTPNoContent(request=req) - - def handle_get_reseller(self, req): - """Handles the GET v2 call for getting general reseller information - (currently just a list of accounts). Can only be called by a - .reseller_admin. - - On success, a JSON dictionary will be returned with a single `accounts` - key whose value is list of dicts. Each dict represents an account and - currently only contains the single key `name`. For example:: - - {"accounts": [{"name": "reseller"}, {"name": "test"}, - {"name": "test2"}]} - - :param req: The swob.Request to process. - :returns: swob.Response, 2xx on success with a JSON dictionary as - explained above. - """ - if not self.is_reseller_admin(req): - return self.denied_response(req) - listing = [] - marker = '' - while True: - path = '/v1/%s?format=json&marker=%s' % (quote(self.auth_account), - quote(marker)) - resp = self.make_pre_authed_request( - req.environ, 'GET', path).get_response(self.app) - if resp.status_int // 100 != 2: - raise Exception('Could not list main auth account: %s %s' % - (path, resp.status)) - sublisting = json.loads(resp.body) - if not sublisting: - break - for container in sublisting: - if container['name'][0] != '.': - listing.append({'name': container['name']}) - marker = sublisting[-1]['name'].encode('utf-8') - return Response(body=json.dumps({'accounts': listing}), - content_type=CONTENT_TYPE_JSON) - - def handle_get_account(self, req): - """Handles the GET v2/ call for getting account information. - Can only be called by an account .admin. - - 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 as - described in the :func:`handle_get_token` call. The `users` value is a - list of dicts, each dict representing a user and currently only - containing the single key `name`. For example:: - - {"account_id": "AUTH_018c3946-23f8-4efb-a8fb-b67aae8e4162", - "services": {"storage": {"default": "local", - "local": "http://127.0.0.1:8080/v1/AUTH_018c3946"}}, - "users": [{"name": "tester"}, {"name": "tester3"}]} - - :param req: The swob.Request to process. - :returns: swob.Response, 2xx on success with a JSON dictionary as - explained above. - """ - account = req.path_info_pop() - if req.path_info or not account or account[0] == '.': - return HTTPBadRequest(request=req) - if not self.is_account_admin(req, account): - return self.denied_response(req) - path = quote('/v1/%s/%s/.services' % (self.auth_account, account)) - resp = self.make_pre_authed_request( - req.environ, 'GET', path).get_response(self.app) - if resp.status_int == 404: - return HTTPNotFound(request=req) - if resp.status_int // 100 != 2: - raise Exception('Could not obtain the .services object: %s %s' % - (path, resp.status)) - services = json.loads(resp.body) - listing = [] - marker = '' - while True: - path = '/v1/%s?format=json&marker=%s' % (quote('%s/%s' % - (self.auth_account, account)), quote(marker)) - resp = self.make_pre_authed_request( - req.environ, 'GET', path).get_response(self.app) - if resp.status_int == 404: - return HTTPNotFound(request=req) - if resp.status_int // 100 != 2: - raise Exception('Could not list in main auth account: %s %s' % - (path, resp.status)) - account_id = resp.headers['X-Container-Meta-Account-Id'] - sublisting = json.loads(resp.body) - if not sublisting: - break - for obj in sublisting: - if obj['name'][0] != '.': - listing.append({'name': obj['name']}) - marker = sublisting[-1]['name'].encode('utf-8') - return Response(content_type=CONTENT_TYPE_JSON, - body=json.dumps({'account_id': account_id, - 'services': services, - 'users': listing})) - - def handle_set_services(self, req): - """Handles the POST v2//.services call for setting services - information. Can only be called by a reseller .admin. - - In the :func:`handle_get_account` (GET v2/) call, a section of - the returned JSON dict is `services`. This section looks something like - this:: - - "services": {"storage": {"default": "local", - "local": "http://127.0.0.1:8080/v1/AUTH_018c3946"}} - - Making use of this section is described in :func:`handle_get_token`. - - This function allows setting values within this section for the - , allowing the addition of new service end points or updating - existing ones. - - 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. - - :param req: The swob.Request to process. - :returns: swob.Response, 2xx on success with the udpated services JSON - dict as described above - """ - if not self.is_reseller_admin(req): - return self.denied_response(req) - account = req.path_info_pop() - if req.path_info != '/.services' or not account or account[0] == '.': - return HTTPBadRequest(request=req) - try: - new_services = json.loads(req.body) - except ValueError as err: - return HTTPBadRequest(body=str(err)) - # Get the current services information - path = quote('/v1/%s/%s/.services' % (self.auth_account, account)) - resp = self.make_pre_authed_request( - req.environ, 'GET', path).get_response(self.app) - if resp.status_int == 404: - return HTTPNotFound(request=req) - if resp.status_int // 100 != 2: - raise Exception('Could not obtain services info: %s %s' % - (path, resp.status)) - services = json.loads(resp.body) - for new_service, value in new_services.iteritems(): - if new_service in services: - services[new_service].update(value) - else: - services[new_service] = value - # Save the new services information - services = json.dumps(services) - resp = self.make_pre_authed_request( - req.environ, 'PUT', path, services).get_response(self.app) - if resp.status_int // 100 != 2: - raise Exception('Could not save .services object: %s %s' % - (path, resp.status)) - return Response(request=req, body=services, - content_type=CONTENT_TYPE_JSON) - - def handle_put_account(self, req): - """Handles the PUT v2/ call for adding an account to the auth - system. Can only be called by a .reseller_admin. - - 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 - UUID4 part. - - :param req: The swob.Request to process. - :returns: swob.Response, 2xx on success. - """ - if not self.is_reseller_admin(req): - return self.denied_response(req) - account = req.path_info_pop() - if req.path_info or not account or account[0] == '.': - return HTTPBadRequest(request=req) - - account_suffix = req.headers.get('x-account-suffix') - if not account_suffix: - account_suffix = str(uuid4()) - # Create the new account in the Swift cluster - path = quote('%s/%s%s' % (self.dsc_parsed2.path, - self.reseller_prefix, account_suffix)) - try: - conn = self.get_conn() - conn.request('PUT', path, - headers={'X-Auth-Token': self.get_itoken(req.environ), - 'Content-Length': '0'}) - resp = conn.getresponse() - resp.read() - if resp.status // 100 != 2: - raise Exception('Could not create account on the Swift ' - 'cluster: %s %s %s' % (path, resp.status, resp.reason)) - except (Exception, TimeoutError): - self.logger.error(_('ERROR: Exception while trying to communicate ' - 'with %(scheme)s://%(host)s:%(port)s/%(path)s'), - {'scheme': self.dsc_parsed2.scheme, - 'host': self.dsc_parsed2.hostname, - 'port': self.dsc_parsed2.port, 'path': path}) - raise - # Ensure the container in the main auth account exists (this - # container represents the new account) - path = quote('/v1/%s/%s' % (self.auth_account, account)) - resp = self.make_pre_authed_request( - req.environ, 'HEAD', path).get_response(self.app) - if resp.status_int == 404: - resp = self.make_pre_authed_request( - req.environ, 'PUT', path).get_response(self.app) - if resp.status_int // 100 != 2: - raise Exception('Could not create account within main auth ' - 'account: %s %s' % (path, resp.status)) - elif resp.status_int // 100 == 2: - if 'x-container-meta-account-id' in resp.headers: - # Account was already created - return HTTPAccepted(request=req) - else: - raise Exception('Could not verify account within main auth ' - 'account: %s %s' % (path, resp.status)) - # Record the mapping from account id back to account name - path = quote('/v1/%s/.account_id/%s%s' % - (self.auth_account, self.reseller_prefix, account_suffix)) - resp = self.make_pre_authed_request( - req.environ, 'PUT', path, account).get_response(self.app) - if resp.status_int // 100 != 2: - raise Exception('Could not create account id mapping: %s %s' % - (path, resp.status)) - # Record the cluster url(s) for the account - path = quote('/v1/%s/%s/.services' % (self.auth_account, account)) - services = {'storage': {}} - services['storage'][self.dsc_name] = '%s/%s%s' % (self.dsc_url, - self.reseller_prefix, account_suffix) - services['storage']['default'] = self.dsc_name - resp = self.make_pre_authed_request( - req.environ, 'PUT', path, - json.dumps(services)).get_response(self.app) - if resp.status_int // 100 != 2: - raise Exception('Could not create .services object: %s %s' % - (path, resp.status)) - # Record the mapping from account name to the account id - path = quote('/v1/%s/%s' % (self.auth_account, account)) - resp = self.make_pre_authed_request( - req.environ, 'POST', path, - headers={'X-Container-Meta-Account-Id': '%s%s' % ( - self.reseller_prefix, account_suffix)}).get_response(self.app) - if resp.status_int // 100 != 2: - raise Exception('Could not record the account id on the account: ' - '%s %s' % (path, resp.status)) - return HTTPCreated(request=req) - - def handle_delete_account(self, req): - """Handles the DELETE v2/ call for removing an account from the - auth system. Can only be called by a .reseller_admin. - - :param req: The swob.Request to process. - :returns: swob.Response, 2xx on success. - """ - if not self.is_reseller_admin(req): - return self.denied_response(req) - account = req.path_info_pop() - if req.path_info or not account or account[0] == '.': - return HTTPBadRequest(request=req) - # Make sure the account has no users and get the account_id - marker = '' - while True: - path = '/v1/%s?format=json&marker=%s' % (quote('%s/%s' % - (self.auth_account, account)), quote(marker)) - resp = self.make_pre_authed_request( - req.environ, 'GET', path).get_response(self.app) - if resp.status_int == 404: - return HTTPNotFound(request=req) - if resp.status_int // 100 != 2: - raise Exception('Could not list in main auth account: %s %s' % - (path, resp.status)) - account_id = resp.headers['x-container-meta-account-id'] - sublisting = json.loads(resp.body) - if not sublisting: - break - for obj in sublisting: - if obj['name'][0] != '.': - return HTTPConflict(request=req) - marker = sublisting[-1]['name'].encode('utf-8') - # Obtain the listing of services the account is on. - path = quote('/v1/%s/%s/.services' % (self.auth_account, account)) - resp = self.make_pre_authed_request( - req.environ, 'GET', path).get_response(self.app) - if resp.status_int // 100 != 2 and resp.status_int != 404: - raise Exception('Could not obtain .services object: %s %s' % - (path, resp.status)) - if resp.status_int // 100 == 2: - services = json.loads(resp.body) - # Delete the account on each cluster it is on. - deleted_any = False - for name, url in services['storage'].iteritems(): - if name != 'default': - parsed = urlparse(url) - conn = self.get_conn(parsed) - conn.request('DELETE', parsed.path, - headers={'X-Auth-Token': self.get_itoken(req.environ)}) - resp = conn.getresponse() - resp.read() - if resp.status == 409: - if deleted_any: - raise Exception('Managed to delete one or more ' - 'service end points, but failed with: ' - '%s %s %s' % (url, resp.status, resp.reason)) - else: - return HTTPConflict(request=req) - if resp.status // 100 != 2 and resp.status != 404: - raise Exception('Could not delete account on the ' - 'Swift cluster: %s %s %s' % - (url, resp.status, resp.reason)) - deleted_any = True - # Delete the .services object itself. - path = quote('/v1/%s/%s/.services' % - (self.auth_account, account)) - resp = self.make_pre_authed_request( - req.environ, 'DELETE', path).get_response(self.app) - if resp.status_int // 100 != 2 and resp.status_int != 404: - raise Exception('Could not delete .services object: %s %s' % - (path, resp.status)) - # Delete the account id mapping for the account. - path = quote('/v1/%s/.account_id/%s' % - (self.auth_account, account_id)) - resp = self.make_pre_authed_request( - req.environ, 'DELETE', path).get_response(self.app) - if resp.status_int // 100 != 2 and resp.status_int != 404: - raise Exception('Could not delete account id mapping: %s %s' % - (path, resp.status)) - # Delete the account marker itself. - path = quote('/v1/%s/%s' % (self.auth_account, account)) - resp = self.make_pre_authed_request( - req.environ, 'DELETE', path).get_response(self.app) - if resp.status_int // 100 != 2 and resp.status_int != 404: - raise Exception('Could not delete account marked: %s %s' % - (path, resp.status)) - return HTTPNoContent(request=req) - - def handle_get_user(self, req): - """Handles the GET v2// call for getting user information. - Can only be called by an account .admin. - - On success, a JSON dict will be returned as described:: - - {"groups": [ # List of groups the user is a member of - {"name": ":"}, - # The first group is a unique user identifier - {"name": ""}, - # The second group is the auth account name - {"name": ""} - # There may be additional groups, .admin being a special - # group indicating an account admin and .reseller_admin - # indicating a reseller admin. - ], - "auth": "plaintext:" - # The auth-type and key for the user; currently only plaintext is - # implemented. - } - - For example:: - - {"groups": [{"name": "test:tester"}, {"name": "test"}, - {"name": ".admin"}], - "auth": "plaintext:testing"} - - If the in the request is the special user `.groups`, the JSON - dict will contain a single key of `groups` whose value is a list of - dicts representing the active groups within the account. Each dict - currently has the single key `name`. For example:: - - {"groups": [{"name": ".admin"}, {"name": "test"}, - {"name": "test:tester"}, {"name": "test:tester3"}]} - - :param req: The swob.Request to process. - :returns: swob.Response, 2xx on success with a JSON dictionary as - explained above. - """ - account = req.path_info_pop() - user = req.path_info_pop() - if req.path_info or not account or account[0] == '.' or not user or \ - (user[0] == '.' and user != '.groups'): - return HTTPBadRequest(request=req) - if not self.is_account_admin(req, account): - return self.denied_response(req) - - # get information for each user for the specified - # account and create a list of all groups that the users - # are part of - if user == '.groups': - # TODO(gholt): This could be very slow for accounts with a really - # large number of users. Speed could be improved by concurrently - # requesting user group information. Then again, I don't *know* - # it's slow for `normal` use cases, so testing should be done. - groups = set() - marker = '' - while True: - path = '/v1/%s?format=json&marker=%s' % (quote('%s/%s' % - (self.auth_account, account)), quote(marker)) - resp = self.make_pre_authed_request( - req.environ, 'GET', path).get_response(self.app) - if resp.status_int == 404: - return HTTPNotFound(request=req) - if resp.status_int // 100 != 2: - raise Exception('Could not list in main auth account: ' - '%s %s' % (path, resp.status)) - sublisting = json.loads(resp.body) - if not sublisting: - break - for obj in sublisting: - if obj['name'][0] != '.': - - # get list of groups for each user - user_json = self.get_user_detail(req, account, - obj['name']) - if user_json is None: - raise Exception('Could not retrieve user object: ' - '%s:%s %s' % (account, user, 404)) - groups.update( - g['name'] for g in json.loads(user_json)['groups']) - marker = sublisting[-1]['name'].encode('utf-8') - body = json.dumps( - {'groups': [{'name': g} for g in sorted(groups)]}) - else: - # get information for specific user, - # if user doesn't exist, return HTTPNotFound - body = self.get_user_detail(req, account, user) - if body is None: - return HTTPNotFound(request=req) - - display_groups = [g['name'] for g in json.loads(body)['groups']] - if ('.admin' in display_groups and - not self.is_reseller_admin(req)) or \ - ('.reseller_admin' in display_groups and - not self.is_super_admin(req)): - return self.denied_response(req) - return Response(body=body, content_type=CONTENT_TYPE_JSON) - - def handle_put_user(self, req): - """Handles the PUT v2// call for adding a user to an - account. - - X-Auth-User-Key represents the user's key (url encoded), - - OR - - X-Auth-User-Key-Hash represents the user's hashed key (url encoded), - X-Auth-User-Admin may be set to `true` to create an account .admin, and - X-Auth-User-Reseller-Admin may be set to `true` to create a - .reseller_admin. - - Creating users - ************** - Can only be called by an account .admin unless the user is to be a - .reseller_admin, in which case the request must be by .super_admin. - - Changing password/key - ********************* - 1) reseller_admin key can be changed by super_admin and by himself. - 2) admin key can be changed by any admin in same account, - reseller_admin, super_admin and himself. - 3) Regular user key can be changed by any admin in his account, - reseller_admin, super_admin and himself. - - :param req: The swob.Request to process. - :returns: swob.Response, 2xx on success. - """ - # Validate path info - account = req.path_info_pop() - user = req.path_info_pop() - key = unquote(req.headers.get('x-auth-user-key', '')) - key_hash = unquote(req.headers.get('x-auth-user-key-hash', '')) - admin = req.headers.get('x-auth-user-admin') == 'true' - reseller_admin = \ - req.headers.get('x-auth-user-reseller-admin') == 'true' - if reseller_admin: - admin = True - if req.path_info or not account or account[0] == '.' or not user or \ - user[0] == '.' or (not key and not key_hash): - return HTTPBadRequest(request=req) - if key_hash: - try: - swauth.authtypes.validate_creds(key_hash) - except ValueError: - return HTTPBadRequest(request=req) - - user_arg = account + ':' + user - if reseller_admin: - if not self.is_super_admin(req) and\ - not self.is_user_changing_own_key(req, user_arg): - return self.denied_response(req) - elif not self.is_account_admin(req, account) and\ - not self.is_user_changing_own_key(req, user_arg): - return self.denied_response(req) - - path = quote('/v1/%s/%s' % (self.auth_account, account)) - resp = self.make_pre_authed_request( - req.environ, 'HEAD', path).get_response(self.app) - if resp.status_int // 100 != 2: - raise Exception('Could not retrieve account id value: %s %s' % - (path, resp.status)) - headers = {'X-Object-Meta-Account-Id': - resp.headers['x-container-meta-account-id']} - # Create the object in the main auth account (this object represents - # the user) - path = quote('/v1/%s/%s/%s' % (self.auth_account, account, user)) - groups = ['%s:%s' % (account, user), account] - if admin: - groups.append('.admin') - if reseller_admin: - groups.append('.reseller_admin') - auth_value = key_hash or self.auth_encoder().encode(key) - resp = self.make_pre_authed_request( - req.environ, 'PUT', path, - json.dumps({'auth': auth_value, - 'groups': [{'name': g} for g in groups]}), - headers=headers).get_response(self.app) - if resp.status_int == 404: - return HTTPNotFound(request=req) - if resp.status_int // 100 != 2: - raise Exception('Could not create user object: %s %s' % - (path, resp.status)) - return HTTPCreated(request=req) - - def handle_delete_user(self, req): - """Handles the DELETE v2// call for deleting a user from an - account. - - Can only be called by an account .admin. - - :param req: The swob.Request to process. - :returns: swob.Response, 2xx on success. - """ - # Validate path info - account = req.path_info_pop() - user = req.path_info_pop() - if req.path_info or not account or account[0] == '.' or not user or \ - user[0] == '.': - return HTTPBadRequest(request=req) - - # if user to be deleted is reseller_admin, then requesting - # user must be the super_admin - is_reseller_admin = self.is_user_reseller_admin(req, account, user) - if not is_reseller_admin and not req.credentials_valid: - # if user to be deleted can't be found, return 404 - return HTTPNotFound(request=req) - elif is_reseller_admin and not self.is_super_admin(req): - return HTTPForbidden(request=req) - - if not self.is_account_admin(req, account): - return self.denied_response(req) - - # Delete the user's existing token, if any. - path = quote('/v1/%s/%s/%s' % (self.auth_account, account, user)) - resp = self.make_pre_authed_request( - req.environ, 'HEAD', path).get_response(self.app) - if resp.status_int == 404: - return HTTPNotFound(request=req) - elif resp.status_int // 100 != 2: - raise Exception('Could not obtain user details: %s %s' % - (path, resp.status)) - candidate_token = resp.headers.get('x-object-meta-auth-token') - if candidate_token: - object_name = self._get_concealed_token(candidate_token) - path = quote('/v1/%s/.token_%s/%s' % - (self.auth_account, object_name[-1], object_name)) - resp = self.make_pre_authed_request( - req.environ, 'DELETE', path).get_response(self.app) - if resp.status_int // 100 != 2 and resp.status_int != 404: - raise Exception('Could not delete possibly existing token: ' - '%s %s' % (path, resp.status)) - # Delete the user entry itself. - path = quote('/v1/%s/%s/%s' % (self.auth_account, account, user)) - resp = self.make_pre_authed_request( - req.environ, 'DELETE', path).get_response(self.app) - if resp.status_int // 100 != 2 and resp.status_int != 404: - raise Exception('Could not delete the user object: %s %s' % - (path, resp.status)) - return HTTPNoContent(request=req) - - def is_user_reseller_admin(self, req, account, user): - """Returns True if the user is a .reseller_admin. - - :param account: account user is part of - :param user: the user - :returns: True if user .reseller_admin, False - if user is not a reseller_admin and None if the user - doesn't exist. - """ - req.credentials_valid = True - user_json = self.get_user_detail(req, account, user) - if user_json is None: - req.credentials_valid = False - return False - - user_detail = json.loads(user_json) - - return '.reseller_admin' in (g['name'] for g in user_detail['groups']) - - def handle_get_token(self, req): - """Handles the various `request for token and service end point(s)` calls. - There are various formats to support the various auth servers in the - past. Examples:: - - GET /v1//auth - X-Auth-User: : or X-Storage-User: - X-Auth-Key: or X-Storage-Pass: - GET /auth - X-Auth-User: : or X-Storage-User: : - X-Auth-Key: or X-Storage-Pass: - GET /v1.0 - X-Auth-User: : or X-Storage-User: : - X-Auth-Key: or X-Storage-Pass: - - Values should be url encoded, "act%3Ausr" instead of "act:usr" for - example; however, for backwards compatibility the colon may be included - unencoded. - - On successful authentication, the response will have X-Auth-Token and - X-Storage-Token set to the token to use with Swift and X-Storage-URL - set to the URL to the default Swift cluster to use. - - The response body will be set to the account's services JSON object as - described here:: - - {"storage": { # Represents the Swift storage service end points - "default": "cluster1", # Indicates which cluster is the default - "cluster1": "", - # A Swift cluster that can be used with this account, - # "cluster1" is the name of the cluster which is usually a - # location indicator (like "dfw" for a datacenter region). - "cluster2": "" - # Another Swift cluster that can be used with this account, - # there will always be at least one Swift cluster to use or - # this whole "storage" dict won't be included at all. - }, - "servers": { # Represents the Nova server service end points - # Expected to be similar to the "storage" dict, but not - # implemented yet. - }, - # Possibly other service dicts, not implemented yet. - } - - One can also include an "X-Auth-New-Token: true" header to - force issuing a new token and revoking any old token, even if - it hasn't expired yet. - - :param req: The swob.Request to process. - :returns: swob.Response, 2xx on success with data set as explained - above. - """ - # Validate the request info - try: - pathsegs = split_path(req.path_info, minsegs=1, maxsegs=3, - rest_with_last=True) - except ValueError: - return HTTPNotFound(request=req) - if pathsegs[0] == 'v1' and pathsegs[2] == 'auth': - account = pathsegs[1] - user = req.headers.get('x-storage-user') - if not user: - user = unquote(req.headers.get('x-auth-user', '')) - if not user or ':' not in user: - return HTTPUnauthorized(request=req) - account2, user = user.split(':', 1) - if account != account2: - return HTTPUnauthorized(request=req) - key = req.headers.get('x-storage-pass') - if not key: - key = unquote(req.headers.get('x-auth-key', '')) - elif pathsegs[0] in ('auth', 'v1.0'): - user = unquote(req.headers.get('x-auth-user', '')) - if not user: - user = req.headers.get('x-storage-user') - if not user or ':' not in user: - return HTTPUnauthorized(request=req) - account, user = user.split(':', 1) - key = unquote(req.headers.get('x-auth-key', '')) - if not key: - key = req.headers.get('x-storage-pass') - else: - return HTTPBadRequest(request=req) - if not all((account, user, key)): - return HTTPUnauthorized(request=req) - if user == '.super_admin' and self.super_admin_key and \ - key == self.super_admin_key: - token = self.get_itoken(req.environ) - url = '%s/%s.auth' % (self.dsc_url, self.reseller_prefix) - return Response( - request=req, - content_type=CONTENT_TYPE_JSON, - body=json.dumps({'storage': {'default': 'local', - 'local': url}}), - headers={'x-auth-token': token, - 'x-storage-token': token, - 'x-storage-url': url}) - - # Authenticate user - path = quote('/v1/%s/%s/%s' % (self.auth_account, account, user)) - resp = self.make_pre_authed_request( - req.environ, 'GET', path).get_response(self.app) - if resp.status_int == 404: - return HTTPUnauthorized(request=req) - if resp.status_int // 100 != 2: - raise Exception('Could not obtain user details: %s %s' % - (path, resp.status)) - user_detail = json.loads(resp.body) - if not self.credentials_match(user_detail, key): - return HTTPUnauthorized(request=req) - # See if a token already exists and hasn't expired - token = None - expires = None - candidate_token = resp.headers.get('x-object-meta-auth-token') - if candidate_token: - object_name = self._get_concealed_token(candidate_token) - path = quote('/v1/%s/.token_%s/%s' % - (self.auth_account, object_name[-1], object_name)) - delete_token = False - try: - if req.headers.get('x-auth-new-token', 'false').lower() in \ - TRUE_VALUES: - delete_token = True - else: - resp = self.make_pre_authed_request( - req.environ, 'GET', path).get_response(self.app) - if resp.status_int // 100 == 2: - token_detail = json.loads(resp.body) - if token_detail['expires'] > time(): - token = candidate_token - expires = token_detail['expires'] - else: - delete_token = True - elif resp.status_int != 404: - raise Exception( - 'Could not detect whether a token already exists: ' - '%s %s' % (path, resp.status)) - finally: - if delete_token: - self.make_pre_authed_request( - req.environ, 'DELETE', path).get_response(self.app) - memcache_client = cache_from_env(req.environ) - if memcache_client: - memcache_key = '%s/auth/%s' % (self.reseller_prefix, - candidate_token) - memcache_client.delete(memcache_key) - # Create a new token if one didn't exist - if not token: - # Retrieve account id, we'll save this in the token - path = quote('/v1/%s/%s' % (self.auth_account, account)) - resp = self.make_pre_authed_request( - req.environ, 'HEAD', path).get_response(self.app) - if resp.status_int // 100 != 2: - raise Exception('Could not retrieve account id value: ' - '%s %s' % (path, resp.status)) - account_id = \ - resp.headers['x-container-meta-account-id'] - # Generate new token - token = '%stk%s' % (self.reseller_prefix, uuid4().hex) - # Save token info - object_name = self._get_concealed_token(token) - path = quote('/v1/%s/.token_%s/%s' % - (self.auth_account, object_name[-1], object_name)) - try: - token_life = min( - int(req.headers.get('x-auth-token-lifetime', - self.token_life)), - self.max_token_life) - except ValueError: - token_life = self.token_life - expires = int(time() + token_life) - resp = self.make_pre_authed_request( - req.environ, 'PUT', path, - json.dumps({'account': account, 'user': user, - 'account_id': account_id, - 'groups': user_detail['groups'], - 'expires': expires})).get_response(self.app) - if resp.status_int // 100 != 2: - raise Exception('Could not create new token: %s %s' % - (path, resp.status)) - # Record the token with the user info for future use. - path = quote('/v1/%s/%s/%s' % (self.auth_account, account, user)) - resp = self.make_pre_authed_request( - req.environ, 'POST', path, - headers={'X-Object-Meta-Auth-Token': token} - ).get_response(self.app) - if resp.status_int // 100 != 2: - raise Exception('Could not save new token: %s %s' % - (path, resp.status)) - # Get the services information - path = quote('/v1/%s/%s/.services' % (self.auth_account, account)) - resp = self.make_pre_authed_request( - req.environ, 'GET', path).get_response(self.app) - if resp.status_int // 100 != 2: - raise Exception('Could not obtain services info: %s %s' % - (path, resp.status)) - detail = json.loads(resp.body) - url = detail['storage'][detail['storage']['default']] - return Response( - request=req, - body=resp.body, - content_type=CONTENT_TYPE_JSON, - headers={'x-auth-token': token, - 'x-storage-token': token, - 'x-auth-token-expires': str(int(expires - time())), - 'x-storage-url': url}) - - def handle_validate_token(self, req): - """Handles the GET v2/.token/ call for validating a token, usually - called by a service like Swift. - - On a successful validation, X-Auth-TTL will be set for how much longer - this token is valid and X-Auth-Groups will contain a comma separated - list of groups the user belongs to. - - The first group listed will be a unique identifier for the user the - token represents. - - .reseller_admin is a special group that indicates the user should be - allowed to do anything on any account. - - :param req: The swob.Request to process. - :returns: swob.Response, 2xx on success with data set as explained - above. - """ - token = req.path_info_pop() - if req.path_info or not token.startswith(self.reseller_prefix): - return HTTPBadRequest(request=req) - expires = groups = None - memcache_client = cache_from_env(req.environ) - if memcache_client: - memcache_key = '%s/auth/%s' % (self.reseller_prefix, token) - cached_auth_data = memcache_client.get(memcache_key) - if cached_auth_data: - expires, groups = cached_auth_data - if expires < time(): - groups = None - if not groups: - object_name = self._get_concealed_token(token) - path = quote('/v1/%s/.token_%s/%s' % - (self.auth_account, object_name[-1], object_name)) - resp = self.make_pre_authed_request( - req.environ, 'GET', path).get_response(self.app) - if resp.status_int // 100 != 2: - return HTTPNotFound(request=req) - detail = json.loads(resp.body) - expires = detail['expires'] - if expires < time(): - self.make_pre_authed_request( - req.environ, 'DELETE', path).get_response(self.app) - return HTTPNotFound(request=req) - groups = [g['name'] for g in detail['groups']] - if '.admin' in groups: - groups.remove('.admin') - groups.append(detail['account_id']) - groups = ','.join(groups) - return HTTPNoContent(headers={'X-Auth-TTL': expires - time(), - 'X-Auth-Groups': groups}) - - def get_conn(self, urlparsed=None): - """Returns an HTTPConnection based on the urlparse result given or the - default Swift cluster (internal url) urlparse result. - - :param urlparsed: The result from urlparse.urlparse or None to use the - default Swift cluster's value - """ - if not urlparsed: - urlparsed = self.dsc_parsed2 - if urlparsed.scheme == 'http': - return HTTPConnection(urlparsed.netloc) - else: - return HTTPSConnection(urlparsed.netloc) - - def get_itoken(self, env): - """Returns the current internal token to use for the auth system's own - actions with other services. Each process will create its own - itoken and the token will be deleted and recreated based on the - token_life configuration value. The itoken information is stored in - memcache because the auth process that is asked by Swift to validate - the token may not be the same as the auth process that created the - token. - """ - if not self.itoken or self.itoken_expires < time() or \ - env.get('HTTP_X_AUTH_NEW_TOKEN', 'false').lower() in \ - TRUE_VALUES: - self.itoken = '%sitk%s' % (self.reseller_prefix, uuid4().hex) - memcache_key = '%s/auth/%s' % (self.reseller_prefix, self.itoken) - self.itoken_expires = time() + self.token_life - memcache_client = cache_from_env(env) - if not memcache_client: - raise Exception( - 'No memcache set up; required for Swauth middleware') - memcache_client.set( - memcache_key, - (self.itoken_expires, - '.auth,.reseller_admin,%s.auth' % self.reseller_prefix), - time=self.token_life) - return self.itoken - - def get_admin_detail(self, req): - """Returns the dict for the user specified as the admin in the request - with the addition of an `account` key set to the admin user's account. - - :param req: The swob request to retrieve X-Auth-Admin-User and - X-Auth-Admin-Key from. - :returns: The dict for the admin user with the addition of the - `account` key. - """ - if ':' not in req.headers.get('x-auth-admin-user', ''): - return None - admin_account, admin_user = \ - req.headers.get('x-auth-admin-user').split(':', 1) - user_json = self.get_user_detail(req, admin_account, admin_user) - if user_json is None: - return None - admin_detail = json.loads(user_json) - admin_detail['account'] = admin_account - return admin_detail - - def get_user_detail(self, req, account, user): - """Returns the response body of a GET request for the specified user - The body is in JSON format and contains all user information. - - :param req: The swob request - :param account: the account the user is a member of - :param user: the user - - :returns: A JSON response with the user detail information, None - if the user doesn't exist - """ - path = quote('/v1/%s/%s/%s' % (self.auth_account, account, user)) - resp = self.make_pre_authed_request( - req.environ, 'GET', path).get_response(self.app) - if resp.status_int == 404: - return None - if resp.status_int // 100 != 2: - raise Exception('Could not get user object: %s %s' % - (path, resp.status)) - return resp.body - - def credentials_match(self, user_detail, key): - """Returns True if the key is valid for the user_detail. - It will use auth_encoder type the password was encoded with, - to check for a key match. - - :param user_detail: The dict for the user. - :param key: The key to validate for the user. - :returns: True if the key is valid for the user, False if not. - """ - if user_detail: - creds = user_detail.get('auth') - try: - auth_encoder, creds_dict = \ - swauth.authtypes.validate_creds(creds) - except ValueError as e: - self.logger.error('%s' % e.args[0]) - return False - return user_detail and auth_encoder.match(key, creds, **creds_dict) - - def is_user_changing_own_key(self, req, user): - """Check if the user is changing his own key. - - :param req: The swob.Request to check. This contains x-auth-admin-user - and x-auth-admin-key headers which are credentials of the - user sending the request. - :param user: User whose password is to be changed. - :returns: True if user is changing his own key, False if not. - """ - admin_detail = self.get_admin_detail(req) - if not admin_detail: - # The user does not exist - return False - - # If user is not admin/reseller_admin and x-auth-user-admin or - # x-auth-user-reseller-admin headers are present in request, he may be - # attempting to escalate himself as admin/reseller_admin! - if '.admin' not in (g['name'] for g in admin_detail['groups']): - if req.headers.get('x-auth-user-admin') == 'true' or \ - req.headers.get('x-auth-user-reseller-admin') == 'true': - return False - if '.reseller_admin' not in \ - (g['name'] for g in admin_detail['groups']) and \ - req.headers.get('x-auth-user-reseller-admin') == 'true': - return False - - return req.headers.get('x-auth-admin-user') == user and \ - self.credentials_match(admin_detail, - req.headers.get('x-auth-admin-key')) - - def is_super_admin(self, req): - """Returns True if the admin specified in the request represents the - .super_admin. - - :param req: The swob.Request to check. - :param returns: True if .super_admin. - """ - return req.headers.get('x-auth-admin-user') == '.super_admin' and \ - self.super_admin_key and \ - req.headers.get('x-auth-admin-key') == self.super_admin_key - - def is_reseller_admin(self, req, admin_detail=None): - """Returns True if the admin specified in the request represents a - .reseller_admin. - - :param req: The swob.Request to check. - :param admin_detail: The previously retrieved dict from - :func:`get_admin_detail` or None for this function - to retrieve the admin_detail itself. - :param returns: True if .reseller_admin. - """ - req.credentials_valid = False - if self.is_super_admin(req): - return True - if not admin_detail: - admin_detail = self.get_admin_detail(req) - if not self.credentials_match(admin_detail, - req.headers.get('x-auth-admin-key')): - return False - req.credentials_valid = True - return '.reseller_admin' in (g['name'] for g in admin_detail['groups']) - - def is_account_admin(self, req, account): - """Returns True if the admin specified in the request represents a .admin - for the account specified. - - :param req: The swob.Request to check. - :param account: The account to check for .admin against. - :param returns: True if .admin. - """ - req.credentials_valid = False - if self.is_super_admin(req): - return True - admin_detail = self.get_admin_detail(req) - if admin_detail: - if self.is_reseller_admin(req, admin_detail=admin_detail): - return True - if not self.credentials_match(admin_detail, - req.headers.get('x-auth-admin-key')): - return False - req.credentials_valid = True - return admin_detail and admin_detail['account'] == account and \ - '.admin' in (g['name'] for g in admin_detail['groups']) - return False - - def posthooklogger(self, env, req): - if not req.path.startswith(self.auth_prefix): - return - response = getattr(req, 'response', None) - if not response: - return - trans_time = '%.4f' % (time() - req.start_time) - the_request = quote(unquote(req.path)) - if req.query_string: - the_request = the_request + '?' + req.query_string - # remote user for zeus - client = req.headers.get('x-cluster-client-ip') - if not client and 'x-forwarded-for' in req.headers: - # remote user for other lbs - client = req.headers['x-forwarded-for'].split(',')[0].strip() - logged_headers = None - if self.log_headers: - logged_headers = '\n'.join('%s: %s' % (k, v) - for k, v in req.headers.items()) - status_int = response.status_int - if getattr(req, 'client_disconnect', False) or \ - getattr(response, 'client_disconnect', False): - status_int = 499 - self.logger.info(' '.join(quote(str(x)) for x in (client or '-', - req.remote_addr or '-', strftime('%d/%b/%Y/%H/%M/%S', gmtime()), - req.method, the_request, req.environ['SERVER_PROTOCOL'], - status_int, req.referer or '-', req.user_agent or '-', - req.headers.get('x-auth-token', - req.headers.get('x-auth-admin-user', '-')), - getattr(req, 'bytes_transferred', 0) or '-', - getattr(response, 'bytes_transferred', 0) or '-', - req.headers.get('etag', '-'), - req.headers.get('x-trans-id', '-'), logged_headers or '-', - trans_time))) - - -def filter_factory(global_conf, **local_conf): - """Returns a WSGI filter app for use with paste.deploy.""" - conf = global_conf.copy() - conf.update(local_conf) - - def auth_filter(app): - return Swauth(app, conf) - return auth_filter diff --git a/swauth/swift_version.py b/swauth/swift_version.py deleted file mode 100644 index dd96f97..0000000 --- a/swauth/swift_version.py +++ /dev/null @@ -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 diff --git a/test-requirements.txt b/test-requirements.txt deleted file mode 100644 index b831318..0000000 --- a/test-requirements.txt +++ /dev/null @@ -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 diff --git a/test/__init__.py b/test/__init__.py deleted file mode 100644 index a940b8c..0000000 --- a/test/__init__.py +++ /dev/null @@ -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) diff --git a/test/unit/__init__.py b/test/unit/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/test/unit/test_authtypes.py b/test/unit/test_authtypes.py deleted file mode 100644 index 669af90..0000000 --- a/test/unit/test_authtypes.py +++ /dev/null @@ -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() diff --git a/test/unit/test_middleware.py b/test/unit/test_middleware.py deleted file mode 100644 index 1d1dd95..0000000 --- a/test/unit/test_middleware.py +++ /dev/null @@ -1,4197 +0,0 @@ -# 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. - -import base64 -from contextlib import contextmanager -import hashlib -import json -import mock -from time import time -import unittest -from urllib import quote - -from swift.common.swob import Request -from swift.common.swob import Response - -from swauth.authtypes import MAX_TOKEN_LENGTH -from swauth import middleware as auth - - -CONTENT_TYPE_JSON = 'application/json' -DEFAULT_TOKEN_LIFE = 86400 -MAX_TOKEN_LIFE = 100000 - - -class FakeMemcache(object): - - def __init__(self): - self.store = {} - - def get(self, key): - return self.store.get(key) - - def set(self, key, value, time=0): - self.store[key] = value - return True - - def incr(self, key, time=0): - self.store[key] = self.store.setdefault(key, 0) + 1 - return self.store[key] - - @contextmanager - def soft_lock(self, key, retries=5, time=0): - yield True - - def delete(self, key): - try: - del self.store[key] - except Exception: - pass - return True - - -class FakeApp(object): - - def __init__(self, status_headers_body_iter=None, acl=None, sync_key=None): - self.calls = 0 - self.status_headers_body_iter = status_headers_body_iter - if not self.status_headers_body_iter: - self.status_headers_body_iter = iter([('404 Not Found', {}, '')]) - self.acl = acl - self.sync_key = sync_key - - def __call__(self, env, start_response): - self.calls += 1 - self.request = Request.blank('', environ=env) - if self.acl: - self.request.acl = self.acl - if self.sync_key: - self.request.environ['swift_sync_key'] = self.sync_key - if 'swift.authorize' in env: - resp = env['swift.authorize'](self.request) - if resp: - return resp(env, start_response) - status, headers, body = self.status_headers_body_iter.next() - return Response(status=status, headers=headers, - body=body)(env, start_response) - - -class FakeConn(object): - - def __init__(self, status_headers_body_iter=None): - self.calls = 0 - self.status_headers_body_iter = status_headers_body_iter - if not self.status_headers_body_iter: - self.status_headers_body_iter = iter([('404 Not Found', {}, '')]) - - def request(self, method, path, headers): - self.calls += 1 - self.request_path = path - self.status, self.headers, self.body = \ - self.status_headers_body_iter.next() - self.status, self.reason = self.status.split(' ', 1) - self.status = int(self.status) - - def getresponse(self): - return self - - def read(self): - body = self.body - self.body = '' - return body - - -class TestAuth(unittest.TestCase): - - def setUp(self): - self.test_auth = \ - auth.filter_factory({ - 'super_admin_key': 'supertest', - 'token_life': str(DEFAULT_TOKEN_LIFE), - 'max_token_life': str(MAX_TOKEN_LIFE)})(FakeApp()) - - def test_salt(self): - for auth_type in ('sha1', 'sha512'): - # Salt not manually set - test_auth = \ - auth.filter_factory({ - 'super_admin_key': 'supertest', - 'token_life': str(DEFAULT_TOKEN_LIFE), - 'max_token_life': str(MAX_TOKEN_LIFE), - 'auth_type': auth_type})(FakeApp()) - self.assertEqual(test_auth.auth_encoder.salt, None) - mock_urandom = mock.Mock(return_value="abc") - with mock.patch("os.urandom", mock_urandom): - h_key = test_auth.auth_encoder().encode("key") - self.assertTrue(mock_urandom.called) - prefix = auth_type + ":" + "abc".encode('base64').rstrip() + '$' - self.assertTrue(h_key.startswith(prefix)) - - # Salt manually set - test_auth = \ - auth.filter_factory({ - 'super_admin_key': 'supertest', - 'token_life': str(DEFAULT_TOKEN_LIFE), - 'max_token_life': str(MAX_TOKEN_LIFE), - 'auth_type': auth_type, - 'auth_type_salt': "mysalt"})(FakeApp()) - self.assertEqual(test_auth.auth_encoder.salt, "mysalt") - mock_urandom = mock.Mock() - with mock.patch("os.urandom", mock_urandom): - h_key = test_auth.auth_encoder().encode("key") - self.assertFalse(mock_urandom.called) - prefix = auth_type + ":" + "mysalt" + '$' - self.assertTrue(h_key.startswith(prefix)) - - def test_swift_version(self): - app = FakeApp() - - with mock.patch('swauth.swift_version.at_least') as mock_at_least: - mock_at_least.return_value = False - self.assertRaises(ValueError, auth.filter_factory({}), app) - - def test_super_admin_key_not_required(self): - auth.filter_factory({})(FakeApp()) - - def test_reseller_prefix_init(self): - app = FakeApp() - ath = auth.filter_factory({'super_admin_key': 'supertest'})(app) - self.assertEqual(ath.reseller_prefix, 'AUTH_') - ath = auth.filter_factory({'super_admin_key': 'supertest', - 'reseller_prefix': 'TEST'})(app) - self.assertEqual(ath.reseller_prefix, 'TEST_') - ath = auth.filter_factory({'super_admin_key': 'supertest', - 'reseller_prefix': 'TEST_'})(app) - self.assertEqual(ath.reseller_prefix, 'TEST_') - - def test_auth_prefix_init(self): - app = FakeApp() - ath = auth.filter_factory({'super_admin_key': 'supertest'})(app) - self.assertEqual(ath.auth_prefix, '/auth/') - ath = auth.filter_factory({'super_admin_key': 'supertest', - 'auth_prefix': ''})(app) - self.assertEqual(ath.auth_prefix, '/auth/') - ath = auth.filter_factory({'super_admin_key': 'supertest', - 'auth_prefix': '/test/'})(app) - self.assertEqual(ath.auth_prefix, '/test/') - ath = auth.filter_factory({'super_admin_key': 'supertest', - 'auth_prefix': '/test'})(app) - self.assertEqual(ath.auth_prefix, '/test/') - ath = auth.filter_factory({'super_admin_key': 'supertest', - 'auth_prefix': 'test/'})(app) - self.assertEqual(ath.auth_prefix, '/test/') - ath = auth.filter_factory({'super_admin_key': 'supertest', - 'auth_prefix': 'test'})(app) - self.assertEqual(ath.auth_prefix, '/test/') - - def test_no_auth_type_init(self): - app = FakeApp() - ath = auth.filter_factory({})(app) - self.assertEqual(ath.auth_type, 'Plaintext') - - def test_valid_auth_type_init(self): - app = FakeApp() - ath = auth.filter_factory({'auth_type': 'sha1'})(app) - self.assertEqual(ath.auth_type, 'Sha1') - ath = auth.filter_factory({'auth_type': 'plaintext'})(app) - self.assertEqual(ath.auth_type, 'Plaintext') - - def test_invalid_auth_type_init(self): - app = FakeApp() - exc = None - try: - auth.filter_factory({'auth_type': 'NONEXISTANT'})(app) - except Exception as err: - exc = err - self.assertEqual(str(exc), - 'Invalid auth_type in config file: %s' % - 'Nonexistant') - - def test_default_swift_cluster_init(self): - app = FakeApp() - self.assertRaises(ValueError, auth.filter_factory({ - 'super_admin_key': 'supertest', - 'default_swift_cluster': 'local#badscheme://host/path'}), app) - ath = auth.filter_factory({'super_admin_key': 'supertest'})(app) - self.assertEqual(ath.default_swift_cluster, - 'local#http://127.0.0.1:8080/v1') - ath = auth.filter_factory({'super_admin_key': 'supertest', - 'default_swift_cluster': 'local#http://host/path'})(app) - self.assertEqual(ath.default_swift_cluster, - 'local#http://host/path') - ath = auth.filter_factory({'super_admin_key': 'supertest', - 'default_swift_cluster': 'local#https://host/path/'})(app) - self.assertEqual(ath.dsc_url, 'https://host/path') - self.assertEqual(ath.dsc_url2, 'https://host/path') - ath = auth.filter_factory({'super_admin_key': 'supertest', - 'default_swift_cluster': - 'local#https://host/path/#http://host2/path2/'})(app) - self.assertEqual(ath.dsc_url, 'https://host/path') - self.assertEqual(ath.dsc_url2, 'http://host2/path2') - - def test_credentials_match_auth_encoder_type(self): - plaintext_auth = {'auth': 'plaintext:key'} - sha1_key = ("sha1:T0YFdhqN4uDRWiYLxWa7H2T8AewG4fEYQyJFRLsgcfk=$46c58" - "07eb8a32e8f404fea9eaaeb60b7e1207ff1") - sha1_auth = {'auth': sha1_key} - sha512_key = ("sha512:aSm0jEeqIp46T5YLZy1r8+cXs/Xzs1S4VUwVauhBs44=$ef" - "7332ec1288bf69c75682eb8d459d5a84baa7e43f45949c242a9af9" - "7130ef16ac361fe1aa33a789e218122b83c54ef1923fc015080741" - "ca21f6187329f6cb7a") - sha512_auth = {'auth': sha512_key} - - # test all possible config settings work with all possible auth types - for auth_type in ('plaintext', 'sha1', 'sha512'): - test_auth = auth.filter_factory({'super_admin_key': 'superkey', - 'auth_type': auth_type})(FakeApp()) - for detail in (plaintext_auth, sha1_auth, sha512_auth): - self.assertTrue(test_auth.credentials_match(detail, 'key')) - # test invalid auth type stored - invalid_detail = {'auth': 'Junk:key'} - test_auth.logger = mock.Mock() - self.assertFalse(test_auth.credentials_match(invalid_detail, - 'key')) - # make sure error is logged - test_auth.logger.called_once_with('Invalid auth_type Junk') - - def test_top_level_denied(self): - resp = Request.blank('/').get_response(self.test_auth) - self.assertEqual(resp.status_int, 401) - - def test_anon(self): - resp = Request.blank('/v1/AUTH_account').get_response(self.test_auth) - self.assertEqual(resp.status_int, 401) - self.assertEqual(resp.environ['swift.authorize'], - self.test_auth.authorize) - - def test_auth_deny_non_reseller_prefix(self): - resp = Request.blank('/v1/BLAH_account', - headers={'X-Auth-Token': 'BLAH_t'}).get_response(self.test_auth) - self.assertEqual(resp.status_int, 401) - self.assertEqual(resp.environ['swift.authorize'], - self.test_auth.denied_response) - - def test_auth_deny_non_reseller_prefix_no_override(self): - fake_authorize = lambda x: Response(status='500 Fake') - resp = Request.blank('/v1/BLAH_account', - headers={'X-Auth-Token': 'BLAH_t'}, - environ={'swift.authorize': fake_authorize} - ).get_response(self.test_auth) - self.assertEqual(resp.status_int, 500) - self.assertEqual(resp.environ['swift.authorize'], fake_authorize) - - def test_auth_no_reseller_prefix_deny(self): - # Ensures that when we have no reseller prefix, we don't deny a request - # outright but set up a denial swift.authorize and pass the request on - # down the chain. - local_app = FakeApp() - local_auth = auth.filter_factory({'super_admin_key': 'supertest', - 'reseller_prefix': ''})(local_app) - resp = Request.blank('/v1/account', - headers={'X-Auth-Token': 't'}).get_response(local_auth) - self.assertEqual(resp.status_int, 401) - # one for checking auth, two for request passed along - self.assertEqual(local_app.calls, 2) - self.assertEqual(resp.environ['swift.authorize'], - local_auth.denied_response) - - def test_auth_no_reseller_prefix_allow(self): - # Ensures that when we have no reseller prefix, we can still allow - # access if our auth server accepts requests - local_app = FakeApp(iter([ - ('200 Ok', {}, - json.dumps({'account': 'act', 'user': 'act:usr', - 'account_id': 'AUTH_cfa', - 'groups': [{'name': 'act:usr'}, {'name': 'act'}, - {'name': '.admin'}], - 'expires': time() + 60})), - ('204 No Content', {}, '')])) - local_auth = auth.filter_factory({'super_admin_key': 'supertest', - 'reseller_prefix': ''})(local_app) - resp = Request.blank('/v1/act', - headers={'X-Auth-Token': 't'}).get_response(local_auth) - self.assertEqual(resp.status_int, 204) - self.assertEqual(local_app.calls, 2) - self.assertEqual(resp.environ['swift.authorize'], - local_auth.authorize) - - def test_auth_no_reseller_prefix_no_token(self): - # Check that normally we set up a call back to our authorize. - local_auth = \ - auth.filter_factory({'super_admin_key': 'supertest', - 'reseller_prefix': ''})(FakeApp(iter([]))) - resp = Request.blank('/v1/account').get_response(local_auth) - self.assertEqual(resp.status_int, 401) - self.assertEqual(resp.environ['swift.authorize'], - local_auth.authorize) - # Now make sure we don't override an existing swift.authorize when we - # have no reseller prefix. - local_auth = \ - auth.filter_factory({'super_admin_key': 'supertest', - 'reseller_prefix': ''})(FakeApp()) - local_authorize = lambda req: Response('test') - resp = Request.blank('/v1/account', environ={'swift.authorize': - local_authorize}).get_response(local_auth) - self.assertEqual(resp.status_int, 200) - self.assertEqual(resp.environ['swift.authorize'], local_authorize) - - def test_auth_fail(self): - resp = Request.blank('/v1/AUTH_cfa', - headers={'X-Auth-Token': 'AUTH_t'}).get_response(self.test_auth) - self.assertEqual(resp.status_int, 401) - - def test_auth_success(self): - self.test_auth.app = FakeApp(iter([ - ('200 Ok', {}, - json.dumps({'account': 'act', 'user': 'act:usr', - 'account_id': 'AUTH_cfa', - 'groups': [{'name': 'act:usr'}, {'name': 'act'}, - {'name': '.admin'}], - 'expires': time() + 60})), - ('204 No Content', {}, '')])) - resp = Request.blank('/v1/AUTH_cfa', - headers={'X-Auth-Token': 'AUTH_t'}).get_response(self.test_auth) - self.assertEqual(resp.status_int, 204) - self.assertEqual(self.test_auth.app.calls, 2) - - def test_auth_memcache(self): - # First run our test without memcache, showing we need to return the - # token contents twice. - self.test_auth.app = FakeApp(iter([ - ('200 Ok', {}, - json.dumps({'account': 'act', 'user': 'act:usr', - 'account_id': 'AUTH_cfa', - 'groups': [{'name': 'act:usr'}, {'name': 'act'}, - {'name': '.admin'}], - 'expires': time() + 60})), - ('204 No Content', {}, ''), - ('200 Ok', {}, - json.dumps({'account': 'act', 'user': 'act:usr', - 'account_id': 'AUTH_cfa', - 'groups': [{'name': 'act:usr'}, {'name': 'act'}, - {'name': '.admin'}], - 'expires': time() + 60})), - ('204 No Content', {}, '')])) - resp = Request.blank('/v1/AUTH_cfa', - headers={'X-Auth-Token': 'AUTH_t'}).get_response(self.test_auth) - self.assertEqual(resp.status_int, 204) - resp = Request.blank('/v1/AUTH_cfa', - headers={'X-Auth-Token': 'AUTH_t'}).get_response(self.test_auth) - self.assertEqual(resp.status_int, 204) - self.assertEqual(self.test_auth.app.calls, 4) - # Now run our test with memcache, showing we no longer need to return - # the token contents twice. - self.test_auth.app = FakeApp(iter([ - ('200 Ok', {}, - json.dumps({'account': 'act', 'user': 'act:usr', - 'account_id': 'AUTH_cfa', - 'groups': [{'name': 'act:usr'}, {'name': 'act'}, - {'name': '.admin'}], - 'expires': time() + 60})), - ('204 No Content', {}, ''), - # Don't need a second token object returned if memcache is used - ('204 No Content', {}, '')])) - fake_memcache = FakeMemcache() - resp = Request.blank('/v1/AUTH_cfa', - headers={'X-Auth-Token': 'AUTH_t'}, - environ={'swift.cache': fake_memcache} - ).get_response(self.test_auth) - self.assertEqual(resp.status_int, 204) - resp = Request.blank('/v1/AUTH_cfa', - headers={'X-Auth-Token': 'AUTH_t'}, - environ={'swift.cache': fake_memcache} - ).get_response(self.test_auth) - self.assertEqual(resp.status_int, 204) - self.assertEqual(self.test_auth.app.calls, 3) - - def test_auth_just_expired(self): - self.test_auth.app = FakeApp(iter([ - # Request for token (which will have expired) - ('200 Ok', {}, - json.dumps({'account': 'act', 'user': 'act:usr', - 'account_id': 'AUTH_cfa', - 'groups': [{'name': 'act:usr'}, {'name': 'act'}, - {'name': '.admin'}], - 'expires': time() - 1})), - # Request to delete token - ('204 No Content', {}, '')])) - resp = Request.blank('/v1/AUTH_cfa', - headers={'X-Auth-Token': 'AUTH_t'}).get_response(self.test_auth) - self.assertEqual(resp.status_int, 401) - self.assertEqual(self.test_auth.app.calls, 2) - - def test_middleware_storage_token(self): - self.test_auth.app = FakeApp(iter([ - ('200 Ok', {}, - json.dumps({'account': 'act', 'user': 'act:usr', - 'account_id': 'AUTH_cfa', - 'groups': [{'name': 'act:usr'}, {'name': 'act'}, - {'name': '.admin'}], - 'expires': time() + 60})), - ('204 No Content', {}, '')])) - resp = Request.blank('/v1/AUTH_cfa', - headers={'X-Storage-Token': 'AUTH_t'}).get_response(self.test_auth) - self.assertEqual(resp.status_int, 204) - self.assertEqual(self.test_auth.app.calls, 2) - - def test_authorize_bad_path(self): - req = Request.blank('/badpath') - resp = self.test_auth.authorize(req) - self.assertEqual(resp.status_int, 401) - req = Request.blank('/badpath') - req.remote_user = 'act:usr,act,AUTH_cfa' - resp = self.test_auth.authorize(req) - self.assertEqual(resp.status_int, 403) - - def test_authorize_account_access(self): - req = Request.blank('/v1/AUTH_cfa') - req.remote_user = 'act:usr,act,AUTH_cfa' - self.assertEqual(self.test_auth.authorize(req), None) - req = Request.blank('/v1/AUTH_cfa') - req.remote_user = 'act:usr,act' - resp = self.test_auth.authorize(req) - self.assertEqual(resp.status_int, 403) - - def test_authorize_acl_group_access(self): - req = Request.blank('/v1/AUTH_cfa') - req.remote_user = 'act:usr,act' - resp = self.test_auth.authorize(req) - self.assertEqual(resp.status_int, 403) - req = Request.blank('/v1/AUTH_cfa') - req.remote_user = 'act:usr,act' - req.acl = 'act' - self.assertEqual(self.test_auth.authorize(req), None) - req = Request.blank('/v1/AUTH_cfa') - req.remote_user = 'act:usr,act' - req.acl = 'act:usr' - self.assertEqual(self.test_auth.authorize(req), None) - req = Request.blank('/v1/AUTH_cfa') - req.remote_user = 'act:usr,act' - req.acl = 'act2' - resp = self.test_auth.authorize(req) - self.assertEqual(resp.status_int, 403) - req = Request.blank('/v1/AUTH_cfa') - req.remote_user = 'act:usr,act' - req.acl = 'act:usr2' - resp = self.test_auth.authorize(req) - self.assertEqual(resp.status_int, 403) - - def test_deny_cross_reseller(self): - # Tests that cross-reseller is denied, even if ACLs/group names match - req = Request.blank('/v1/OTHER_cfa') - req.remote_user = 'act:usr,act,AUTH_cfa' - req.acl = 'act' - resp = self.test_auth.authorize(req) - self.assertEqual(resp.status_int, 403) - - def test_authorize_acl_referrer_access(self): - req = Request.blank('/v1/AUTH_cfa/c') - req.remote_user = 'act:usr,act' - resp = self.test_auth.authorize(req) - self.assertEqual(resp.status_int, 403) - req = Request.blank('/v1/AUTH_cfa/c') - req.remote_user = 'act:usr,act' - req.acl = '.r:*,.rlistings' - self.assertEqual(self.test_auth.authorize(req), None) - req = Request.blank('/v1/AUTH_cfa/c') - req.remote_user = 'act:usr,act' - req.acl = '.r:*' # No listings allowed - resp = self.test_auth.authorize(req) - self.assertEqual(resp.status_int, 403) - req = Request.blank('/v1/AUTH_cfa/c') - req.remote_user = 'act:usr,act' - req.acl = '.r:.example.com,.rlistings' - resp = self.test_auth.authorize(req) - self.assertEqual(resp.status_int, 403) - req = Request.blank('/v1/AUTH_cfa/c') - req.remote_user = 'act:usr,act' - req.referer = 'http://www.example.com/index.html' - req.acl = '.r:.example.com,.rlistings' - self.assertEqual(self.test_auth.authorize(req), None) - req = Request.blank('/v1/AUTH_cfa/c') - resp = self.test_auth.authorize(req) - self.assertEqual(resp.status_int, 401) - req = Request.blank('/v1/AUTH_cfa/c') - req.acl = '.r:*,.rlistings' - self.assertEqual(self.test_auth.authorize(req), None) - req = Request.blank('/v1/AUTH_cfa/c') - req.acl = '.r:*' # No listings allowed - resp = self.test_auth.authorize(req) - self.assertEqual(resp.status_int, 401) - req = Request.blank('/v1/AUTH_cfa/c') - req.acl = '.r:.example.com,.rlistings' - resp = self.test_auth.authorize(req) - self.assertEqual(resp.status_int, 401) - req = Request.blank('/v1/AUTH_cfa/c') - req.referer = 'http://www.example.com/index.html' - req.acl = '.r:.example.com,.rlistings' - self.assertEqual(self.test_auth.authorize(req), None) - - def test_detect_reseller_request(self): - req = self._make_request('/v1/AUTH_admin', - headers={'X-Auth-Token': 'AUTH_t'}) - cache_key = 'AUTH_/auth/AUTH_t' - cache_entry = (time() + 3600, '.reseller_admin') - req.environ['swift.cache'].set(cache_key, cache_entry) - req.get_response(self.test_auth) - self.assertTrue(req.environ.get('reseller_request')) - - def test_account_put_permissions(self): - req = Request.blank('/v1/AUTH_new', environ={'REQUEST_METHOD': 'PUT'}) - req.remote_user = 'act:usr,act' - resp = self.test_auth.authorize(req) - self.assertEqual(resp.status_int, 403) - - req = Request.blank('/v1/AUTH_new', environ={'REQUEST_METHOD': 'PUT'}) - req.remote_user = 'act:usr,act,AUTH_other' - resp = self.test_auth.authorize(req) - self.assertEqual(resp.status_int, 403) - - # Even PUTs to your own account as account admin should fail - req = Request.blank('/v1/AUTH_old', environ={'REQUEST_METHOD': 'PUT'}) - req.remote_user = 'act:usr,act,AUTH_old' - resp = self.test_auth.authorize(req) - self.assertEqual(resp.status_int, 403) - - req = Request.blank('/v1/AUTH_new', environ={'REQUEST_METHOD': 'PUT'}) - req.remote_user = 'act:usr,act,.reseller_admin' - resp = self.test_auth.authorize(req) - self.assertEqual(resp, None) - - # .super_admin is not something the middleware should ever see or care - # about - req = Request.blank('/v1/AUTH_new', environ={'REQUEST_METHOD': 'PUT'}) - req.remote_user = 'act:usr,act,.super_admin' - resp = self.test_auth.authorize(req) - self.assertEqual(resp.status_int, 403) - - def test_account_delete_permissions(self): - req = Request.blank('/v1/AUTH_new', - environ={'REQUEST_METHOD': 'DELETE'}) - req.remote_user = 'act:usr,act' - resp = self.test_auth.authorize(req) - self.assertEqual(resp.status_int, 403) - - req = Request.blank('/v1/AUTH_new', - environ={'REQUEST_METHOD': 'DELETE'}) - req.remote_user = 'act:usr,act,AUTH_other' - resp = self.test_auth.authorize(req) - self.assertEqual(resp.status_int, 403) - - # Even DELETEs to your own account as account admin should fail - req = Request.blank('/v1/AUTH_old', - environ={'REQUEST_METHOD': 'DELETE'}) - req.remote_user = 'act:usr,act,AUTH_old' - resp = self.test_auth.authorize(req) - self.assertEqual(resp.status_int, 403) - - req = Request.blank('/v1/AUTH_new', - environ={'REQUEST_METHOD': 'DELETE'}) - req.remote_user = 'act:usr,act,.reseller_admin' - resp = self.test_auth.authorize(req) - self.assertEqual(resp, None) - - # .super_admin is not something the middleware should ever see or care - # about - req = Request.blank('/v1/AUTH_new', - environ={'REQUEST_METHOD': 'DELETE'}) - req.remote_user = 'act:usr,act,.super_admin' - resp = self.test_auth.authorize(req) - resp = self.test_auth.authorize(req) - self.assertEqual(resp.status_int, 403) - - def test_get_token_fail(self): - resp = Request.blank('/auth/v1.0').get_response(self.test_auth) - self.assertEqual(resp.status_int, 401) - resp = Request.blank('/auth/v1.0', - headers={'X-Auth-User': 'act:usr', - 'X-Auth-Key': 'key'}).get_response(self.test_auth) - self.assertEqual(resp.status_int, 401) - - def test_get_token_fail_invalid_key(self): - self.test_auth.app = FakeApp(iter([ - # GET of user object - ('200 Ok', {}, - json.dumps({"auth": "plaintext:key", - "groups": [{'name': "act:usr"}, {'name': "act"}, - {'name': ".admin"}]}))])) - resp = Request.blank('/auth/v1.0', - headers={'X-Auth-User': 'act:usr', - 'X-Auth-Key': 'invalid'}).get_response(self.test_auth) - self.assertEqual(resp.status_int, 401) - self.assertEqual(self.test_auth.app.calls, 1) - - def test_get_token_fail_invalid_x_auth_user_format(self): - resp = Request.blank('/auth/v1/act/auth', - headers={'X-Auth-User': 'usr', - 'X-Auth-Key': 'key'}).get_response(self.test_auth) - self.assertEqual(resp.status_int, 401) - - def test_get_token_fail_non_matching_account_in_request(self): - resp = Request.blank('/auth/v1/act/auth', - headers={'X-Auth-User': 'act2:usr', - 'X-Auth-Key': 'key'}).get_response(self.test_auth) - self.assertEqual(resp.status_int, 401) - - def test_get_token_fail_bad_path(self): - resp = Request.blank('/auth/v1/act/auth/invalid', - headers={'X-Auth-User': 'act:usr', - 'X-Auth-Key': 'key'}).get_response(self.test_auth) - self.assertEqual(resp.status_int, 400) - - def test_get_token_fail_missing_key(self): - resp = Request.blank('/auth/v1/act/auth', - headers={'X-Auth-User': 'act:usr'}).get_response(self.test_auth) - self.assertEqual(resp.status_int, 401) - - def test_get_token_fail_get_user_details(self): - self.test_auth.app = FakeApp(iter([ - ('503 Service Unavailable', {}, '')])) - resp = Request.blank('/auth/v1.0', - headers={'X-Auth-User': 'act:usr', - 'X-Auth-Key': 'key'}).get_response(self.test_auth) - self.assertEqual(resp.status_int, 500) - self.assertEqual(self.test_auth.app.calls, 1) - - def test_get_token_fail_get_account(self): - self.test_auth.app = FakeApp(iter([ - # GET of user object - ('200 Ok', {}, - json.dumps({"auth": "plaintext:key", - "groups": [{'name': "act:usr"}, {'name': "act"}, - {'name': ".admin"}]})), - # GET of account - ('503 Service Unavailable', {}, '')])) - resp = Request.blank('/auth/v1.0', - headers={'X-Auth-User': 'act:usr', - 'X-Auth-Key': 'key'}).get_response(self.test_auth) - self.assertEqual(resp.status_int, 500) - self.assertEqual(self.test_auth.app.calls, 2) - - def test_get_token_fail_put_new_token(self): - self.test_auth.app = FakeApp(iter([ - # GET of user object - ('200 Ok', {}, - json.dumps({"auth": "plaintext:key", - "groups": [{'name': "act:usr"}, {'name': "act"}, - {'name': ".admin"}]})), - # GET of account - ('204 Ok', {'X-Container-Meta-Account-Id': 'AUTH_cfa'}, ''), - # PUT of new token - ('503 Service Unavailable', {}, '')])) - resp = Request.blank('/auth/v1.0', - headers={'X-Auth-User': 'act:usr', - 'X-Auth-Key': 'key'}).get_response(self.test_auth) - self.assertEqual(resp.status_int, 500) - self.assertEqual(self.test_auth.app.calls, 3) - - def test_get_token_fail_post_to_user(self): - self.test_auth.app = FakeApp(iter([ - # GET of user object - ('200 Ok', {}, - json.dumps({"auth": "plaintext:key", - "groups": [{'name': "act:usr"}, {'name': "act"}, - {'name': ".admin"}]})), - # GET of account - ('204 Ok', {'X-Container-Meta-Account-Id': 'AUTH_cfa'}, ''), - # PUT of new token - ('201 Created', {}, ''), - # POST of token to user object - ('503 Service Unavailable', {}, '')])) - resp = Request.blank('/auth/v1.0', - headers={'X-Auth-User': 'act:usr', - 'X-Auth-Key': 'key'}).get_response(self.test_auth) - self.assertEqual(resp.status_int, 500) - self.assertEqual(self.test_auth.app.calls, 4) - - def test_get_token_fail_get_services(self): - self.test_auth.app = FakeApp(iter([ - # GET of user object - ('200 Ok', {}, - json.dumps({"auth": "plaintext:key", - "groups": [{'name': "act:usr"}, {'name': "act"}, - {'name': ".admin"}]})), - # GET of account - ('204 Ok', {'X-Container-Meta-Account-Id': 'AUTH_cfa'}, ''), - # PUT of new token - ('201 Created', {}, ''), - # POST of token to user object - ('204 No Content', {}, ''), - # GET of services object - ('503 Service Unavailable', {}, '')])) - resp = Request.blank('/auth/v1.0', - headers={'X-Auth-User': 'act:usr', - 'X-Auth-Key': 'key'}).get_response(self.test_auth) - self.assertEqual(resp.status_int, 500) - self.assertEqual(self.test_auth.app.calls, 5) - - def test_get_token_fail_get_existing_token(self): - self.test_auth.app = FakeApp(iter([ - # GET of user object - ('200 Ok', {'X-Object-Meta-Auth-Token': 'AUTH_tktest'}, - json.dumps({"auth": "plaintext:key", - "groups": [{'name': "act:usr"}, {'name': "act"}, - {'name': ".admin"}]})), - # GET of token - ('503 Service Unavailable', {}, '')])) - resp = Request.blank('/auth/v1.0', - headers={'X-Auth-User': 'act:usr', - 'X-Auth-Key': 'key'}).get_response(self.test_auth) - self.assertEqual(resp.status_int, 500) - self.assertEqual(self.test_auth.app.calls, 2) - - def test_get_token_success_v1_0(self): - self.test_auth.app = FakeApp(iter([ - # GET of user object - ('200 Ok', {}, - json.dumps({"auth": "plaintext:key", - "groups": [{'name': "act:usr"}, {'name': "act"}, - {'name': ".admin"}]})), - # GET of account - ('204 Ok', {'X-Container-Meta-Account-Id': 'AUTH_cfa'}, ''), - # PUT of new token - ('201 Created', {}, ''), - # POST of token to user object - ('204 No Content', {}, ''), - # GET of services object - ('200 Ok', {}, json.dumps({"storage": {"default": "local", - "local": "http://127.0.0.1:8080/v1/AUTH_cfa"}}))])) - resp = Request.blank('/auth/v1.0', - headers={'X-Auth-User': 'act:usr', - 'X-Auth-Key': 'key'}).get_response(self.test_auth) - self.assertEqual(resp.status_int, 200) - self.assertEqual(resp.content_type, CONTENT_TYPE_JSON) - self.assertTrue(resp.headers.get('x-auth-token', - '').startswith('AUTH_tk'), resp.headers.get('x-auth-token')) - self.assertEqual(resp.headers.get('x-auth-token'), - resp.headers.get('x-storage-token')) - self.assertEqual(resp.headers.get('x-storage-url'), - 'http://127.0.0.1:8080/v1/AUTH_cfa') - self.assertEqual(json.loads(resp.body), - {"storage": {"default": "local", - "local": "http://127.0.0.1:8080/v1/AUTH_cfa"}}) - self.assertEqual(self.test_auth.app.calls, 5) - - def test_get_token_success_v1_0_with_user_token_life(self): - self.test_auth.app = FakeApp(iter([ - # GET of user object - ('200 Ok', {}, - json.dumps({"auth": "plaintext:key", - "groups": [{'name': "act:usr"}, {'name': "act"}, - {'name': ".admin"}]})), - # GET of account - ('204 Ok', {'X-Container-Meta-Account-Id': 'AUTH_cfa'}, ''), - # PUT of new token - ('201 Created', {}, ''), - # POST of token to user object - ('204 No Content', {}, ''), - # GET of services object - ('200 Ok', {}, json.dumps({"storage": {"default": "local", - "local": "http://127.0.0.1:8080/v1/AUTH_cfa"}}))])) - resp = Request.blank('/auth/v1.0', - headers={'X-Auth-User': 'act:usr', - 'X-Auth-Key': 'key', - 'X-Auth-Token-Lifetime': 10}).get_response(self.test_auth) - self.assertEqual(resp.status_int, 200) - self.assertEqual(resp.content_type, CONTENT_TYPE_JSON) - left = int(resp.headers['x-auth-token-expires']) - self.assertTrue(left > 0, '%d > 0' % left) - self.assertTrue(left <= 10, '%d <= 10' % left) - self.assertTrue(resp.headers.get('x-auth-token', - '').startswith('AUTH_tk'), resp.headers.get('x-auth-token')) - self.assertEqual(resp.headers.get('x-auth-token'), - resp.headers.get('x-storage-token')) - self.assertEqual(resp.headers.get('x-storage-url'), - 'http://127.0.0.1:8080/v1/AUTH_cfa') - self.assertEqual(json.loads(resp.body), - {"storage": {"default": "local", - "local": "http://127.0.0.1:8080/v1/AUTH_cfa"}}) - self.assertEqual(self.test_auth.app.calls, 5) - - def test_get_token_success_v1_0_with_user_token_life_past_max(self): - self.test_auth.app = FakeApp(iter([ - # GET of user object - ('200 Ok', {}, - json.dumps({"auth": "plaintext:key", - "groups": [{'name': "act:usr"}, {'name': "act"}, - {'name': ".admin"}]})), - # GET of account - ('204 Ok', {'X-Container-Meta-Account-Id': 'AUTH_cfa'}, ''), - # PUT of new token - ('201 Created', {}, ''), - # POST of token to user object - ('204 No Content', {}, ''), - # GET of services object - ('200 Ok', {}, json.dumps({"storage": {"default": "local", - "local": "http://127.0.0.1:8080/v1/AUTH_cfa"}}))])) - req = Request.blank( - '/auth/v1.0', - headers={'X-Auth-User': 'act:usr', - 'X-Auth-Key': 'key', - 'X-Auth-Token-Lifetime': MAX_TOKEN_LIFE * 10}) - resp = req.get_response(self.test_auth) - self.assertEqual(resp.status_int, 200) - self.assertEqual(resp.content_type, CONTENT_TYPE_JSON) - left = int(resp.headers['x-auth-token-expires']) - self.assertTrue(left > DEFAULT_TOKEN_LIFE, - '%d > %d' % (left, DEFAULT_TOKEN_LIFE)) - self.assertTrue(left <= MAX_TOKEN_LIFE, - '%d <= %d' % (left, MAX_TOKEN_LIFE)) - self.assertTrue(resp.headers.get('x-auth-token', - '').startswith('AUTH_tk'), resp.headers.get('x-auth-token')) - self.assertEqual(resp.headers.get('x-auth-token'), - resp.headers.get('x-storage-token')) - self.assertEqual(resp.headers.get('x-storage-url'), - 'http://127.0.0.1:8080/v1/AUTH_cfa') - self.assertEqual(json.loads(resp.body), - {"storage": {"default": "local", - "local": "http://127.0.0.1:8080/v1/AUTH_cfa"}}) - self.assertEqual(self.test_auth.app.calls, 5) - - def test_get_token_success_v1_act_auth(self): - self.test_auth.app = FakeApp(iter([ - # GET of user object - ('200 Ok', {}, - json.dumps({"auth": "plaintext:key", - "groups": [{'name': "act:usr"}, {'name': "act"}, - {'name': ".admin"}]})), - # GET of account - ('204 Ok', {'X-Container-Meta-Account-Id': 'AUTH_cfa'}, ''), - # PUT of new token - ('201 Created', {}, ''), - # POST of token to user object - ('204 No Content', {}, ''), - # GET of services object - ('200 Ok', {}, json.dumps({"storage": {"default": "local", - "local": "http://127.0.0.1:8080/v1/AUTH_cfa"}}))])) - resp = Request.blank('/auth/v1/act/auth', - headers={'X-Storage-User': 'usr', - 'X-Storage-Pass': 'key'}).get_response(self.test_auth) - self.assertEqual(resp.status_int, 200) - self.assertEqual(resp.content_type, CONTENT_TYPE_JSON) - self.assertTrue(resp.headers.get('x-auth-token', - '').startswith('AUTH_tk'), resp.headers.get('x-auth-token')) - self.assertEqual(resp.headers.get('x-auth-token'), - resp.headers.get('x-storage-token')) - self.assertEqual(resp.headers.get('x-storage-url'), - 'http://127.0.0.1:8080/v1/AUTH_cfa') - self.assertEqual(json.loads(resp.body), - {"storage": {"default": "local", - "local": "http://127.0.0.1:8080/v1/AUTH_cfa"}}) - self.assertEqual(self.test_auth.app.calls, 5) - - def test_get_token_success_storage_instead_of_auth(self): - self.test_auth.app = FakeApp(iter([ - # GET of user object - ('200 Ok', {}, - json.dumps({"auth": "plaintext:key", - "groups": [{'name': "act:usr"}, {'name': "act"}, - {'name': ".admin"}]})), - # GET of account - ('204 Ok', {'X-Container-Meta-Account-Id': 'AUTH_cfa'}, ''), - # PUT of new token - ('201 Created', {}, ''), - # POST of token to user object - ('204 No Content', {}, ''), - # GET of services object - ('200 Ok', {}, json.dumps({"storage": {"default": "local", - "local": "http://127.0.0.1:8080/v1/AUTH_cfa"}}))])) - resp = Request.blank('/auth/v1.0', - headers={'X-Storage-User': 'act:usr', - 'X-Storage-Pass': 'key'}).get_response(self.test_auth) - self.assertEqual(resp.status_int, 200) - self.assertEqual(resp.content_type, CONTENT_TYPE_JSON) - self.assertTrue(resp.headers.get('x-auth-token', - '').startswith('AUTH_tk'), resp.headers.get('x-auth-token')) - self.assertEqual(resp.headers.get('x-auth-token'), - resp.headers.get('x-storage-token')) - self.assertEqual(resp.headers.get('x-storage-url'), - 'http://127.0.0.1:8080/v1/AUTH_cfa') - self.assertEqual(json.loads(resp.body), - {"storage": {"default": "local", - "local": "http://127.0.0.1:8080/v1/AUTH_cfa"}}) - self.assertEqual(self.test_auth.app.calls, 5) - - def test_get_token_success_v1_act_auth_auth_instead_of_storage(self): - self.test_auth.app = FakeApp(iter([ - # GET of user object - ('200 Ok', {}, - json.dumps({"auth": "plaintext:key", - "groups": [{'name': "act:usr"}, {'name': "act"}, - {'name': ".admin"}]})), - # GET of account - ('204 Ok', {'X-Container-Meta-Account-Id': 'AUTH_cfa'}, ''), - # PUT of new token - ('201 Created', {}, ''), - # POST of token to user object - ('204 No Content', {}, ''), - # GET of services object - ('200 Ok', {}, json.dumps({"storage": {"default": "local", - "local": "http://127.0.0.1:8080/v1/AUTH_cfa"}}))])) - resp = Request.blank('/auth/v1/act/auth', - headers={'X-Auth-User': 'act:usr', - 'X-Auth-Key': 'key'}).get_response(self.test_auth) - self.assertEqual(resp.status_int, 200) - self.assertEqual(resp.content_type, CONTENT_TYPE_JSON) - self.assertTrue(resp.headers.get('x-auth-token', - '').startswith('AUTH_tk'), resp.headers.get('x-auth-token')) - self.assertEqual(resp.headers.get('x-auth-token'), - resp.headers.get('x-storage-token')) - self.assertEqual(resp.headers.get('x-storage-url'), - 'http://127.0.0.1:8080/v1/AUTH_cfa') - self.assertEqual(json.loads(resp.body), - {"storage": {"default": "local", - "local": "http://127.0.0.1:8080/v1/AUTH_cfa"}}) - self.assertEqual(self.test_auth.app.calls, 5) - - def test_get_token_success_existing_token(self): - self.test_auth.app = FakeApp(iter([ - # GET of user object - ('200 Ok', {'X-Object-Meta-Auth-Token': 'AUTH_tktest'}, - json.dumps({"auth": "plaintext:key", - "groups": [{'name': "act:usr"}, {'name': "act"}, - {'name': ".admin"}]})), - # GET of token - ('200 Ok', {}, json.dumps({"account": "act", "user": "usr", - "account_id": "AUTH_cfa", "groups": [{'name': "act:usr"}, - {'name': "key"}, {'name': ".admin"}], - "expires": 9999999999.9999999})), - # GET of services object - ('200 Ok', {}, json.dumps({"storage": {"default": "local", - "local": "http://127.0.0.1:8080/v1/AUTH_cfa"}}))])) - resp = Request.blank('/auth/v1.0', - headers={'X-Auth-User': 'act:usr', - 'X-Auth-Key': 'key'}).get_response(self.test_auth) - self.assertEqual(resp.status_int, 200) - self.assertEqual(resp.content_type, CONTENT_TYPE_JSON) - self.assertEqual(resp.headers.get('x-auth-token'), 'AUTH_tktest') - self.assertEqual(resp.headers.get('x-auth-token'), - resp.headers.get('x-storage-token')) - self.assertEqual(resp.headers.get('x-storage-url'), - 'http://127.0.0.1:8080/v1/AUTH_cfa') - self.assertEqual(json.loads(resp.body), - {"storage": {"default": "local", - "local": "http://127.0.0.1:8080/v1/AUTH_cfa"}}) - self.assertEqual(self.test_auth.app.calls, 3) - - def test_get_token_success_existing_token_but_request_new_one(self): - self.test_auth.app = FakeApp(iter([ - # GET of user object - ('200 Ok', {'X-Object-Meta-Auth-Token': 'AUTH_tktest'}, - json.dumps({"auth": "plaintext:key", - "groups": [{'name': "act:usr"}, {'name': "act"}, - {'name': ".admin"}]})), - # DELETE of expired token - ('204 No Content', {}, ''), - # GET of account - ('204 Ok', {'X-Container-Meta-Account-Id': 'AUTH_cfa'}, ''), - # PUT of new token - ('201 Created', {}, ''), - # POST of token to user object - ('204 No Content', {}, ''), - # GET of services object - ('200 Ok', {}, json.dumps({"storage": {"default": "local", - "local": "http://127.0.0.1:8080/v1/AUTH_cfa"}}))])) - resp = Request.blank('/auth/v1.0', - headers={'X-Auth-User': 'act:usr', - 'X-Auth-Key': 'key', - 'X-Auth-New-Token': 'true'}).get_response(self.test_auth) - self.assertEqual(resp.status_int, 200) - self.assertEqual(resp.content_type, CONTENT_TYPE_JSON) - self.assertNotEqual(resp.headers.get('x-auth-token'), 'AUTH_tktest') - self.assertEqual(resp.headers.get('x-auth-token'), - resp.headers.get('x-storage-token')) - self.assertEqual(resp.headers.get('x-storage-url'), - 'http://127.0.0.1:8080/v1/AUTH_cfa') - self.assertEqual(json.loads(resp.body), - {"storage": {"default": "local", - "local": "http://127.0.0.1:8080/v1/AUTH_cfa"}}) - self.assertEqual(self.test_auth.app.calls, 6) - - def test_get_token_success_existing_token_expired(self): - self.test_auth.app = FakeApp(iter([ - # GET of user object - ('200 Ok', {'X-Object-Meta-Auth-Token': 'AUTH_tktest'}, - json.dumps({"auth": "plaintext:key", - "groups": [{'name': "act:usr"}, {'name': "act"}, - {'name': ".admin"}]})), - # GET of token - ('200 Ok', {}, json.dumps({"account": "act", "user": "usr", - "account_id": "AUTH_cfa", "groups": [{'name': "act:usr"}, - {'name': "key"}, {'name': ".admin"}], - "expires": 0.0})), - # DELETE of expired token - ('204 No Content', {}, ''), - # GET of account - ('204 Ok', {'X-Container-Meta-Account-Id': 'AUTH_cfa'}, ''), - # PUT of new token - ('201 Created', {}, ''), - # POST of token to user object - ('204 No Content', {}, ''), - # GET of services object - ('200 Ok', {}, json.dumps({"storage": {"default": "local", - "local": "http://127.0.0.1:8080/v1/AUTH_cfa"}}))])) - resp = Request.blank('/auth/v1.0', - headers={'X-Auth-User': 'act:usr', - 'X-Auth-Key': 'key'}).get_response(self.test_auth) - self.assertEqual(resp.status_int, 200) - self.assertEqual(resp.content_type, CONTENT_TYPE_JSON) - self.assertNotEqual(resp.headers.get('x-auth-token'), 'AUTH_tktest') - self.assertEqual(resp.headers.get('x-auth-token'), - resp.headers.get('x-storage-token')) - self.assertEqual(resp.headers.get('x-storage-url'), - 'http://127.0.0.1:8080/v1/AUTH_cfa') - self.assertEqual(json.loads(resp.body), - {"storage": {"default": "local", - "local": "http://127.0.0.1:8080/v1/AUTH_cfa"}}) - self.assertEqual(self.test_auth.app.calls, 7) - - def test_get_token_success_existing_token_expired_fail_deleting_old(self): - self.test_auth.app = FakeApp(iter([ - # GET of user object - ('200 Ok', {'X-Object-Meta-Auth-Token': 'AUTH_tktest'}, - json.dumps({"auth": "plaintext:key", - "groups": [{'name': "act:usr"}, {'name': "act"}, - {'name': ".admin"}]})), - # GET of token - ('200 Ok', {}, json.dumps({"account": "act", "user": "usr", - "account_id": "AUTH_cfa", "groups": [{'name': "act:usr"}, - {'name': "key"}, {'name': ".admin"}], - "expires": 0.0})), - # DELETE of expired token - ('503 Service Unavailable', {}, ''), - # GET of account - ('204 Ok', {'X-Container-Meta-Account-Id': 'AUTH_cfa'}, ''), - # PUT of new token - ('201 Created', {}, ''), - # POST of token to user object - ('204 No Content', {}, ''), - # GET of services object - ('200 Ok', {}, json.dumps({"storage": {"default": "local", - "local": "http://127.0.0.1:8080/v1/AUTH_cfa"}}))])) - resp = Request.blank('/auth/v1.0', - headers={'X-Auth-User': 'act:usr', - 'X-Auth-Key': 'key'}).get_response(self.test_auth) - self.assertEqual(resp.status_int, 200) - self.assertEqual(resp.content_type, CONTENT_TYPE_JSON) - self.assertNotEqual(resp.headers.get('x-auth-token'), 'AUTH_tktest') - self.assertEqual(resp.headers.get('x-auth-token'), - resp.headers.get('x-storage-token')) - self.assertEqual(resp.headers.get('x-storage-url'), - 'http://127.0.0.1:8080/v1/AUTH_cfa') - self.assertEqual(json.loads(resp.body), - {"storage": {"default": "local", - "local": "http://127.0.0.1:8080/v1/AUTH_cfa"}}) - self.assertEqual(self.test_auth.app.calls, 7) - - def test_prep_success(self): - list_to_iter = [ - # PUT of .auth account - ('201 Created', {}, ''), - # PUT of .account_id container - ('201 Created', {}, '')] - # PUT of .token* containers - for x in xrange(16): - list_to_iter.append(('201 Created', {}, '')) - self.test_auth.app = FakeApp(iter(list_to_iter)) - resp = Request.blank('/auth/v2/.prep', - environ={'REQUEST_METHOD': 'POST'}, - headers={'X-Auth-Admin-User': '.super_admin', - 'X-Auth-Admin-Key': 'supertest'} - ).get_response(self.test_auth) - self.assertEqual(resp.status_int, 204) - self.assertEqual(self.test_auth.app.calls, 18) - - def test_prep_bad_method(self): - resp = Request.blank('/auth/v2/.prep', - headers={'X-Auth-Admin-User': '.super_admin', - 'X-Auth-Admin-Key': 'supertest'} - ).get_response(self.test_auth) - self.assertEqual(resp.status_int, 400) - resp = Request.blank('/auth/v2/.prep', - environ={'REQUEST_METHOD': 'HEAD'}, - headers={'X-Auth-Admin-User': '.super_admin', - 'X-Auth-Admin-Key': 'supertest'} - ).get_response(self.test_auth) - self.assertEqual(resp.status_int, 400) - resp = Request.blank('/auth/v2/.prep', - environ={'REQUEST_METHOD': 'PUT'}, - headers={'X-Auth-Admin-User': '.super_admin', - 'X-Auth-Admin-Key': 'supertest'} - ).get_response(self.test_auth) - self.assertEqual(resp.status_int, 400) - - def test_prep_bad_creds(self): - resp = Request.blank('/auth/v2/.prep', - environ={'REQUEST_METHOD': 'POST'}, - headers={'X-Auth-Admin-User': 'super_admin', - 'X-Auth-Admin-Key': 'supertest'} - ).get_response(self.test_auth) - self.assertEqual(resp.status_int, 401) - resp = Request.blank('/auth/v2/.prep', - environ={'REQUEST_METHOD': 'POST'}, - headers={'X-Auth-Admin-User': '.super_admin', - 'X-Auth-Admin-Key': 'upertest'} - ).get_response(self.test_auth) - self.assertEqual(resp.status_int, 401) - resp = Request.blank('/auth/v2/.prep', - environ={'REQUEST_METHOD': 'POST'}, - headers={'X-Auth-Admin-User': '.super_admin'} - ).get_response(self.test_auth) - self.assertEqual(resp.status_int, 401) - resp = Request.blank('/auth/v2/.prep', - environ={'REQUEST_METHOD': 'POST'}, - headers={'X-Auth-Admin-Key': 'supertest'} - ).get_response(self.test_auth) - self.assertEqual(resp.status_int, 401) - resp = Request.blank('/auth/v2/.prep', - environ={'REQUEST_METHOD': 'POST'}).get_response(self.test_auth) - self.assertEqual(resp.status_int, 401) - - def test_prep_fail_account_create(self): - self.test_auth.app = FakeApp(iter([ - # PUT of .auth account - ('503 Service Unavailable', {}, '')])) - resp = Request.blank('/auth/v2/.prep', - environ={'REQUEST_METHOD': 'POST'}, - headers={'X-Auth-Admin-User': '.super_admin', - 'X-Auth-Admin-Key': 'supertest'} - ).get_response(self.test_auth) - self.assertEqual(resp.status_int, 500) - self.assertEqual(self.test_auth.app.calls, 1) - - def test_prep_fail_token_container_create(self): - self.test_auth.app = FakeApp(iter([ - # PUT of .auth account - ('201 Created', {}, ''), - # PUT of .token container - ('503 Service Unavailable', {}, '')])) - resp = Request.blank('/auth/v2/.prep', - environ={'REQUEST_METHOD': 'POST'}, - headers={'X-Auth-Admin-User': '.super_admin', - 'X-Auth-Admin-Key': 'supertest'} - ).get_response(self.test_auth) - self.assertEqual(resp.status_int, 500) - self.assertEqual(self.test_auth.app.calls, 2) - - def test_prep_fail_account_id_container_create(self): - self.test_auth.app = FakeApp(iter([ - # PUT of .auth account - ('201 Created', {}, ''), - # PUT of .token container - ('201 Created', {}, ''), - # PUT of .account_id container - ('503 Service Unavailable', {}, '')])) - resp = Request.blank('/auth/v2/.prep', - environ={'REQUEST_METHOD': 'POST'}, - headers={'X-Auth-Admin-User': '.super_admin', - 'X-Auth-Admin-Key': 'supertest'} - ).get_response(self.test_auth) - self.assertEqual(resp.status_int, 500) - self.assertEqual(self.test_auth.app.calls, 3) - - def test_get_reseller_success(self): - self.test_auth.app = FakeApp(iter([ - # GET of .auth account (list containers) - ('200 Ok', {}, json.dumps([ - {"name": ".token", "count": 0, "bytes": 0}, - {"name": ".account_id", "count": 0, "bytes": 0}, - {"name": "act", "count": 0, "bytes": 0}])), - # GET of .auth account (list containers continuation) - ('200 Ok', {}, '[]')])) - resp = Request.blank('/auth/v2', - headers={'X-Auth-Admin-User': '.super_admin', - 'X-Auth-Admin-Key': 'supertest'} - ).get_response(self.test_auth) - self.assertEqual(resp.status_int, 200) - self.assertEqual(resp.content_type, CONTENT_TYPE_JSON) - self.assertEqual(json.loads(resp.body), - {"accounts": [{"name": "act"}]}) - self.assertEqual(self.test_auth.app.calls, 2) - - self.test_auth.app = FakeApp(iter([ - # GET of user object - ('200 Ok', {}, json.dumps({"groups": [{"name": "act:adm"}, - {"name": "test"}, {"name": ".admin"}, - {"name": ".reseller_admin"}], "auth": "plaintext:key"})), - # GET of .auth account (list containers) - ('200 Ok', {}, json.dumps([ - {"name": ".token", "count": 0, "bytes": 0}, - {"name": ".account_id", "count": 0, "bytes": 0}, - {"name": "act", "count": 0, "bytes": 0}])), - # GET of .auth account (list containers continuation) - ('200 Ok', {}, '[]')])) - resp = Request.blank('/auth/v2', - headers={'X-Auth-Admin-User': 'act:adm', - 'X-Auth-Admin-Key': 'key'} - ).get_response(self.test_auth) - self.assertEqual(resp.status_int, 200) - self.assertEqual(resp.content_type, CONTENT_TYPE_JSON) - self.assertEqual(json.loads(resp.body), - {"accounts": [{"name": "act"}]}) - self.assertEqual(self.test_auth.app.calls, 3) - - def test_get_reseller_fail_bad_creds(self): - self.test_auth.app = FakeApp(iter([ - # GET of user object - ('404 Not Found', {}, '')])) - resp = Request.blank('/auth/v2', - headers={'X-Auth-Admin-User': 'super:admin', - 'X-Auth-Admin-Key': 'supertest'} - ).get_response(self.test_auth) - self.assertEqual(resp.status_int, 401) - self.assertEqual(self.test_auth.app.calls, 1) - - self.test_auth.app = FakeApp(iter([ - # GET of user object (account admin, but not reseller admin) - ('200 Ok', {}, json.dumps({"groups": [{"name": "act:adm"}, - {"name": "test"}, {"name": ".admin"}], - "auth": "plaintext:key"}))])) - resp = Request.blank('/auth/v2', - headers={'X-Auth-Admin-User': 'act:adm', - 'X-Auth-Admin-Key': 'key'} - ).get_response(self.test_auth) - self.assertEqual(resp.status_int, 403) - self.assertEqual(self.test_auth.app.calls, 1) - - self.test_auth.app = FakeApp(iter([ - # GET of user object (regular user) - ('200 Ok', {}, json.dumps({"groups": [{"name": "act:usr"}, - {"name": "test"}], "auth": "plaintext:key"}))])) - resp = Request.blank('/auth/v2', - headers={'X-Auth-Admin-User': 'act:usr', - 'X-Auth-Admin-Key': 'key'} - ).get_response(self.test_auth) - self.assertEqual(resp.status_int, 403) - self.assertEqual(self.test_auth.app.calls, 1) - - def test_get_reseller_fail_listing(self): - self.test_auth.app = FakeApp(iter([ - # GET of .auth account (list containers) - ('503 Service Unavailable', {}, '')])) - resp = Request.blank('/auth/v2', - headers={'X-Auth-Admin-User': '.super_admin', - 'X-Auth-Admin-Key': 'supertest'} - ).get_response(self.test_auth) - self.assertEqual(resp.status_int, 500) - self.assertEqual(self.test_auth.app.calls, 1) - - self.test_auth.app = FakeApp(iter([ - # GET of .auth account (list containers) - ('200 Ok', {}, json.dumps([ - {"name": ".token", "count": 0, "bytes": 0}, - {"name": ".account_id", "count": 0, "bytes": 0}, - {"name": "act", "count": 0, "bytes": 0}])), - # GET of .auth account (list containers continuation) - ('503 Service Unavailable', {}, '')])) - resp = Request.blank('/auth/v2', - headers={'X-Auth-Admin-User': '.super_admin', - 'X-Auth-Admin-Key': 'supertest'} - ).get_response(self.test_auth) - self.assertEqual(resp.status_int, 500) - self.assertEqual(self.test_auth.app.calls, 2) - - def test_get_account_success(self): - self.test_auth.app = FakeApp(iter([ - # GET of .services object - ('200 Ok', {}, json.dumps({"storage": {"default": "local", - "local": "http://127.0.0.1:8080/v1/AUTH_cfa"}})), - # GET of account container (list objects) - ('200 Ok', {'X-Container-Meta-Account-Id': 'AUTH_cfa'}, - json.dumps([ - {"name": ".services", "hash": "etag", "bytes": 112, - "content_type": "application/octet-stream", - "last_modified": "2010-12-03T17:16:27.618110"}, - {"name": "tester", "hash": "etag", "bytes": 104, - "content_type": "application/octet-stream", - "last_modified": "2010-12-03T17:16:27.736680"}, - {"name": "tester3", "hash": "etag", "bytes": 86, - "content_type": "application/octet-stream", - "last_modified": "2010-12-03T17:16:28.135530"}])), - # GET of account container (list objects continuation) - ('200 Ok', {'X-Container-Meta-Account-Id': 'AUTH_cfa'}, '[]')])) - resp = Request.blank('/auth/v2/act', - headers={'X-Auth-Admin-User': '.super_admin', - 'X-Auth-Admin-Key': 'supertest'} - ).get_response(self.test_auth) - self.assertEqual(resp.status_int, 200) - self.assertEqual(resp.content_type, CONTENT_TYPE_JSON) - self.assertEqual(json.loads(resp.body), - {'account_id': 'AUTH_cfa', - 'services': {'storage': - {'default': 'local', - 'local': 'http://127.0.0.1:8080/v1/AUTH_cfa'}}, - 'users': [{'name': 'tester'}, {'name': 'tester3'}]}) - self.assertEqual(self.test_auth.app.calls, 3) - - self.test_auth.app = FakeApp(iter([ - # GET of user object - ('200 Ok', {}, json.dumps({"groups": [{"name": "act:adm"}, - {"name": "test"}, {"name": ".admin"}], - "auth": "plaintext:key"})), - # GET of .services object - ('200 Ok', {}, json.dumps({"storage": {"default": "local", - "local": "http://127.0.0.1:8080/v1/AUTH_cfa"}})), - # GET of account container (list objects) - ('200 Ok', {'X-Container-Meta-Account-Id': 'AUTH_cfa'}, - json.dumps([ - {"name": ".services", "hash": "etag", "bytes": 112, - "content_type": "application/octet-stream", - "last_modified": "2010-12-03T17:16:27.618110"}, - {"name": "tester", "hash": "etag", "bytes": 104, - "content_type": "application/octet-stream", - "last_modified": "2010-12-03T17:16:27.736680"}, - {"name": "tester3", "hash": "etag", "bytes": 86, - "content_type": "application/octet-stream", - "last_modified": "2010-12-03T17:16:28.135530"}])), - # GET of account container (list objects continuation) - ('200 Ok', {'X-Container-Meta-Account-Id': 'AUTH_cfa'}, '[]')])) - resp = Request.blank('/auth/v2/act', - headers={'X-Auth-Admin-User': 'act:adm', - 'X-Auth-Admin-Key': 'key'} - ).get_response(self.test_auth) - self.assertEqual(resp.status_int, 200) - self.assertEqual(resp.content_type, CONTENT_TYPE_JSON) - self.assertEqual(json.loads(resp.body), - {'account_id': 'AUTH_cfa', - 'services': {'storage': - {'default': 'local', - 'local': 'http://127.0.0.1:8080/v1/AUTH_cfa'}}, - 'users': [{'name': 'tester'}, {'name': 'tester3'}]}) - self.assertEqual(self.test_auth.app.calls, 4) - - def test_get_account_fail_bad_account_name(self): - resp = Request.blank('/auth/v2/.token', - headers={'X-Auth-Admin-User': '.super_admin', - 'X-Auth-Admin-Key': 'supertest'} - ).get_response(self.test_auth) - self.assertEqual(resp.status_int, 400) - resp = Request.blank('/auth/v2/.anything', - headers={'X-Auth-Admin-User': '.super_admin', - 'X-Auth-Admin-Key': 'supertest'} - ).get_response(self.test_auth) - self.assertEqual(resp.status_int, 400) - - def test_get_account_fail_creds(self): - self.test_auth.app = FakeApp(iter([ - # GET of user object - ('404 Not Found', {}, '')])) - resp = Request.blank('/auth/v2/act', - headers={'X-Auth-Admin-User': 'super:admin', - 'X-Auth-Admin-Key': 'supertest'} - ).get_response(self.test_auth) - self.assertEqual(resp.status_int, 401) - self.assertEqual(self.test_auth.app.calls, 1) - - self.test_auth.app = FakeApp(iter([ - # GET of user object (account admin, but wrong account) - ('200 Ok', {}, json.dumps({"groups": [{"name": "act2:adm"}, - {"name": "test"}, {"name": ".admin"}], - "auth": "plaintext:key"}))])) - resp = Request.blank('/auth/v2/act', - headers={'X-Auth-Admin-User': 'act2:adm', - 'X-Auth-Admin-Key': 'key'} - ).get_response(self.test_auth) - self.assertEqual(resp.status_int, 403) - self.assertEqual(self.test_auth.app.calls, 1) - - self.test_auth.app = FakeApp(iter([ - # GET of user object (regular user) - ('200 Ok', {}, json.dumps({"groups": [{"name": "act:usr"}, - {"name": "test"}], "auth": "plaintext:key"}))])) - resp = Request.blank('/auth/v2/act', - headers={'X-Auth-Admin-User': 'act:usr', - 'X-Auth-Admin-Key': 'key'} - ).get_response(self.test_auth) - self.assertEqual(resp.status_int, 403) - self.assertEqual(self.test_auth.app.calls, 1) - - def test_get_account_fail_get_services(self): - self.test_auth.app = FakeApp(iter([ - # GET of .services object - ('503 Service Unavailable', {}, '')])) - resp = Request.blank('/auth/v2/act', - headers={'X-Auth-Admin-User': '.super_admin', - 'X-Auth-Admin-Key': 'supertest'} - ).get_response(self.test_auth) - self.assertEqual(resp.status_int, 500) - self.assertEqual(self.test_auth.app.calls, 1) - - self.test_auth.app = FakeApp(iter([ - # GET of .services object - ('404 Not Found', {}, '')])) - resp = Request.blank('/auth/v2/act', - headers={'X-Auth-Admin-User': '.super_admin', - 'X-Auth-Admin-Key': 'supertest'} - ).get_response(self.test_auth) - self.assertEqual(resp.status_int, 404) - self.assertEqual(self.test_auth.app.calls, 1) - - def test_get_account_fail_listing(self): - self.test_auth.app = FakeApp(iter([ - # GET of .services object - ('200 Ok', {}, json.dumps({"storage": {"default": "local", - "local": "http://127.0.0.1:8080/v1/AUTH_cfa"}})), - # GET of account container (list objects) - ('503 Service Unavailable', {}, '')])) - resp = Request.blank('/auth/v2/act', - headers={'X-Auth-Admin-User': '.super_admin', - 'X-Auth-Admin-Key': 'supertest'} - ).get_response(self.test_auth) - self.assertEqual(resp.status_int, 500) - self.assertEqual(self.test_auth.app.calls, 2) - - self.test_auth.app = FakeApp(iter([ - # GET of .services object - ('200 Ok', {}, json.dumps({"storage": {"default": "local", - "local": "http://127.0.0.1:8080/v1/AUTH_cfa"}})), - # GET of account container (list objects) - ('404 Not Found', {}, '')])) - resp = Request.blank('/auth/v2/act', - headers={'X-Auth-Admin-User': '.super_admin', - 'X-Auth-Admin-Key': 'supertest'} - ).get_response(self.test_auth) - self.assertEqual(resp.status_int, 404) - self.assertEqual(self.test_auth.app.calls, 2) - - self.test_auth.app = FakeApp(iter([ - # GET of .services object - ('200 Ok', {}, json.dumps({"storage": {"default": "local", - "local": "http://127.0.0.1:8080/v1/AUTH_cfa"}})), - # GET of account container (list objects) - ('200 Ok', {'X-Container-Meta-Account-Id': 'AUTH_cfa'}, - json.dumps([ - {"name": ".services", "hash": "etag", "bytes": 112, - "content_type": "application/octet-stream", - "last_modified": "2010-12-03T17:16:27.618110"}, - {"name": "tester", "hash": "etag", "bytes": 104, - "content_type": "application/octet-stream", - "last_modified": "2010-12-03T17:16:27.736680"}, - {"name": "tester3", "hash": "etag", "bytes": 86, - "content_type": "application/octet-stream", - "last_modified": "2010-12-03T17:16:28.135530"}])), - # GET of account container (list objects continuation) - ('503 Service Unavailable', {}, '')])) - resp = Request.blank('/auth/v2/act', - headers={'X-Auth-Admin-User': '.super_admin', - 'X-Auth-Admin-Key': 'supertest'} - ).get_response(self.test_auth) - self.assertEqual(resp.status_int, 500) - self.assertEqual(self.test_auth.app.calls, 3) - - def test_set_services_new_service(self): - self.test_auth.app = FakeApp(iter([ - # GET of .services object - ('200 Ok', {}, json.dumps({"storage": {"default": "local", - "local": "http://127.0.0.1:8080/v1/AUTH_cfa"}})), - # PUT of new .services object - ('204 No Content', {}, '')])) - resp = Request.blank('/auth/v2/act/.services', - environ={'REQUEST_METHOD': 'POST'}, - headers={'X-Auth-Admin-User': '.super_admin', - 'X-Auth-Admin-Key': 'supertest'}, - body=json.dumps({'new_service': {'new_endpoint': 'new_value'}}) - ).get_response(self.test_auth) - self.assertEqual(resp.status_int, 200) - self.assertEqual(resp.content_type, CONTENT_TYPE_JSON) - self.assertEqual(json.loads(resp.body), - {'storage': {'default': 'local', - 'local': 'http://127.0.0.1:8080/v1/AUTH_cfa'}, - 'new_service': {'new_endpoint': 'new_value'}}) - self.assertEqual(self.test_auth.app.calls, 2) - - def test_set_services_new_endpoint(self): - self.test_auth.app = FakeApp(iter([ - # GET of .services object - ('200 Ok', {}, json.dumps({"storage": {"default": "local", - "local": "http://127.0.0.1:8080/v1/AUTH_cfa"}})), - # PUT of new .services object - ('204 No Content', {}, '')])) - resp = Request.blank('/auth/v2/act/.services', - environ={'REQUEST_METHOD': 'POST'}, - headers={'X-Auth-Admin-User': '.super_admin', - 'X-Auth-Admin-Key': 'supertest'}, - body=json.dumps({'storage': {'new_endpoint': 'new_value'}}) - ).get_response(self.test_auth) - self.assertEqual(resp.status_int, 200) - self.assertEqual(resp.content_type, CONTENT_TYPE_JSON) - self.assertEqual(json.loads(resp.body), - {'storage': {'default': 'local', - 'local': 'http://127.0.0.1:8080/v1/AUTH_cfa', - 'new_endpoint': 'new_value'}}) - self.assertEqual(self.test_auth.app.calls, 2) - - def test_set_services_update_endpoint(self): - self.test_auth.app = FakeApp(iter([ - # GET of .services object - ('200 Ok', {}, json.dumps({"storage": {"default": "local", - "local": "http://127.0.0.1:8080/v1/AUTH_cfa"}})), - # PUT of new .services object - ('204 No Content', {}, '')])) - resp = Request.blank('/auth/v2/act/.services', - environ={'REQUEST_METHOD': 'POST'}, - headers={'X-Auth-Admin-User': '.super_admin', - 'X-Auth-Admin-Key': 'supertest'}, - body=json.dumps({'storage': {'local': 'new_value'}}) - ).get_response(self.test_auth) - self.assertEqual(resp.status_int, 200) - self.assertEqual(resp.content_type, CONTENT_TYPE_JSON) - self.assertEqual(json.loads(resp.body), - {'storage': {'default': 'local', - 'local': 'new_value'}}) - self.assertEqual(self.test_auth.app.calls, 2) - - def test_set_services_fail_bad_creds(self): - self.test_auth.app = FakeApp(iter([ - # GET of user object - ('404 Not Found', {}, '')])) - resp = Request.blank('/auth/v2/act/.services', - environ={'REQUEST_METHOD': 'POST'}, - headers={'X-Auth-Admin-User': 'super:admin', - 'X-Auth-Admin-Key': 'supertest'}, - body=json.dumps({'storage': {'local': 'new_value'}}) - ).get_response(self.test_auth) - self.assertEqual(resp.status_int, 401) - self.assertEqual(self.test_auth.app.calls, 1) - - self.test_auth.app = FakeApp(iter([ - # GET of user object (account admin, but not reseller admin) - ('200 Ok', {}, json.dumps({"groups": [{"name": "act:adm"}, - {"name": "test"}, {"name": ".admin"}], - "auth": "plaintext:key"}))])) - resp = Request.blank('/auth/v2/act/.services', - environ={'REQUEST_METHOD': 'POST'}, - headers={'X-Auth-Admin-User': 'act:adm', - 'X-Auth-Admin-Key': 'key'}, - body=json.dumps({'storage': {'local': 'new_value'}}) - ).get_response(self.test_auth) - self.assertEqual(resp.status_int, 403) - self.assertEqual(self.test_auth.app.calls, 1) - - self.test_auth.app = FakeApp(iter([ - # GET of user object (regular user) - ('200 Ok', {}, json.dumps({"groups": [{"name": "act:usr"}, - {"name": "test"}], "auth": "plaintext:key"}))])) - resp = Request.blank('/auth/v2/act/.services', - environ={'REQUEST_METHOD': 'POST'}, - headers={'X-Auth-Admin-User': 'act:usr', - 'X-Auth-Admin-Key': 'key'}, - body=json.dumps({'storage': {'local': 'new_value'}}) - ).get_response(self.test_auth) - self.assertEqual(resp.status_int, 403) - self.assertEqual(self.test_auth.app.calls, 1) - - def test_set_services_fail_bad_account_name(self): - resp = Request.blank('/auth/v2/.act/.services', - environ={'REQUEST_METHOD': 'POST'}, - headers={'X-Auth-Admin-User': '.super_admin', - 'X-Auth-Admin-Key': 'supertest'}, - body=json.dumps({'storage': {'local': 'new_value'}}) - ).get_response(self.test_auth) - self.assertEqual(resp.status_int, 400) - - def test_set_services_fail_bad_json(self): - resp = Request.blank('/auth/v2/act/.services', - environ={'REQUEST_METHOD': 'POST'}, - headers={'X-Auth-Admin-User': '.super_admin', - 'X-Auth-Admin-Key': 'supertest'}, - body='garbage' - ).get_response(self.test_auth) - self.assertEqual(resp.status_int, 400) - resp = Request.blank('/auth/v2/act/.services', - environ={'REQUEST_METHOD': 'POST'}, - headers={'X-Auth-Admin-User': '.super_admin', - 'X-Auth-Admin-Key': 'supertest'}, - body='' - ).get_response(self.test_auth) - self.assertEqual(resp.status_int, 400) - - def test_set_services_fail_get_services(self): - self.test_auth.app = FakeApp(iter([ - # GET of .services object - ('503 Unavailable', {}, '')])) - resp = Request.blank('/auth/v2/act/.services', - environ={'REQUEST_METHOD': 'POST'}, - headers={'X-Auth-Admin-User': '.super_admin', - 'X-Auth-Admin-Key': 'supertest'}, - body=json.dumps({'new_service': {'new_endpoint': 'new_value'}}) - ).get_response(self.test_auth) - self.assertEqual(resp.status_int, 500) - self.assertEqual(self.test_auth.app.calls, 1) - - self.test_auth.app = FakeApp(iter([ - # GET of .services object - ('404 Not Found', {}, '')])) - resp = Request.blank('/auth/v2/act/.services', - environ={'REQUEST_METHOD': 'POST'}, - headers={'X-Auth-Admin-User': '.super_admin', - 'X-Auth-Admin-Key': 'supertest'}, - body=json.dumps({'new_service': {'new_endpoint': 'new_value'}}) - ).get_response(self.test_auth) - self.assertEqual(resp.status_int, 404) - self.assertEqual(self.test_auth.app.calls, 1) - - def test_set_services_fail_put_services(self): - self.test_auth.app = FakeApp(iter([ - # GET of .services object - ('200 Ok', {}, json.dumps({"storage": {"default": "local", - "local": "http://127.0.0.1:8080/v1/AUTH_cfa"}})), - # PUT of new .services object - ('503 Unavailable', {}, '')])) - resp = Request.blank('/auth/v2/act/.services', - environ={'REQUEST_METHOD': 'POST'}, - headers={'X-Auth-Admin-User': '.super_admin', - 'X-Auth-Admin-Key': 'supertest'}, - body=json.dumps({'new_service': {'new_endpoint': 'new_value'}}) - ).get_response(self.test_auth) - self.assertEqual(resp.status_int, 500) - self.assertEqual(self.test_auth.app.calls, 2) - - def test_put_account_success(self): - conn = FakeConn(iter([ - # PUT of storage account itself - ('201 Created', {}, '')])) - self.test_auth.get_conn = lambda: conn - self.test_auth.app = FakeApp(iter([ - # Initial HEAD of account container to check for pre-existence - ('404 Not Found', {}, ''), - # PUT of account container - ('204 No Content', {}, ''), - # PUT of .account_id mapping object - ('204 No Content', {}, ''), - # PUT of .services object - ('204 No Content', {}, ''), - # POST to account container updating X-Container-Meta-Account-Id - ('204 No Content', {}, '')])) - resp = Request.blank('/auth/v2/act', - environ={'REQUEST_METHOD': 'PUT', 'swift.cache': FakeMemcache()}, - headers={'X-Auth-Admin-User': '.super_admin', - 'X-Auth-Admin-Key': 'supertest'} - ).get_response(self.test_auth) - self.assertEqual(resp.status_int, 201) - self.assertEqual(self.test_auth.app.calls, 5) - self.assertEqual(conn.calls, 1) - - def test_put_account_success_preexist_but_not_completed(self): - conn = FakeConn(iter([ - # PUT of storage account itself - ('201 Created', {}, '')])) - self.test_auth.get_conn = lambda: conn - self.test_auth.app = FakeApp(iter([ - # Initial HEAD of account container to check for pre-existence - # We're going to show it as existing this time, but with no - # X-Container-Meta-Account-Id, indicating a failed previous attempt - ('200 Ok', {}, ''), - # PUT of .account_id mapping object - ('204 No Content', {}, ''), - # PUT of .services object - ('204 No Content', {}, ''), - # POST to account container updating X-Container-Meta-Account-Id - ('204 No Content', {}, '')])) - resp = Request.blank('/auth/v2/act', - environ={'REQUEST_METHOD': 'PUT', 'swift.cache': FakeMemcache()}, - headers={'X-Auth-Admin-User': '.super_admin', - 'X-Auth-Admin-Key': 'supertest'} - ).get_response(self.test_auth) - self.assertEqual(resp.status_int, 201) - self.assertEqual(self.test_auth.app.calls, 4) - self.assertEqual(conn.calls, 1) - - def test_put_account_success_preexist_and_completed(self): - conn = FakeConn(iter([ - # PUT of storage account itself - ('201 Created', {}, '')])) - self.test_auth.get_conn = lambda: conn - self.test_auth.app = FakeApp(iter([ - # Initial HEAD of account container to check for pre-existence - # We're going to show it as existing this time, and with an - # X-Container-Meta-Account-Id, indicating it already exists - ('200 Ok', {'X-Container-Meta-Account-Id': 'AUTH_cfa'}, '')])) - resp = Request.blank('/auth/v2/act', - environ={'REQUEST_METHOD': 'PUT', 'swift.cache': FakeMemcache()}, - headers={'X-Auth-Admin-User': '.super_admin', - 'X-Auth-Admin-Key': 'supertest'} - ).get_response(self.test_auth) - self.assertEqual(resp.status_int, 202) - self.assertEqual(self.test_auth.app.calls, 1) - - def test_put_account_success_with_given_suffix(self): - conn = FakeConn(iter([ - # PUT of storage account itself - ('201 Created', {}, '')])) - self.test_auth.get_conn = lambda: conn - self.test_auth.app = FakeApp(iter([ - # Initial HEAD of account container to check for pre-existence - ('404 Not Found', {}, ''), - # PUT of account container - ('204 No Content', {}, ''), - # PUT of .account_id mapping object - ('204 No Content', {}, ''), - # PUT of .services object - ('204 No Content', {}, ''), - # POST to account container updating X-Container-Meta-Account-Id - ('204 No Content', {}, '')])) - resp = Request.blank('/auth/v2/act', - environ={'REQUEST_METHOD': 'PUT', 'swift.cache': FakeMemcache()}, - headers={'X-Auth-Admin-User': '.super_admin', - 'X-Auth-Admin-Key': 'supertest', - 'X-Account-Suffix': 'test-suffix'} - ).get_response(self.test_auth) - self.assertEqual(resp.status_int, 201) - self.assertEqual(conn.request_path, '/v1/AUTH_test-suffix') - self.assertEqual(self.test_auth.app.calls, 5) - self.assertEqual(conn.calls, 1) - - def test_put_account_fail_bad_creds(self): - self.test_auth.app = FakeApp(iter([ - # GET of user object - ('404 Not Found', {}, '')])) - resp = Request.blank('/auth/v2/act', - environ={'REQUEST_METHOD': 'PUT', 'swift.cache': FakeMemcache()}, - headers={'X-Auth-Admin-User': 'super:admin', - 'X-Auth-Admin-Key': 'supertest'}, - ).get_response(self.test_auth) - self.assertEqual(resp.status_int, 401) - self.assertEqual(self.test_auth.app.calls, 1) - - self.test_auth.app = FakeApp(iter([ - # GET of user object (account admin, but not reseller admin) - ('200 Ok', {}, json.dumps({"groups": [{"name": "act:adm"}, - {"name": "test"}, {"name": ".admin"}], - "auth": "plaintext:key"}))])) - resp = Request.blank('/auth/v2/act', - environ={'REQUEST_METHOD': 'PUT', 'swift.cache': FakeMemcache()}, - headers={'X-Auth-Admin-User': 'act:adm', - 'X-Auth-Admin-Key': 'key'}, - ).get_response(self.test_auth) - self.assertEqual(resp.status_int, 403) - self.assertEqual(self.test_auth.app.calls, 1) - - self.test_auth.app = FakeApp(iter([ - # GET of user object (regular user) - ('200 Ok', {}, json.dumps({"groups": [{"name": "act:usr"}, - {"name": "test"}], "auth": "plaintext:key"}))])) - resp = Request.blank('/auth/v2/act', - environ={'REQUEST_METHOD': 'PUT', 'swift.cache': FakeMemcache()}, - headers={'X-Auth-Admin-User': 'act:usr', - 'X-Auth-Admin-Key': 'key'}, - ).get_response(self.test_auth) - self.assertEqual(resp.status_int, 403) - self.assertEqual(self.test_auth.app.calls, 1) - - def test_put_account_fail_invalid_account_name(self): - resp = Request.blank('/auth/v2/.act', - environ={'REQUEST_METHOD': 'PUT', 'swift.cache': FakeMemcache()}, - headers={'X-Auth-Admin-User': '.super_admin', - 'X-Auth-Admin-Key': 'supertest'}, - ).get_response(self.test_auth) - self.assertEqual(resp.status_int, 400) - - def test_put_account_fail_on_storage_account_put(self): - conn = FakeConn(iter([ - # PUT of storage account itself - ('503 Service Unavailable', {}, '')])) - self.test_auth.get_conn = lambda: conn - self.test_auth.app = FakeApp(iter([ - ])) - resp = Request.blank('/auth/v2/act', - environ={'REQUEST_METHOD': 'PUT', 'swift.cache': FakeMemcache()}, - headers={'X-Auth-Admin-User': '.super_admin', - 'X-Auth-Admin-Key': 'supertest'} - ).get_response(self.test_auth) - self.assertEqual(resp.status_int, 500) - self.assertEqual(conn.calls, 1) - self.assertEqual(self.test_auth.app.calls, 0) - - def test_put_account_fail_on_initial_account_head(self): - conn = FakeConn(iter([ - # PUT of storage account itself - ('201 Created', {}, '')])) - self.test_auth.get_conn = lambda: conn - self.test_auth.app = FakeApp(iter([ - # Initial HEAD of account container to check for pre-existence - ('503 Service Unavailable', {}, '')])) - resp = Request.blank('/auth/v2/act', - environ={'REQUEST_METHOD': 'PUT', 'swift.cache': FakeMemcache()}, - headers={'X-Auth-Admin-User': '.super_admin', - 'X-Auth-Admin-Key': 'supertest'} - ).get_response(self.test_auth) - self.assertEqual(resp.status_int, 500) - self.assertEqual(self.test_auth.app.calls, 1) - - def test_put_account_fail_on_account_marker_put(self): - conn = FakeConn(iter([ - # PUT of storage account itself - ('201 Created', {}, '')])) - self.test_auth.get_conn = lambda: conn - self.test_auth.app = FakeApp(iter([ - # Initial HEAD of account container to check for pre-existence - ('404 Not Found', {}, ''), - # PUT of account container - ('503 Service Unavailable', {}, '')])) - resp = Request.blank('/auth/v2/act', - environ={'REQUEST_METHOD': 'PUT', 'swift.cache': FakeMemcache()}, - headers={'X-Auth-Admin-User': '.super_admin', - 'X-Auth-Admin-Key': 'supertest'} - ).get_response(self.test_auth) - self.assertEqual(resp.status_int, 500) - self.assertEqual(self.test_auth.app.calls, 2) - - def test_put_account_fail_on_account_id_mapping(self): - conn = FakeConn(iter([ - # PUT of storage account itself - ('201 Created', {}, '')])) - self.test_auth.get_conn = lambda: conn - self.test_auth.app = FakeApp(iter([ - # Initial HEAD of account container to check for pre-existence - ('404 Not Found', {}, ''), - # PUT of account container - ('204 No Content', {}, ''), - # PUT of .account_id mapping object - ('503 Service Unavailable', {}, '')])) - resp = Request.blank('/auth/v2/act', - environ={'REQUEST_METHOD': 'PUT', 'swift.cache': FakeMemcache()}, - headers={'X-Auth-Admin-User': '.super_admin', - 'X-Auth-Admin-Key': 'supertest'} - ).get_response(self.test_auth) - self.assertEqual(resp.status_int, 500) - self.assertEqual(conn.calls, 1) - self.assertEqual(self.test_auth.app.calls, 3) - - def test_put_account_fail_on_services_object(self): - conn = FakeConn(iter([ - # PUT of storage account itself - ('201 Created', {}, '')])) - self.test_auth.get_conn = lambda: conn - self.test_auth.app = FakeApp(iter([ - # Initial HEAD of account container to check for pre-existence - ('404 Not Found', {}, ''), - # PUT of account container - ('204 No Content', {}, ''), - # PUT of .account_id mapping object - ('204 No Content', {}, ''), - # PUT of .services object - ('503 Service Unavailable', {}, '')])) - resp = Request.blank('/auth/v2/act', - environ={'REQUEST_METHOD': 'PUT', 'swift.cache': FakeMemcache()}, - headers={'X-Auth-Admin-User': '.super_admin', - 'X-Auth-Admin-Key': 'supertest'} - ).get_response(self.test_auth) - self.assertEqual(resp.status_int, 500) - self.assertEqual(conn.calls, 1) - self.assertEqual(self.test_auth.app.calls, 4) - - def test_put_account_fail_on_post_mapping(self): - conn = FakeConn(iter([ - # PUT of storage account itself - ('201 Created', {}, '')])) - self.test_auth.get_conn = lambda: conn - self.test_auth.app = FakeApp(iter([ - # Initial HEAD of account container to check for pre-existence - ('404 Not Found', {}, ''), - # PUT of account container - ('204 No Content', {}, ''), - # PUT of .account_id mapping object - ('204 No Content', {}, ''), - # PUT of .services object - ('204 No Content', {}, ''), - # POST to account container updating X-Container-Meta-Account-Id - ('503 Service Unavailable', {}, '')])) - resp = Request.blank('/auth/v2/act', - environ={'REQUEST_METHOD': 'PUT', 'swift.cache': FakeMemcache()}, - headers={'X-Auth-Admin-User': '.super_admin', - 'X-Auth-Admin-Key': 'supertest'} - ).get_response(self.test_auth) - self.assertEqual(resp.status_int, 500) - self.assertEqual(conn.calls, 1) - self.assertEqual(self.test_auth.app.calls, 5) - - def test_delete_account_success(self): - conn = FakeConn(iter([ - # DELETE of storage account itself - ('204 No Content', {}, '')])) - self.test_auth.get_conn = lambda x: conn - self.test_auth.app = FakeApp(iter([ - # Account's container listing, checking for users - ('200 Ok', {'X-Container-Meta-Account-Id': 'AUTH_cfa'}, - json.dumps([ - {"name": ".services", "hash": "etag", "bytes": 112, - "content_type": "application/octet-stream", - "last_modified": "2010-12-03T17:16:27.618110"}])), - # Account's container listing, checking for users (continuation) - ('200 Ok', {'X-Container-Meta-Account-Id': 'AUTH_cfa'}, '[]'), - # GET the .services object - ('200 Ok', {}, json.dumps({"storage": {"default": "local", - "local": "http://127.0.0.1:8080/v1/AUTH_cfa"}})), - # DELETE the .services object - ('204 No Content', {}, ''), - # DELETE the .account_id mapping object - ('204 No Content', {}, ''), - # DELETE the account container - ('204 No Content', {}, '')])) - resp = Request.blank('/auth/v2/act', - environ={'REQUEST_METHOD': 'DELETE', - 'swift.cache': FakeMemcache()}, - headers={'X-Auth-Admin-User': '.super_admin', - 'X-Auth-Admin-Key': 'supertest'} - ).get_response(self.test_auth) - self.assertEqual(resp.status_int, 204) - self.assertEqual(self.test_auth.app.calls, 6) - self.assertEqual(conn.calls, 1) - - def test_delete_account_success_missing_services(self): - self.test_auth.app = FakeApp(iter([ - # Account's container listing, checking for users - ('200 Ok', {'X-Container-Meta-Account-Id': 'AUTH_cfa'}, - json.dumps([ - {"name": ".services", "hash": "etag", "bytes": 112, - "content_type": "application/octet-stream", - "last_modified": "2010-12-03T17:16:27.618110"}])), - # Account's container listing, checking for users (continuation) - ('200 Ok', {'X-Container-Meta-Account-Id': 'AUTH_cfa'}, '[]'), - # GET the .services object - ('404 Not Found', {}, ''), - # DELETE the .account_id mapping object - ('204 No Content', {}, ''), - # DELETE the account container - ('204 No Content', {}, '')])) - resp = Request.blank('/auth/v2/act', - environ={'REQUEST_METHOD': 'DELETE', - 'swift.cache': FakeMemcache()}, - headers={'X-Auth-Admin-User': '.super_admin', - 'X-Auth-Admin-Key': 'supertest'} - ).get_response(self.test_auth) - self.assertEqual(resp.status_int, 204) - self.assertEqual(self.test_auth.app.calls, 5) - - def test_delete_account_success_missing_storage_account(self): - conn = FakeConn(iter([ - # DELETE of storage account itself - ('404 Not Found', {}, '')])) - self.test_auth.get_conn = lambda x: conn - self.test_auth.app = FakeApp(iter([ - # Account's container listing, checking for users - ('200 Ok', {'X-Container-Meta-Account-Id': 'AUTH_cfa'}, - json.dumps([ - {"name": ".services", "hash": "etag", "bytes": 112, - "content_type": "application/octet-stream", - "last_modified": "2010-12-03T17:16:27.618110"}])), - # Account's container listing, checking for users (continuation) - ('200 Ok', {'X-Container-Meta-Account-Id': 'AUTH_cfa'}, '[]'), - # GET the .services object - ('200 Ok', {}, json.dumps({"storage": {"default": "local", - "local": "http://127.0.0.1:8080/v1/AUTH_cfa"}})), - # DELETE the .services object - ('204 No Content', {}, ''), - # DELETE the .account_id mapping object - ('204 No Content', {}, ''), - # DELETE the account container - ('204 No Content', {}, '')])) - resp = Request.blank('/auth/v2/act', - environ={'REQUEST_METHOD': 'DELETE', - 'swift.cache': FakeMemcache()}, - headers={'X-Auth-Admin-User': '.super_admin', - 'X-Auth-Admin-Key': 'supertest'} - ).get_response(self.test_auth) - self.assertEqual(resp.status_int, 204) - self.assertEqual(self.test_auth.app.calls, 6) - self.assertEqual(conn.calls, 1) - - def test_delete_account_success_missing_account_id_mapping(self): - conn = FakeConn(iter([ - # DELETE of storage account itself - ('204 No Content', {}, '')])) - self.test_auth.get_conn = lambda x: conn - self.test_auth.app = FakeApp(iter([ - # Account's container listing, checking for users - ('200 Ok', {'X-Container-Meta-Account-Id': 'AUTH_cfa'}, - json.dumps([ - {"name": ".services", "hash": "etag", "bytes": 112, - "content_type": "application/octet-stream", - "last_modified": "2010-12-03T17:16:27.618110"}])), - # Account's container listing, checking for users (continuation) - ('200 Ok', {'X-Container-Meta-Account-Id': 'AUTH_cfa'}, '[]'), - # GET the .services object - ('200 Ok', {}, json.dumps({"storage": {"default": "local", - "local": "http://127.0.0.1:8080/v1/AUTH_cfa"}})), - # DELETE the .services object - ('204 No Content', {}, ''), - # DELETE the .account_id mapping object - ('404 Not Found', {}, ''), - # DELETE the account container - ('204 No Content', {}, '')])) - resp = Request.blank('/auth/v2/act', - environ={'REQUEST_METHOD': 'DELETE', - 'swift.cache': FakeMemcache()}, - headers={'X-Auth-Admin-User': '.super_admin', - 'X-Auth-Admin-Key': 'supertest'} - ).get_response(self.test_auth) - self.assertEqual(resp.status_int, 204) - self.assertEqual(self.test_auth.app.calls, 6) - self.assertEqual(conn.calls, 1) - - def test_delete_account_success_missing_account_container_at_end(self): - conn = FakeConn(iter([ - # DELETE of storage account itself - ('204 No Content', {}, '')])) - self.test_auth.get_conn = lambda x: conn - self.test_auth.app = FakeApp(iter([ - # Account's container listing, checking for users - ('200 Ok', {'X-Container-Meta-Account-Id': 'AUTH_cfa'}, - json.dumps([ - {"name": ".services", "hash": "etag", "bytes": 112, - "content_type": "application/octet-stream", - "last_modified": "2010-12-03T17:16:27.618110"}])), - # Account's container listing, checking for users (continuation) - ('200 Ok', {'X-Container-Meta-Account-Id': 'AUTH_cfa'}, '[]'), - # GET the .services object - ('200 Ok', {}, json.dumps({"storage": {"default": "local", - "local": "http://127.0.0.1:8080/v1/AUTH_cfa"}})), - # DELETE the .services object - ('204 No Content', {}, ''), - # DELETE the .account_id mapping object - ('204 No Content', {}, ''), - # DELETE the account container - ('404 Not Found', {}, '')])) - resp = Request.blank('/auth/v2/act', - environ={'REQUEST_METHOD': 'DELETE', - 'swift.cache': FakeMemcache()}, - headers={'X-Auth-Admin-User': '.super_admin', - 'X-Auth-Admin-Key': 'supertest'} - ).get_response(self.test_auth) - self.assertEqual(resp.status_int, 204) - self.assertEqual(self.test_auth.app.calls, 6) - self.assertEqual(conn.calls, 1) - - def test_delete_account_fail_bad_creds(self): - self.test_auth.app = FakeApp(iter([ - # GET of user object - ('404 Not Found', {}, '')])) - resp = Request.blank('/auth/v2/act', - environ={'REQUEST_METHOD': 'DELETE', - 'swift.cache': FakeMemcache()}, - headers={'X-Auth-Admin-User': 'super:admin', - 'X-Auth-Admin-Key': 'supertest'}, - ).get_response(self.test_auth) - self.assertEqual(resp.status_int, 401) - self.assertEqual(self.test_auth.app.calls, 1) - - self.test_auth.app = FakeApp(iter([ - # GET of user object (account admin, but not reseller admin) - ('200 Ok', {}, json.dumps({"groups": [{"name": "act:adm"}, - {"name": "test"}, {"name": ".admin"}], - "auth": "plaintext:key"}))])) - resp = Request.blank('/auth/v2/act', - environ={'REQUEST_METHOD': 'DELETE', - 'swift.cache': FakeMemcache()}, - headers={'X-Auth-Admin-User': 'act:adm', - 'X-Auth-Admin-Key': 'key'}, - ).get_response(self.test_auth) - self.assertEqual(resp.status_int, 403) - self.assertEqual(self.test_auth.app.calls, 1) - - self.test_auth.app = FakeApp(iter([ - # GET of user object (regular user) - ('200 Ok', {}, json.dumps({"groups": [{"name": "act:usr"}, - {"name": "test"}], "auth": "plaintext:key"}))])) - resp = Request.blank('/auth/v2/act', - environ={'REQUEST_METHOD': 'DELETE', - 'swift.cache': FakeMemcache()}, - headers={'X-Auth-Admin-User': 'act:usr', - 'X-Auth-Admin-Key': 'key'}, - ).get_response(self.test_auth) - self.assertEqual(resp.status_int, 403) - self.assertEqual(self.test_auth.app.calls, 1) - - def test_delete_account_fail_invalid_account_name(self): - resp = Request.blank('/auth/v2/.act', - environ={'REQUEST_METHOD': 'DELETE'}, - headers={'X-Auth-Admin-User': '.super_admin', - 'X-Auth-Admin-Key': 'supertest'} - ).get_response(self.test_auth) - self.assertEqual(resp.status_int, 400) - - def test_delete_account_fail_not_found(self): - self.test_auth.app = FakeApp(iter([ - # Account's container listing, checking for users - ('404 Not Found', {}, '')])) - resp = Request.blank('/auth/v2/act', - environ={'REQUEST_METHOD': 'DELETE', - 'swift.cache': FakeMemcache()}, - headers={'X-Auth-Admin-User': '.super_admin', - 'X-Auth-Admin-Key': 'supertest'} - ).get_response(self.test_auth) - self.assertEqual(resp.status_int, 404) - self.assertEqual(self.test_auth.app.calls, 1) - - def test_delete_account_fail_not_found_concurrency(self): - self.test_auth.app = FakeApp(iter([ - # Account's container listing, checking for users - ('200 Ok', {'X-Container-Meta-Account-Id': 'AUTH_cfa'}, - json.dumps([ - {"name": ".services", "hash": "etag", "bytes": 112, - "content_type": "application/octet-stream", - "last_modified": "2010-12-03T17:16:27.618110"}])), - # Account's container listing, checking for users (continuation) - ('404 Not Found', {}, '')])) - resp = Request.blank('/auth/v2/act', - environ={'REQUEST_METHOD': 'DELETE', - 'swift.cache': FakeMemcache()}, - headers={'X-Auth-Admin-User': '.super_admin', - 'X-Auth-Admin-Key': 'supertest'} - ).get_response(self.test_auth) - self.assertEqual(resp.status_int, 404) - self.assertEqual(self.test_auth.app.calls, 2) - - def test_delete_account_fail_list_account(self): - self.test_auth.app = FakeApp(iter([ - # Account's container listing, checking for users - ('503 Service Unavailable', {}, '')])) - resp = Request.blank('/auth/v2/act', - environ={'REQUEST_METHOD': 'DELETE', - 'swift.cache': FakeMemcache()}, - headers={'X-Auth-Admin-User': '.super_admin', - 'X-Auth-Admin-Key': 'supertest'} - ).get_response(self.test_auth) - self.assertEqual(resp.status_int, 500) - self.assertEqual(self.test_auth.app.calls, 1) - - def test_delete_account_fail_list_account_concurrency(self): - self.test_auth.app = FakeApp(iter([ - # Account's container listing, checking for users - ('200 Ok', {'X-Container-Meta-Account-Id': 'AUTH_cfa'}, - json.dumps([ - {"name": ".services", "hash": "etag", "bytes": 112, - "content_type": "application/octet-stream", - "last_modified": "2010-12-03T17:16:27.618110"}])), - # Account's container listing, checking for users (continuation) - ('503 Service Unavailable', {}, '')])) - resp = Request.blank('/auth/v2/act', - environ={'REQUEST_METHOD': 'DELETE', - 'swift.cache': FakeMemcache()}, - headers={'X-Auth-Admin-User': '.super_admin', - 'X-Auth-Admin-Key': 'supertest'} - ).get_response(self.test_auth) - self.assertEqual(resp.status_int, 500) - self.assertEqual(self.test_auth.app.calls, 2) - - def test_delete_account_fail_has_users(self): - self.test_auth.app = FakeApp(iter([ - # Account's container listing, checking for users - ('200 Ok', {'X-Container-Meta-Account-Id': 'AUTH_cfa'}, - json.dumps([ - {"name": ".services", "hash": "etag", "bytes": 112, - "content_type": "application/octet-stream", - "last_modified": "2010-12-03T17:16:27.618110"}, - {"name": "tester", "hash": "etag", "bytes": 104, - "content_type": "application/octet-stream", - "last_modified": "2010-12-03T17:16:27.736680"}]))])) - resp = Request.blank('/auth/v2/act', - environ={'REQUEST_METHOD': 'DELETE', - 'swift.cache': FakeMemcache()}, - headers={'X-Auth-Admin-User': '.super_admin', - 'X-Auth-Admin-Key': 'supertest'} - ).get_response(self.test_auth) - self.assertEqual(resp.status_int, 409) - self.assertEqual(self.test_auth.app.calls, 1) - - def test_delete_account_fail_has_users2(self): - self.test_auth.app = FakeApp(iter([ - # Account's container listing, checking for users - ('200 Ok', {'X-Container-Meta-Account-Id': 'AUTH_cfa'}, - json.dumps([ - {"name": ".services", "hash": "etag", "bytes": 112, - "content_type": "application/octet-stream", - "last_modified": "2010-12-03T17:16:27.618110"}])), - # Account's container listing, checking for users (continuation) - ('200 Ok', {'X-Container-Meta-Account-Id': 'AUTH_cfa'}, - json.dumps([ - {"name": "tester", "hash": "etag", "bytes": 104, - "content_type": "application/octet-stream", - "last_modified": "2010-12-03T17:16:27.736680"}]))])) - resp = Request.blank('/auth/v2/act', - environ={'REQUEST_METHOD': 'DELETE', - 'swift.cache': FakeMemcache()}, - headers={'X-Auth-Admin-User': '.super_admin', - 'X-Auth-Admin-Key': 'supertest'} - ).get_response(self.test_auth) - self.assertEqual(resp.status_int, 409) - self.assertEqual(self.test_auth.app.calls, 2) - - def test_delete_account_fail_get_services(self): - self.test_auth.app = FakeApp(iter([ - # Account's container listing, checking for users - ('200 Ok', {'X-Container-Meta-Account-Id': 'AUTH_cfa'}, - json.dumps([ - {"name": ".services", "hash": "etag", "bytes": 112, - "content_type": "application/octet-stream", - "last_modified": "2010-12-03T17:16:27.618110"}])), - # Account's container listing, checking for users (continuation) - ('200 Ok', {'X-Container-Meta-Account-Id': 'AUTH_cfa'}, '[]'), - # GET the .services object - ('503 Service Unavailable', {}, '')])) - resp = Request.blank('/auth/v2/act', - environ={'REQUEST_METHOD': 'DELETE', - 'swift.cache': FakeMemcache()}, - headers={'X-Auth-Admin-User': '.super_admin', - 'X-Auth-Admin-Key': 'supertest'} - ).get_response(self.test_auth) - self.assertEqual(resp.status_int, 500) - self.assertEqual(self.test_auth.app.calls, 3) - - def test_delete_account_fail_delete_storage_account(self): - conn = FakeConn(iter([ - # DELETE of storage account itself - ('409 Conflict', {}, '')])) - self.test_auth.get_conn = lambda x: conn - self.test_auth.app = FakeApp(iter([ - # Account's container listing, checking for users - ('200 Ok', {'X-Container-Meta-Account-Id': 'AUTH_cfa'}, - json.dumps([ - {"name": ".services", "hash": "etag", "bytes": 112, - "content_type": "application/octet-stream", - "last_modified": "2010-12-03T17:16:27.618110"}])), - # Account's container listing, checking for users (continuation) - ('200 Ok', {'X-Container-Meta-Account-Id': 'AUTH_cfa'}, '[]'), - # GET the .services object - ('200 Ok', {}, json.dumps({"storage": {"default": "local", - "local": "http://127.0.0.1:8080/v1/AUTH_cfa"}}))])) - resp = Request.blank('/auth/v2/act', - environ={'REQUEST_METHOD': 'DELETE', - 'swift.cache': FakeMemcache()}, - headers={'X-Auth-Admin-User': '.super_admin', - 'X-Auth-Admin-Key': 'supertest'} - ).get_response(self.test_auth) - self.assertEqual(resp.status_int, 409) - self.assertEqual(self.test_auth.app.calls, 3) - self.assertEqual(conn.calls, 1) - - def test_delete_account_fail_delete_storage_account2(self): - conn = FakeConn(iter([ - # DELETE of storage account itself - ('204 No Content', {}, ''), - # DELETE of storage account itself - ('409 Conflict', {}, '')])) - self.test_auth.get_conn = lambda x: conn - self.test_auth.app = FakeApp(iter([ - # Account's container listing, checking for users - ('200 Ok', {'X-Container-Meta-Account-Id': 'AUTH_cfa'}, - json.dumps([ - {"name": ".services", "hash": "etag", "bytes": 112, - "content_type": "application/octet-stream", - "last_modified": "2010-12-03T17:16:27.618110"}])), - # Account's container listing, checking for users (continuation) - ('200 Ok', {'X-Container-Meta-Account-Id': 'AUTH_cfa'}, '[]'), - # GET the .services object - ('200 Ok', {}, json.dumps({"storage": {"default": "local", - "local": "http://127.0.0.1:8080/v1/AUTH_cfa", - "other": "http://127.0.0.1:8080/v1/AUTH_cfa2"}}))])) - resp = Request.blank('/auth/v2/act', - environ={'REQUEST_METHOD': 'DELETE', - 'swift.cache': FakeMemcache()}, - headers={'X-Auth-Admin-User': '.super_admin', - 'X-Auth-Admin-Key': 'supertest'} - ).get_response(self.test_auth) - self.assertEqual(resp.status_int, 500) - self.assertEqual(self.test_auth.app.calls, 3) - self.assertEqual(conn.calls, 2) - - def test_delete_account_fail_delete_storage_account3(self): - conn = FakeConn(iter([ - # DELETE of storage account itself - ('503 Service Unavailable', {}, '')])) - self.test_auth.get_conn = lambda x: conn - self.test_auth.app = FakeApp(iter([ - # Account's container listing, checking for users - ('200 Ok', {'X-Container-Meta-Account-Id': 'AUTH_cfa'}, - json.dumps([ - {"name": ".services", "hash": "etag", "bytes": 112, - "content_type": "application/octet-stream", - "last_modified": "2010-12-03T17:16:27.618110"}])), - # Account's container listing, checking for users (continuation) - ('200 Ok', {'X-Container-Meta-Account-Id': 'AUTH_cfa'}, '[]'), - # GET the .services object - ('200 Ok', {}, json.dumps({"storage": {"default": "local", - "local": "http://127.0.0.1:8080/v1/AUTH_cfa"}}))])) - resp = Request.blank('/auth/v2/act', - environ={'REQUEST_METHOD': 'DELETE', - 'swift.cache': FakeMemcache()}, - headers={'X-Auth-Admin-User': '.super_admin', - 'X-Auth-Admin-Key': 'supertest'} - ).get_response(self.test_auth) - self.assertEqual(resp.status_int, 500) - self.assertEqual(self.test_auth.app.calls, 3) - self.assertEqual(conn.calls, 1) - - def test_delete_account_fail_delete_storage_account4(self): - conn = FakeConn(iter([ - # DELETE of storage account itself - ('204 No Content', {}, ''), - # DELETE of storage account itself - ('503 Service Unavailable', {}, '')])) - self.test_auth.get_conn = lambda x: conn - self.test_auth.app = FakeApp(iter([ - # Account's container listing, checking for users - ('200 Ok', {'X-Container-Meta-Account-Id': 'AUTH_cfa'}, - json.dumps([ - {"name": ".services", "hash": "etag", "bytes": 112, - "content_type": "application/octet-stream", - "last_modified": "2010-12-03T17:16:27.618110"}])), - # Account's container listing, checking for users (continuation) - ('200 Ok', {'X-Container-Meta-Account-Id': 'AUTH_cfa'}, '[]'), - # GET the .services object - ('200 Ok', {}, json.dumps({"storage": {"default": "local", - "local": "http://127.0.0.1:8080/v1/AUTH_cfa", - "other": "http://127.0.0.1:8080/v1/AUTH_cfa2"}}))])) - resp = Request.blank('/auth/v2/act', - environ={'REQUEST_METHOD': 'DELETE', - 'swift.cache': FakeMemcache()}, - headers={'X-Auth-Admin-User': '.super_admin', - 'X-Auth-Admin-Key': 'supertest'} - ).get_response(self.test_auth) - self.assertEqual(resp.status_int, 500) - self.assertEqual(self.test_auth.app.calls, 3) - self.assertEqual(conn.calls, 2) - - def test_delete_account_fail_delete_services(self): - conn = FakeConn(iter([ - # DELETE of storage account itself - ('204 No Content', {}, '')])) - self.test_auth.get_conn = lambda x: conn - self.test_auth.app = FakeApp(iter([ - # Account's container listing, checking for users - ('200 Ok', {'X-Container-Meta-Account-Id': 'AUTH_cfa'}, - json.dumps([ - {"name": ".services", "hash": "etag", "bytes": 112, - "content_type": "application/octet-stream", - "last_modified": "2010-12-03T17:16:27.618110"}])), - # Account's container listing, checking for users (continuation) - ('200 Ok', {'X-Container-Meta-Account-Id': 'AUTH_cfa'}, '[]'), - # GET the .services object - ('200 Ok', {}, json.dumps({"storage": {"default": "local", - "local": "http://127.0.0.1:8080/v1/AUTH_cfa"}})), - # DELETE the .services object - ('503 Service Unavailable', {}, '')])) - resp = Request.blank('/auth/v2/act', - environ={'REQUEST_METHOD': 'DELETE', - 'swift.cache': FakeMemcache()}, - headers={'X-Auth-Admin-User': '.super_admin', - 'X-Auth-Admin-Key': 'supertest'} - ).get_response(self.test_auth) - self.assertEqual(resp.status_int, 500) - self.assertEqual(self.test_auth.app.calls, 4) - self.assertEqual(conn.calls, 1) - - def test_delete_account_fail_delete_account_id_mapping(self): - conn = FakeConn(iter([ - # DELETE of storage account itself - ('204 No Content', {}, '')])) - self.test_auth.get_conn = lambda x: conn - self.test_auth.app = FakeApp(iter([ - # Account's container listing, checking for users - ('200 Ok', {'X-Container-Meta-Account-Id': 'AUTH_cfa'}, - json.dumps([ - {"name": ".services", "hash": "etag", "bytes": 112, - "content_type": "application/octet-stream", - "last_modified": "2010-12-03T17:16:27.618110"}])), - # Account's container listing, checking for users (continuation) - ('200 Ok', {'X-Container-Meta-Account-Id': 'AUTH_cfa'}, '[]'), - # GET the .services object - ('200 Ok', {}, json.dumps({"storage": {"default": "local", - "local": "http://127.0.0.1:8080/v1/AUTH_cfa"}})), - # DELETE the .services object - ('204 No Content', {}, ''), - # DELETE the .account_id mapping object - ('503 Service Unavailable', {}, '')])) - resp = Request.blank('/auth/v2/act', - environ={'REQUEST_METHOD': 'DELETE', - 'swift.cache': FakeMemcache()}, - headers={'X-Auth-Admin-User': '.super_admin', - 'X-Auth-Admin-Key': 'supertest'} - ).get_response(self.test_auth) - self.assertEqual(resp.status_int, 500) - self.assertEqual(self.test_auth.app.calls, 5) - self.assertEqual(conn.calls, 1) - - def test_delete_account_fail_delete_account_container(self): - conn = FakeConn(iter([ - # DELETE of storage account itself - ('204 No Content', {}, '')])) - self.test_auth.get_conn = lambda x: conn - self.test_auth.app = FakeApp(iter([ - # Account's container listing, checking for users - ('200 Ok', {'X-Container-Meta-Account-Id': 'AUTH_cfa'}, - json.dumps([ - {"name": ".services", "hash": "etag", "bytes": 112, - "content_type": "application/octet-stream", - "last_modified": "2010-12-03T17:16:27.618110"}])), - # Account's container listing, checking for users (continuation) - ('200 Ok', {'X-Container-Meta-Account-Id': 'AUTH_cfa'}, '[]'), - # GET the .services object - ('200 Ok', {}, json.dumps({"storage": {"default": "local", - "local": "http://127.0.0.1:8080/v1/AUTH_cfa"}})), - # DELETE the .services object - ('204 No Content', {}, ''), - # DELETE the .account_id mapping object - ('204 No Content', {}, ''), - # DELETE the account container - ('503 Service Unavailable', {}, '')])) - resp = Request.blank('/auth/v2/act', - environ={'REQUEST_METHOD': 'DELETE', - 'swift.cache': FakeMemcache()}, - headers={'X-Auth-Admin-User': '.super_admin', - 'X-Auth-Admin-Key': 'supertest'} - ).get_response(self.test_auth) - self.assertEqual(resp.status_int, 500) - self.assertEqual(self.test_auth.app.calls, 6) - self.assertEqual(conn.calls, 1) - - def test_get_user_success(self): - self.test_auth.app = FakeApp(iter([ - # GET of user object - ('200 Ok', {}, json.dumps( - {"groups": [{"name": "act:usr"}, {"name": "act"}, - {"name": ".admin"}], - "auth": "plaintext:key"}))])) - resp = Request.blank('/auth/v2/act/usr', - headers={'X-Auth-Admin-User': '.super_admin', - 'X-Auth-Admin-Key': 'supertest'} - ).get_response(self.test_auth) - self.assertEqual(resp.status_int, 200) - self.assertEqual(resp.content_type, CONTENT_TYPE_JSON) - self.assertEqual(resp.body, json.dumps( - {"groups": [{"name": "act:usr"}, {"name": "act"}, - {"name": ".admin"}], - "auth": "plaintext:key"})) - self.assertEqual(self.test_auth.app.calls, 1) - - def test_get_user_fail_no_super_admin_key(self): - local_auth = auth.filter_factory({})(FakeApp(iter([ - # GET of user object (but we should never get here) - ('200 Ok', {}, json.dumps( - {"groups": [{"name": "act:usr"}, {"name": "act"}, - {"name": ".admin"}], - "auth": "plaintext:key"}))]))) - resp = Request.blank('/auth/v2/act/usr', - headers={'X-Auth-Admin-User': '.super_admin', - 'X-Auth-Admin-Key': 'supertest'} - ).get_response(local_auth) - self.assertEqual(resp.status_int, 404) - self.assertEqual(local_auth.app.calls, 0) - - def test_get_user_groups_success(self): - self.test_auth.app = FakeApp(iter([ - # GET of account container (list objects) - ('200 Ok', {'X-Container-Meta-Account-Id': 'AUTH_cfa'}, - json.dumps([ - {"name": ".services", "hash": "etag", "bytes": 112, - "content_type": "application/octet-stream", - "last_modified": "2010-12-03T17:16:27.618110"}, - {"name": "tester", "hash": "etag", "bytes": 104, - "content_type": "application/octet-stream", - "last_modified": "2010-12-03T17:16:27.736680"}, - {"name": "tester3", "hash": "etag", "bytes": 86, - "content_type": "application/octet-stream", - "last_modified": "2010-12-03T17:16:28.135530"}])), - # GET of user object - ('200 Ok', {}, json.dumps( - {"groups": [{"name": "act:tester"}, {"name": "act"}, - {"name": ".admin"}], - "auth": "plaintext:key"})), - # GET of user object - ('200 Ok', {}, json.dumps( - {"groups": [{"name": "act:tester3"}, {"name": "act"}], - "auth": "plaintext:key3"})), - # GET of account container (list objects continuation) - ('200 Ok', {'X-Container-Meta-Account-Id': 'AUTH_cfa'}, '[]')])) - resp = Request.blank('/auth/v2/act/.groups', - headers={'X-Auth-Admin-User': '.super_admin', - 'X-Auth-Admin-Key': 'supertest'} - ).get_response(self.test_auth) - self.assertEqual(resp.status_int, 200) - self.assertEqual(resp.content_type, CONTENT_TYPE_JSON) - self.assertEqual(resp.body, json.dumps( - {"groups": [{"name": ".admin"}, {"name": "act"}, - {"name": "act:tester"}, {"name": "act:tester3"}]})) - self.assertEqual(self.test_auth.app.calls, 4) - - def test_get_user_groups_success2(self): - self.test_auth.app = FakeApp(iter([ - # GET of account container (list objects) - ('200 Ok', {'X-Container-Meta-Account-Id': 'AUTH_cfa'}, - json.dumps([ - {"name": ".services", "hash": "etag", "bytes": 112, - "content_type": "application/octet-stream", - "last_modified": "2010-12-03T17:16:27.618110"}, - {"name": "tester", "hash": "etag", "bytes": 104, - "content_type": "application/octet-stream", - "last_modified": "2010-12-03T17:16:27.736680"}])), - # GET of user object - ('200 Ok', {}, json.dumps( - {"groups": [{"name": "act:tester"}, {"name": "act"}, - {"name": ".admin"}], - "auth": "plaintext:key"})), - # GET of account container (list objects continuation) - ('200 Ok', {'X-Container-Meta-Account-Id': 'AUTH_cfa'}, - json.dumps([ - {"name": "tester3", "hash": "etag", "bytes": 86, - "content_type": "application/octet-stream", - "last_modified": "2010-12-03T17:16:28.135530"}])), - # GET of user object - ('200 Ok', {}, json.dumps( - {"groups": [{"name": "act:tester3"}, {"name": "act"}], - "auth": "plaintext:key3"})), - # GET of account container (list objects continuation) - ('200 Ok', {'X-Container-Meta-Account-Id': 'AUTH_cfa'}, '[]')])) - resp = Request.blank('/auth/v2/act/.groups', - headers={'X-Auth-Admin-User': '.super_admin', - 'X-Auth-Admin-Key': 'supertest'} - ).get_response(self.test_auth) - self.assertEqual(resp.status_int, 200) - self.assertEqual(resp.content_type, CONTENT_TYPE_JSON) - self.assertEqual(resp.body, json.dumps( - {"groups": [{"name": ".admin"}, {"name": "act"}, - {"name": "act:tester"}, {"name": "act:tester3"}]})) - self.assertEqual(self.test_auth.app.calls, 5) - - def test_get_user_fail_invalid_account(self): - resp = Request.blank('/auth/v2/.invalid/usr', - headers={'X-Auth-Admin-User': '.super_admin', - 'X-Auth-Admin-Key': 'supertest'} - ).get_response(self.test_auth) - self.assertEqual(resp.status_int, 400) - - def test_get_user_fail_invalid_user(self): - resp = Request.blank('/auth/v2/act/.invalid', - headers={'X-Auth-Admin-User': '.super_admin', - 'X-Auth-Admin-Key': 'supertest'} - ).get_response(self.test_auth) - self.assertEqual(resp.status_int, 400) - - def test_get_user_fail_bad_creds(self): - self.test_auth.app = FakeApp(iter([ - # GET of user object - ('404 Not Found', {}, '')])) - resp = Request.blank('/auth/v2/act/usr', - headers={'X-Auth-Admin-User': 'super:admin', - 'X-Auth-Admin-Key': 'supertest'}, - ).get_response(self.test_auth) - self.assertEqual(resp.status_int, 401) - self.assertEqual(self.test_auth.app.calls, 1) - - self.test_auth.app = FakeApp(iter([ - # GET of user object (regular user) - ('200 Ok', {}, json.dumps({"groups": [{"name": "act:usr"}, - {"name": "test"}], "auth": "plaintext:key"}))])) - resp = Request.blank('/auth/v2/act/usr', - headers={'X-Auth-Admin-User': 'act:usr', - 'X-Auth-Admin-Key': 'key'}, - ).get_response(self.test_auth) - self.assertEqual(resp.status_int, 403) - self.assertEqual(self.test_auth.app.calls, 1) - - def test_get_user_account_admin_success(self): - self.test_auth.app = FakeApp(iter([ - # GET of user object (account admin, but not reseller admin) - ('200 Ok', {}, json.dumps({"groups": [{"name": "act:adm"}, - {"name": "test"}, {"name": ".admin"}], - "auth": "plaintext:key"})), - # GET of requested user object - ('200 Ok', {}, json.dumps( - {"groups": [{"name": "act:usr"}, {"name": "act"}], - "auth": "plaintext:key"}))])) - resp = Request.blank('/auth/v2/act/usr', - headers={'X-Auth-Admin-User': 'act:adm', - 'X-Auth-Admin-Key': 'key'} - ).get_response(self.test_auth) - self.assertEqual(resp.status_int, 200) - self.assertEqual(resp.content_type, CONTENT_TYPE_JSON) - self.assertEqual(resp.body, json.dumps( - {"groups": [{"name": "act:usr"}, {"name": "act"}], - "auth": "plaintext:key"})) - self.assertEqual(self.test_auth.app.calls, 2) - - def test_get_user_account_admin_fail_getting_account_admin(self): - self.test_auth.app = FakeApp(iter([ - # GET of user object (account admin check) - ('200 Ok', {}, json.dumps({"groups": [{"name": "act:adm"}, - {"name": "test"}, {"name": ".admin"}], - "auth": "plaintext:key"})), - # GET of requested user object [who is an .admin as well] - ('200 Ok', {}, json.dumps( - {"groups": [{"name": "act:usr"}, {"name": "act"}, - {"name": ".admin"}], - "auth": "plaintext:key"})), - # GET of user object (reseller admin check [and fail here]) - ('200 Ok', {}, json.dumps({"groups": [{"name": "act:adm"}, - {"name": "test"}, {"name": ".admin"}], - "auth": "plaintext:key"}))])) - resp = Request.blank('/auth/v2/act/usr', - headers={'X-Auth-Admin-User': 'act:adm', - 'X-Auth-Admin-Key': 'key'} - ).get_response(self.test_auth) - self.assertEqual(resp.status_int, 403) - self.assertEqual(self.test_auth.app.calls, 3) - - def test_get_user_account_admin_fail_getting_reseller_admin(self): - self.test_auth.app = FakeApp(iter([ - # GET of user object (account admin check) - ('200 Ok', {}, json.dumps({"groups": [{"name": "act:adm"}, - {"name": "test"}, {"name": ".admin"}], - "auth": "plaintext:key"})), - # GET of requested user object [who is a .reseller_admin] - ('200 Ok', {}, json.dumps( - {"groups": [{"name": "act:usr"}, {"name": "act"}, - {"name": ".reseller_admin"}], - "auth": "plaintext:key"}))])) - resp = Request.blank('/auth/v2/act/usr', - headers={'X-Auth-Admin-User': 'act:adm', - 'X-Auth-Admin-Key': 'key'} - ).get_response(self.test_auth) - self.assertEqual(resp.status_int, 403) - self.assertEqual(self.test_auth.app.calls, 2) - - def test_get_user_reseller_admin_fail_getting_reseller_admin(self): - self.test_auth.app = FakeApp(iter([ - # GET of user object (account admin check) - ('200 Ok', {}, json.dumps({"groups": [{"name": "act:adm"}, - {"name": "test"}, {"name": ".reseller_admin"}], - "auth": "plaintext:key"})), - # GET of requested user object [who also is a .reseller_admin] - ('200 Ok', {}, json.dumps( - {"groups": [{"name": "act:usr"}, {"name": "act"}, - {"name": ".reseller_admin"}], - "auth": "plaintext:key"}))])) - resp = Request.blank('/auth/v2/act/usr', - headers={'X-Auth-Admin-User': 'act:adm', - 'X-Auth-Admin-Key': 'key'} - ).get_response(self.test_auth) - self.assertEqual(resp.status_int, 403) - self.assertEqual(self.test_auth.app.calls, 2) - - def test_get_user_super_admin_succeed_getting_reseller_admin(self): - self.test_auth.app = FakeApp(iter([ - # GET of requested user object - ('200 Ok', {}, json.dumps( - {"groups": [{"name": "act:usr"}, {"name": "act"}, - {"name": ".reseller_admin"}], - "auth": "plaintext:key"}))])) - resp = Request.blank('/auth/v2/act/usr', - headers={'X-Auth-Admin-User': '.super_admin', - 'X-Auth-Admin-Key': 'supertest'} - ).get_response(self.test_auth) - self.assertEqual(resp.status_int, 200) - self.assertEqual(resp.content_type, CONTENT_TYPE_JSON) - self.assertEqual(resp.body, json.dumps( - {"groups": [{"name": "act:usr"}, {"name": "act"}, - {"name": ".reseller_admin"}], - "auth": "plaintext:key"})) - self.assertEqual(self.test_auth.app.calls, 1) - - def test_get_user_groups_not_found(self): - self.test_auth.app = FakeApp(iter([ - # GET of account container (list objects) - ('404 Not Found', {}, '')])) - resp = Request.blank('/auth/v2/act/.groups', - headers={'X-Auth-Admin-User': '.super_admin', - 'X-Auth-Admin-Key': 'supertest'} - ).get_response(self.test_auth) - self.assertEqual(resp.status_int, 404) - self.assertEqual(self.test_auth.app.calls, 1) - - def test_get_user_groups_fail_listing(self): - self.test_auth.app = FakeApp(iter([ - # GET of account container (list objects) - ('503 Service Unavailable', {}, '')])) - resp = Request.blank('/auth/v2/act/.groups', - headers={'X-Auth-Admin-User': '.super_admin', - 'X-Auth-Admin-Key': 'supertest'} - ).get_response(self.test_auth) - self.assertEqual(resp.status_int, 500) - self.assertEqual(self.test_auth.app.calls, 1) - - def test_get_user_groups_fail_get_user(self): - self.test_auth.app = FakeApp(iter([ - # GET of account container (list objects) - ('200 Ok', {'X-Container-Meta-Account-Id': 'AUTH_cfa'}, - json.dumps([ - {"name": ".services", "hash": "etag", "bytes": 112, - "content_type": "application/octet-stream", - "last_modified": "2010-12-03T17:16:27.618110"}, - {"name": "tester", "hash": "etag", "bytes": 104, - "content_type": "application/octet-stream", - "last_modified": "2010-12-03T17:16:27.736680"}, - {"name": "tester3", "hash": "etag", "bytes": 86, - "content_type": "application/octet-stream", - "last_modified": "2010-12-03T17:16:28.135530"}])), - # GET of user object - ('503 Service Unavailable', {}, '')])) - resp = Request.blank('/auth/v2/act/.groups', - headers={'X-Auth-Admin-User': '.super_admin', - 'X-Auth-Admin-Key': 'supertest'} - ).get_response(self.test_auth) - self.assertEqual(resp.status_int, 500) - self.assertEqual(self.test_auth.app.calls, 2) - - def test_get_user_not_found(self): - self.test_auth.app = FakeApp(iter([ - # GET of user object - ('404 Not Found', {}, '')])) - resp = Request.blank('/auth/v2/act/usr', - headers={'X-Auth-Admin-User': '.super_admin', - 'X-Auth-Admin-Key': 'supertest'} - ).get_response(self.test_auth) - self.assertEqual(resp.status_int, 404) - self.assertEqual(self.test_auth.app.calls, 1) - - def test_get_user_fail(self): - self.test_auth.app = FakeApp(iter([ - # GET of user object - ('503 Service Unavailable', {}, '')])) - resp = Request.blank('/auth/v2/act/usr', - headers={'X-Auth-Admin-User': '.super_admin', - 'X-Auth-Admin-Key': 'supertest', - 'X-Auth-User-Key': 'key'} - ).get_response(self.test_auth) - self.assertEqual(resp.status_int, 500) - self.assertEqual(self.test_auth.app.calls, 1) - - def test_put_user_fail_invalid_account(self): - resp = Request.blank('/auth/v2/.invalid/usr', - environ={'REQUEST_METHOD': 'PUT'}, - headers={'X-Auth-Admin-User': '.super_admin', - 'X-Auth-Admin-Key': 'supertest', - 'X-Auth-User-Key': 'key'} - ).get_response(self.test_auth) - self.assertEqual(resp.status_int, 400) - - def test_put_user_fail_invalid_user(self): - resp = Request.blank('/auth/v2/act/.usr', - environ={'REQUEST_METHOD': 'PUT'}, - headers={'X-Auth-Admin-User': '.super_admin', - 'X-Auth-Admin-Key': 'supertest', - 'X-Auth-User-Key': 'key'} - ).get_response(self.test_auth) - self.assertEqual(resp.status_int, 400) - - def test_put_user_fail_no_user_key(self): - resp = Request.blank('/auth/v2/act/usr', - environ={'REQUEST_METHOD': 'PUT'}, - headers={'X-Auth-Admin-User': '.super_admin', - 'X-Auth-Admin-Key': 'supertest'} - ).get_response(self.test_auth) - self.assertEqual(resp.status_int, 400) - - def test_put_user_reseller_admin_fail_bad_creds(self): - self.test_auth.app = FakeApp(iter([ - # Checking if user is changing his own key. This is called. - ('200 Ok', {}, json.dumps({"groups": [{"name": "act:rdm"}, - {"name": "test"}, {"name": ".admin"}, - {"name": ".reseller_admin"}], "auth": "plaintext:key"})), - # GET of user object (reseller admin) - # This shouldn't actually get called, checked - # below - ('200 Ok', {}, json.dumps({"groups": [{"name": "act:rdm"}, - {"name": "test"}, {"name": ".admin"}, - {"name": ".reseller_admin"}], "auth": "plaintext:key"}))])) - resp = Request.blank('/auth/v2/act/usr', - environ={'REQUEST_METHOD': 'PUT'}, - headers={'X-Auth-Admin-User': 'act:rdm', - 'X-Auth-Admin-Key': 'key', - 'X-Auth-User-Key': 'key', - 'X-Auth-User-Reseller-Admin': 'true'} - ).get_response(self.test_auth) - self.assertEqual(resp.status_int, 401) - self.assertEqual(self.test_auth.app.calls, 1) - - self.test_auth.app = FakeApp(iter([ - # Checking if user is changing his own key. This is called. - ('200 Ok', {}, json.dumps({"groups": [{"name": "act:adm"}, - {"name": "test"}, {"name": ".admin"}], - "auth": "plaintext:key"})), - # GET of user object (account admin, but not reseller admin) - # This shouldn't actually get called, checked - # below - ('200 Ok', {}, json.dumps({"groups": [{"name": "act:adm"}, - {"name": "test"}, {"name": ".admin"}], - "auth": "plaintext:key"}))])) - resp = Request.blank('/auth/v2/act/usr', - environ={'REQUEST_METHOD': 'PUT'}, - headers={'X-Auth-Admin-User': 'act:adm', - 'X-Auth-Admin-Key': 'key', - 'X-Auth-User-Key': 'key', - 'X-Auth-User-Reseller-Admin': 'true'} - ).get_response(self.test_auth) - self.assertEqual(resp.status_int, 401) - self.assertEqual(self.test_auth.app.calls, 1) - - self.test_auth.app = FakeApp(iter([ - # Checking if user is changing his own key. This is called. - ('200 Ok', {}, json.dumps({"groups": [{"name": "act:usr"}, - {"name": "test"}], "auth": "plaintext:key"})), - # GET of user object (regular user) - # This shouldn't actually get called, checked - # below - ('200 Ok', {}, json.dumps({"groups": [{"name": "act:usr"}, - {"name": "test"}], "auth": "plaintext:key"}))])) - resp = Request.blank('/auth/v2/act/usr', - environ={'REQUEST_METHOD': 'PUT'}, - headers={'X-Auth-Admin-User': 'act:adm', - 'X-Auth-Admin-Key': 'key', - 'X-Auth-User-Key': 'key', - 'X-Auth-User-Reseller-Admin': 'true'} - ).get_response(self.test_auth) - self.assertEqual(resp.status_int, 401) - self.assertEqual(self.test_auth.app.calls, 1) - - def test_put_user_account_admin_fail_bad_creds(self): - self.test_auth.app = FakeApp(iter([ - # GET of user object (account admin, but wrong account) - ('200 Ok', {}, json.dumps({"groups": [{"name": "act2:adm"}, - {"name": "test"}, {"name": ".admin"}], - "auth": "plaintext:key"})), - # Checking if user is changing his own key. - ('200 Ok', {}, json.dumps({"groups": [{"name": "act:adm"}, - {"name": "test"}, {"name": ".admin"}], - "auth": "plaintext:key"}))])) - resp = Request.blank('/auth/v2/act/usr', - environ={'REQUEST_METHOD': 'PUT'}, - headers={'X-Auth-Admin-User': 'act2:adm', - 'X-Auth-Admin-Key': 'key', - 'X-Auth-User-Key': 'key', - 'X-Auth-User-Admin': 'true'} - ).get_response(self.test_auth) - self.assertEqual(resp.status_int, 403) - self.assertEqual(self.test_auth.app.calls, 2) - - self.test_auth.app = FakeApp(iter([ - # GET of user object (regular user) - ('200 Ok', {}, json.dumps({"groups": [{"name": "act:usr"}, - {"name": "test"}], "auth": "plaintext:key"})), - # Checking if user is changing his own key. - ('200 Ok', {}, json.dumps({"groups": [{"name": "act:usr"}, - {"name": "test"}], "auth": "plaintext:key"}))])) - resp = Request.blank('/auth/v2/act/usr', - environ={'REQUEST_METHOD': 'PUT'}, - headers={'X-Auth-Admin-User': 'act:usr', - 'X-Auth-Admin-Key': 'key', - 'X-Auth-User-Key': 'key', - 'X-Auth-User-Admin': 'true'} - ).get_response(self.test_auth) - self.assertEqual(resp.status_int, 403) - self.assertEqual(self.test_auth.app.calls, 2) - - def test_put_user_regular_fail_bad_creds(self): - self.test_auth.app = FakeApp(iter([ - # GET of user object (account admin, but wrong - # account) - ('200 Ok', {}, json.dumps({"groups": [{"name": "act2:adm"}, - {"name": "test"}, {"name": ".admin"}], - "auth": "plaintext:key"})), - # Checking if user is changing his own key. - ('200 Ok', {}, json.dumps({"groups": [{"name": "act:adm"}, - {"name": "test"}, {"name": ".admin"}], - "auth": "plaintext:key"}))])) - resp = Request.blank('/auth/v2/act/usr', - environ={'REQUEST_METHOD': 'PUT'}, - headers={'X-Auth-Admin-User': 'act2:adm', - 'X-Auth-Admin-Key': 'key', - 'X-Auth-User-Key': 'key'} - ).get_response(self.test_auth) - self.assertEqual(resp.status_int, 403) - self.assertEqual(self.test_auth.app.calls, 2) - - self.test_auth.app = FakeApp(iter([ - # GET of user object (regular user) - ('200 Ok', {}, json.dumps({"groups": [{"name": "act:usr"}, - {"name": "test"}], "auth": "plaintext:key"})), - # Checking if user is changing his own key. - ('200 Ok', {}, json.dumps({"groups": [{"name": "act:usr"}, - {"name": "test"}], "auth": "plaintext:key"}))])) - resp = Request.blank('/auth/v2/act2/usr', - environ={'REQUEST_METHOD': 'PUT'}, - headers={'X-Auth-Admin-User': 'act:usr', - 'X-Auth-Admin-Key': 'key', - 'X-Auth-User-Key': 'key'} - ).get_response(self.test_auth) - self.assertEqual(resp.status_int, 403) - self.assertEqual(self.test_auth.app.calls, 2) - - def test_put_user_regular_success(self): - self.test_auth.app = FakeApp(iter([ - ('200 Ok', {'X-Container-Meta-Account-Id': 'AUTH_cfa'}, ''), - # PUT of user object - ('201 Created', {}, '')])) - resp = Request.blank('/auth/v2/act/usr', - environ={'REQUEST_METHOD': 'PUT'}, - headers={'X-Auth-Admin-User': '.super_admin', - 'X-Auth-Admin-Key': 'supertest', - 'X-Auth-User-Key': 'key'} - ).get_response(self.test_auth) - self.assertEqual(resp.status_int, 201) - self.assertEqual(self.test_auth.app.calls, 2) - self.assertEqual(json.loads(self.test_auth.app.request.body), - {"groups": [{"name": "act:usr"}, {"name": "act"}], - "auth": "plaintext:key"}) - - def test_put_user_special_chars_success(self): - self.test_auth.app = FakeApp(iter([ - ('200 Ok', {'X-Container-Meta-Account-Id': 'AUTH_cfa'}, ''), - # PUT of user object - ('201 Created', {}, '')])) - resp = Request.blank('/auth/v2/act/u_s-r', - environ={'REQUEST_METHOD': 'PUT'}, - headers={'X-Auth-Admin-User': '.super_admin', - 'X-Auth-Admin-Key': 'supertest', - 'X-Auth-User-Key': 'key'} - ).get_response(self.test_auth) - self.assertEqual(resp.status_int, 201) - self.assertEqual(self.test_auth.app.calls, 2) - self.assertEqual(json.loads(self.test_auth.app.request.body), - {"groups": [{"name": "act:u_s-r"}, {"name": "act"}], - "auth": "plaintext:key"}) - - def test_put_user_account_admin_success(self): - self.test_auth.app = FakeApp(iter([ - ('200 Ok', {'X-Container-Meta-Account-Id': 'AUTH_cfa'}, ''), - # PUT of user object - ('201 Created', {}, '')])) - resp = Request.blank('/auth/v2/act/usr', - environ={'REQUEST_METHOD': 'PUT'}, - headers={'X-Auth-Admin-User': '.super_admin', - 'X-Auth-Admin-Key': 'supertest', - 'X-Auth-User-Key': 'key', - 'X-Auth-User-Admin': 'true'} - ).get_response(self.test_auth) - self.assertEqual(resp.status_int, 201) - self.assertEqual(self.test_auth.app.calls, 2) - self.assertEqual(json.loads(self.test_auth.app.request.body), - {"groups": [{"name": "act:usr"}, {"name": "act"}, - {"name": ".admin"}], - "auth": "plaintext:key"}) - - def test_put_user_reseller_admin_success(self): - self.test_auth.app = FakeApp(iter([ - ('200 Ok', {'X-Container-Meta-Account-Id': 'AUTH_cfa'}, ''), - # PUT of user object - ('201 Created', {}, '')])) - resp = Request.blank('/auth/v2/act/usr', - environ={'REQUEST_METHOD': 'PUT'}, - headers={'X-Auth-Admin-User': '.super_admin', - 'X-Auth-Admin-Key': 'supertest', - 'X-Auth-User-Key': 'key', - 'X-Auth-User-Reseller-Admin': 'true'} - ).get_response(self.test_auth) - self.assertEqual(resp.status_int, 201) - self.assertEqual(self.test_auth.app.calls, 2) - self.assertEqual(json.loads(self.test_auth.app.request.body), - {"groups": [{"name": "act:usr"}, {"name": "act"}, - {"name": ".admin"}, {"name": ".reseller_admin"}], - "auth": "plaintext:key"}) - - def test_put_user_fail_not_found(self): - self.test_auth.app = FakeApp(iter([ - ('200 Ok', {'X-Container-Meta-Account-Id': 'AUTH_cfa'}, ''), - # PUT of user object - ('404 Not Found', {}, '')])) - resp = Request.blank('/auth/v2/act/usr', - environ={'REQUEST_METHOD': 'PUT'}, - headers={'X-Auth-Admin-User': '.super_admin', - 'X-Auth-Admin-Key': 'supertest', - 'X-Auth-User-Key': 'key'} - ).get_response(self.test_auth) - self.assertEqual(resp.status_int, 404) - self.assertEqual(self.test_auth.app.calls, 2) - - def test_put_user_fail(self): - self.test_auth.app = FakeApp(iter([ - # PUT of user object - ('503 Service Unavailable', {}, '')])) - resp = Request.blank('/auth/v2/act/usr', - environ={'REQUEST_METHOD': 'PUT'}, - headers={'X-Auth-Admin-User': '.super_admin', - 'X-Auth-Admin-Key': 'supertest', - 'X-Auth-User-Key': 'key'} - ).get_response(self.test_auth) - self.assertEqual(resp.status_int, 500) - self.assertEqual(self.test_auth.app.calls, 1) - - def test_put_user_key_hash(self): - key_hash = ("sha512:aSm0jEeqIp46T5YLZy1r8+cXs/Xzs1S4VUwVauhBs44=$ef" - "7332ec1288bf69c75682eb8d459d5a84baa7e43f45949c242a9af9" - "7130ef16ac361fe1aa33a789e218122b83c54ef1923fc015080741" - "ca21f6187329f6cb7a") - - self.test_auth.app = FakeApp(iter([ - ('200 Ok', {'X-Container-Meta-Account-Id': 'AUTH_cfa'}, ''), - # PUT of user object - ('201 Created', {}, '')])) - resp = Request.blank('/auth/v2/act/usr', - environ={'REQUEST_METHOD': 'PUT'}, - headers={'X-Auth-Admin-User': '.super_admin', - 'X-Auth-Admin-Key': 'supertest', - 'X-Auth-User-Key-Hash': quote(key_hash)} - ).get_response(self.test_auth) - self.assertEqual(resp.status_int, 201) - self.assertEqual(self.test_auth.app.calls, 2) - self.assertEqual(json.loads(self.test_auth.app.request.body), - {"groups": [{"name": "act:usr"}, {"name": "act"}], - "auth": key_hash}) - - def test_put_user_key_hash_wrong_type(self): - key_hash = "wrong_auth_type:1234" - - self.test_auth.app = FakeApp(iter([ - ('200 Ok', {'X-Container-Meta-Account-Id': 'AUTH_cfa'}, ''), - # PUT of user object - ('201 Created', {}, '')])) - resp = Request.blank('/auth/v2/act/usr', - environ={'REQUEST_METHOD': 'PUT'}, - headers={'X-Auth-Admin-User': '.super_admin', - 'X-Auth-Admin-Key': 'supertest', - 'X-Auth-User-Key-Hash': quote(key_hash)} - ).get_response(self.test_auth) - self.assertEqual(resp.status_int, 400) - self.assertEqual(self.test_auth.app.calls, 0) - - def test_put_user_key_hash_wrong_format(self): - key_hash = "1234" - - self.test_auth.app = FakeApp(iter([ - ('200 Ok', {'X-Container-Meta-Account-Id': 'AUTH_cfa'}, ''), - # PUT of user object - ('201 Created', {}, '')])) - resp = Request.blank('/auth/v2/act/usr', - environ={'REQUEST_METHOD': 'PUT'}, - headers={'X-Auth-Admin-User': '.super_admin', - 'X-Auth-Admin-Key': 'supertest', - 'X-Auth-User-Key-Hash': quote(key_hash)} - ).get_response(self.test_auth) - self.assertEqual(resp.status_int, 400) - self.assertEqual(self.test_auth.app.calls, 0) - - def test_delete_user_bad_creds(self): - self.test_auth.app = FakeApp(iter([ - ('200 Ok', {}, json.dumps({"groups": [{"name": "act2:adm"}, - {"name": "test"}, {"name": ".admin"}], - "auth": "plaintext:key"})), - # GET of user object (account admin, but wrong account) - ('200 Ok', {}, json.dumps({"groups": [{"name": "act2:adm"}, - {"name": "test"}, {"name": ".admin"}], - "auth": "plaintext:key"}))])) - resp = Request.blank('/auth/v2/act/usr', - environ={'REQUEST_METHOD': 'DELETE'}, - headers={'X-Auth-Admin-User': 'act2:adm', - 'X-Auth-Admin-Key': 'key'} - ).get_response(self.test_auth) - self.assertEqual(resp.status_int, 403) - self.assertEqual(self.test_auth.app.calls, 2) - - self.test_auth.app = FakeApp(iter([ - ('200 Ok', {}, json.dumps({"groups": [{"name": "act:usr"}, - {"name": "test"}], "auth": "plaintext:key"})), - # GET of user object (regular user) - ('200 Ok', {}, json.dumps({"groups": [{"name": "act:usr"}, - {"name": "test"}], "auth": "plaintext:key"}))])) - resp = Request.blank('/auth/v2/act/usr', - environ={'REQUEST_METHOD': 'DELETE'}, - headers={'X-Auth-Admin-User': 'act:usr', - 'X-Auth-Admin-Key': 'key'} - ).get_response(self.test_auth) - self.assertEqual(resp.status_int, 403) - self.assertEqual(self.test_auth.app.calls, 2) - - def test_delete_reseller_admin_user_fail(self): - self.test_auth.app = FakeApp(iter([ - # is user being deleted a reseller_admin - ('200 Ok', {}, json.dumps({"groups": [{"name": "act2:re_adm"}, - {"name": "act2"}, {"name": ".admin"}, - {"name": ".reseller_admin"}], "auth": "plaintext:key"})), - # GET of user object - ('200 Ok', {}, json.dumps({"groups": [{"name": "act2:adm"}, - {"name": "act2"}, {"name": ".admin"}], - "auth": "plaintext:key"}))])) - - resp = Request.blank('/auth/v2/act2/re_adm', - environ={ - 'REQUEST_METHOD': 'DELETE'}, - headers={ - 'X-Auth-Admin-User': - 'act2:adm', - 'X-Auth-Admin-Key': 'key'} - ).get_response(self.test_auth) - self.assertEqual(resp.status_int, 403) - self.assertEqual(self.test_auth.app.calls, 1) - - def test_delete_reseller_admin_user_success(self): - self.test_auth.app = FakeApp(iter([ - # is user being deleted a reseller_admin - ('200 Ok', {}, json.dumps({"groups": [{"name": "act2:re_adm"}, - {"name": "act2"}, {"name": ".admin"}, - {"name": ".reseller_admin"}], "auth": "plaintext:key"})), - # HEAD of user object - ('200 Ok', - {'X-Object-Meta-Auth-Token': 'AUTH_tk'}, ''), - # DELETE of token - ('204 No Content', {}, ''), - # DELETE of user object - ('204 No Content', {}, '')])) - - resp = Request.blank('/auth/v2/act2/re_adm', - environ={ - 'REQUEST_METHOD': 'DELETE'}, - headers={ - 'X-Auth-Admin-User': - '.super_admin', - 'X-Auth-Admin-Key': 'supertest'} - ).get_response(self.test_auth) - self.assertEqual(resp.status_int, 204) - self.assertEqual(self.test_auth.app.calls, 4) - - def test_delete_user_invalid_account(self): - resp = Request.blank('/auth/v2/.invalid/usr', - environ={'REQUEST_METHOD': 'DELETE'}, - headers={'X-Auth-Admin-User': '.super_admin', - 'X-Auth-Admin-Key': 'supertest'} - ).get_response(self.test_auth) - self.assertEqual(resp.status_int, 400) - - def test_delete_user_invalid_user(self): - resp = Request.blank('/auth/v2/act/.invalid', - environ={'REQUEST_METHOD': 'DELETE'}, - headers={'X-Auth-Admin-User': '.super_admin', - 'X-Auth-Admin-Key': 'supertest'} - ).get_response(self.test_auth) - self.assertEqual(resp.status_int, 400) - - def test_delete_user_not_found(self): - self.test_auth.app = FakeApp(iter([ - # HEAD of user object - ('404 Not Found', {}, '')])) - resp = Request.blank('/auth/v2/act/usr', - environ={'REQUEST_METHOD': 'DELETE'}, - headers={'X-Auth-Admin-User': '.super_admin', - 'X-Auth-Admin-Key': 'supertest'} - ).get_response(self.test_auth) - self.assertEqual(resp.status_int, 404) - self.assertEqual(self.test_auth.app.calls, 1) - - def test_delete_user_fail_head_user(self): - self.test_auth.app = FakeApp(iter([ - # HEAD of user object - ('503 Service Unavailable', {}, '')])) - resp = Request.blank('/auth/v2/act/usr', - environ={'REQUEST_METHOD': 'DELETE'}, - headers={'X-Auth-Admin-User': '.super_admin', - 'X-Auth-Admin-Key': 'supertest'} - ).get_response(self.test_auth) - self.assertEqual(resp.status_int, 500) - self.assertEqual(self.test_auth.app.calls, 1) - - def test_delete_user_fail_delete_token(self): - self.test_auth.app = FakeApp(iter([ - # is user reseller_admin - ('200 Ok', {}, json.dumps({"groups": [{"name": "act:usr"}, - {"name": "test"}], "auth": "plaintext:key"})), - # HEAD of user object - ('200 Ok', {'X-Object-Meta-Auth-Token': 'AUTH_tk'}, ''), - # DELETE of token - ('503 Service Unavailable', {}, '')])) - resp = Request.blank('/auth/v2/act/usr', - environ={'REQUEST_METHOD': 'DELETE'}, - headers={'X-Auth-Admin-User': '.super_admin', - 'X-Auth-Admin-Key': 'supertest'} - ).get_response(self.test_auth) - self.assertEqual(resp.status_int, 500) - self.assertEqual(self.test_auth.app.calls, 3) - - def test_delete_user_fail_delete_user(self): - self.test_auth.app = FakeApp(iter([ - # is user reseller_admin - ('200 Ok', {}, json.dumps({"groups": [{"name": "act:usr"}, - {"name": "test"}], "auth": "plaintext:key"})), - # HEAD of user object - ('200 Ok', {'X-Object-Meta-Auth-Token': 'AUTH_tk'}, ''), - # DELETE of token - ('204 No Content', {}, ''), - # DELETE of user object - ('503 Service Unavailable', {}, '')])) - resp = Request.blank('/auth/v2/act/usr', - environ={'REQUEST_METHOD': 'DELETE'}, - headers={'X-Auth-Admin-User': '.super_admin', - 'X-Auth-Admin-Key': 'supertest'} - ).get_response(self.test_auth) - self.assertEqual(resp.status_int, 500) - self.assertEqual(self.test_auth.app.calls, 4) - - def test_delete_user_success(self): - self.test_auth.app = FakeApp(iter([ - # is user reseller_admin - ('200 Ok', {}, json.dumps({"groups": [{"name": "act:usr"}, - {"name": "test"}], "auth": "plaintext:key"})), - # HEAD of user object - ('200 Ok', {'X-Object-Meta-Auth-Token': 'AUTH_tk'}, ''), - # DELETE of token - ('204 No Content', {}, ''), - # DELETE of user object - ('204 No Content', {}, '')])) - resp = Request.blank('/auth/v2/act/usr', - environ={'REQUEST_METHOD': 'DELETE'}, - headers={'X-Auth-Admin-User': '.super_admin', - 'X-Auth-Admin-Key': 'supertest'} - ).get_response(self.test_auth) - self.assertEqual(resp.status_int, 204) - self.assertEqual(self.test_auth.app.calls, 4) - - def test_delete_user_success_missing_user_at_end(self): - self.test_auth.app = FakeApp(iter([ - # is user reseller_admin - ('200 Ok', {}, json.dumps({"groups": [{"name": "act:usr"}, - {"name": "test"}], "auth": "plaintext:key"})), - # HEAD of user object - ('200 Ok', {'X-Object-Meta-Auth-Token': 'AUTH_tk'}, ''), - # DELETE of token - ('204 No Content', {}, ''), - # DELETE of user object - ('404 Not Found', {}, '')])) - resp = Request.blank('/auth/v2/act/usr', - environ={'REQUEST_METHOD': 'DELETE'}, - headers={'X-Auth-Admin-User': '.super_admin', - 'X-Auth-Admin-Key': 'supertest'} - ).get_response(self.test_auth) - self.assertEqual(resp.status_int, 204) - self.assertEqual(self.test_auth.app.calls, 4) - - def test_delete_user_success_missing_token(self): - self.test_auth.app = FakeApp(iter([ - # is user reseller_admin - ('200 Ok', {}, json.dumps({"groups": [{"name": "act:usr"}, - {"name": "test"}], "auth": "plaintext:key"})), - # HEAD of user object - ('200 Ok', {'X-Object-Meta-Auth-Token': 'AUTH_tk'}, ''), - # DELETE of token - ('404 Not Found', {}, ''), - # DELETE of user object - ('204 No Content', {}, '')])) - resp = Request.blank('/auth/v2/act/usr', - environ={'REQUEST_METHOD': 'DELETE'}, - headers={'X-Auth-Admin-User': '.super_admin', - 'X-Auth-Admin-Key': 'supertest'} - ).get_response(self.test_auth) - self.assertEqual(resp.status_int, 204) - self.assertEqual(self.test_auth.app.calls, 4) - - def test_delete_user_success_no_token(self): - self.test_auth.app = FakeApp(iter([ - # is user reseller_admin - ('200 Ok', {}, json.dumps({"groups": [{"name": "act:usr"}, - {"name": "test"}], "auth": "plaintext:key"})), - # HEAD of user object - ('200 Ok', {}, ''), - # DELETE of user object - ('204 No Content', {}, '')])) - resp = Request.blank('/auth/v2/act/usr', - environ={'REQUEST_METHOD': 'DELETE'}, - headers={'X-Auth-Admin-User': '.super_admin', - 'X-Auth-Admin-Key': 'supertest'} - ).get_response(self.test_auth) - self.assertEqual(resp.status_int, 204) - self.assertEqual(self.test_auth.app.calls, 3) - - def test_validate_token_bad_prefix(self): - resp = Request.blank('/auth/v2/.token/BAD_token') \ - .get_response(self.test_auth) - self.assertEqual(resp.status_int, 400) - - def test_validate_token_tmi(self): - resp = Request.blank('/auth/v2/.token/AUTH_token/tmi') \ - .get_response(self.test_auth) - self.assertEqual(resp.status_int, 400) - - def test_validate_token_bad_memcache(self): - fake_memcache = FakeMemcache() - fake_memcache.set('AUTH_/auth/AUTH_token', 'bogus') - resp = Request.blank('/auth/v2/.token/AUTH_token', - environ={'swift.cache': - fake_memcache}).get_response(self.test_auth) - self.assertEqual(resp.status_int, 500) - - def test_validate_token_from_memcache(self): - fake_memcache = FakeMemcache() - fake_memcache.set('AUTH_/auth/AUTH_token', (time() + 1, 'act:usr,act')) - resp = Request.blank('/auth/v2/.token/AUTH_token', - environ={'swift.cache': - fake_memcache}).get_response(self.test_auth) - self.assertEqual(resp.status_int, 204) - self.assertEqual(resp.headers.get('x-auth-groups'), 'act:usr,act') - self.assertTrue(float(resp.headers['x-auth-ttl']) < 1, - resp.headers['x-auth-ttl']) - - def test_validate_token_from_memcache_expired(self): - fake_memcache = FakeMemcache() - fake_memcache.set('AUTH_/auth/AUTH_token', (time() - 1, 'act:usr,act')) - resp = Request.blank('/auth/v2/.token/AUTH_token', - environ={'swift.cache': - fake_memcache}).get_response(self.test_auth) - self.assertEqual(resp.status_int, 404) - self.assertTrue('x-auth-groups' not in resp.headers) - self.assertTrue('x-auth-ttl' not in resp.headers) - - def test_validate_token_from_object(self): - self.test_auth.app = FakeApp(iter([ - # GET of token object - ('200 Ok', {}, json.dumps({'groups': [{'name': 'act:usr'}, - {'name': 'act'}], 'expires': time() + 1}))])) - resp = Request.blank('/auth/v2/.token/AUTH_token' - ).get_response(self.test_auth) - self.assertEqual(resp.status_int, 204) - self.assertEqual(self.test_auth.app.calls, 1) - self.assertEqual(resp.headers.get('x-auth-groups'), 'act:usr,act') - self.assertTrue(float(resp.headers['x-auth-ttl']) < 1, - resp.headers['x-auth-ttl']) - - def test_validate_token_from_object_expired(self): - self.test_auth.app = FakeApp(iter([ - # GET of token object - ('200 Ok', {}, json.dumps({'groups': 'act:usr,act', - 'expires': time() - 1})), - # DELETE of expired token object - ('204 No Content', {}, '')])) - resp = Request.blank('/auth/v2/.token/AUTH_token' - ).get_response(self.test_auth) - self.assertEqual(resp.status_int, 404) - self.assertEqual(self.test_auth.app.calls, 2) - - def test_validate_token_from_object_with_admin(self): - self.test_auth.app = FakeApp(iter([ - # GET of token object - ('200 Ok', {}, json.dumps({'account_id': 'AUTH_cfa', 'groups': - [{'name': 'act:usr'}, {'name': 'act'}, {'name': '.admin'}], - 'expires': time() + 1}))])) - resp = Request.blank('/auth/v2/.token/AUTH_token' - ).get_response(self.test_auth) - self.assertEqual(resp.status_int, 204) - self.assertEqual(self.test_auth.app.calls, 1) - self.assertEqual(resp.headers.get('x-auth-groups'), - 'act:usr,act,AUTH_cfa') - self.assertTrue(float(resp.headers['x-auth-ttl']) < 1, - resp.headers['x-auth-ttl']) - - def test_get_conn_default(self): - conn = self.test_auth.get_conn() - self.assertEqual(conn.__class__, auth.HTTPConnection) - self.assertEqual(conn.host, '127.0.0.1') - self.assertEqual(conn.port, 8080) - - def test_get_conn_default_https(self): - local_auth = auth.filter_factory({'super_admin_key': 'supertest', - 'default_swift_cluster': 'local#https://1.2.3.4/v1'})(FakeApp()) - conn = local_auth.get_conn() - self.assertEqual(conn.__class__, auth.HTTPSConnection) - self.assertEqual(conn.host, '1.2.3.4') - self.assertEqual(conn.port, 443) - - def test_get_conn_overridden(self): - local_auth = auth.filter_factory({'super_admin_key': 'supertest', - 'default_swift_cluster': 'local#https://1.2.3.4/v1'})(FakeApp()) - conn = \ - local_auth.get_conn(urlparsed=auth.urlparse('http://5.6.7.8/v1')) - self.assertEqual(conn.__class__, auth.HTTPConnection) - self.assertEqual(conn.host, '5.6.7.8') - self.assertEqual(conn.port, 80) - - def test_get_conn_overridden_https(self): - local_auth = auth.filter_factory({'super_admin_key': 'supertest', - 'default_swift_cluster': 'local#http://1.2.3.4/v1'})(FakeApp()) - conn = \ - local_auth.get_conn(urlparsed=auth.urlparse('https://5.6.7.8/v1')) - self.assertEqual(conn.__class__, auth.HTTPSConnection) - self.assertEqual(conn.host, '5.6.7.8') - self.assertEqual(conn.port, 443) - - def test_get_itoken_fail_no_memcache(self): - exc = None - try: - self.test_auth.get_itoken({}) - except Exception as err: - exc = err - self.assertEqual(str(exc), - 'No memcache set up; required for Swauth middleware') - - def test_get_itoken_success(self): - fmc = FakeMemcache() - itk = self.test_auth.get_itoken({'swift.cache': fmc}) - self.assertTrue(itk.startswith('AUTH_itk'), itk) - expires, groups = fmc.get('AUTH_/auth/%s' % itk) - self.assertTrue(expires > time(), expires) - self.assertEqual(groups, '.auth,.reseller_admin,AUTH_.auth') - - def test_get_admin_detail_fail_no_colon(self): - self.test_auth.app = FakeApp(iter([])) - self.assertEqual(self.test_auth.get_admin_detail(Request.blank('/')), - None) - self.assertEqual(self.test_auth.get_admin_detail(Request.blank('/', - headers={'X-Auth-Admin-User': 'usr'})), None) - self.assertRaises(StopIteration, self.test_auth.get_admin_detail, - Request.blank('/', headers={'X-Auth-Admin-User': 'act:usr'})) - - def test_get_admin_detail_fail_user_not_found(self): - self.test_auth.app = FakeApp(iter([('404 Not Found', {}, '')])) - self.assertEqual(self.test_auth.get_admin_detail(Request.blank('/', - headers={'X-Auth-Admin-User': 'act:usr'})), None) - self.assertEqual(self.test_auth.app.calls, 1) - - def test_get_admin_detail_fail_get_user_error(self): - self.test_auth.app = FakeApp(iter([ - ('503 Service Unavailable', {}, '')])) - exc = None - try: - self.test_auth.get_admin_detail(Request.blank('/', - headers={'X-Auth-Admin-User': 'act:usr'})) - except Exception as err: - exc = err - self.assertEqual(str(exc), 'Could not get user object: ' - '/v1/AUTH_.auth/act/usr 503 Service Unavailable') - self.assertEqual(self.test_auth.app.calls, 1) - - def test_get_admin_detail_success(self): - self.test_auth.app = FakeApp(iter([ - ('200 Ok', {}, - json.dumps({"auth": "plaintext:key", - "groups": [{'name': "act:usr"}, {'name': "act"}, - {'name': ".admin"}]}))])) - detail = self.test_auth.get_admin_detail(Request.blank('/', - headers={'X-Auth-Admin-User': 'act:usr'})) - self.assertEqual(self.test_auth.app.calls, 1) - self.assertEqual(detail, {'account': 'act', - 'auth': 'plaintext:key', - 'groups': [{'name': 'act:usr'}, {'name': 'act'}, - {'name': '.admin'}]}) - - def test_get_user_detail_success(self): - self.test_auth.app = FakeApp(iter([ - ('200 Ok', {}, - json.dumps({"auth": "plaintext:key", - "groups": [{'name': "act:usr"}, {'name': "act"}, - {'name': ".admin"}]}))])) - detail = self.test_auth.get_user_detail( - Request.blank('/', - headers={'X-Auth-Admin-User': 'act:usr'}), - 'act', 'usr') - self.assertEqual(self.test_auth.app.calls, 1) - detail_json = json.loads(detail) - self.assertEqual("plaintext:key", detail_json['auth']) - - def test_get_user_detail_fail_user_doesnt_exist(self): - self.test_auth.app = FakeApp( - iter([('404 Not Found', {}, '')])) - detail = self.test_auth.get_user_detail( - Request.blank('/', - headers={'X-Auth-Admin-User': 'act:usr'}), - 'act', 'usr') - self.assertEqual(self.test_auth.app.calls, 1) - self.assertEqual(detail, None) - - def test_get_user_detail_fail_exception(self): - self.test_auth.app = FakeApp(iter([ - ('503 Service Unavailable', {}, '')])) - exc = None - try: - self.test_auth.get_user_detail( - Request.blank('/', - headers={'X-Auth-Admin-User': 'act:usr'}), - 'act', 'usr') - except Exception as err: - exc = err - self.assertEqual(str(exc), 'Could not get user object: ' - '/v1/AUTH_.auth/act/usr 503 Service Unavailable') - self.assertEqual(self.test_auth.app.calls, 1) - - def test_is_user_reseller_admin_success(self): - self.test_auth.app = FakeApp(iter([ - ('200 Ok', {}, - json.dumps({"auth": "plaintext:key", - "groups": [{'name': "act:usr"}, {'name': "act"}, - {'name': ".reseller_admin"}]}))])) - result = self.test_auth.is_user_reseller_admin( - Request.blank('/', - headers={'X-Auth-Admin-User': 'act:usr'}), - 'act', 'usr') - self.assertEqual(self.test_auth.app.calls, 1) - self.assertTrue(result) - - def test_is_user_reseller_admin_fail(self): - self.test_auth.app = FakeApp(iter([ - ('200 Ok', {}, - json.dumps({"auth": "plaintext:key", - "groups": [{'name': "act:usr"}, {'name': "act"}, - {'name': ".admin"}]}))])) - result = self.test_auth.is_user_reseller_admin( - Request.blank('/', - headers={'X-Auth-Admin-User': 'act:usr'}), - 'act', 'usr') - self.assertEqual(self.test_auth.app.calls, 1) - self.assertFalse(result) - - def test_is_user_reseller_admin_fail_user_doesnt_exist(self): - self.test_auth.app = FakeApp( - iter([('404 Not Found', {}, '')])) - req = Request.blank('/', headers={'X-Auth-Admin-User': 'act:usr'}) - result = self.test_auth.is_user_reseller_admin(req, 'act', 'usr') - self.assertEqual(self.test_auth.app.calls, 1) - self.assertFalse(result) - self.assertFalse(req.credentials_valid) - - def test_credentials_match_success(self): - self.assertTrue(self.test_auth.credentials_match( - {'auth': 'plaintext:key'}, 'key')) - - def test_credentials_match_fail_no_details(self): - self.assertTrue(not self.test_auth.credentials_match(None, 'notkey')) - - def test_credentials_match_fail_plaintext(self): - self.assertTrue(not self.test_auth.credentials_match( - {'auth': 'plaintext:key'}, 'notkey')) - - def test_is_user_changing_own_key_err(self): - # User does not exist - self.test_auth.app = FakeApp( - iter([('404 Not Found', {}, '')])) - req = Request.blank('/auth/v2/act/usr', - environ={ - 'REQUEST_METHOD': 'PUT'}, - headers={ - 'X-Auth-Admin-User': 'act:usr', - 'X-Auth-Admin-Key': 'key', - 'X-Auth-User-Key': 'key'}) - self.assertTrue( - not self.test_auth.is_user_changing_own_key(req, 'act:usr')) - self.assertEqual(self.test_auth.app.calls, 1) - - # user attempting to escalate himself as admin - self.test_auth.app = FakeApp(iter([ - ('200 Ok', {}, json.dumps({"groups": [{"name": "act:usr"}, - {"name": "test"}], "auth": "plaintext:key"}))])) - req = Request.blank('/auth/v2/act/usr', - environ={ - 'REQUEST_METHOD': 'PUT'}, - headers={ - 'X-Auth-Admin-User': 'act:usr', - 'X-Auth-Admin-Key': 'key', - 'X-Auth-User-Key': 'key', - 'X-Auth-User-Admin': 'true'}) - self.assertTrue( - not self.test_auth.is_user_changing_own_key(req, 'act:usr')) - self.assertEqual(self.test_auth.app.calls, 1) - - # admin attempting to escalate himself as reseller_admin - self.test_auth.app = FakeApp(iter([ - ('200 Ok', {}, json.dumps({"groups": [{"name": "act:adm"}, - {"name": "test"}, {"name": ".admin"}], - "auth": "plaintext:key"}))])) - req = Request.blank('/auth/v2/act/adm', - environ={ - 'REQUEST_METHOD': 'PUT'}, - headers={ - 'X-Auth-Admin-User': 'act:adm', - 'X-Auth-Admin-Key': 'key', - 'X-Auth-User-Key': 'key', - 'X-Auth-User-Reseller-Admin': 'true'}) - self.assertTrue( - not self.test_auth.is_user_changing_own_key(req, 'act:adm')) - self.assertEqual(self.test_auth.app.calls, 1) - - # different user - self.test_auth.app = FakeApp(iter([ - ('200 Ok', {}, json.dumps({"groups": [{"name": "act:usr"}, - {"name": "test"}], "auth": "plaintext:key"}))])) - req = Request.blank('/auth/v2/act/usr2', - environ={ - 'REQUEST_METHOD': 'PUT'}, - headers={ - 'X-Auth-Admin-User': 'act:usr', - 'X-Auth-Admin-Key': 'key', - 'X-Auth-User-Key': 'key'}) - self.assertTrue( - not self.test_auth.is_user_changing_own_key(req, 'act:usr2')) - self.assertEqual(self.test_auth.app.calls, 1) - - # wrong key - self.test_auth.app = FakeApp(iter([ - ('200 Ok', {}, json.dumps({"groups": [{"name": "act:usr"}, - {"name": "test"}], "auth": "plaintext:key"}))])) - req = Request.blank('/auth/v2/act/usr', - environ={ - 'REQUEST_METHOD': 'PUT'}, - headers={ - 'X-Auth-Admin-User': 'act:usr', - 'X-Auth-Admin-Key': 'wrongkey', - 'X-Auth-User-Key': 'newkey'}) - self.assertTrue( - not self.test_auth.is_user_changing_own_key(req, 'act:usr')) - self.assertEqual(self.test_auth.app.calls, 1) - - def test_is_super_admin_success(self): - self.assertTrue(self.test_auth.is_super_admin(Request.blank('/', - headers={'X-Auth-Admin-User': '.super_admin', - 'X-Auth-Admin-Key': 'supertest'}))) - - def test_is_super_admin_fail_bad_key(self): - self.assertTrue(not self.test_auth.is_super_admin(Request.blank('/', - headers={'X-Auth-Admin-User': '.super_admin', - 'X-Auth-Admin-Key': 'bad'}))) - self.assertTrue(not self.test_auth.is_super_admin(Request.blank('/', - headers={'X-Auth-Admin-User': '.super_admin'}))) - self.assertTrue(not self.test_auth.is_super_admin(Request.blank('/'))) - - def test_is_super_admin_fail_bad_user(self): - self.assertTrue(not self.test_auth.is_super_admin(Request.blank('/', - headers={'X-Auth-Admin-User': 'bad', - 'X-Auth-Admin-Key': 'supertest'}))) - self.assertTrue(not self.test_auth.is_super_admin(Request.blank('/', - headers={'X-Auth-Admin-Key': 'supertest'}))) - self.assertTrue(not self.test_auth.is_super_admin(Request.blank('/'))) - - def test_is_reseller_admin_success_is_super_admin(self): - self.assertTrue(self.test_auth.is_reseller_admin(Request.blank('/', - headers={'X-Auth-Admin-User': '.super_admin', - 'X-Auth-Admin-Key': 'supertest'}))) - - def test_is_reseller_admin_success_called_get_admin_detail(self): - self.test_auth.app = FakeApp(iter([ - ('200 Ok', {}, - json.dumps({'auth': 'plaintext:key', - 'groups': [{'name': 'act:rdm'}, {'name': 'act'}, - {'name': '.admin'}, - {'name': '.reseller_admin'}]}))])) - self.assertTrue(self.test_auth.is_reseller_admin(Request.blank('/', - headers={'X-Auth-Admin-User': 'act:rdm', - 'X-Auth-Admin-Key': 'key'}))) - - def test_is_reseller_admin_fail_only_account_admin(self): - self.test_auth.app = FakeApp(iter([ - ('200 Ok', {}, - json.dumps({'auth': 'plaintext:key', - 'groups': [{'name': 'act:adm'}, {'name': 'act'}, - {'name': '.admin'}]}))])) - self.assertTrue(not self.test_auth.is_reseller_admin(Request.blank('/', - headers={'X-Auth-Admin-User': 'act:adm', - 'X-Auth-Admin-Key': 'key'}))) - - def test_is_reseller_admin_fail_regular_user(self): - self.test_auth.app = FakeApp(iter([ - ('200 Ok', {}, - json.dumps({'auth': 'plaintext:key', - 'groups': [{'name': 'act:usr'}, {'name': 'act'}]}))])) - self.assertTrue(not self.test_auth.is_reseller_admin(Request.blank('/', - headers={'X-Auth-Admin-User': 'act:usr', - 'X-Auth-Admin-Key': 'key'}))) - - def test_is_reseller_admin_fail_bad_key(self): - self.test_auth.app = FakeApp(iter([ - ('200 Ok', {}, - json.dumps({'auth': 'plaintext:key', - 'groups': [{'name': 'act:rdm'}, {'name': 'act'}, - {'name': '.admin'}, - {'name': '.reseller_admin'}]}))])) - self.assertTrue(not self.test_auth.is_reseller_admin(Request.blank('/', - headers={'X-Auth-Admin-User': 'act:rdm', - 'X-Auth-Admin-Key': 'bad'}))) - - def test_is_account_admin_success_is_super_admin(self): - self.assertTrue(self.test_auth.is_account_admin(Request.blank('/', - headers={'X-Auth-Admin-User': '.super_admin', - 'X-Auth-Admin-Key': 'supertest'}), 'act')) - - def test_is_account_admin_success_is_reseller_admin(self): - self.test_auth.app = FakeApp(iter([ - ('200 Ok', {}, - json.dumps({'auth': 'plaintext:key', - 'groups': [{'name': 'act:rdm'}, {'name': 'act'}, - {'name': '.admin'}, - {'name': '.reseller_admin'}]}))])) - self.assertTrue(self.test_auth.is_account_admin(Request.blank('/', - headers={'X-Auth-Admin-User': 'act:rdm', - 'X-Auth-Admin-Key': 'key'}), 'act')) - - def test_is_account_admin_success(self): - self.test_auth.app = FakeApp(iter([ - ('200 Ok', {}, - json.dumps({'auth': 'plaintext:key', - 'groups': [{'name': 'act:adm'}, {'name': 'act'}, - {'name': '.admin'}]}))])) - self.assertTrue(self.test_auth.is_account_admin(Request.blank('/', - headers={'X-Auth-Admin-User': 'act:adm', - 'X-Auth-Admin-Key': 'key'}), 'act')) - - def test_is_account_admin_fail_account_admin_different_account(self): - self.test_auth.app = FakeApp(iter([ - ('200 Ok', {}, - json.dumps({'auth': 'plaintext:key', - 'groups': [{'name': 'act2:adm'}, {'name': 'act2'}, - {'name': '.admin'}]}))])) - self.assertTrue(not self.test_auth.is_account_admin(Request.blank('/', - headers={'X-Auth-Admin-User': 'act2:adm', - 'X-Auth-Admin-Key': 'key'}), 'act')) - - def test_is_account_admin_fail_regular_user(self): - self.test_auth.app = FakeApp(iter([ - ('200 Ok', {}, - json.dumps({'auth': 'plaintext:key', - 'groups': [{'name': 'act:usr'}, {'name': 'act'}]}))])) - self.assertTrue(not self.test_auth.is_account_admin(Request.blank('/', - headers={'X-Auth-Admin-User': 'act:usr', - 'X-Auth-Admin-Key': 'key'}), 'act')) - - def test_is_account_admin_fail_bad_key(self): - self.test_auth.app = FakeApp(iter([ - ('200 Ok', {}, - json.dumps({'auth': 'plaintext:key', - 'groups': [{'name': 'act:rdm'}, {'name': 'act'}, - {'name': '.admin'}, - {'name': '.reseller_admin'}]}))])) - self.assertTrue(not self.test_auth.is_account_admin(Request.blank('/', - headers={'X-Auth-Admin-User': 'act:rdm', - 'X-Auth-Admin-Key': 'bad'}), 'act')) - - def test_reseller_admin_but_account_is_internal_use_only(self): - req = Request.blank('/v1/AUTH_.auth', - environ={'REQUEST_METHOD': 'GET'}) - req.remote_user = 'act:usr,act,.reseller_admin' - resp = self.test_auth.authorize(req) - self.assertEqual(resp.status_int, 403) - - def test_reseller_admin_but_account_is_exactly_reseller_prefix(self): - req = Request.blank('/v1/AUTH_', environ={'REQUEST_METHOD': 'GET'}) - req.remote_user = 'act:usr,act,.reseller_admin' - resp = self.test_auth.authorize(req) - self.assertEqual(resp.status_int, 403) - - def _get_token_success_v1_0_encoded(self, saved_user, saved_key, sent_user, - sent_key): - self.test_auth.app = FakeApp(iter([ - # GET of user object - ('200 Ok', {}, - json.dumps({"auth": "plaintext:%s" % saved_key, - "groups": [{'name': saved_user}, {'name': "act"}, - {'name': ".admin"}]})), - # GET of account - ('204 Ok', {'X-Container-Meta-Account-Id': 'AUTH_cfa'}, ''), - # PUT of new token - ('201 Created', {}, ''), - # POST of token to user object - ('204 No Content', {}, ''), - # GET of services object - ('200 Ok', {}, json.dumps({"storage": {"default": "local", - "local": "http://127.0.0.1:8080/v1/AUTH_cfa"}}))])) - resp = Request.blank('/auth/v1.0', - headers={'X-Auth-User': sent_user, - 'X-Auth-Key': sent_key}).get_response(self.test_auth) - self.assertEqual(resp.status_int, 200) - self.assertEqual(resp.content_type, CONTENT_TYPE_JSON) - self.assertTrue(resp.headers.get('x-auth-token', - '').startswith('AUTH_tk'), resp.headers.get('x-auth-token')) - self.assertEqual(resp.headers.get('x-auth-token'), - resp.headers.get('x-storage-token')) - self.assertEqual(resp.headers.get('x-storage-url'), - 'http://127.0.0.1:8080/v1/AUTH_cfa') - self.assertEqual(json.loads(resp.body), - {"storage": {"default": "local", - "local": "http://127.0.0.1:8080/v1/AUTH_cfa"}}) - self.assertEqual(self.test_auth.app.calls, 5) - - def test_get_token_success_v1_0_encoded1(self): - self._get_token_success_v1_0_encoded( - 'act:usr', 'key', 'act%3ausr', 'key') - - def test_get_token_success_v1_0_encoded2(self): - self._get_token_success_v1_0_encoded( - 'act:u s r', 'key', 'act%3au%20s%20r', 'key') - - def test_get_token_success_v1_0_encoded3(self): - self._get_token_success_v1_0_encoded( - 'act:u s r', 'k:e:y', 'act%3au%20s%20r', 'k%3Ae%3ay') - - def test_allowed_sync_hosts(self): - a = auth.filter_factory({'super_admin_key': 'supertest'})(FakeApp()) - self.assertEqual(a.allowed_sync_hosts, ['127.0.0.1']) - a = auth.filter_factory({'super_admin_key': 'supertest', - 'allowed_sync_hosts': - '1.1.1.1,2.1.1.1, 3.1.1.1 , 4.1.1.1,, , 5.1.1.1'})(FakeApp()) - self.assertEqual(a.allowed_sync_hosts, - ['1.1.1.1', '2.1.1.1', '3.1.1.1', '4.1.1.1', '5.1.1.1']) - - def test_reseller_admin_is_owner(self): - orig_authorize = self.test_auth.authorize - owner_values = [] - - def mitm_authorize(req): - rv = orig_authorize(req) - owner_values.append(req.environ.get('swift_owner', False)) - return rv - - self.test_auth.authorize = mitm_authorize - - self.test_auth.app = FakeApp(iter([ - ('200 Ok', {}, - json.dumps({'account': 'other', 'user': 'other:usr', - 'account_id': 'AUTH_other', - 'groups': [{'name': 'other:usr'}, {'name': 'other'}, - {'name': '.reseller_admin'}], - 'expires': time() + 60})), - ('204 No Content', {}, '')])) - req = Request.blank('/v1/AUTH_cfa', headers={'X-Auth-Token': 'AUTH_t'}) - resp = req.get_response(self.test_auth) - self.assertEqual(resp.status_int, 204) - self.assertEqual(owner_values, [True]) - - def test_admin_is_owner(self): - orig_authorize = self.test_auth.authorize - owner_values = [] - - def mitm_authorize(req): - rv = orig_authorize(req) - owner_values.append(req.environ.get('swift_owner', False)) - return rv - - self.test_auth.authorize = mitm_authorize - - self.test_auth.app = FakeApp(iter([ - ('200 Ok', {}, - json.dumps({'account': 'act', 'user': 'act:usr', - 'account_id': 'AUTH_cfa', - 'groups': [{'name': 'act:usr'}, {'name': 'act'}, - {'name': '.admin'}], - 'expires': time() + 60})), - ('204 No Content', {}, '')])) - req = Request.blank('/v1/AUTH_cfa', headers={'X-Auth-Token': 'AUTH_t'}) - resp = req.get_response(self.test_auth) - self.assertEqual(resp.status_int, 204) - self.assertEqual(owner_values, [True]) - - def test_regular_is_not_owner(self): - orig_authorize = self.test_auth.authorize - owner_values = [] - - def mitm_authorize(req): - rv = orig_authorize(req) - owner_values.append(req.environ.get('swift_owner', False)) - return rv - - self.test_auth.authorize = mitm_authorize - - self.test_auth.app = FakeApp(iter([ - ('200 Ok', {}, - json.dumps({'account': 'act', 'user': 'act:usr', - 'account_id': 'AUTH_cfa', - 'groups': [{'name': 'act:usr'}, {'name': 'act'}], - 'expires': time() + 60})), - ('204 No Content', {}, '')]), acl='act:usr') - req = Request.blank('/v1/AUTH_cfa/c', - headers={'X-Auth-Token': 'AUTH_t'}) - resp = req.get_response(self.test_auth) - self.assertEqual(resp.status_int, 204) - self.assertEqual(owner_values, [False]) - - def test_sync_request_success(self): - self.test_auth.app = FakeApp(iter([('204 No Content', {}, '')]), - sync_key='secret') - req = Request.blank('/v1/AUTH_cfa/c/o', - environ={'REQUEST_METHOD': 'DELETE'}, - headers={'x-container-sync-key': 'secret', - 'x-timestamp': '123.456'}) - req.remote_addr = '127.0.0.1' - resp = req.get_response(self.test_auth) - self.assertEqual(resp.status_int, 204) - - def test_sync_request_fail_key(self): - self.test_auth.app = FakeApp(iter([('204 No Content', {}, '')]), - sync_key='secret') - req = Request.blank('/v1/AUTH_cfa/c/o', - environ={'REQUEST_METHOD': 'DELETE'}, - headers={'x-container-sync-key': 'wrongsecret', - 'x-timestamp': '123.456'}) - req.remote_addr = '127.0.0.1' - resp = req.get_response(self.test_auth) - self.assertEqual(resp.status_int, 401) - - self.test_auth.app = FakeApp(iter([('204 No Content', {}, '')]), - sync_key='othersecret') - req = Request.blank('/v1/AUTH_cfa/c/o', - environ={'REQUEST_METHOD': 'DELETE'}, - headers={'x-container-sync-key': 'secret', - 'x-timestamp': '123.456'}) - req.remote_addr = '127.0.0.1' - resp = req.get_response(self.test_auth) - self.assertEqual(resp.status_int, 401) - - self.test_auth.app = FakeApp(iter([('204 No Content', {}, '')]), - sync_key=None) - req = Request.blank('/v1/AUTH_cfa/c/o', - environ={'REQUEST_METHOD': 'DELETE'}, - headers={'x-container-sync-key': 'secret', - 'x-timestamp': '123.456'}) - req.remote_addr = '127.0.0.1' - resp = req.get_response(self.test_auth) - self.assertEqual(resp.status_int, 401) - - def test_sync_request_fail_no_timestamp(self): - self.test_auth.app = FakeApp(iter([('204 No Content', {}, '')]), - sync_key='secret') - req = Request.blank('/v1/AUTH_cfa/c/o', - environ={'REQUEST_METHOD': 'DELETE'}, - headers={'x-container-sync-key': 'secret'}) - req.remote_addr = '127.0.0.1' - resp = req.get_response(self.test_auth) - self.assertEqual(resp.status_int, 401) - - def test_sync_request_fail_sync_host(self): - self.test_auth.app = FakeApp(iter([('204 No Content', {}, '')]), - sync_key='secret') - req = Request.blank('/v1/AUTH_cfa/c/o', - environ={'REQUEST_METHOD': 'DELETE'}, - headers={'x-container-sync-key': 'secret', - 'x-timestamp': '123.456'}) - req.remote_addr = '127.0.0.2' - resp = req.get_response(self.test_auth) - self.assertEqual(resp.status_int, 401) - - def test_sync_request_success_lb_sync_host(self): - self.test_auth.app = FakeApp(iter([('204 No Content', {}, '')]), - sync_key='secret') - req = Request.blank('/v1/AUTH_cfa/c/o', - environ={'REQUEST_METHOD': 'DELETE'}, - headers={'x-container-sync-key': 'secret', - 'x-timestamp': '123.456', - 'x-forwarded-for': '127.0.0.1'}) - req.remote_addr = '127.0.0.2' - resp = req.get_response(self.test_auth) - self.assertEqual(resp.status_int, 204) - - self.test_auth.app = FakeApp(iter([('204 No Content', {}, '')]), - sync_key='secret') - req = Request.blank('/v1/AUTH_cfa/c/o', - environ={'REQUEST_METHOD': 'DELETE'}, - headers={'x-container-sync-key': 'secret', - 'x-timestamp': '123.456', - 'x-cluster-client-ip': '127.0.0.1'}) - req.remote_addr = '127.0.0.2' - resp = req.get_response(self.test_auth) - self.assertEqual(resp.status_int, 204) - - def _make_request(self, path, **kwargs): - req = Request.blank(path, **kwargs) - req.environ['swift.cache'] = FakeMemcache() - return req - - def test_override_asked_for_but_not_allowed(self): - self.test_auth = \ - auth.filter_factory({'allow_overrides': 'false'})(FakeApp()) - req = self._make_request('/v1/AUTH_account', - environ={'swift.authorize_override': True}) - resp = req.get_response(self.test_auth) - self.assertEqual(resp.status_int, 401) - self.assertEqual(resp.environ['swift.authorize'], - self.test_auth.authorize) - - def test_override_asked_for_and_allowed(self): - self.test_auth = \ - auth.filter_factory({'allow_overrides': 'true'})(FakeApp()) - req = self._make_request('/v1/AUTH_account', - environ={'swift.authorize_override': True}) - resp = req.get_response(self.test_auth) - self.assertEqual(resp.status_int, 404) - self.assertTrue('swift.authorize' not in resp.environ) - - def test_override_default_allowed(self): - req = self._make_request('/v1/AUTH_account', - environ={'swift.authorize_override': True}) - resp = req.get_response(self.test_auth) - self.assertEqual(resp.status_int, 404) - self.assertTrue('swift.authorize' not in resp.environ) - - def test_token_too_long(self): - req = self._make_request('/v1/AUTH_account', headers={ - 'x-auth-token': 'a' * MAX_TOKEN_LENGTH}) - resp = req.get_response(self.test_auth) - self.assertEqual(resp.status_int, 401) - self.assertNotEqual(resp.body, 'Token exceeds maximum length.') - req = self._make_request('/v1/AUTH_account', headers={ - 'x-auth-token': 'a' * (MAX_TOKEN_LENGTH + 1)}) - resp = req.get_response(self.test_auth) - self.assertEqual(resp.status_int, 400) - self.assertEqual(resp.body, 'Token exceeds maximum length.') - - def test_s3_enabled_when_conditions_are_met(self): - # auth_type_salt needs to be set - for atype in ('Sha1', 'Sha512'): - test_auth = \ - auth.filter_factory({ - 'super_admin_key': 'supertest', - 's3_support': 'on', - 'auth_type_salt': 'blah', - 'auth_type': atype})(FakeApp()) - self.assertTrue(test_auth.s3_support) - # auth_type_salt need not be set for Plaintext - test_auth = \ - auth.filter_factory({ - 'super_admin_key': 'supertest', - 's3_support': 'on', - 'auth_type': 'Plaintext'})(FakeApp()) - self.assertTrue(test_auth.s3_support) - - def test_s3_disabled_when_conditions_not_met(self): - # Conf says that it wants s3 support but other conditions are not met - # In that case s3 support should be disabled. - for atype in ('Sha1', 'Sha512'): - # auth_type_salt is not set - test_auth = \ - auth.filter_factory({ - 'super_admin_key': 'supertest', - 's3_support': 'on', - 'auth_type': atype})(FakeApp()) - self.assertFalse(test_auth.s3_support) - - def test_s3_authorization_default_off(self): - self.assertFalse(self.test_auth.s3_support) - req = self._make_request('/v1/AUTH_account', environ={ - 'swift3.auth_details': {'unused': 'stuff'}}) - resp = req.get_response(self.test_auth) - self.assertEqual(resp.status_int, 400) # HTTPBadRequest - self.assertTrue(resp.environ.get('swift.authorize') is None) - - def test_s3_turned_off_get_groups(self): - env = { - 'swift3.auth_details': {'unused': 'stuff'}} - token = 'whatever' - self.test_auth.logger = mock.Mock() - self.assertEqual(self.test_auth.get_groups(env, token), None) - - def test_default_storage_policy(self): - ath = auth.filter_factory({})(FakeApp()) - self.assertEqual(ath.default_storage_policy, None) - - ath = \ - auth.filter_factory({'default_storage_policy': 'ssd'})(FakeApp()) - self.assertEqual(ath.default_storage_policy, 'ssd') - - def test_s3_creds_unicode_bad(self): - self.test_auth.s3_support = True - self.test_auth.app = FakeApp(iter([ - ('200 Ok', {}, - json.dumps({"auth": unicode("plaintext:key)"), - "groups": [{'name': "act:usr"}, {'name': "act"}, - {'name': ".admin"}]})), - ('204 Ok', {'X-Container-Meta-Account-Id': 'AUTH_act'}, '')])) - env = \ - {'swift3.auth_details': { - 'access_key': 'act:user', - # NOTE: signature uses password of 'key', not 'key)' - 'signature': '3yW7oFFWOn+fhHMu7E47RKotL1Q=', - 'string_to_sign': base64.urlsafe_b64decode( - 'UFVUCgoKRnJpLCAyNiBGZWIgMjAxNiAwNjo0NT' - 'ozNCArMDAwMAovY29udGFpbmVyMw==')}, - 'PATH_INFO': '/v1/AUTH_act/c1'} - token = 'not used' - self.assertEqual(self.test_auth.get_groups(env, token), None) - - def test_s3_creds_unicode_good(self): - self.test_auth.s3_support = True - self.test_auth.app = FakeApp(iter([ - ('200 Ok', {}, - json.dumps({"auth": unicode("plaintext:key)"), - "groups": [{'name': "act:usr"}, {'name': "act"}, - {'name': ".admin"}]})), - ('204 Ok', {'X-Container-Meta-Account-Id': 'AUTH_act'}, '')])) - env = \ - {'swift3.auth_details': { - 'access_key': 'act:user', - 'signature': 'dElf49mbXP8t7F+P1qXZzaf3a50=', - 'string_to_sign': base64.urlsafe_b64decode( - 'UFVUCgoKRnJpLCAyNiBGZWIgMjAxNiAwNjo0NT' - 'ozNCArMDAwMAovY29udGFpbmVyMw==')}, - 'PATH_INFO': '/v1/AUTH_act/c1'} - token = 'UFVUCgoKRnJpLCAyNiBGZWIgMjAxNiAwNjo0NT'\ - 'ozNCArMDAwMAovY29udGFpbmVyMw==' - self.assertEqual(self.test_auth.get_groups(env, token), - 'act:usr,act,AUTH_act') - - def test_s3_only_hash_passed_to_hmac(self): - self.test_auth.s3_support = True - key = 'dadada' - salt = 'zuck' - key_hash = hashlib.sha1('%s%s' % (salt, key)).hexdigest() - auth_stored = "sha1:%s$%s" % (salt, key_hash) - self.test_auth.app = FakeApp(iter([ - ('200 Ok', {}, - json.dumps({"auth": auth_stored, - "groups": [{'name': "act:usr"}, {'name': "act"}, - {'name': ".admin"}]})), - ('204 Ok', {'X-Container-Meta-Account-Id': 'AUTH_act'}, '')])) - env = \ - {'swift3.auth_details': { - 'access_key': 'act:user', - 'signature': 'whatever', - 'string_to_sign': base64.urlsafe_b64decode( - 'UFVUCgoKRnJpLCAyNiBGZWIgMjAxNiAwNjo0NT' - 'ozNCArMDAwMAovY29udGFpbmVyMw==')}, - 'PATH_INFO': '/v1/AUTH_act/c1'} - token = 'not used' - mock_hmac_new = mock.MagicMock() - with mock.patch('hmac.new', mock_hmac_new): - self.test_auth.get_groups(env, token) - self.assertTrue(mock_hmac_new.called) - # Assert that string passed to hmac.new is only the hash - self.assertEqual(mock_hmac_new.call_args[0][0], key_hash) - - def test_get_concealed_token(self): - auth.HASH_PATH_PREFIX = 'start' - auth.HASH_PATH_SUFFIX = 'end' - token = 'token' - - # Check sha512 of "start:token:end" - hashed_token = self.test_auth._get_concealed_token(token) - self.assertEqual(hashed_token, - 'cb320540b0b4c69eb83de2ffb80714cb6766e2d06b5579d1a35a9c4c3fb62' - '981ec50bcc3fb94521133e69a87d1efcb83efd78f35a06b6375e410201476' - '0722f6') - - # Check sha512 of "start:token2:end" - token = 'token2' - hashed_token = self.test_auth._get_concealed_token(token) - self.assertEqual(hashed_token, - 'ca400a6f884c168357f6af0609fda66aecd5aa613147167487495dd9f39fd' - '8a77288568e65857294f01e398d7f14328e855f18517ccf94185d849e7f34' - 'f4259d') - - # Check sha512 of "start2:token2:end" - auth.HASH_PATH_PREFIX = 'start2' - hashed_token = self.test_auth._get_concealed_token(token) - self.assertEqual(hashed_token, - 'ad594a69f44dd6e0aad54e360b01f15bd4833ccb4dcd9116d7aba0c25fb95' - '670155b8cc7175def7aeeb4624a0f2bb7da5f0b204a4680ea7947d3d6a045' - '22bdde') - - # Check sha512 of "start2:token2:end2" - auth.HASH_PATH_SUFFIX = 'end2' - hashed_token = self.test_auth._get_concealed_token(token) - self.assertEqual(hashed_token, - '446af2473ad6b28319a0fe02719a9d715b9941d12e0709851aedb4f53b890' - '693e7f1328e68d870fe114f35f4ed9648b16a5013182db50d3d1f79a660f2' - '0e078e') - - -if __name__ == '__main__': - unittest.main() diff --git a/test/unit/test_swift_version.py b/test/unit/test_swift_version.py deleted file mode 100644 index 5fe278b..0000000 --- a/test/unit/test_swift_version.py +++ /dev/null @@ -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 diff --git a/tox.ini b/tox.ini deleted file mode 100644 index 6d7f101..0000000 --- a/tox.ini +++ /dev/null @@ -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 diff --git a/webadmin/index.html b/webadmin/index.html deleted file mode 100644 index 46103ee..0000000 --- a/webadmin/index.html +++ /dev/null @@ -1,575 +0,0 @@ - - - - - - -
-
-
Swauth
-
-
-
-
- -