Removing DevAuth
This commit is contained in:
		@@ -1,73 +0,0 @@
 | 
			
		||||
#!/usr/bin/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 ConfigParser import ConfigParser
 | 
			
		||||
from optparse import OptionParser
 | 
			
		||||
from os.path import basename
 | 
			
		||||
from sys import argv, exit
 | 
			
		||||
 | 
			
		||||
from swift.common.bufferedhttp import http_connect_raw as http_connect
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
if __name__ == '__main__':
 | 
			
		||||
    default_conf = '/etc/swift/auth-server.conf'
 | 
			
		||||
    parser = OptionParser(
 | 
			
		||||
        usage='Usage: %prog [options] <account> <user> <password>')
 | 
			
		||||
    parser.add_option('-c', '--conf', dest='conf', default=default_conf,
 | 
			
		||||
        help='Configuration file to determine how to connect to the local '
 | 
			
		||||
        'auth server (default: %s).' % default_conf)
 | 
			
		||||
    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('-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'])
 | 
			
		||||
    account, user, password = args
 | 
			
		||||
    c = ConfigParser()
 | 
			
		||||
    if not c.read(options.conf):
 | 
			
		||||
        exit('Unable to read conf file: %s' % options.conf)
 | 
			
		||||
    conf = dict(c.items('app:auth-server'))
 | 
			
		||||
    host = conf.get('bind_ip', '127.0.0.1')
 | 
			
		||||
    port = int(conf.get('bind_port', 11000))
 | 
			
		||||
    ssl = conf.get('cert_file') is not None
 | 
			
		||||
    path = '/account/%s/%s' % (account, user)
 | 
			
		||||
    headers = {'X-Auth-Admin-User': options.admin_user,
 | 
			
		||||
               'X-Auth-Admin-Key': options.admin_key,
 | 
			
		||||
               'X-Auth-User-Key': password}
 | 
			
		||||
    if options.admin:
 | 
			
		||||
        headers['X-Auth-User-Admin'] = 'true'
 | 
			
		||||
    if options.reseller_admin:
 | 
			
		||||
        headers['X-Auth-User-Reseller-Admin'] = 'true'
 | 
			
		||||
    conn = http_connect(host, port, 'PUT', path, headers, ssl=ssl)
 | 
			
		||||
    resp = conn.getresponse()
 | 
			
		||||
    if resp.status == 204:
 | 
			
		||||
        print resp.getheader('x-storage-url')
 | 
			
		||||
    else:
 | 
			
		||||
        print 'Update failed: %s %s' % (resp.status, resp.reason)
 | 
			
		||||
@@ -1,53 +0,0 @@
 | 
			
		||||
#!/usr/bin/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 ConfigParser import ConfigParser
 | 
			
		||||
from optparse import OptionParser
 | 
			
		||||
from sys import argv, exit
 | 
			
		||||
 | 
			
		||||
from swift.common.bufferedhttp import http_connect_raw as http_connect
 | 
			
		||||
 | 
			
		||||
if __name__ == '__main__':
 | 
			
		||||
    default_conf = '/etc/swift/auth-server.conf'
 | 
			
		||||
    parser = OptionParser(usage='Usage: %prog [options]')
 | 
			
		||||
    parser.add_option('-c', '--conf', dest='conf', default=default_conf,
 | 
			
		||||
        help='Configuration file to determine how to connect to the local '
 | 
			
		||||
        'auth server (default: %s).' % default_conf)
 | 
			
		||||
    parser.add_option('-U', '--admin-user', dest='admin_user',
 | 
			
		||||
        default='.super_admin', help='The user with admin rights to recreate '
 | 
			
		||||
        'accounts (default: .super_admin).')
 | 
			
		||||
    parser.add_option('-K', '--admin-key', dest='admin_key',
 | 
			
		||||
        help='The key for the user with admin rights to recreate accounts.')
 | 
			
		||||
    args = argv[1:]
 | 
			
		||||
    if not args:
 | 
			
		||||
        args.append('-h')
 | 
			
		||||
    (options, args) = parser.parse_args(args)
 | 
			
		||||
    c = ConfigParser()
 | 
			
		||||
    if not c.read(options.conf):
 | 
			
		||||
        exit('Unable to read conf file: %s' % options.conf)
 | 
			
		||||
    conf = dict(c.items('app:auth-server'))
 | 
			
		||||
    host = conf.get('bind_ip', '127.0.0.1')
 | 
			
		||||
    port = int(conf.get('bind_port', 11000))
 | 
			
		||||
    ssl = conf.get('cert_file') is not None
 | 
			
		||||
    path = '/recreate_accounts'
 | 
			
		||||
    conn = http_connect(host, port, 'POST', path, ssl=ssl,
 | 
			
		||||
            headers={'X-Auth-Admin-User': options.admin_user,
 | 
			
		||||
                     'X-Auth-Admin-Key': options.admin_key})
 | 
			
		||||
    resp = conn.getresponse()
 | 
			
		||||
    if resp.status == 200:
 | 
			
		||||
        print resp.read()
 | 
			
		||||
    else:
 | 
			
		||||
        print 'Recreating accounts failed. (%d)' % resp.status
 | 
			
		||||
@@ -1,22 +0,0 @@
 | 
			
		||||
#!/usr/bin/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 swift.common.utils import parse_options
 | 
			
		||||
from swift.common.wsgi import run_wsgi
 | 
			
		||||
 | 
			
		||||
if __name__ == '__main__':
 | 
			
		||||
    conf_file, options = parse_options()
 | 
			
		||||
    run_wsgi(conf_file, 'auth-server', default_port=11000, **options)
 | 
			
		||||
@@ -1,44 +0,0 @@
 | 
			
		||||
#!/usr/bin/python
 | 
			
		||||
# Copyright (c) 2010 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
 | 
			
		||||
from subprocess import call
 | 
			
		||||
from sys import argv, exit
 | 
			
		||||
 | 
			
		||||
import sqlite3
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
if __name__ == '__main__':
 | 
			
		||||
    gettext.install('swift', unicode=1)
 | 
			
		||||
    if len(argv) != 2:
 | 
			
		||||
        exit('Syntax: %s <path_to_auth.db>' % argv[0])
 | 
			
		||||
    _junk, auth_db = argv
 | 
			
		||||
    conn = sqlite3.connect(auth_db)
 | 
			
		||||
    try:
 | 
			
		||||
        listing = conn.execute('SELECT account, cfaccount, user, password, '
 | 
			
		||||
                               'admin, reseller_admin FROM account')
 | 
			
		||||
    except sqlite3.OperationalError, err:
 | 
			
		||||
        listing = conn.execute('SELECT account, cfaccount, user, password, '
 | 
			
		||||
                               '"f", "f" FROM account')
 | 
			
		||||
    for account, cfaccount, user, password, admin, reseller_admin in listing:
 | 
			
		||||
        cmd = ['swauth-add-user', '-K', '<your_swauth_key>', '-s',
 | 
			
		||||
               cfaccount.split('_', 1)[1]]
 | 
			
		||||
        if admin == 't':
 | 
			
		||||
            cmd.append('-a')
 | 
			
		||||
        if reseller_admin == 't':
 | 
			
		||||
            cmd.append('-r')
 | 
			
		||||
        cmd.extend([account, user, password])
 | 
			
		||||
        print ' '.join(cmd)
 | 
			
		||||
@@ -1,48 +0,0 @@
 | 
			
		||||
#!/usr/bin/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 os.path import basename
 | 
			
		||||
from sys import argv, exit
 | 
			
		||||
 | 
			
		||||
from swift.common.db import get_db_connection
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
if __name__ == '__main__':
 | 
			
		||||
    app = basename(argv[0])
 | 
			
		||||
    if len(argv) != 3:
 | 
			
		||||
        exit('''
 | 
			
		||||
Syntax : %s <auth.db> <new_prefix>
 | 
			
		||||
Example: %s /etc/swift/auth.db AUTH'''.strip() % (app, app))
 | 
			
		||||
    db = argv[1]
 | 
			
		||||
    new_prefix = argv[2].rstrip('_')
 | 
			
		||||
    print 'Updating %s' % db
 | 
			
		||||
    conn = get_db_connection(db)
 | 
			
		||||
    rows = conn.execute('SELECT url, cfaccount FROM account').fetchall()
 | 
			
		||||
    for row in rows:
 | 
			
		||||
        old_prefix = ''
 | 
			
		||||
        uuid = row[1]
 | 
			
		||||
        if '_' in row[1]:
 | 
			
		||||
            old_prefix, uuid = row[1].split('_', 1)
 | 
			
		||||
        new_cfaccount = '%s_%s' % (new_prefix, uuid)
 | 
			
		||||
        new_url = row[0].replace(row[1], new_cfaccount)
 | 
			
		||||
        print '%s ->\n%s' % (row[0], new_url)
 | 
			
		||||
        print '%s ->\n%s' % (row[1], new_cfaccount)
 | 
			
		||||
        print
 | 
			
		||||
        conn.execute('''UPDATE account SET url = ?, cfaccount = ?
 | 
			
		||||
                        WHERE url = ? AND cfaccount = ?''',
 | 
			
		||||
                     (new_url, new_cfaccount, row[0], row[1]))
 | 
			
		||||
    conn.commit()
 | 
			
		||||
    print 'Updated %s rows.' % len(rows)
 | 
			
		||||
@@ -159,15 +159,12 @@ of the cluster, we need to run the swift-stats-report tool to check the health
 | 
			
		||||
of each of these containers and objects.
 | 
			
		||||
 | 
			
		||||
These tools need direct access to the entire cluster and to the ring files
 | 
			
		||||
(installing them on an auth server or a proxy server will probably do). Both
 | 
			
		||||
(installing them on a proxy server will probably do). Both
 | 
			
		||||
swift-stats-populate and swift-stats-report use the same configuration file,
 | 
			
		||||
/etc/swift/stats.conf. Example conf file::
 | 
			
		||||
 | 
			
		||||
    [stats]
 | 
			
		||||
    # For DevAuth:
 | 
			
		||||
    auth_url = http://saio:11000/v1.0
 | 
			
		||||
    # For Swauth:
 | 
			
		||||
    # auth_url = http://saio:11000/auth/v1.0
 | 
			
		||||
    auth_url = http://saio:11000/auth/v1.0
 | 
			
		||||
    auth_user = test:tester
 | 
			
		||||
    auth_key = testing
 | 
			
		||||
 | 
			
		||||
@@ -236,15 +233,16 @@ then be graphed to see how cluster performance is trending.
 | 
			
		||||
Additional Cleanup Script for Swauth
 | 
			
		||||
------------------------------------
 | 
			
		||||
 | 
			
		||||
If you decide to use Swauth, you'll want to install a cronjob to clean up any
 | 
			
		||||
With Swauth, you'll want to install a cronjob to clean up any
 | 
			
		||||
orphaned expired tokens. These orphaned tokens can occur when a "stampede"
 | 
			
		||||
occurs where a single user authenticates several times concurrently. Generally,
 | 
			
		||||
these orphaned tokens don't pose much of an issue, but it's good to clean them
 | 
			
		||||
up once a "token life" period (default: 1 day or 86400 seconds).
 | 
			
		||||
 | 
			
		||||
This should be as simple as adding `swauth-cleanup-tokens -K swauthkey >
 | 
			
		||||
/dev/null` to a crontab entry on one of the proxies that is running Swauth; but
 | 
			
		||||
run `swauth-cleanup-tokens` with no arguments for detailed help on the options
 | 
			
		||||
This should be as simple as adding `swauth-cleanup-tokens -A
 | 
			
		||||
https://<PROXY_HOSTNAME>:8080/auth/ -K swauthkey > /dev/null` to a crontab
 | 
			
		||||
entry on one of the proxies that is running Swauth; but run
 | 
			
		||||
`swauth-cleanup-tokens` with no arguments for detailed help on the options
 | 
			
		||||
available.
 | 
			
		||||
 | 
			
		||||
------------------------
 | 
			
		||||
 
 | 
			
		||||
@@ -1,15 +0,0 @@
 | 
			
		||||
.. _auth:
 | 
			
		||||
 | 
			
		||||
*************************
 | 
			
		||||
Developer's Authorization
 | 
			
		||||
*************************
 | 
			
		||||
 | 
			
		||||
.. _auth_server:
 | 
			
		||||
 | 
			
		||||
Auth Server
 | 
			
		||||
===========
 | 
			
		||||
 | 
			
		||||
.. automodule:: swift.auth.server
 | 
			
		||||
    :members:
 | 
			
		||||
    :undoc-members:
 | 
			
		||||
    :show-inheritance:
 | 
			
		||||
@@ -6,13 +6,11 @@ Auth Server and Middleware
 | 
			
		||||
Creating Your Own Auth Server and Middleware
 | 
			
		||||
--------------------------------------------
 | 
			
		||||
 | 
			
		||||
The included swift/auth/server.py and swift/common/middleware/auth.py are good
 | 
			
		||||
minimal examples of how to create an external auth server and proxy server auth
 | 
			
		||||
middleware. Also, see swift/common/middleware/swauth.py for
 | 
			
		||||
a more complete implementation. The main points are that the auth middleware
 | 
			
		||||
can reject requests up front, before they ever get to the Swift Proxy
 | 
			
		||||
application, and afterwards when the proxy issues callbacks to verify
 | 
			
		||||
authorization.
 | 
			
		||||
The included swift/common/middleware/swauth.py is a good example of how to
 | 
			
		||||
create an auth subsystem with proxy server auth middleware. The main points are
 | 
			
		||||
that the auth middleware can reject requests up front, before they ever get to
 | 
			
		||||
the Swift Proxy application, and afterwards when the proxy issues callbacks to
 | 
			
		||||
verify authorization.
 | 
			
		||||
 | 
			
		||||
It's generally good to separate the authentication and authorization
 | 
			
		||||
procedures. Authentication verifies that a request actually comes from who it
 | 
			
		||||
@@ -29,7 +27,7 @@ specific information, it just passes it along. Convention has
 | 
			
		||||
environ['REMOTE_USER'] set to the authenticated user string but often more
 | 
			
		||||
information is needed than just that.
 | 
			
		||||
 | 
			
		||||
The included DevAuth will set the REMOTE_USER to a comma separated list of
 | 
			
		||||
The included Swauth will set the REMOTE_USER to a comma separated list of
 | 
			
		||||
groups the user belongs to. The first group will be the "user's group", a group
 | 
			
		||||
that only the user belongs to. The second group will be the "account's group",
 | 
			
		||||
a group that includes all users for that auth account (different than the
 | 
			
		||||
@@ -39,7 +37,7 @@ will be omitted.
 | 
			
		||||
 | 
			
		||||
It is highly recommended that authentication server implementers prefix their
 | 
			
		||||
tokens and Swift storage accounts they create with a configurable reseller
 | 
			
		||||
prefix (`AUTH_` by default with the included DevAuth). This prefix will avoid
 | 
			
		||||
prefix (`AUTH_` by default with the included Swauth). This prefix will avoid
 | 
			
		||||
conflicts with other authentication servers that might be using the same
 | 
			
		||||
Swift cluster. Otherwise, the Swift cluster will have to try all the resellers
 | 
			
		||||
until one validates a token or all fail.
 | 
			
		||||
@@ -48,22 +46,20 @@ A restriction with group names is that no group name should begin with a period
 | 
			
		||||
'.' as that is reserved for internal Swift use (such as the .r for referrer
 | 
			
		||||
designations as you'll see later).
 | 
			
		||||
 | 
			
		||||
Example Authentication with DevAuth:
 | 
			
		||||
Example Authentication with Swauth:
 | 
			
		||||
 | 
			
		||||
    * Token AUTH_tkabcd is given to the DevAuth middleware in a request's
 | 
			
		||||
    * Token AUTH_tkabcd is given to the Swauth middleware in a request's
 | 
			
		||||
      X-Auth-Token header.
 | 
			
		||||
    * The DevAuth middleware makes a validate token AUTH_tkabcd call to the
 | 
			
		||||
      external DevAuth server.
 | 
			
		||||
    * The external DevAuth server validates the token AUTH_tkabcd and discovers
 | 
			
		||||
    * The Swauth middleware validates the token AUTH_tkabcd and discovers
 | 
			
		||||
      it matches the "tester" user within the "test" account for the storage
 | 
			
		||||
      account "AUTH_storage_xyz".
 | 
			
		||||
    * The external DevAuth server responds with "X-Auth-Groups:
 | 
			
		||||
      test:tester,test,AUTH_storage_xyz"
 | 
			
		||||
    * The Swauth server sets the REMOTE_USER to
 | 
			
		||||
      "test:tester,test,AUTH_storage_xyz"
 | 
			
		||||
    * Now this user will have full access (via authorization procedures later)
 | 
			
		||||
      to the AUTH_storage_xyz Swift storage account and access to containers in
 | 
			
		||||
      other storage accounts, provided the storage account begins with the same
 | 
			
		||||
      `AUTH_` reseller prefix and the container has an ACL specifying at least
 | 
			
		||||
      one of those three groups returned.
 | 
			
		||||
      one of those three groups.
 | 
			
		||||
 | 
			
		||||
Authorization is performed through callbacks by the Swift Proxy server to the
 | 
			
		||||
WSGI environment's swift.authorize value, if one is set. The swift.authorize
 | 
			
		||||
@@ -283,11 +279,9 @@ sometimes that's less important than meeting certain ACL requirements.
 | 
			
		||||
Integrating With repoze.what
 | 
			
		||||
----------------------------
 | 
			
		||||
 | 
			
		||||
Here's an example of integration with repoze.what, though honestly it just does
 | 
			
		||||
what the default swift/common/middleware/auth.py does in a slightly different
 | 
			
		||||
way. I'm no repoze.what expert by any stretch; this is just included here to
 | 
			
		||||
hopefully give folks a start on their own code if they want to use
 | 
			
		||||
repoze.what::
 | 
			
		||||
Here's an example of integration with repoze.what, though honestly I'm no
 | 
			
		||||
repoze.what expert by any stretch; this is just included here to hopefully give
 | 
			
		||||
folks a start on their own code if they want to use repoze.what::
 | 
			
		||||
 | 
			
		||||
    from time import time
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -215,22 +215,6 @@ Configuring each node
 | 
			
		||||
 | 
			
		||||
Sample configuration files are provided with all defaults in line-by-line comments.
 | 
			
		||||
  
 | 
			
		||||
  #. If you're going to use the DevAuth (the default swift-auth-server), create
 | 
			
		||||
     `/etc/swift/auth-server.conf` (you can skip this if you're going to use
 | 
			
		||||
     Swauth)::
 | 
			
		||||
 | 
			
		||||
        [DEFAULT]
 | 
			
		||||
        user = <your-user-name>
 | 
			
		||||
 | 
			
		||||
        [pipeline:main]
 | 
			
		||||
        pipeline = auth-server
 | 
			
		||||
 | 
			
		||||
        [app:auth-server]
 | 
			
		||||
        use = egg:swift#auth
 | 
			
		||||
        default_cluster_url = http://127.0.0.1:8080/v1
 | 
			
		||||
        # Highly recommended to change this.
 | 
			
		||||
        super_admin_key = devauth
 | 
			
		||||
 | 
			
		||||
  #. Create `/etc/swift/proxy-server.conf`::
 | 
			
		||||
 | 
			
		||||
        [DEFAULT]
 | 
			
		||||
@@ -238,20 +222,12 @@ Sample configuration files are provided with all defaults in line-by-line commen
 | 
			
		||||
        user = <your-user-name>
 | 
			
		||||
 | 
			
		||||
        [pipeline:main]
 | 
			
		||||
        # For DevAuth:
 | 
			
		||||
        pipeline = healthcheck cache auth proxy-server
 | 
			
		||||
        # For Swauth:
 | 
			
		||||
        # pipeline = healthcheck cache swauth proxy-server
 | 
			
		||||
        pipeline = healthcheck cache swauth proxy-server
 | 
			
		||||
        
 | 
			
		||||
        [app:proxy-server]
 | 
			
		||||
        use = egg:swift#proxy
 | 
			
		||||
        allow_account_management = true
 | 
			
		||||
 | 
			
		||||
        # Only needed for DevAuth
 | 
			
		||||
        [filter:auth]
 | 
			
		||||
        use = egg:swift#auth
 | 
			
		||||
 | 
			
		||||
        # Only needed for Swauth
 | 
			
		||||
        [filter:swauth]
 | 
			
		||||
        use = egg:swift#swauth
 | 
			
		||||
        # Highly recommended to change this.
 | 
			
		||||
@@ -573,14 +549,12 @@ Setting up scripts for running Swift
 | 
			
		||||
        #!/bin/bash
 | 
			
		||||
 | 
			
		||||
        swift-init main start
 | 
			
		||||
        # The auth-server line is only needed for DevAuth:
 | 
			
		||||
        swift-init auth-server start
 | 
			
		||||
 | 
			
		||||
  #. For Swauth (not needed for DevAuth), create `~/bin/recreateaccounts`::
 | 
			
		||||
  #. Create `~/bin/recreateaccounts`::
 | 
			
		||||
  
 | 
			
		||||
        #!/bin/bash
 | 
			
		||||
 | 
			
		||||
        # Replace devauth with whatever your super_admin key is (recorded in
 | 
			
		||||
        # Replace swauthkey with whatever your super_admin key is (recorded in
 | 
			
		||||
        # /etc/swift/proxy-server.conf).
 | 
			
		||||
        swauth-prep -K swauthkey
 | 
			
		||||
        swauth-add-user -K swauthkey -a test tester testing
 | 
			
		||||
@@ -592,24 +566,17 @@ Setting up scripts for running Swift
 | 
			
		||||
 | 
			
		||||
        #!/bin/bash
 | 
			
		||||
 | 
			
		||||
        # Replace devauth with whatever your super_admin key is (recorded in
 | 
			
		||||
        # /etc/swift/auth-server.conf). This swift-auth-recreate-accounts line
 | 
			
		||||
        # is only needed for DevAuth:
 | 
			
		||||
        swift-auth-recreate-accounts -K devauth
 | 
			
		||||
        swift-init rest start
 | 
			
		||||
 | 
			
		||||
  #. `chmod +x ~/bin/*`
 | 
			
		||||
  #. `remakerings`
 | 
			
		||||
  #. `cd ~/swift/trunk; ./.unittests`
 | 
			
		||||
  #. `startmain` (The ``Unable to increase file descriptor limit.  Running as non-root?`` warnings are expected and ok.)
 | 
			
		||||
  #. For Swauth: `recreateaccounts`
 | 
			
		||||
  #. For DevAuth: `swift-auth-add-user -K devauth -a test tester testing` # Replace ``devauth`` with whatever your super_admin key is (recorded in /etc/swift/auth-server.conf).
 | 
			
		||||
  #. Get an `X-Storage-Url` and `X-Auth-Token`: ``curl -v -H 'X-Storage-User: test:tester' -H 'X-Storage-Pass: testing' http://127.0.0.1:11000/v1.0`` # For Swauth, make the last URL `http://127.0.0.1:8080/auth/v1.0`
 | 
			
		||||
  #. `recreateaccounts`
 | 
			
		||||
  #. Get an `X-Storage-Url` and `X-Auth-Token`: ``curl -v -H 'X-Storage-User: test:tester' -H 'X-Storage-Pass: testing' http://127.0.0.1:8080/auth/v1.0``
 | 
			
		||||
  #. Check that you can GET account: ``curl -v -H 'X-Auth-Token: <token-from-x-auth-token-above>' <url-from-x-storage-url-above>``
 | 
			
		||||
  #. Check that `st` works: `st -A http://127.0.0.1:11000/v1.0 -U test:tester -K testing stat` # For Swauth, make the URL `http://127.0.0.1:8080/auth/v1.0`
 | 
			
		||||
  #. For DevAuth: `swift-auth-add-user -K devauth -a test2 tester2 testing2` # Replace ``devauth`` with whatever your super_admin key is (recorded in /etc/swift/auth-server.conf).
 | 
			
		||||
  #. For DevAuth: `swift-auth-add-user -K devauth test tester3 testing3` # Replace ``devauth`` with whatever your super_admin key is (recorded in /etc/swift/auth-server.conf).
 | 
			
		||||
  #. `cp ~/swift/trunk/test/functional/sample.conf /etc/swift/func_test.conf` # For Swauth, add auth_prefix = /auth/ and change auth_port = 8080.
 | 
			
		||||
  #. Check that `st` works: `st -A http://127.0.0.1:8080/auth/v1.0 -U test:tester -K testing stat`
 | 
			
		||||
  #. `cp ~/swift/trunk/test/functional/sample.conf /etc/swift/func_test.conf`
 | 
			
		||||
  #. `cd ~/swift/trunk; ./.functests` (Note: functional tests will first delete
 | 
			
		||||
     everything in the configured accounts.)
 | 
			
		||||
  #. `cd ~/swift/trunk; ./.probetests` (Note: probe tests will reset your
 | 
			
		||||
@@ -634,7 +601,7 @@ If all doesn't go as planned, and tests fail, or you can't auth, or something do
 | 
			
		||||
#. Everything is logged in /var/log/syslog, so that is a good first place to
 | 
			
		||||
   look for errors (most likely python tracebacks).
 | 
			
		||||
#. Make sure all of the server processes are running.  For the base
 | 
			
		||||
   functionality, the Proxy, Account, Container, Object and Auth servers
 | 
			
		||||
   functionality, the Proxy, Account, Container, and Object servers
 | 
			
		||||
   should be running
 | 
			
		||||
#. If one of the servers are not running, and no errors are logged to syslog,
 | 
			
		||||
   it may be useful to try to start the server manually, for example: 
 | 
			
		||||
 
 | 
			
		||||
@@ -1,160 +0,0 @@
 | 
			
		||||
===============================
 | 
			
		||||
Talking to Swift with Cyberduck
 | 
			
		||||
===============================
 | 
			
		||||
 | 
			
		||||
.. note::
 | 
			
		||||
    Put together by Caleb Tennis, thanks Caleb!
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#.  Install Swift, or have credentials for an existing Swift installation. If
 | 
			
		||||
    you plan to install Swift on your own server, follow the general guidelines
 | 
			
		||||
    in the section following this one. (This documentation assumes the use of
 | 
			
		||||
    the DevAuth auth server; if you're using Swauth, you should change all auth
 | 
			
		||||
    URLs /v1.0 to /auth/v1.0)
 | 
			
		||||
 | 
			
		||||
#.  Verify you can connect using the standard Swift Tool `st` from your
 | 
			
		||||
    "public" URL (yes I know this resolves privately inside EC2)::
 | 
			
		||||
 | 
			
		||||
        ubuntu@domU-12-31-39-03-CD-06:/home/swift/swift/bin$ st -A https://ec2-184-72-156-130.compute-1.amazonaws.com:11000/v1.0 -U a3:b3 -K c3 stat
 | 
			
		||||
           Account: 06228ccf-6d0a-4395-889e-e971e8de8781
 | 
			
		||||
        Containers: 0
 | 
			
		||||
           Objects: 0
 | 
			
		||||
             Bytes: 0
 | 
			
		||||
 | 
			
		||||
    .. note::
 | 
			
		||||
 | 
			
		||||
        The Swift Tool `st` can be copied from Swift sources to most any
 | 
			
		||||
        machine with Python installed. You can grab it from
 | 
			
		||||
        http://bazaar.launchpad.net/%7Ehudson-openstack/swift/trunk/annotate/head%3A/bin/st
 | 
			
		||||
        if you don't have the Swift code handy.
 | 
			
		||||
 | 
			
		||||
#.  Download and extract the Cyberduck sources (3.5.1 as of this writing). They
 | 
			
		||||
    should be available at http://trac.cyberduck.ch/
 | 
			
		||||
 | 
			
		||||
#.  Edit the Cyberduck source. Look for lib/cloudfiles.properties, and edit
 | 
			
		||||
    this file. Change auth_url to your public auth URL (note the https)::
 | 
			
		||||
 | 
			
		||||
        auth_url=https://ec2-184-72-156-130.compute-1.amazonaws.com:11000/v1.0
 | 
			
		||||
 | 
			
		||||
#.  Edit source/ch/cyberduck/core/Protocol.java. Look for the line saying
 | 
			
		||||
    "storage.clouddrive.com". Just above that, change::
 | 
			
		||||
 | 
			
		||||
        public boolean isHostnameConfigurable() {
 | 
			
		||||
            return true;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
#.  In the root directory, run "make" to rebuild Cyberduck. When done, type:
 | 
			
		||||
    `open build/Release/Cyberduck.app/` to start the program.
 | 
			
		||||
 | 
			
		||||
#.  Go to "Open Connection", select Rackspace Cloud Files, and connect.
 | 
			
		||||
 | 
			
		||||
    .. image:: images/howto_cyberduck_config.png
 | 
			
		||||
 | 
			
		||||
#.  If you get SSL errors, make sure your auth and proxy server are both setup
 | 
			
		||||
    for SSL. If you get certificate errors (specifically, 'unable to find valid
 | 
			
		||||
    certification path to requested target'), you are using a self signed
 | 
			
		||||
    certificate, you need to perform a few more steps:
 | 
			
		||||
 | 
			
		||||
    .. note::
 | 
			
		||||
 | 
			
		||||
        For some folks, just telling the OS to trust the cert works fine, for
 | 
			
		||||
        others use the following steps.
 | 
			
		||||
 | 
			
		||||
#.  As outlined here: http://blogs.sun.com/andreas/entry/no_more_unable_to_find,
 | 
			
		||||
    download http://blogs.sun.com/andreas/resource/InstallCert.java, run "javac
 | 
			
		||||
    InstallCert.java" to compile it, then run "java InstallCert
 | 
			
		||||
    https://your-auth-server-url:8080". This script will pull down that
 | 
			
		||||
    certificate and put it into a Java cert store, in your local directory. The
 | 
			
		||||
    file is jssecacerts.
 | 
			
		||||
 | 
			
		||||
#.  You need to move that file to $JAVA_HOME/jre/lib/security, so your java run
 | 
			
		||||
    time picks it up.
 | 
			
		||||
 | 
			
		||||
#.  Restart Cyberduck, and it should now allow you to use that certificate
 | 
			
		||||
    without an error.
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
---------------------------------------
 | 
			
		||||
Installing Swift For Use With Cyberduck
 | 
			
		||||
---------------------------------------
 | 
			
		||||
 | 
			
		||||
#.  Both the proxy and auth servers will ultimately need to be running with
 | 
			
		||||
    SSL. You will need a key and certificate to do this, self signed is ok (but
 | 
			
		||||
    a little more work getting Cyberduck to accept it). Put these in
 | 
			
		||||
    /etc/swift/cert.crt and /etc/swift/cert.key.
 | 
			
		||||
 | 
			
		||||
    .. note::
 | 
			
		||||
 | 
			
		||||
        Creating a self-signed cert can usually be done with::
 | 
			
		||||
 | 
			
		||||
            cd /etc/swift
 | 
			
		||||
            openssl req -new -x509 -nodes -out cert.crt -keyout cert.key
 | 
			
		||||
 | 
			
		||||
#.  Example proxy-server config::
 | 
			
		||||
 | 
			
		||||
        [DEFAULT]
 | 
			
		||||
        cert_file = /etc/swift/cert.crt
 | 
			
		||||
        key_file = /etc/swift/cert.key
 | 
			
		||||
        
 | 
			
		||||
        [pipeline:main]
 | 
			
		||||
        pipeline = healthcheck cache auth proxy-server
 | 
			
		||||
        
 | 
			
		||||
        [app:proxy-server]
 | 
			
		||||
        use = egg:swift#proxy
 | 
			
		||||
        
 | 
			
		||||
        [filter:auth]
 | 
			
		||||
        use = egg:swift#auth
 | 
			
		||||
        ssl = true
 | 
			
		||||
        
 | 
			
		||||
        [filter:healthcheck]
 | 
			
		||||
        use = egg:swift#healthcheck
 | 
			
		||||
        
 | 
			
		||||
        [filter:cache]
 | 
			
		||||
        use = egg:swift#memcache
 | 
			
		||||
 | 
			
		||||
#.  Example auth-server config::
 | 
			
		||||
 | 
			
		||||
        [DEFAULT]
 | 
			
		||||
        cert_file = /etc/swift/cert.crt
 | 
			
		||||
        key_file = /etc/swift/cert.key
 | 
			
		||||
        
 | 
			
		||||
        [pipeline:main]
 | 
			
		||||
        pipeline = auth-server
 | 
			
		||||
        
 | 
			
		||||
        [app:auth-server]
 | 
			
		||||
        use = egg:swift#auth
 | 
			
		||||
        super_admin_key = devauth
 | 
			
		||||
        default_cluster_url = https://ec2-184-72-156-130.compute-1.amazonaws.com:8080/v1
 | 
			
		||||
 | 
			
		||||
#.  Use swift-auth-add-user to create a new account and admin user::
 | 
			
		||||
 | 
			
		||||
        ubuntu@domU-12-31-39-03-CD-06:/home/swift/swift/bin$ swift-auth-add-user -K devauth -a a3 b3 c3
 | 
			
		||||
        https://ec2-184-72-156-130.compute-1.amazonaws.com:8080/v1/06228ccf-6d0a-4395-889e-e971e8de8781
 | 
			
		||||
 | 
			
		||||
    .. note::
 | 
			
		||||
        It's important that the URL that is given back to you be accessible
 | 
			
		||||
        publicly. This URL is tied to this account, and will be served
 | 
			
		||||
        back to Cyberduck after authorization. If this URL gives back
 | 
			
		||||
        something like: http://127.0.0.1/v1/... this won't work, because
 | 
			
		||||
        Cyberduck will attempt to connect to 127.0.0.1.
 | 
			
		||||
 | 
			
		||||
        This URL is specified in the auth-server config's
 | 
			
		||||
        default_cluster_url. However, once you have created an
 | 
			
		||||
        account/user, this URL is fixed and won't change even if you change
 | 
			
		||||
        that configuration item. You will have to use sqlite to manually
 | 
			
		||||
        edit the auth.db in order to change it (limitation of using the
 | 
			
		||||
        development auth server, but perhaps someone will patch in this
 | 
			
		||||
        ability someday).
 | 
			
		||||
 | 
			
		||||
#.  Verify you can connect using the standard Swift Tool `st`::
 | 
			
		||||
 | 
			
		||||
        ubuntu@domU-12-31-39-03-CD-06:/home/swift/swift/bin$ st -A https://127.0.0.1:11000/v1.0 -U a3:b3 -K c3 stat
 | 
			
		||||
           Account: 06228ccf-6d0a-4395-889e-e971e8de8781
 | 
			
		||||
        Containers: 0
 | 
			
		||||
           Objects: 0
 | 
			
		||||
             Bytes: 0
 | 
			
		||||
 | 
			
		||||
.. note::
 | 
			
		||||
 | 
			
		||||
    Please let me know if you find any changes that need to be made: ctennis on
 | 
			
		||||
    IRC
 | 
			
		||||
@@ -13,8 +13,7 @@ Prerequisites
 | 
			
		||||
Basic architecture and terms
 | 
			
		||||
----------------------------
 | 
			
		||||
- *node* - a host machine running one or more Swift services
 | 
			
		||||
- *Proxy node* - node that runs Proxy services; can also run Swauth
 | 
			
		||||
- *Auth node* - node that runs the Auth service; only required for DevAuth
 | 
			
		||||
- *Proxy node* - node that runs Proxy services; also runs Swauth
 | 
			
		||||
- *Storage node* - node that runs Account, Container, and Object services
 | 
			
		||||
- *ring* - a set of mappings of Swift data to physical devices
 | 
			
		||||
 | 
			
		||||
@@ -23,15 +22,9 @@ This document shows a cluster using the following types of nodes:
 | 
			
		||||
- one Proxy node
 | 
			
		||||
 | 
			
		||||
  - Runs the swift-proxy-server processes which proxy requests to the
 | 
			
		||||
    appropriate Storage nodes. For Swauth, the proxy server will also contain
 | 
			
		||||
    appropriate Storage nodes. The proxy server will also contain
 | 
			
		||||
    the Swauth service as WSGI middleware.
 | 
			
		||||
 | 
			
		||||
- one Auth node
 | 
			
		||||
 | 
			
		||||
  - Runs the swift-auth-server which controls authentication and
 | 
			
		||||
    authorization for all requests.  This can be on the same node as a
 | 
			
		||||
    Proxy node. This is only required for DevAuth.
 | 
			
		||||
 | 
			
		||||
- five Storage nodes
 | 
			
		||||
 | 
			
		||||
  - Runs the swift-account-server, swift-container-server, and 
 | 
			
		||||
@@ -92,7 +85,6 @@ General OS configuration and partitioning for each node
 | 
			
		||||
 | 
			
		||||
        export STORAGE_LOCAL_NET_IP=10.1.2.3
 | 
			
		||||
        export PROXY_LOCAL_NET_IP=10.1.2.4
 | 
			
		||||
        export AUTH_LOCAL_NET_IP=10.1.2.5
 | 
			
		||||
 | 
			
		||||
.. note::
 | 
			
		||||
    The random string of text in /etc/swift/swift.conf is 
 | 
			
		||||
@@ -136,26 +128,14 @@ Configure the Proxy node
 | 
			
		||||
        bind_port = 8080
 | 
			
		||||
        workers = 8
 | 
			
		||||
        user = swift
 | 
			
		||||
        # For non-local Auth server
 | 
			
		||||
        ip = $AUTH_LOCAL_NET_IP
 | 
			
		||||
        
 | 
			
		||||
        
 | 
			
		||||
        [pipeline:main]
 | 
			
		||||
        # For DevAuth:
 | 
			
		||||
        pipeline = healthcheck cache auth proxy-server
 | 
			
		||||
        # For Swauth:
 | 
			
		||||
        # pipeline = healthcheck cache swauth proxy-server
 | 
			
		||||
        pipeline = healthcheck cache swauth proxy-server
 | 
			
		||||
        
 | 
			
		||||
        [app:proxy-server]
 | 
			
		||||
        use = egg:swift#proxy
 | 
			
		||||
        allow_account_management = true
 | 
			
		||||
        
 | 
			
		||||
        # Only needed for DevAuth
 | 
			
		||||
        [filter:auth]
 | 
			
		||||
        use = egg:swift#auth
 | 
			
		||||
        ssl = true
 | 
			
		||||
        
 | 
			
		||||
        # Only needed for Swauth
 | 
			
		||||
        [filter:swauth]
 | 
			
		||||
        use = egg:swift#swauth
 | 
			
		||||
        default_swift_cluster = local#https://$PROXY_LOCAL_NET_IP:8080/v1
 | 
			
		||||
@@ -228,42 +208,6 @@ Configure the Proxy node
 | 
			
		||||
        swift-init proxy start
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
Configure the Auth node
 | 
			
		||||
-----------------------
 | 
			
		||||
 | 
			
		||||
.. note:: Only required for DevAuth; you can skip this section for Swauth.
 | 
			
		||||
 | 
			
		||||
#. If this node is not running on the same node as a proxy, create a
 | 
			
		||||
   self-signed cert as you did for the Proxy node
 | 
			
		||||
 | 
			
		||||
#. Install swift-auth service::
 | 
			
		||||
 | 
			
		||||
        apt-get install swift-auth
 | 
			
		||||
 | 
			
		||||
#. Create /etc/swift/auth-server.conf::
 | 
			
		||||
 | 
			
		||||
        cat >/etc/swift/auth-server.conf <<EOF
 | 
			
		||||
        [DEFAULT]
 | 
			
		||||
        cert_file = /etc/swift/cert.crt
 | 
			
		||||
        key_file = /etc/swift/cert.key
 | 
			
		||||
        user = swift
 | 
			
		||||
        
 | 
			
		||||
        [pipeline:main]
 | 
			
		||||
        pipeline = auth-server
 | 
			
		||||
        
 | 
			
		||||
        [app:auth-server]
 | 
			
		||||
        use = egg:swift#auth
 | 
			
		||||
        default_cluster_url = https://<PROXY_HOSTNAME>:8080/v1
 | 
			
		||||
        # Highly recommended to change this key to something else!
 | 
			
		||||
        super_admin_key = devauth
 | 
			
		||||
        EOF
 | 
			
		||||
        
 | 
			
		||||
#. Start Auth services::
 | 
			
		||||
 | 
			
		||||
        swift-init auth start
 | 
			
		||||
        chown swift:swift /etc/swift/auth.db
 | 
			
		||||
        swift-init auth restart            # 1.1.0 workaround because swift creates auth.db owned as root
 | 
			
		||||
 | 
			
		||||
Configure the Storage nodes
 | 
			
		||||
---------------------------
 | 
			
		||||
 | 
			
		||||
@@ -418,26 +362,21 @@ replicator, updater, or auditor.::
 | 
			
		||||
Create Swift admin account and test
 | 
			
		||||
-----------------------------------
 | 
			
		||||
 | 
			
		||||
You run these commands from the Auth node.
 | 
			
		||||
 | 
			
		||||
.. note:: For Swauth, replace the https://<AUTH_HOSTNAME>:11000/v1.0 with
 | 
			
		||||
          https://<PROXY_HOSTNAME>:8080/auth/v1.0
 | 
			
		||||
You run these commands from the Proxy node.
 | 
			
		||||
 | 
			
		||||
#. Create a user with administrative privileges (account = system,
 | 
			
		||||
   username = root, password = testpass).  Make sure to replace 
 | 
			
		||||
   ``devauth`` (or ``swauthkey``) with whatever super_admin key you assigned in
 | 
			
		||||
   the auth-server.conf file (or proxy-server.conf file in the case of Swauth)
 | 
			
		||||
   ``swauthkey`` with whatever super_admin key you assigned in
 | 
			
		||||
   the proxy-server.conf file
 | 
			
		||||
   above.  *Note: None of the values of 
 | 
			
		||||
   account, username, or password are special - they can be anything.*::
 | 
			
		||||
 | 
			
		||||
        # For DevAuth:
 | 
			
		||||
        swift-auth-add-user -K devauth -a system root testpass
 | 
			
		||||
        # For Swauth:
 | 
			
		||||
        swauth-add-user -K swauthkey -a system root testpass
 | 
			
		||||
        swauth-prep -A https://<PROXY_HOSTNAME>:8080/auth/ -K swauthkey
 | 
			
		||||
        swauth-add-user -A https://<PROXY_HOSTNAME>:8080/auth/ -K swauthkey -a system root testpass
 | 
			
		||||
 | 
			
		||||
#. Get an X-Storage-Url and X-Auth-Token::
 | 
			
		||||
 | 
			
		||||
        curl -k -v -H 'X-Storage-User: system:root' -H 'X-Storage-Pass: testpass' https://<AUTH_HOSTNAME>:11000/v1.0
 | 
			
		||||
        curl -k -v -H 'X-Storage-User: system:root' -H 'X-Storage-Pass: testpass' https://<PROXY_HOSTNAME>:8080/auth/v1.0
 | 
			
		||||
 | 
			
		||||
#. Check that you can HEAD the account::
 | 
			
		||||
 | 
			
		||||
@@ -445,32 +384,32 @@ You run these commands from the Auth node.
 | 
			
		||||
 | 
			
		||||
#. Check that ``st`` works  (at this point, expect zero containers, zero objects, and zero bytes)::
 | 
			
		||||
 | 
			
		||||
        st -A https://<AUTH_HOSTNAME>:11000/v1.0 -U system:root -K testpass stat
 | 
			
		||||
        st -A https://<PROXY_HOSTNAME>:8080/auth/v1.0 -U system:root -K testpass stat
 | 
			
		||||
 | 
			
		||||
#. Use ``st`` to upload a few files named 'bigfile[1-2].tgz' to a container named 'myfiles'::
 | 
			
		||||
 | 
			
		||||
        st -A https://<AUTH_HOSTNAME>:11000/v1.0 -U system:root -K testpass upload myfiles bigfile1.tgz
 | 
			
		||||
        st -A https://<AUTH_HOSTNAME>:11000/v1.0 -U system:root -K testpass upload myfiles bigfile2.tgz
 | 
			
		||||
        st -A https://<PROXY_HOSTNAME>:8080/auth/v1.0 -U system:root -K testpass upload myfiles bigfile1.tgz
 | 
			
		||||
        st -A https://<PROXY_HOSTNAME>:8080/auth/v1.0 -U system:root -K testpass upload myfiles bigfile2.tgz
 | 
			
		||||
 | 
			
		||||
#. Use ``st`` to download all files from the 'myfiles' container::
 | 
			
		||||
 | 
			
		||||
        st -A https://<AUTH_HOSTNAME>:11000/v1.0 -U system:root -K testpass download myfiles
 | 
			
		||||
        st -A https://<PROXY_HOSTNAME>:8080/auth/v1.0 -U system:root -K testpass download myfiles
 | 
			
		||||
 | 
			
		||||
#. Use ``st`` to save a backup of your builder files to a container named 'builders'. Very important not to lose your builders!::
 | 
			
		||||
 | 
			
		||||
        st -A https://<AUTH_HOSTNAME>:11000/v1.0 -U system:root -K testpass upload builders /etc/swift/*.builder
 | 
			
		||||
        st -A https://<PROXY_HOSTNAME>:8080/auth/v1.0 -U system:root -K testpass upload builders /etc/swift/*.builder
 | 
			
		||||
 | 
			
		||||
#. Use ``st`` to list your containers::
 | 
			
		||||
 | 
			
		||||
        st -A https://<AUTH_HOSTNAME>:11000/v1.0 -U system:root -K testpass list
 | 
			
		||||
        st -A https://<PROXY_HOSTNAME>:8080/auth/v1.0 -U system:root -K testpass list
 | 
			
		||||
 | 
			
		||||
#. Use ``st`` to list the contents of your 'builders' container::
 | 
			
		||||
 | 
			
		||||
        st -A https://<AUTH_HOSTNAME>:11000/v1.0 -U system:root -K testpass list builders
 | 
			
		||||
        st -A https://<PROXY_HOSTNAME>:8080/auth/v1.0 -U system:root -K testpass list builders
 | 
			
		||||
 | 
			
		||||
#. Use ``st`` to download all files from the 'builders' container::
 | 
			
		||||
 | 
			
		||||
        st -A https://<AUTH_HOSTNAME>:11000/v1.0 -U system:root -K testpass download builders
 | 
			
		||||
        st -A https://<PROXY_HOSTNAME>:8080/auth/v1.0 -U system:root -K testpass download builders
 | 
			
		||||
 | 
			
		||||
.. _add-proxy-server:
 | 
			
		||||
 | 
			
		||||
@@ -489,31 +428,25 @@ See :ref:`config-proxy` for the initial setup, and then follow these additional
 | 
			
		||||
        use = egg:swift#memcache
 | 
			
		||||
        memcache_servers = <PROXY_LOCAL_NET_IP>:11211
 | 
			
		||||
 | 
			
		||||
#. Change the default_cluster_url to point to the load balanced url, rather than the first proxy server you created in /etc/swift/auth-server.conf (for DevAuth) or in /etc/swift/proxy-server.conf (for Swauth)::
 | 
			
		||||
#. Change the default_cluster_url to point to the load balanced url, rather than the first proxy server you created in /etc/swift/proxy-server.conf::
 | 
			
		||||
 | 
			
		||||
        # For DevAuth, in /etc/swift/auth-server.conf
 | 
			
		||||
        [app:auth-server]
 | 
			
		||||
        use = egg:swift#auth
 | 
			
		||||
        default_cluster_url = https://<LOAD_BALANCER_HOSTNAME>/v1
 | 
			
		||||
        # Highly recommended to change this key to something else!
 | 
			
		||||
        super_admin_key = devauth
 | 
			
		||||
 | 
			
		||||
        # For Swauth, in /etc/swift/proxy-server.conf
 | 
			
		||||
        [filter:swauth]
 | 
			
		||||
        use = egg:swift#swauth
 | 
			
		||||
        default_swift_cluster = local#http://<LOAD_BALANCER_HOSTNAME>/v1
 | 
			
		||||
        # Highly recommended to change this key to something else!
 | 
			
		||||
        super_admin_key = swauthkey
 | 
			
		||||
 | 
			
		||||
#. For DevAuth, after you change the default_cluster_url setting, you have to delete the auth database and recreate the Swift users, or manually update the auth database with the correct URL for each account.
 | 
			
		||||
#. The above will make new accounts with the new default_swift_cluster URL, however it won't change any existing accounts. You can change a service URL for existing accounts with::
 | 
			
		||||
 | 
			
		||||
   For Swauth, you can change a service URL with::
 | 
			
		||||
    First retreve what the URL was::
 | 
			
		||||
 | 
			
		||||
        swauth-set-account-service -K swauthkey <account> storage local <new_url_for_the_account>
 | 
			
		||||
         swauth-list -A https://<PROXY_HOSTNAME>:8080/auth/ -K swauthkey <account>
 | 
			
		||||
 | 
			
		||||
   You can obtain old service URLs with::
 | 
			
		||||
     And then update it with::
 | 
			
		||||
 | 
			
		||||
        swauth-list -K swauthkey <account>
 | 
			
		||||
         swauth-set-account-service -A https://<PROXY_HOSTNAME>:8080/auth/ -K swauthkey <account> storage local <new_url_for_the_account>
 | 
			
		||||
 | 
			
		||||
    Make the <new_url_for_the_account> look just like it's original URL but with the host:port update you want.
 | 
			
		||||
 | 
			
		||||
#. Next, copy all the ring information to all the nodes, including your new proxy nodes, and ensure the ring info gets to all the storage nodes as well. 
 | 
			
		||||
 | 
			
		||||
@@ -522,15 +455,16 @@ See :ref:`config-proxy` for the initial setup, and then follow these additional
 | 
			
		||||
Additional Cleanup Script for Swauth
 | 
			
		||||
------------------------------------
 | 
			
		||||
 | 
			
		||||
If you decide to use Swauth, you'll want to install a cronjob to clean up any
 | 
			
		||||
With Swauth, you'll want to install a cronjob to clean up any
 | 
			
		||||
orphaned expired tokens. These orphaned tokens can occur when a "stampede"
 | 
			
		||||
occurs where a single user authenticates several times concurrently. Generally,
 | 
			
		||||
these orphaned tokens don't pose much of an issue, but it's good to clean them
 | 
			
		||||
up once a "token life" period (default: 1 day or 86400 seconds).
 | 
			
		||||
 | 
			
		||||
This should be as simple as adding `swauth-cleanup-tokens -K swauthkey >
 | 
			
		||||
/dev/null` to a crontab entry on one of the proxies that is running Swauth; but
 | 
			
		||||
run `swauth-cleanup-tokens` with no arguments for detailed help on the options
 | 
			
		||||
This should be as simple as adding `swauth-cleanup-tokens -A
 | 
			
		||||
https://<PROXY_HOSTNAME>:8080/auth/ -K swauthkey > /dev/null` to a crontab
 | 
			
		||||
entry on one of the proxies that is running Swauth; but run
 | 
			
		||||
`swauth-cleanup-tokens` with no arguments for detailed help on the options
 | 
			
		||||
available.
 | 
			
		||||
 | 
			
		||||
Troubleshooting Notes
 | 
			
		||||
 
 | 
			
		||||
										
											Binary file not shown.
										
									
								
							| 
		 Before Width: | Height: | Size: 40 KiB  | 
@@ -67,14 +67,6 @@ Administrator Documentation
 | 
			
		||||
    admin_guide
 | 
			
		||||
    debian_package_guide
 | 
			
		||||
 | 
			
		||||
End User Guides
 | 
			
		||||
===============
 | 
			
		||||
 | 
			
		||||
.. toctree::
 | 
			
		||||
    :maxdepth: 1
 | 
			
		||||
 | 
			
		||||
    howto_cyberduck
 | 
			
		||||
 | 
			
		||||
Source Documentation
 | 
			
		||||
====================
 | 
			
		||||
 | 
			
		||||
@@ -87,7 +79,6 @@ Source Documentation
 | 
			
		||||
    container
 | 
			
		||||
    db
 | 
			
		||||
    object
 | 
			
		||||
    auth
 | 
			
		||||
    misc
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -2,61 +2,57 @@
 | 
			
		||||
The Auth System
 | 
			
		||||
===============
 | 
			
		||||
 | 
			
		||||
--------------
 | 
			
		||||
Developer Auth
 | 
			
		||||
--------------
 | 
			
		||||
 | 
			
		||||
The auth system for Swift is loosely based on the auth system from the existing
 | 
			
		||||
Rackspace architecture -- actually from a few existing auth systems -- and is
 | 
			
		||||
therefore a bit disjointed. The distilled points about it are:
 | 
			
		||||
 | 
			
		||||
* The authentication/authorization part is outside Swift itself
 | 
			
		||||
* The user of Swift passes in an auth token with each request
 | 
			
		||||
* Swift validates each token with the external auth system and caches the
 | 
			
		||||
  result
 | 
			
		||||
* The token does not change from request to request, but does expire
 | 
			
		||||
 | 
			
		||||
The token can be passed into Swift using the X-Auth-Token or the
 | 
			
		||||
X-Storage-Token header. Both have the same format: just a simple string
 | 
			
		||||
representing the token. Some external systems use UUID tokens, some an MD5 hash
 | 
			
		||||
of something unique, some use "something else" but the salient point is that
 | 
			
		||||
the token is a string which can be sent as-is back to the auth system for
 | 
			
		||||
validation.
 | 
			
		||||
 | 
			
		||||
Swift will make calls to the external auth system, giving the auth token to be
 | 
			
		||||
validated. For a valid token, the auth system responds with an overall
 | 
			
		||||
expiration in seconds from now. Swift will cache the token up to the expiration
 | 
			
		||||
time. The included devauth also has the concept of admin and non-admin users
 | 
			
		||||
within an account. Admin users can do anything within the account. Non-admin
 | 
			
		||||
users can only perform operations per container based on the container's
 | 
			
		||||
X-Container-Read and X-Container-Write ACLs. For more information on ACLs, see
 | 
			
		||||
:mod:`swift.common.middleware.acl`
 | 
			
		||||
 | 
			
		||||
The user starts a session by sending a ReST request to the external auth system
 | 
			
		||||
to receive the auth token and a URL to the Swift system.
 | 
			
		||||
 | 
			
		||||
--------------
 | 
			
		||||
Extending Auth
 | 
			
		||||
--------------
 | 
			
		||||
 | 
			
		||||
Auth is written as wsgi middleware, so implementing your own auth is as easy
 | 
			
		||||
as writing new wsgi middleware, and plugging it in to the proxy server.
 | 
			
		||||
 | 
			
		||||
The current middleware is implemented in the DevAuthMiddleware class in
 | 
			
		||||
swift/common/middleware/auth.py, and should be a good starting place for
 | 
			
		||||
implementing your own auth.
 | 
			
		||||
 | 
			
		||||
Also, see :doc:`development_auth`.
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
------
 | 
			
		||||
Swauth
 | 
			
		||||
------
 | 
			
		||||
 | 
			
		||||
The Swauth system is an optional DevAuth replacement included at
 | 
			
		||||
swift/common/middleware/swauth.py; a scalable authentication and
 | 
			
		||||
authorization system that uses Swift itself as its backing store. This section
 | 
			
		||||
will describe how it stores its data.
 | 
			
		||||
The auth system for Swift is loosely based on the auth system from the existing
 | 
			
		||||
Rackspace architecture -- actually from a few existing auth systems -- and is
 | 
			
		||||
therefore a bit disjointed. The distilled points about it are:
 | 
			
		||||
 | 
			
		||||
* The authentication/authorization part can be an external system or a
 | 
			
		||||
  subsystem run within Swift as WSGI middleware
 | 
			
		||||
* The user of Swift passes in an auth token with each request
 | 
			
		||||
* Swift validates each token with the external auth system or auth subsystem
 | 
			
		||||
  and caches the result
 | 
			
		||||
* The token does not change from request to request, but does expire
 | 
			
		||||
 | 
			
		||||
The token can be passed into Swift using the X-Auth-Token or the
 | 
			
		||||
X-Storage-Token header. Both have the same format: just a simple string
 | 
			
		||||
representing the token. Some auth systems use UUID tokens, some an MD5 hash of
 | 
			
		||||
something unique, some use "something else" but the salient point is that the
 | 
			
		||||
token is a string which can be sent as-is back to the auth system for
 | 
			
		||||
validation.
 | 
			
		||||
 | 
			
		||||
Swift will make calls to the auth system, giving the auth token to be
 | 
			
		||||
validated. For a valid token, the auth system responds with an overall
 | 
			
		||||
expiration in seconds from now. Swift will cache the token up to the expiration
 | 
			
		||||
time. The included Swauth also has the concept of admin and non-admin users
 | 
			
		||||
within an account. Admin users can do anything within the account. Non-admin
 | 
			
		||||
users can only perform operations per container based on the container's
 | 
			
		||||
X-Container-Read and X-Container-Write ACLs. For more information on ACLs, see
 | 
			
		||||
:mod:`swift.common.middleware.acl`
 | 
			
		||||
 | 
			
		||||
The user starts a session by sending a ReST request to the auth system to
 | 
			
		||||
receive the auth token and a URL to the Swift system.
 | 
			
		||||
 | 
			
		||||
--------------
 | 
			
		||||
Extending Auth
 | 
			
		||||
--------------
 | 
			
		||||
 | 
			
		||||
Swauth is written as wsgi middleware, so implementing your own auth is as easy
 | 
			
		||||
as writing new wsgi middleware, and plugging it in to the proxy server.
 | 
			
		||||
 | 
			
		||||
Also, see :doc:`development_auth`.
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
--------------
 | 
			
		||||
Swauth Details
 | 
			
		||||
--------------
 | 
			
		||||
 | 
			
		||||
The Swauth system is included at swift/common/middleware/swauth.py; a scalable
 | 
			
		||||
authentication and authorization system that uses Swift itself as its backing
 | 
			
		||||
store. This section will describe how it stores its data.
 | 
			
		||||
 | 
			
		||||
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
 | 
			
		||||
 
 | 
			
		||||
@@ -1,30 +0,0 @@
 | 
			
		||||
# Only needed for DevAuth; Swauth is within the proxy-server.conf
 | 
			
		||||
[DEFAULT]
 | 
			
		||||
# bind_ip = 0.0.0.0
 | 
			
		||||
# bind_port = 11000
 | 
			
		||||
# workers = 1
 | 
			
		||||
# user = swift
 | 
			
		||||
# swift_dir = /etc/swift
 | 
			
		||||
# cert_file = Default is no cert; format is path like /etc/swift/auth.crt
 | 
			
		||||
# key_file = Default is no key; format is path like /etc/swift/auth.key
 | 
			
		||||
# You can specify default log routing here if you want:
 | 
			
		||||
# log_name = swift
 | 
			
		||||
# log_facility = LOG_LOCAL0
 | 
			
		||||
# log_level = INFO
 | 
			
		||||
 | 
			
		||||
[pipeline:main]
 | 
			
		||||
pipeline = auth-server
 | 
			
		||||
 | 
			
		||||
[app:auth-server]
 | 
			
		||||
use = egg:swift#auth
 | 
			
		||||
# Highly recommended to change this.
 | 
			
		||||
super_admin_key = devauth
 | 
			
		||||
# You can override the default log routing for this app here:
 | 
			
		||||
# set log_name = proxy-server
 | 
			
		||||
# set log_facility = LOG_LOCAL0
 | 
			
		||||
# set log_level = INFO
 | 
			
		||||
# set log_headers = False
 | 
			
		||||
# reseller_prefix = AUTH
 | 
			
		||||
# default_cluster_url = http://127.0.0.1:8080/v1
 | 
			
		||||
# token_life = 86400
 | 
			
		||||
# node_timeout = 10
 | 
			
		||||
@@ -13,10 +13,7 @@
 | 
			
		||||
# log_level = INFO
 | 
			
		||||
 | 
			
		||||
[pipeline:main]
 | 
			
		||||
# For DevAuth:
 | 
			
		||||
pipeline = catch_errors healthcheck cache ratelimit auth proxy-server
 | 
			
		||||
# For Swauth:
 | 
			
		||||
# pipeline = catch_errors healthcheck cache ratelimit swauth proxy-server
 | 
			
		||||
pipeline = catch_errors healthcheck cache ratelimit swauth proxy-server
 | 
			
		||||
 | 
			
		||||
[app:proxy-server]
 | 
			
		||||
use = egg:swift#proxy
 | 
			
		||||
@@ -44,27 +41,6 @@ use = egg:swift#proxy
 | 
			
		||||
# 'false' no one, even authorized, can.
 | 
			
		||||
# allow_account_management = false
 | 
			
		||||
 | 
			
		||||
# Only needed for DevAuth
 | 
			
		||||
[filter:auth]
 | 
			
		||||
use = egg:swift#auth
 | 
			
		||||
# You can override the default log routing for this filter here:
 | 
			
		||||
# set log_name = auth-server
 | 
			
		||||
# set log_facility = LOG_LOCAL0
 | 
			
		||||
# set log_level = INFO
 | 
			
		||||
# set log_headers = False
 | 
			
		||||
# The reseller prefix will verify a token begins with this prefix before even
 | 
			
		||||
# attempting to validate it with the external authentication server. 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
 | 
			
		||||
# ip = 127.0.0.1
 | 
			
		||||
# port = 11000
 | 
			
		||||
# ssl = false
 | 
			
		||||
# prefix = /
 | 
			
		||||
# node_timeout = 10
 | 
			
		||||
 | 
			
		||||
# Only needed for Swauth
 | 
			
		||||
[filter:swauth]
 | 
			
		||||
use = egg:swift#swauth
 | 
			
		||||
# You can override the default log routing for this filter here:
 | 
			
		||||
@@ -97,7 +73,7 @@ super_admin_key = swauthkey
 | 
			
		||||
[filter:healthcheck]
 | 
			
		||||
use = egg:swift#healthcheck
 | 
			
		||||
# You can override the default log routing for this filter here:
 | 
			
		||||
# set log_name = auth-server
 | 
			
		||||
# set log_name = healthcheck
 | 
			
		||||
# set log_facility = LOG_LOCAL0
 | 
			
		||||
# set log_level = INFO
 | 
			
		||||
# set log_headers = False
 | 
			
		||||
@@ -105,7 +81,7 @@ use = egg:swift#healthcheck
 | 
			
		||||
[filter:cache]
 | 
			
		||||
use = egg:swift#memcache
 | 
			
		||||
# You can override the default log routing for this filter here:
 | 
			
		||||
# set log_name = auth-server
 | 
			
		||||
# set log_name = cache
 | 
			
		||||
# set log_facility = LOG_LOCAL0
 | 
			
		||||
# set log_level = INFO
 | 
			
		||||
# set log_headers = False
 | 
			
		||||
@@ -116,7 +92,7 @@ use = egg:swift#memcache
 | 
			
		||||
[filter:ratelimit]
 | 
			
		||||
use = egg:swift#ratelimit
 | 
			
		||||
# You can override the default log routing for this filter here:
 | 
			
		||||
# set log_name = auth-server
 | 
			
		||||
# set log_name = ratelimit
 | 
			
		||||
# set log_facility = LOG_LOCAL0
 | 
			
		||||
# set log_level = INFO
 | 
			
		||||
# set log_headers = False
 | 
			
		||||
@@ -148,7 +124,7 @@ use = egg:swift#ratelimit
 | 
			
		||||
[filter:domain_remap]
 | 
			
		||||
use = egg:swift#domain_remap
 | 
			
		||||
# You can override the default log routing for this filter here:
 | 
			
		||||
# set log_name = auth-server
 | 
			
		||||
# set log_name = domain_remap
 | 
			
		||||
# set log_facility = LOG_LOCAL0
 | 
			
		||||
# set log_level = INFO
 | 
			
		||||
# set log_headers = False
 | 
			
		||||
@@ -159,7 +135,7 @@ use = egg:swift#domain_remap
 | 
			
		||||
[filter:catch_errors]
 | 
			
		||||
use = egg:swift#catch_errors
 | 
			
		||||
# You can override the default log routing for this filter here:
 | 
			
		||||
# set log_name = auth-server
 | 
			
		||||
# set log_name = catch_errors
 | 
			
		||||
# set log_facility = LOG_LOCAL0
 | 
			
		||||
# set log_level = INFO
 | 
			
		||||
# set log_headers = False
 | 
			
		||||
@@ -168,7 +144,7 @@ use = egg:swift#catch_errors
 | 
			
		||||
# Note: this middleware requires python-dnspython
 | 
			
		||||
use = egg:swift#cname_lookup
 | 
			
		||||
# You can override the default log routing for this filter here:
 | 
			
		||||
# set log_name = auth-server
 | 
			
		||||
# set log_name = cname_lookup
 | 
			
		||||
# set log_facility = LOG_LOCAL0
 | 
			
		||||
# set log_level = INFO
 | 
			
		||||
# set log_headers = False
 | 
			
		||||
 
 | 
			
		||||
@@ -1,8 +1,5 @@
 | 
			
		||||
[stats]
 | 
			
		||||
# For DevAuth:
 | 
			
		||||
auth_url = http://saio:11000/auth
 | 
			
		||||
# For Swauth:
 | 
			
		||||
# auth_url = http://saio:8080/auth/v1.0
 | 
			
		||||
auth_url = http://saio:8080/auth/v1.0
 | 
			
		||||
auth_user = test:tester
 | 
			
		||||
auth_key = testing
 | 
			
		||||
# swift_dir = /etc/swift
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										5
									
								
								setup.py
									
									
									
									
									
								
							
							
						
						
									
										5
									
								
								setup.py
									
									
									
									
									
								
							@@ -79,9 +79,6 @@ setup(
 | 
			
		||||
        'bin/st', 'bin/swift-account-auditor',
 | 
			
		||||
        'bin/swift-account-audit', 'bin/swift-account-reaper',
 | 
			
		||||
        'bin/swift-account-replicator', 'bin/swift-account-server',
 | 
			
		||||
        'bin/swift-auth-add-user',
 | 
			
		||||
        'bin/swift-auth-recreate-accounts', 'bin/swift-auth-server',
 | 
			
		||||
        'bin/swift-auth-update-reseller-prefixes',
 | 
			
		||||
        'bin/swift-container-auditor',
 | 
			
		||||
        'bin/swift-container-replicator',
 | 
			
		||||
        'bin/swift-container-server', 'bin/swift-container-updater',
 | 
			
		||||
@@ -108,10 +105,8 @@ setup(
 | 
			
		||||
            'object=swift.obj.server:app_factory',
 | 
			
		||||
            'container=swift.container.server:app_factory',
 | 
			
		||||
            'account=swift.account.server:app_factory',
 | 
			
		||||
            'auth=swift.auth.server:app_factory',
 | 
			
		||||
            ],
 | 
			
		||||
        'paste.filter_factory': [
 | 
			
		||||
            'auth=swift.common.middleware.auth:filter_factory',
 | 
			
		||||
            'swauth=swift.common.middleware.swauth:filter_factory',
 | 
			
		||||
            'healthcheck=swift.common.middleware.healthcheck:filter_factory',
 | 
			
		||||
            'memcache=swift.common.middleware.memcache:filter_factory',
 | 
			
		||||
 
 | 
			
		||||
@@ -1,693 +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.
 | 
			
		||||
 | 
			
		||||
from __future__ import with_statement
 | 
			
		||||
import os
 | 
			
		||||
import sys
 | 
			
		||||
from contextlib import contextmanager
 | 
			
		||||
from time import gmtime, strftime, time
 | 
			
		||||
from urllib import unquote, quote
 | 
			
		||||
from uuid import uuid4
 | 
			
		||||
from hashlib import md5, sha1
 | 
			
		||||
import hmac
 | 
			
		||||
import base64
 | 
			
		||||
 | 
			
		||||
import sqlite3
 | 
			
		||||
from webob import Request, Response
 | 
			
		||||
from webob.exc import HTTPBadRequest, HTTPConflict, HTTPForbidden, \
 | 
			
		||||
    HTTPNoContent, HTTPUnauthorized, HTTPServiceUnavailable, HTTPNotFound
 | 
			
		||||
 | 
			
		||||
from swift.common.bufferedhttp import http_connect_raw as http_connect
 | 
			
		||||
from swift.common.db import get_db_connection
 | 
			
		||||
from swift.common.utils import get_logger, split_path, urlparse
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class AuthController(object):
 | 
			
		||||
    """
 | 
			
		||||
    Sample implementation of an authorization server for development work. This
 | 
			
		||||
    server only implements the basic functionality and isn't written for high
 | 
			
		||||
    availability or to scale to thousands (or even hundreds) of requests per
 | 
			
		||||
    second. It is mainly for use by developers working on the rest of the
 | 
			
		||||
    system.
 | 
			
		||||
 | 
			
		||||
    The design of the auth system was restricted by a couple of existing
 | 
			
		||||
    systems.
 | 
			
		||||
 | 
			
		||||
    This implementation stores an account name, user name, and password (in
 | 
			
		||||
    plain text!) as well as a corresponding Swift cluster url and account hash.
 | 
			
		||||
    One existing auth system used account, user, and password whereas another
 | 
			
		||||
    used just account and an "API key". Here, we support both systems with
 | 
			
		||||
    their various, sometimes colliding headers.
 | 
			
		||||
 | 
			
		||||
    The most common use case is by the end user:
 | 
			
		||||
 | 
			
		||||
    * The user makes a ReST call to the auth server requesting a token and url
 | 
			
		||||
      to use to access the Swift cluster.
 | 
			
		||||
    * The auth system validates the user info and returns a token and url for
 | 
			
		||||
      the user to use with the Swift cluster.
 | 
			
		||||
    * The user makes a ReST call to the Swift cluster using the url given with
 | 
			
		||||
      the token as the X-Auth-Token header.
 | 
			
		||||
    * The Swift cluster makes an ReST call to the auth server to validate the
 | 
			
		||||
      token, caching the result for future requests up to the expiration the
 | 
			
		||||
      auth server returns.
 | 
			
		||||
    * The auth server validates the token given and returns the expiration for
 | 
			
		||||
      the token.
 | 
			
		||||
    * The Swift cluster completes the user's request.
 | 
			
		||||
 | 
			
		||||
    Another use case is creating a new user:
 | 
			
		||||
 | 
			
		||||
    * The developer makes a ReST call to create a new user.
 | 
			
		||||
    * If the account for the user does not yet exist, the auth server makes
 | 
			
		||||
      a ReST call to the Swift cluster to create a new account on its end.
 | 
			
		||||
    * The auth server records the information in its database.
 | 
			
		||||
 | 
			
		||||
    A last use case is recreating existing accounts; this is really only useful
 | 
			
		||||
    on a development system when the drives are reformatted quite often but
 | 
			
		||||
    the auth server's database is retained:
 | 
			
		||||
 | 
			
		||||
    * A developer makes an ReST call to have the existing accounts recreated.
 | 
			
		||||
    * For each account in its database, the auth server makes a ReST call to
 | 
			
		||||
      the Swift cluster to create the specific account on its end.
 | 
			
		||||
 | 
			
		||||
    :param conf: The [auth-server] dictionary of the auth server configuration
 | 
			
		||||
                 file
 | 
			
		||||
 | 
			
		||||
    See the etc/auth-server.conf-sample for information on the possible
 | 
			
		||||
    configuration parameters.
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    def __init__(self, conf):
 | 
			
		||||
        self.logger = get_logger(conf, log_route='auth-server')
 | 
			
		||||
        self.super_admin_key = conf.get('super_admin_key')
 | 
			
		||||
        if not self.super_admin_key:
 | 
			
		||||
            msg = _('No super_admin_key set in conf file! Exiting.')
 | 
			
		||||
            try:
 | 
			
		||||
                self.logger.critical(msg)
 | 
			
		||||
            except Exception:
 | 
			
		||||
                pass
 | 
			
		||||
            raise ValueError(msg)
 | 
			
		||||
        self.swift_dir = conf.get('swift_dir', '/etc/swift')
 | 
			
		||||
        self.reseller_prefix = conf.get('reseller_prefix', 'AUTH').strip()
 | 
			
		||||
        if self.reseller_prefix and self.reseller_prefix[-1] != '_':
 | 
			
		||||
            self.reseller_prefix += '_'
 | 
			
		||||
        self.default_cluster_url = conf.get('default_cluster_url',
 | 
			
		||||
                                    'http://127.0.0.1:8080/v1').rstrip('/')
 | 
			
		||||
        self.token_life = int(conf.get('token_life', 86400))
 | 
			
		||||
        self.log_headers = conf.get('log_headers') == 'True'
 | 
			
		||||
        self.db_file = os.path.join(self.swift_dir, 'auth.db')
 | 
			
		||||
        self.conn = get_db_connection(self.db_file, okay_to_create=True)
 | 
			
		||||
        try:
 | 
			
		||||
            self.conn.execute('SELECT admin FROM account LIMIT 1')
 | 
			
		||||
        except sqlite3.OperationalError, err:
 | 
			
		||||
            if str(err) == 'no such column: admin':
 | 
			
		||||
                self.conn.execute("ALTER TABLE account ADD COLUMN admin TEXT")
 | 
			
		||||
                self.conn.execute("UPDATE account SET admin = 't'")
 | 
			
		||||
        try:
 | 
			
		||||
            self.conn.execute('SELECT reseller_admin FROM account LIMIT 1')
 | 
			
		||||
        except sqlite3.OperationalError, err:
 | 
			
		||||
            if str(err) == 'no such column: reseller_admin':
 | 
			
		||||
                self.conn.execute(
 | 
			
		||||
                    "ALTER TABLE account ADD COLUMN reseller_admin TEXT")
 | 
			
		||||
        self.conn.execute('''CREATE TABLE IF NOT EXISTS account (
 | 
			
		||||
                                account TEXT, url TEXT, cfaccount TEXT,
 | 
			
		||||
                                user TEXT, password TEXT, admin TEXT,
 | 
			
		||||
                                reseller_admin TEXT)''')
 | 
			
		||||
        self.conn.execute('''CREATE INDEX IF NOT EXISTS ix_account_account
 | 
			
		||||
                             ON account (account)''')
 | 
			
		||||
        try:
 | 
			
		||||
            self.conn.execute('SELECT user FROM token LIMIT 1')
 | 
			
		||||
        except sqlite3.OperationalError, err:
 | 
			
		||||
            if str(err) == 'no such column: user':
 | 
			
		||||
                self.conn.execute('DROP INDEX IF EXISTS ix_token_created')
 | 
			
		||||
                self.conn.execute('DROP INDEX IF EXISTS ix_token_cfaccount')
 | 
			
		||||
                self.conn.execute('DROP TABLE IF EXISTS token')
 | 
			
		||||
        self.conn.execute('''CREATE TABLE IF NOT EXISTS token (
 | 
			
		||||
                                token TEXT, created FLOAT,
 | 
			
		||||
                                account TEXT, user TEXT, cfaccount TEXT)''')
 | 
			
		||||
        self.conn.execute('''CREATE INDEX IF NOT EXISTS ix_token_token
 | 
			
		||||
                             ON token (token)''')
 | 
			
		||||
        self.conn.execute('''CREATE INDEX IF NOT EXISTS ix_token_created
 | 
			
		||||
                             ON token (created)''')
 | 
			
		||||
        self.conn.execute('''CREATE INDEX IF NOT EXISTS ix_token_account
 | 
			
		||||
                             ON token (account)''')
 | 
			
		||||
        self.conn.commit()
 | 
			
		||||
        for row in self.conn.execute('SELECT cfaccount FROM account'):
 | 
			
		||||
            if not row[0].startswith(self.reseller_prefix):
 | 
			
		||||
                previous_prefix = ''
 | 
			
		||||
                if '_' in row[0]:
 | 
			
		||||
                    previous_prefix = row[0].split('_', 1)[0]
 | 
			
		||||
                msg = (_('''
 | 
			
		||||
THERE ARE ACCOUNTS IN YOUR auth.db THAT DO NOT BEGIN WITH YOUR NEW RESELLER
 | 
			
		||||
PREFIX OF "%(reseller)s".
 | 
			
		||||
YOU HAVE A FEW OPTIONS:
 | 
			
		||||
    1. RUN "swift-auth-update-reseller-prefixes %(db_file)s %(reseller)s",
 | 
			
		||||
       "swift-init auth-server restart", AND
 | 
			
		||||
       "swift-auth-recreate-accounts -K ..." TO CREATE FRESH ACCOUNTS.
 | 
			
		||||
    OR
 | 
			
		||||
    2. REMOVE %(db_file)s, RUN "swift-init auth-server restart", AND RUN
 | 
			
		||||
       "swift-auth-add-user ..." TO CREATE BRAND NEW ACCOUNTS THAT WAY.
 | 
			
		||||
    OR
 | 
			
		||||
    3. ADD "reseller_prefix = %(previous)s" (WITHOUT THE QUOTES) TO YOUR
 | 
			
		||||
       proxy-server.conf IN THE [filter:auth] SECTION AND TO YOUR
 | 
			
		||||
       auth-server.conf IN THE [app:auth-server] SECTION AND RUN
 | 
			
		||||
       "swift-init proxy-server restart" AND "swift-init auth-server restart"
 | 
			
		||||
       TO REVERT BACK TO YOUR PREVIOUS RESELLER PREFIX.
 | 
			
		||||
 | 
			
		||||
    %(note)s
 | 
			
		||||
                    ''') % {'reseller': self.reseller_prefix.rstrip('_'),
 | 
			
		||||
                            'db_file': self.db_file,
 | 
			
		||||
                            'previous': previous_prefix,
 | 
			
		||||
                            'note': previous_prefix and ' ' or _('''
 | 
			
		||||
    SINCE YOUR PREVIOUS RESELLER PREFIX WAS AN EMPTY STRING, IT IS NOT
 | 
			
		||||
    RECOMMENDED TO PERFORM OPTION 3 AS THAT WOULD MAKE SUPPORTING MULTIPLE
 | 
			
		||||
    RESELLERS MORE DIFFICULT.
 | 
			
		||||
                    ''').strip()}).strip()
 | 
			
		||||
                self.logger.critical(_('CRITICAL: ') + ' '.join(msg.split()))
 | 
			
		||||
                raise Exception('\n' + msg)
 | 
			
		||||
 | 
			
		||||
    def add_storage_account(self, account_name=''):
 | 
			
		||||
        """
 | 
			
		||||
        Creates an account within the Swift cluster by making a ReST call.
 | 
			
		||||
 | 
			
		||||
        :param account_name: The desired name for the account; if omitted a
 | 
			
		||||
                             UUID4 will be used.
 | 
			
		||||
        :returns: False upon failure, otherwise the name of the account
 | 
			
		||||
                  within the Swift cluster.
 | 
			
		||||
        """
 | 
			
		||||
        orig_account_name = account_name
 | 
			
		||||
        if not account_name:
 | 
			
		||||
            account_name = '%s%s' % (self.reseller_prefix, uuid4().hex)
 | 
			
		||||
        url = '%s/%s' % (self.default_cluster_url, account_name)
 | 
			
		||||
        parsed = urlparse(url)
 | 
			
		||||
        # Create a single use token.
 | 
			
		||||
        token = '%stk%s' % (self.reseller_prefix, uuid4().hex)
 | 
			
		||||
        with self.get_conn() as conn:
 | 
			
		||||
            conn.execute('''
 | 
			
		||||
                INSERT INTO token
 | 
			
		||||
                (token, created, account, user, cfaccount) VALUES
 | 
			
		||||
                (?, ?, '.super_admin', '.single_use', '.reseller_admin')''',
 | 
			
		||||
                (token, time()))
 | 
			
		||||
            conn.commit()
 | 
			
		||||
        if parsed.port is None:
 | 
			
		||||
            port = {'http': 80, 'https': 443}.get(parsed.scheme, 80)
 | 
			
		||||
        else:
 | 
			
		||||
            port = parsed.port
 | 
			
		||||
        conn = http_connect(parsed.hostname, port, 'PUT', parsed.path,
 | 
			
		||||
                {'X-Auth-Token': token}, ssl=(parsed.scheme == 'https'))
 | 
			
		||||
        resp = conn.getresponse()
 | 
			
		||||
        resp.read()
 | 
			
		||||
        if resp.status // 100 != 2:
 | 
			
		||||
            self.logger.error(_('ERROR attempting to create account %(url)s:' \
 | 
			
		||||
                  ' %(status)s %(reason)s') %
 | 
			
		||||
                  {'url': url, 'status': resp.status, 'reason': resp.reason})
 | 
			
		||||
            return False
 | 
			
		||||
        return account_name
 | 
			
		||||
 | 
			
		||||
    @contextmanager
 | 
			
		||||
    def get_conn(self):
 | 
			
		||||
        """
 | 
			
		||||
        Returns a DB API connection instance to the auth server's SQLite
 | 
			
		||||
        database. This is a contextmanager call to be use with the 'with'
 | 
			
		||||
        statement. It takes no parameters.
 | 
			
		||||
        """
 | 
			
		||||
        if not self.conn:
 | 
			
		||||
            # We go ahead and make another db connection even if this is a
 | 
			
		||||
            # reentry call; just in case we had an error that caused self.conn
 | 
			
		||||
            # to become None. Even if we make an extra conn, we'll only keep
 | 
			
		||||
            # one after the 'with' block.
 | 
			
		||||
            self.conn = get_db_connection(self.db_file)
 | 
			
		||||
        conn = self.conn
 | 
			
		||||
        self.conn = None
 | 
			
		||||
        try:
 | 
			
		||||
            yield conn
 | 
			
		||||
            conn.rollback()
 | 
			
		||||
            self.conn = conn
 | 
			
		||||
        except Exception, err:
 | 
			
		||||
            try:
 | 
			
		||||
                conn.close()
 | 
			
		||||
            except Exception:
 | 
			
		||||
                pass
 | 
			
		||||
            self.conn = get_db_connection(self.db_file)
 | 
			
		||||
            raise err
 | 
			
		||||
 | 
			
		||||
    def validate_s3_sign(self, request, token):
 | 
			
		||||
        account, user, sign = \
 | 
			
		||||
            request.headers['Authorization'].split(' ')[-1].split(':')
 | 
			
		||||
        msg = base64.urlsafe_b64decode(unquote(token))
 | 
			
		||||
        rv = False
 | 
			
		||||
        with self.get_conn() as conn:
 | 
			
		||||
            row = conn.execute('''
 | 
			
		||||
                SELECT password, cfaccount FROM account
 | 
			
		||||
                WHERE account = ? AND user = ?''',
 | 
			
		||||
                (account, user)).fetchone()
 | 
			
		||||
            rv = (84000, account, user, row[1])
 | 
			
		||||
        if rv:
 | 
			
		||||
            s = base64.encodestring(hmac.new(row[0], msg,
 | 
			
		||||
                                             sha1).digest()).strip()
 | 
			
		||||
            self.logger.info("orig %s, calc %s" % (sign, s))
 | 
			
		||||
            if sign != s:
 | 
			
		||||
                rv = False
 | 
			
		||||
        return rv
 | 
			
		||||
 | 
			
		||||
    def purge_old_tokens(self):
 | 
			
		||||
        """
 | 
			
		||||
        Removes tokens that have expired from the auth server's database. This
 | 
			
		||||
        is called by :func:`validate_token` and :func:`GET` to help keep the
 | 
			
		||||
        database clean.
 | 
			
		||||
        """
 | 
			
		||||
        with self.get_conn() as conn:
 | 
			
		||||
            conn.execute('DELETE FROM token WHERE created < ?',
 | 
			
		||||
                         (time() - self.token_life,))
 | 
			
		||||
            conn.commit()
 | 
			
		||||
 | 
			
		||||
    def validate_token(self, token):
 | 
			
		||||
        """
 | 
			
		||||
        Tests if the given token is a valid token
 | 
			
		||||
 | 
			
		||||
        :param token: The token to validate
 | 
			
		||||
        :returns: (TTL, account, user, cfaccount) if valid, False otherwise.
 | 
			
		||||
                  cfaccount will be None for users without admin access for the
 | 
			
		||||
                  account. cfaccount will be .reseller_admin for users with
 | 
			
		||||
                  full reseller admin rights.
 | 
			
		||||
        """
 | 
			
		||||
        begin = time()
 | 
			
		||||
        self.purge_old_tokens()
 | 
			
		||||
        rv = False
 | 
			
		||||
        with self.get_conn() as conn:
 | 
			
		||||
            row = conn.execute('''
 | 
			
		||||
                SELECT created, account, user, cfaccount FROM token
 | 
			
		||||
                WHERE token = ?''',
 | 
			
		||||
                (token,)).fetchone()
 | 
			
		||||
            if row is not None:
 | 
			
		||||
                created = row[0]
 | 
			
		||||
                if time() - created < self.token_life:
 | 
			
		||||
                    rv = (self.token_life - (time() - created), row[1], row[2],
 | 
			
		||||
                          row[3])
 | 
			
		||||
                # Remove the token if it was expired or single use.
 | 
			
		||||
                if not rv or rv[2] == '.single_use':
 | 
			
		||||
                    conn.execute('''
 | 
			
		||||
                        DELETE FROM token WHERE token = ?''', (token,))
 | 
			
		||||
                    conn.commit()
 | 
			
		||||
        self.logger.info('validate_token(%s, _, _) = %s [%.02f]' %
 | 
			
		||||
                         (repr(token), repr(rv), time() - begin))
 | 
			
		||||
        return rv
 | 
			
		||||
 | 
			
		||||
    def create_user(self, account, user, password, admin=False,
 | 
			
		||||
                    reseller_admin=False):
 | 
			
		||||
        """
 | 
			
		||||
        Handles the create_user call for developers, used to request a user be
 | 
			
		||||
        added in the auth server database. If the account does not yet exist,
 | 
			
		||||
        it will be created on the Swift cluster and the details recorded in the
 | 
			
		||||
        auth server database.
 | 
			
		||||
 | 
			
		||||
        The url for the storage account is constructed now and stored
 | 
			
		||||
        separately to support changing the configuration file's
 | 
			
		||||
        default_cluster_url for directing new accounts to a different Swift
 | 
			
		||||
        cluster while still supporting old accounts going to the Swift clusters
 | 
			
		||||
        they were created on.
 | 
			
		||||
 | 
			
		||||
        Currently, updating a user's information (password, admin access) must
 | 
			
		||||
        be done by directly updating the sqlite database.
 | 
			
		||||
 | 
			
		||||
        :param account: The name for the new account
 | 
			
		||||
        :param user: The name for the new user
 | 
			
		||||
        :param password: The password for the new account
 | 
			
		||||
        :param admin: If true, the user will be granted full access to the
 | 
			
		||||
                      account; otherwise, another user will have to add the
 | 
			
		||||
                      user to the ACLs for containers to grant access.
 | 
			
		||||
        :param reseller_admin: If true, the user will be granted full access to
 | 
			
		||||
                               all accounts within this reseller, including the
 | 
			
		||||
                               ability to create additional accounts.
 | 
			
		||||
 | 
			
		||||
        :returns: False if the create fails, 'already exists' if the user
 | 
			
		||||
                  already exists, or storage url if successful
 | 
			
		||||
        """
 | 
			
		||||
        begin = time()
 | 
			
		||||
        if not all((account, user, password)):
 | 
			
		||||
            return False
 | 
			
		||||
        with self.get_conn() as conn:
 | 
			
		||||
            row = conn.execute(
 | 
			
		||||
                'SELECT url FROM account WHERE account = ? AND user = ?',
 | 
			
		||||
                (account, user)).fetchone()
 | 
			
		||||
            if row:
 | 
			
		||||
                self.logger.info(_('ALREADY EXISTS create_user(%(account)s, '
 | 
			
		||||
                    '%(user)s, _, %(admin)s, %(reseller_admin)s) '
 | 
			
		||||
                    '[%(elapsed).02f]') %
 | 
			
		||||
                    {'account': repr(account),
 | 
			
		||||
                     'user': repr(user),
 | 
			
		||||
                     'admin': repr(admin),
 | 
			
		||||
                     'reseller_admin': repr(reseller_admin),
 | 
			
		||||
                     'elapsed': time() - begin})
 | 
			
		||||
                return 'already exists'
 | 
			
		||||
            row = conn.execute(
 | 
			
		||||
                'SELECT url, cfaccount FROM account WHERE account = ?',
 | 
			
		||||
                (account,)).fetchone()
 | 
			
		||||
            if row:
 | 
			
		||||
                url = row[0]
 | 
			
		||||
                account_hash = row[1]
 | 
			
		||||
            else:
 | 
			
		||||
                account_hash = self.add_storage_account()
 | 
			
		||||
                if not account_hash:
 | 
			
		||||
                    self.logger.info(_('FAILED create_user(%(account)s, '
 | 
			
		||||
                        '%(user)s, _, %(admin)s, %(reseller_admin)s) '
 | 
			
		||||
                        '[%(elapsed).02f]') %
 | 
			
		||||
                        {'account': repr(account),
 | 
			
		||||
                         'user': repr(user),
 | 
			
		||||
                         'admin': repr(admin),
 | 
			
		||||
                         'reseller_admin': repr(reseller_admin),
 | 
			
		||||
                         'elapsed': time() - begin})
 | 
			
		||||
                    return False
 | 
			
		||||
                url = self.default_cluster_url.rstrip('/') + '/' + account_hash
 | 
			
		||||
            conn.execute('''INSERT INTO account
 | 
			
		||||
                (account, url, cfaccount, user, password, admin,
 | 
			
		||||
                 reseller_admin)
 | 
			
		||||
                VALUES (?, ?, ?, ?, ?, ?, ?)''',
 | 
			
		||||
                (account, url, account_hash, user, password,
 | 
			
		||||
                 admin and 't' or '', reseller_admin and 't' or ''))
 | 
			
		||||
            conn.commit()
 | 
			
		||||
        self.logger.info(_('SUCCESS create_user(%(account)s, %(user)s, _, '
 | 
			
		||||
            '%(admin)s, %(reseller_admin)s) = %(url)s [%(elapsed).02f]') %
 | 
			
		||||
            {'account': repr(account), 'user': repr(user),
 | 
			
		||||
             'admin': repr(admin), 'reseller_admin': repr(reseller_admin),
 | 
			
		||||
             'url': repr(url), 'elapsed': time() - begin})
 | 
			
		||||
        return url
 | 
			
		||||
 | 
			
		||||
    def recreate_accounts(self):
 | 
			
		||||
        """
 | 
			
		||||
        Recreates the accounts from the existing auth database in the Swift
 | 
			
		||||
        cluster. This is useful on a development system when the drives are
 | 
			
		||||
        reformatted quite often but the auth server's database is retained.
 | 
			
		||||
 | 
			
		||||
        :returns: A string indicating accounts and failures
 | 
			
		||||
        """
 | 
			
		||||
        begin = time()
 | 
			
		||||
        with self.get_conn() as conn:
 | 
			
		||||
            account_hashes = [r[0] for r in conn.execute(
 | 
			
		||||
                'SELECT distinct(cfaccount) FROM account').fetchall()]
 | 
			
		||||
        failures = []
 | 
			
		||||
        for i, account_hash in enumerate(account_hashes):
 | 
			
		||||
            if not self.add_storage_account(account_hash):
 | 
			
		||||
                failures.append(account_hash)
 | 
			
		||||
        rv = '%d accounts, failures %s' % (len(account_hashes), repr(failures))
 | 
			
		||||
        self.logger.info('recreate_accounts(_, _) = %s [%.02f]' %
 | 
			
		||||
                         (rv, time() - begin))
 | 
			
		||||
        return rv
 | 
			
		||||
 | 
			
		||||
    def is_account_admin(self, request, for_account):
 | 
			
		||||
        """
 | 
			
		||||
        Returns True if the request represents coming from .super_admin, a
 | 
			
		||||
        .reseller_admin, or an admin for the account specified.
 | 
			
		||||
        """
 | 
			
		||||
        if request.headers.get('X-Auth-Admin-User') == '.super_admin' and \
 | 
			
		||||
               request.headers.get('X-Auth-Admin-Key') == self.super_admin_key:
 | 
			
		||||
            return True
 | 
			
		||||
        try:
 | 
			
		||||
            account, user = \
 | 
			
		||||
                request.headers.get('X-Auth-Admin-User').split(':', 1)
 | 
			
		||||
        except (AttributeError, ValueError):
 | 
			
		||||
            return False
 | 
			
		||||
        with self.get_conn() as conn:
 | 
			
		||||
            row = conn.execute('''
 | 
			
		||||
                SELECT reseller_admin, admin FROM account
 | 
			
		||||
                WHERE account = ? AND user = ? AND password = ?''',
 | 
			
		||||
                (account, user,
 | 
			
		||||
                 request.headers.get('X-Auth-Admin-Key'))).fetchone()
 | 
			
		||||
            if row:
 | 
			
		||||
                if row[0] == 't':
 | 
			
		||||
                    return True
 | 
			
		||||
                if row[1] == 't' and account == for_account:
 | 
			
		||||
                    return True
 | 
			
		||||
        return False
 | 
			
		||||
 | 
			
		||||
    def handle_token(self, request):
 | 
			
		||||
        """
 | 
			
		||||
        Handles ReST requests from Swift to validate tokens
 | 
			
		||||
 | 
			
		||||
        Valid URL paths:
 | 
			
		||||
            * GET /token/<token>
 | 
			
		||||
 | 
			
		||||
        If the HTTP request returns with a 204, then the token is valid, the
 | 
			
		||||
        TTL of the token will be available in the X-Auth-Ttl header, and a
 | 
			
		||||
        comma separated list of the "groups" the user belongs to will be in the
 | 
			
		||||
        X-Auth-Groups header.
 | 
			
		||||
 | 
			
		||||
        :param request: webob.Request object
 | 
			
		||||
        """
 | 
			
		||||
        try:
 | 
			
		||||
            _junk, token = split_path(request.path, minsegs=2)
 | 
			
		||||
        except ValueError:
 | 
			
		||||
            return HTTPBadRequest()
 | 
			
		||||
        # Retrieves (TTL, account, user, cfaccount) if valid, False otherwise
 | 
			
		||||
        headers = {}
 | 
			
		||||
        if 'Authorization' in request.headers:
 | 
			
		||||
            validation = self.validate_s3_sign(request, token)
 | 
			
		||||
            if validation:
 | 
			
		||||
                headers['X-Auth-Account-Suffix'] = validation[3]
 | 
			
		||||
        else:
 | 
			
		||||
            validation = self.validate_token(token)
 | 
			
		||||
        if not validation:
 | 
			
		||||
            return HTTPNotFound()
 | 
			
		||||
        groups = ['%s:%s' % (validation[1], validation[2]), validation[1]]
 | 
			
		||||
        if validation[3]:
 | 
			
		||||
            # admin access to a cfaccount or ".reseller_admin" to access to all
 | 
			
		||||
            # accounts, including creating new ones.
 | 
			
		||||
            groups.append(validation[3])
 | 
			
		||||
        headers['X-Auth-TTL'] = validation[0]
 | 
			
		||||
        headers['X-Auth-Groups'] = ','.join(groups)
 | 
			
		||||
        return HTTPNoContent(headers=headers)
 | 
			
		||||
 | 
			
		||||
    def handle_add_user(self, request):
 | 
			
		||||
        """
 | 
			
		||||
        Handles Rest requests from developers to have a user added. If the
 | 
			
		||||
        account specified doesn't exist, it will also be added. Currently,
 | 
			
		||||
        updating a user's information (password, admin access) must be done by
 | 
			
		||||
        directly updating the sqlite database.
 | 
			
		||||
 | 
			
		||||
        Valid URL paths:
 | 
			
		||||
            * PUT /account/<account-name>/<user-name> - create the account
 | 
			
		||||
 | 
			
		||||
        Valid headers:
 | 
			
		||||
            * X-Auth-User-Key: <password>
 | 
			
		||||
            * X-Auth-User-Admin: <true|false>
 | 
			
		||||
            * X-Auth-User-Reseller-Admin: <true|false>
 | 
			
		||||
 | 
			
		||||
        If the HTTP request returns with a 204, then the user was added,
 | 
			
		||||
        and the storage url will be available in the X-Storage-Url header.
 | 
			
		||||
 | 
			
		||||
        :param request: webob.Request object
 | 
			
		||||
        """
 | 
			
		||||
        try:
 | 
			
		||||
            _junk, account_name, user_name = \
 | 
			
		||||
                split_path(request.path, minsegs=3)
 | 
			
		||||
        except ValueError:
 | 
			
		||||
            return HTTPBadRequest()
 | 
			
		||||
        create_reseller_admin = \
 | 
			
		||||
            request.headers.get('x-auth-user-reseller-admin') == 'true'
 | 
			
		||||
        if create_reseller_admin and (
 | 
			
		||||
              request.headers.get('X-Auth-Admin-User') != '.super_admin' or
 | 
			
		||||
              request.headers.get('X-Auth-Admin-Key') != self.super_admin_key):
 | 
			
		||||
            return HTTPUnauthorized(request=request)
 | 
			
		||||
        create_account_admin = \
 | 
			
		||||
            request.headers.get('x-auth-user-admin') == 'true'
 | 
			
		||||
        if create_account_admin and \
 | 
			
		||||
                not self.is_account_admin(request, account_name):
 | 
			
		||||
            return HTTPForbidden(request=request)
 | 
			
		||||
        if 'X-Auth-User-Key' not in request.headers:
 | 
			
		||||
            return HTTPBadRequest(body='X-Auth-User-Key is required')
 | 
			
		||||
        password = request.headers['x-auth-user-key']
 | 
			
		||||
        storage_url = self.create_user(account_name, user_name, password,
 | 
			
		||||
                        create_account_admin, create_reseller_admin)
 | 
			
		||||
        if storage_url == 'already exists':
 | 
			
		||||
            return HTTPConflict(body=storage_url)
 | 
			
		||||
        if not storage_url:
 | 
			
		||||
            return HTTPServiceUnavailable()
 | 
			
		||||
        return HTTPNoContent(headers={'x-storage-url': storage_url})
 | 
			
		||||
 | 
			
		||||
    def handle_account_recreate(self, request):
 | 
			
		||||
        """
 | 
			
		||||
        Handles ReST requests from developers to have accounts in the Auth
 | 
			
		||||
        system recreated in Swift. I know this is bad ReST style, but this
 | 
			
		||||
        isn't production right? :)
 | 
			
		||||
 | 
			
		||||
        Valid URL paths:
 | 
			
		||||
            * POST /recreate_accounts
 | 
			
		||||
 | 
			
		||||
        :param request: webob.Request object
 | 
			
		||||
        """
 | 
			
		||||
        if request.headers.get('X-Auth-Admin-User') != '.super_admin' or \
 | 
			
		||||
               request.headers.get('X-Auth-Admin-Key') != self.super_admin_key:
 | 
			
		||||
            return HTTPUnauthorized(request=request)
 | 
			
		||||
        result = self.recreate_accounts()
 | 
			
		||||
        return Response(result, 200, request=request)
 | 
			
		||||
 | 
			
		||||
    def handle_auth(self, request):
 | 
			
		||||
        """
 | 
			
		||||
        Handles ReST requests from end users for a Swift cluster url and auth
 | 
			
		||||
        token. This can handle all the various headers and formats that
 | 
			
		||||
        existing auth systems used, so it's a bit of a chameleon.
 | 
			
		||||
 | 
			
		||||
        Valid URL paths:
 | 
			
		||||
            * GET /v1/<account-name>/auth
 | 
			
		||||
            * GET /auth
 | 
			
		||||
            * GET /v1.0
 | 
			
		||||
 | 
			
		||||
        Valid headers:
 | 
			
		||||
            * X-Auth-User: <account-name>:<user-name>
 | 
			
		||||
            * X-Auth-Key: <password>
 | 
			
		||||
            * X-Storage-User: [<account-name>:]<user-name>
 | 
			
		||||
                    The [<account-name>:] is only optional here if the
 | 
			
		||||
                    /v1/<account-name>/auth path is used.
 | 
			
		||||
            * X-Storage-Pass: <password>
 | 
			
		||||
 | 
			
		||||
        The (currently) preferred method is to use /v1.0 path and the
 | 
			
		||||
        X-Auth-User and X-Auth-Key headers.
 | 
			
		||||
 | 
			
		||||
        :param request: A webob.Request instance.
 | 
			
		||||
        """
 | 
			
		||||
        try:
 | 
			
		||||
            pathsegs = split_path(request.path, minsegs=1, maxsegs=3,
 | 
			
		||||
                                  rest_with_last=True)
 | 
			
		||||
        except ValueError:
 | 
			
		||||
            return HTTPBadRequest()
 | 
			
		||||
        if pathsegs[0] == 'v1' and pathsegs[2] == 'auth':
 | 
			
		||||
            account = pathsegs[1]
 | 
			
		||||
            user = request.headers.get('x-storage-user')
 | 
			
		||||
            if not user:
 | 
			
		||||
                user = request.headers.get('x-auth-user')
 | 
			
		||||
                if not user or ':' not in user:
 | 
			
		||||
                    return HTTPUnauthorized()
 | 
			
		||||
                account2, user = user.split(':', 1)
 | 
			
		||||
                if account != account2:
 | 
			
		||||
                    return HTTPUnauthorized()
 | 
			
		||||
            password = request.headers.get('x-storage-pass')
 | 
			
		||||
            if not password:
 | 
			
		||||
                password = request.headers.get('x-auth-key')
 | 
			
		||||
        elif pathsegs[0] in ('auth', 'v1.0'):
 | 
			
		||||
            user = request.headers.get('x-auth-user')
 | 
			
		||||
            if not user:
 | 
			
		||||
                user = request.headers.get('x-storage-user')
 | 
			
		||||
            if not user or ':' not in user:
 | 
			
		||||
                return HTTPUnauthorized()
 | 
			
		||||
            account, user = user.split(':', 1)
 | 
			
		||||
            password = request.headers.get('x-auth-key')
 | 
			
		||||
            if not password:
 | 
			
		||||
                password = request.headers.get('x-storage-pass')
 | 
			
		||||
        else:
 | 
			
		||||
            return HTTPBadRequest()
 | 
			
		||||
        if not all((account, user, password)):
 | 
			
		||||
            return HTTPUnauthorized()
 | 
			
		||||
        self.purge_old_tokens()
 | 
			
		||||
        with self.get_conn() as conn:
 | 
			
		||||
            row = conn.execute('''
 | 
			
		||||
                SELECT cfaccount, url, admin, reseller_admin FROM account
 | 
			
		||||
                WHERE account = ? AND user = ? AND password = ?''',
 | 
			
		||||
                (account, user, password)).fetchone()
 | 
			
		||||
            if row is None:
 | 
			
		||||
                return HTTPUnauthorized()
 | 
			
		||||
            cfaccount = row[0]
 | 
			
		||||
            url = row[1]
 | 
			
		||||
            admin = row[2] == 't'
 | 
			
		||||
            reseller_admin = row[3] == 't'
 | 
			
		||||
            row = conn.execute('''
 | 
			
		||||
                SELECT token FROM token WHERE account = ? AND user = ?''',
 | 
			
		||||
                (account, user)).fetchone()
 | 
			
		||||
            if row:
 | 
			
		||||
                token = row[0]
 | 
			
		||||
            else:
 | 
			
		||||
                token = '%stk%s' % (self.reseller_prefix, uuid4().hex)
 | 
			
		||||
                token_cfaccount = ''
 | 
			
		||||
                if admin:
 | 
			
		||||
                    token_cfaccount = cfaccount
 | 
			
		||||
                if reseller_admin:
 | 
			
		||||
                    token_cfaccount = '.reseller_admin'
 | 
			
		||||
                conn.execute('''
 | 
			
		||||
                    INSERT INTO token
 | 
			
		||||
                    (token, created, account, user, cfaccount)
 | 
			
		||||
                    VALUES (?, ?, ?, ?, ?)''',
 | 
			
		||||
                    (token, time(), account, user, token_cfaccount))
 | 
			
		||||
                conn.commit()
 | 
			
		||||
            return HTTPNoContent(headers={'x-auth-token': token,
 | 
			
		||||
                                          'x-storage-token': token,
 | 
			
		||||
                                          'x-storage-url': url})
 | 
			
		||||
 | 
			
		||||
    def handleREST(self, env, start_response):
 | 
			
		||||
        """
 | 
			
		||||
        Handles routing of ReST requests. This handler also logs all requests.
 | 
			
		||||
 | 
			
		||||
        :param env: WSGI environment
 | 
			
		||||
        :param start_response: WSGI start_response function
 | 
			
		||||
        """
 | 
			
		||||
        req = Request(env)
 | 
			
		||||
        logged_headers = None
 | 
			
		||||
        if self.log_headers:
 | 
			
		||||
            logged_headers = '\n'.join('%s: %s' % (k, v)
 | 
			
		||||
                for k, v in req.headers.items()).replace('"', "#042")
 | 
			
		||||
        start_time = time()
 | 
			
		||||
        # Figure out how to handle the request
 | 
			
		||||
        try:
 | 
			
		||||
            if req.method == 'GET' and req.path.startswith('/v1') or \
 | 
			
		||||
                    req.path.startswith('/auth'):
 | 
			
		||||
                handler = self.handle_auth
 | 
			
		||||
            elif req.method == 'GET' and req.path.startswith('/token/'):
 | 
			
		||||
                handler = self.handle_token
 | 
			
		||||
            elif req.method == 'PUT' and req.path.startswith('/account/'):
 | 
			
		||||
                handler = self.handle_add_user
 | 
			
		||||
            elif req.method == 'POST' and \
 | 
			
		||||
                    req.path == '/recreate_accounts':
 | 
			
		||||
                handler = self.handle_account_recreate
 | 
			
		||||
            else:
 | 
			
		||||
                return HTTPBadRequest(request=env)(env, start_response)
 | 
			
		||||
            response = handler(req)
 | 
			
		||||
        except Exception:
 | 
			
		||||
            self.logger.exception(
 | 
			
		||||
                    _('ERROR Unhandled exception in ReST request'))
 | 
			
		||||
            return HTTPServiceUnavailable(request=req)(env, start_response)
 | 
			
		||||
        trans_time = '%.4f' % (time() - start_time)
 | 
			
		||||
        if not response.content_length and response.app_iter and \
 | 
			
		||||
                    hasattr(response.app_iter, '__len__'):
 | 
			
		||||
            response.content_length = sum(map(len, response.app_iter))
 | 
			
		||||
        the_request = '%s %s' % (req.method, quote(unquote(req.path)))
 | 
			
		||||
        if req.query_string:
 | 
			
		||||
            the_request = the_request + '?' + req.query_string
 | 
			
		||||
        the_request += ' ' + req.environ['SERVER_PROTOCOL']
 | 
			
		||||
        client = req.headers.get('x-cluster-client-ip')
 | 
			
		||||
        if not client and 'x-forwarded-for' in req.headers:
 | 
			
		||||
            client = req.headers['x-forwarded-for'].split(',')[0].strip()
 | 
			
		||||
        if not client:
 | 
			
		||||
            client = req.remote_addr
 | 
			
		||||
        self.logger.info(
 | 
			
		||||
            '%s - - [%s] "%s" %s %s "%s" "%s" - - - - - - - - - "-" "%s" '
 | 
			
		||||
            '"%s" %s' % (
 | 
			
		||||
                client,
 | 
			
		||||
                strftime('%d/%b/%Y:%H:%M:%S +0000', gmtime()),
 | 
			
		||||
                the_request,
 | 
			
		||||
                response.status_int,
 | 
			
		||||
                response.content_length or '-',
 | 
			
		||||
                req.referer or '-',
 | 
			
		||||
                req.user_agent or '-',
 | 
			
		||||
                req.remote_addr,
 | 
			
		||||
                logged_headers or '-',
 | 
			
		||||
                trans_time))
 | 
			
		||||
        return response(env, start_response)
 | 
			
		||||
 | 
			
		||||
    def __call__(self, env, start_response):
 | 
			
		||||
        """ Used by the eventlet.wsgi.server """
 | 
			
		||||
        return self.handleREST(env, start_response)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def app_factory(global_conf, **local_conf):
 | 
			
		||||
    conf = global_conf.copy()
 | 
			
		||||
    conf.update(local_conf)
 | 
			
		||||
    return AuthController(conf)
 | 
			
		||||
@@ -1,213 +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.
 | 
			
		||||
 | 
			
		||||
from time import time
 | 
			
		||||
 | 
			
		||||
from eventlet.timeout import Timeout
 | 
			
		||||
from webob.exc import HTTPForbidden, HTTPUnauthorized, HTTPNotFound
 | 
			
		||||
 | 
			
		||||
from swift.common.bufferedhttp import http_connect_raw as http_connect
 | 
			
		||||
from swift.common.middleware.acl import clean_acl, parse_acl, referrer_allowed
 | 
			
		||||
from swift.common.utils import cache_from_env, split_path, TRUE_VALUES
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class DevAuth(object):
 | 
			
		||||
    """Auth Middleware that uses the dev auth server."""
 | 
			
		||||
 | 
			
		||||
    def __init__(self, app, conf):
 | 
			
		||||
        self.app = app
 | 
			
		||||
        self.conf = conf
 | 
			
		||||
        self.reseller_prefix = conf.get('reseller_prefix', 'AUTH').strip()
 | 
			
		||||
        if self.reseller_prefix and self.reseller_prefix[-1] != '_':
 | 
			
		||||
            self.reseller_prefix += '_'
 | 
			
		||||
        self.auth_host = conf.get('ip', '127.0.0.1')
 | 
			
		||||
        self.auth_port = int(conf.get('port', 11000))
 | 
			
		||||
        self.ssl = conf.get('ssl', 'false').lower() in TRUE_VALUES
 | 
			
		||||
        self.auth_prefix = conf.get('prefix', '/')
 | 
			
		||||
        self.timeout = int(conf.get('node_timeout', 10))
 | 
			
		||||
 | 
			
		||||
    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.
 | 
			
		||||
        """
 | 
			
		||||
        s3 = env.get('HTTP_AUTHORIZATION')
 | 
			
		||||
        token = env.get('HTTP_X_AUTH_TOKEN', env.get('HTTP_X_STORAGE_TOKEN'))
 | 
			
		||||
        if s3 or (token and token.startswith(self.reseller_prefix)):
 | 
			
		||||
            # Note: Empty reseller_prefix will match all tokens.
 | 
			
		||||
            # Attempt to auth my token with my auth server
 | 
			
		||||
            groups = self.get_groups(env, token,
 | 
			
		||||
                                     memcache_client=cache_from_env(env))
 | 
			
		||||
            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, token)
 | 
			
		||||
                env['swift.authorize'] = self.authorize
 | 
			
		||||
                env['swift.clean_acl'] = clean_acl
 | 
			
		||||
            else:
 | 
			
		||||
                # Unauthorized token
 | 
			
		||||
                if 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 for empty
 | 
			
		||||
                # reseller_prefixed tokens, I won't overwrite swift.authorize.
 | 
			
		||||
                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:
 | 
			
		||||
                    return HTTPNotFound()(env, start_response)
 | 
			
		||||
                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_groups(self, env, token, memcache_client=None):
 | 
			
		||||
        """
 | 
			
		||||
        Get groups for the given token.
 | 
			
		||||
 | 
			
		||||
        If memcache_client is set, token credentials will be cached
 | 
			
		||||
        appropriately.
 | 
			
		||||
 | 
			
		||||
        With a cache miss, or no memcache_client, the configurated external
 | 
			
		||||
        authentication server will be queried for the group information.
 | 
			
		||||
 | 
			
		||||
        :param token: Token to validate and return a group string for.
 | 
			
		||||
        :param memcache_client: Memcached client to use for caching token
 | 
			
		||||
                                credentials; None if no caching is desired.
 | 
			
		||||
        :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
 | 
			
		||||
        key = '%s/token/%s' % (self.reseller_prefix, token)
 | 
			
		||||
        cached_auth_data = memcache_client and memcache_client.get(key)
 | 
			
		||||
        if cached_auth_data:
 | 
			
		||||
            start, expiration, groups = cached_auth_data
 | 
			
		||||
            if time() - start > expiration:
 | 
			
		||||
                groups = None
 | 
			
		||||
 | 
			
		||||
        headers = {}
 | 
			
		||||
        if env.get('HTTP_AUTHORIZATION'):
 | 
			
		||||
            groups = None
 | 
			
		||||
            headers["Authorization"] = env.get('HTTP_AUTHORIZATION')
 | 
			
		||||
 | 
			
		||||
        if not groups:
 | 
			
		||||
            with Timeout(self.timeout):
 | 
			
		||||
                conn = http_connect(self.auth_host, self.auth_port, 'GET',
 | 
			
		||||
                                    '%stoken/%s' % (self.auth_prefix, token),
 | 
			
		||||
                                    headers, ssl=self.ssl)
 | 
			
		||||
 | 
			
		||||
                resp = conn.getresponse()
 | 
			
		||||
                resp.read()
 | 
			
		||||
                conn.close()
 | 
			
		||||
            if resp.status // 100 != 2:
 | 
			
		||||
                return None
 | 
			
		||||
            expiration = float(resp.getheader('x-auth-ttl'))
 | 
			
		||||
            groups = resp.getheader('x-auth-groups')
 | 
			
		||||
            if memcache_client:
 | 
			
		||||
                memcache_client.set(key, (time(), expiration, groups),
 | 
			
		||||
                                    timeout=expiration)
 | 
			
		||||
 | 
			
		||||
        if env.get('HTTP_AUTHORIZATION'):
 | 
			
		||||
            account, user, sign = \
 | 
			
		||||
                env['HTTP_AUTHORIZATION'].split(' ')[-1].split(':')
 | 
			
		||||
            cfaccount = resp.getheader('x-auth-account-suffix')
 | 
			
		||||
            path = env['PATH_INFO']
 | 
			
		||||
            env['PATH_INFO'] = \
 | 
			
		||||
                path.replace("%s:%s" % (account, user), cfaccount, 1)
 | 
			
		||||
 | 
			
		||||
        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:
 | 
			
		||||
            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...
 | 
			
		||||
            return None
 | 
			
		||||
        referrers, groups = parse_acl(getattr(req, 'acl', None))
 | 
			
		||||
        if referrer_allowed(req.referer, referrers):
 | 
			
		||||
            return None
 | 
			
		||||
        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 req.remote_user:
 | 
			
		||||
            return HTTPForbidden(request=req)
 | 
			
		||||
        else:
 | 
			
		||||
            return HTTPUnauthorized(request=req)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
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 DevAuth(app, conf)
 | 
			
		||||
    return auth_filter
 | 
			
		||||
@@ -1,13 +1,9 @@
 | 
			
		||||
[func_test]
 | 
			
		||||
# sample config
 | 
			
		||||
auth_host = 127.0.0.1
 | 
			
		||||
# For DevAuth:
 | 
			
		||||
auth_port = 11000
 | 
			
		||||
# For Swauth:
 | 
			
		||||
# auth_port = 8080
 | 
			
		||||
auth_port = 8080
 | 
			
		||||
auth_ssl = no
 | 
			
		||||
# For Swauth:
 | 
			
		||||
# auth_prefix = /auth/
 | 
			
		||||
auth_prefix = /auth/
 | 
			
		||||
 | 
			
		||||
# Primary functional test account (needs admin access to the account)
 | 
			
		||||
account = test
 | 
			
		||||
 
 | 
			
		||||
@@ -25,24 +25,15 @@ from swift.common.ring import Ring
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
SUPER_ADMIN_KEY = None
 | 
			
		||||
AUTH_TYPE = None
 | 
			
		||||
 | 
			
		||||
c = ConfigParser()
 | 
			
		||||
AUTH_SERVER_CONF_FILE = environ.get('SWIFT_AUTH_SERVER_CONF_FILE',
 | 
			
		||||
                                    '/etc/swift/auth-server.conf')
 | 
			
		||||
if c.read(AUTH_SERVER_CONF_FILE):
 | 
			
		||||
    conf = dict(c.items('app:auth-server'))
 | 
			
		||||
    SUPER_ADMIN_KEY = conf.get('super_admin_key', 'devauth')
 | 
			
		||||
    AUTH_TYPE = 'devauth'
 | 
			
		||||
else:
 | 
			
		||||
    PROXY_SERVER_CONF_FILE = environ.get('SWIFT_PROXY_SERVER_CONF_FILE',
 | 
			
		||||
PROXY_SERVER_CONF_FILE = environ.get('SWIFT_PROXY_SERVER_CONF_FILE',
 | 
			
		||||
                                     '/etc/swift/proxy-server.conf')
 | 
			
		||||
    if c.read(PROXY_SERVER_CONF_FILE):
 | 
			
		||||
if c.read(PROXY_SERVER_CONF_FILE):
 | 
			
		||||
    conf = dict(c.items('filter:swauth'))
 | 
			
		||||
    SUPER_ADMIN_KEY = conf.get('super_admin_key', 'swauthkey')
 | 
			
		||||
        AUTH_TYPE = 'swauth'
 | 
			
		||||
    else:
 | 
			
		||||
        exit('Unable to read config file: %s' % AUTH_SERVER_CONF_FILE)
 | 
			
		||||
else:
 | 
			
		||||
    exit('Unable to read config file: %s' % PROXY_SERVER_CONF_FILE)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def kill_pids(pids):
 | 
			
		||||
@@ -57,9 +48,6 @@ def reset_environment():
 | 
			
		||||
    call(['resetswift'])
 | 
			
		||||
    pids = {}
 | 
			
		||||
    try:
 | 
			
		||||
        if AUTH_TYPE == 'devauth':
 | 
			
		||||
            pids['auth'] = Popen(['swift-auth-server',
 | 
			
		||||
                                  '/etc/swift/auth-server.conf']).pid
 | 
			
		||||
        pids['proxy'] = Popen(['swift-proxy-server',
 | 
			
		||||
                               '/etc/swift/proxy-server.conf']).pid
 | 
			
		||||
        port2server = {}
 | 
			
		||||
@@ -73,18 +61,6 @@ def reset_environment():
 | 
			
		||||
        container_ring = Ring('/etc/swift/container.ring.gz')
 | 
			
		||||
        object_ring = Ring('/etc/swift/object.ring.gz')
 | 
			
		||||
        sleep(5)
 | 
			
		||||
        if AUTH_TYPE == 'devauth':
 | 
			
		||||
            conn = http_connect('127.0.0.1', '11000', 'POST',
 | 
			
		||||
                    '/recreate_accounts',
 | 
			
		||||
                    headers={'X-Auth-Admin-User': '.super_admin',
 | 
			
		||||
                             'X-Auth-Admin-Key': SUPER_ADMIN_KEY})
 | 
			
		||||
            resp = conn.getresponse()
 | 
			
		||||
            if resp.status != 200:
 | 
			
		||||
                raise Exception('Recreating accounts failed. (%d)' %
 | 
			
		||||
                                resp.status)
 | 
			
		||||
            url, token = get_auth('http://127.0.0.1:11000/auth', 'test:tester',
 | 
			
		||||
                                  'testing')
 | 
			
		||||
        elif AUTH_TYPE == 'swauth':
 | 
			
		||||
        call(['recreateaccounts'])
 | 
			
		||||
        url, token = get_auth('http://127.0.0.1:8080/auth/v1.0',
 | 
			
		||||
                              'test:tester', 'testing')
 | 
			
		||||
 
 | 
			
		||||
@@ -1,977 +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.
 | 
			
		||||
 | 
			
		||||
from __future__ import with_statement
 | 
			
		||||
import unittest
 | 
			
		||||
import os
 | 
			
		||||
from shutil import rmtree
 | 
			
		||||
from StringIO import StringIO
 | 
			
		||||
from uuid import uuid4
 | 
			
		||||
from logging import StreamHandler
 | 
			
		||||
 | 
			
		||||
import sqlite3
 | 
			
		||||
from webob import Request
 | 
			
		||||
 | 
			
		||||
from swift.auth import server as auth_server
 | 
			
		||||
from swift.common.db import DatabaseConnectionError, get_db_connection
 | 
			
		||||
from swift.common.utils import get_logger
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TestException(Exception):
 | 
			
		||||
    pass
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def fake_http_connect(*code_iter, **kwargs):
 | 
			
		||||
    class FakeConn(object):
 | 
			
		||||
        def __init__(self, status):
 | 
			
		||||
            self.status = status
 | 
			
		||||
            self.reason = 'Fake'
 | 
			
		||||
            self.host = '1.2.3.4'
 | 
			
		||||
            self.port = '1234'
 | 
			
		||||
        def getresponse(self):
 | 
			
		||||
            if 'slow' in kwargs:
 | 
			
		||||
                sleep(0.2)
 | 
			
		||||
            if 'raise_exc' in kwargs:
 | 
			
		||||
                raise kwargs['raise_exc']
 | 
			
		||||
            return self
 | 
			
		||||
        def getheaders(self):
 | 
			
		||||
            return {'x-account-bytes-used': '20'}
 | 
			
		||||
        def read(self, amt=None):
 | 
			
		||||
            return ''
 | 
			
		||||
        def getheader(self, name):
 | 
			
		||||
            return self.getheaders().get(name.lower())
 | 
			
		||||
    code_iter = iter(code_iter)
 | 
			
		||||
    def connect(*args, **kwargs):
 | 
			
		||||
        connect.last_args = args
 | 
			
		||||
        connect.last_kwargs = kwargs
 | 
			
		||||
        return FakeConn(code_iter.next())
 | 
			
		||||
    return connect
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TestAuthServer(unittest.TestCase):
 | 
			
		||||
 | 
			
		||||
    def setUp(self):
 | 
			
		||||
        self.ohttp_connect = auth_server.http_connect
 | 
			
		||||
        self.testdir = os.path.join(os.path.dirname(__file__),
 | 
			
		||||
                        'auth_server')
 | 
			
		||||
        rmtree(self.testdir, ignore_errors=1)
 | 
			
		||||
        os.mkdir(self.testdir)
 | 
			
		||||
        self.conf = {'swift_dir': self.testdir, 'log_name': 'auth',
 | 
			
		||||
                     'super_admin_key': 'testkey'}
 | 
			
		||||
        self.controller = auth_server.AuthController(self.conf)
 | 
			
		||||
 | 
			
		||||
    def tearDown(self):
 | 
			
		||||
        auth_server.http_connect = self.ohttp_connect
 | 
			
		||||
        rmtree(self.testdir, ignore_errors=1)
 | 
			
		||||
 | 
			
		||||
    def test_get_conn(self):
 | 
			
		||||
        with self.controller.get_conn() as conn:
 | 
			
		||||
            pass
 | 
			
		||||
        exc = False
 | 
			
		||||
        try:
 | 
			
		||||
            with self.controller.get_conn() as conn:
 | 
			
		||||
                raise TestException('test')
 | 
			
		||||
        except TestException:
 | 
			
		||||
            exc = True
 | 
			
		||||
        self.assert_(exc)
 | 
			
		||||
        # We allow reentrant calls for the auth-server
 | 
			
		||||
        with self.controller.get_conn() as conn1:
 | 
			
		||||
            exc = False
 | 
			
		||||
            try:
 | 
			
		||||
                with self.controller.get_conn() as conn2:
 | 
			
		||||
                    self.assert_(conn1 is not conn2)
 | 
			
		||||
            except DatabaseConnectionError:
 | 
			
		||||
                exc = True
 | 
			
		||||
            self.assert_(not exc)
 | 
			
		||||
        self.controller.conn = None
 | 
			
		||||
        with self.controller.get_conn() as conn:
 | 
			
		||||
            self.assert_(conn is not None)
 | 
			
		||||
 | 
			
		||||
    def test_validate_token_non_existant_token(self):
 | 
			
		||||
        auth_server.http_connect = fake_http_connect(201)
 | 
			
		||||
        cfaccount = self.controller.create_user(
 | 
			
		||||
            'test', 'tester', 'testing',).split('/')[-1]
 | 
			
		||||
        res = self.controller.handle_auth(Request.blank('/v1/test/auth',
 | 
			
		||||
                environ={'REQUEST_METHOD': 'GET'},
 | 
			
		||||
                headers={'X-Storage-User': 'tester',
 | 
			
		||||
                         'X-Storage-Pass': 'testing'}))
 | 
			
		||||
        token = res.headers['x-storage-token']
 | 
			
		||||
        self.assertEquals(self.controller.validate_token(token + 'bad'), False)
 | 
			
		||||
 | 
			
		||||
    def test_validate_token_good(self):
 | 
			
		||||
        auth_server.http_connect = fake_http_connect(201)
 | 
			
		||||
        cfaccount = self.controller.create_user(
 | 
			
		||||
            'test', 'tester', 'testing',).split('/')[-1]
 | 
			
		||||
        res = self.controller.handle_auth(Request.blank('/v1/test/auth',
 | 
			
		||||
                environ={'REQUEST_METHOD': 'GET'},
 | 
			
		||||
                headers={'X-Storage-User': 'tester',
 | 
			
		||||
                         'X-Storage-Pass': 'testing'}))
 | 
			
		||||
        token = res.headers['x-storage-token']
 | 
			
		||||
        ttl, _junk, _junk, _junk = self.controller.validate_token(token)
 | 
			
		||||
        self.assert_(ttl > 0, repr(ttl))
 | 
			
		||||
 | 
			
		||||
    def test_validate_token_expired(self):
 | 
			
		||||
        orig_time = auth_server.time
 | 
			
		||||
        try:
 | 
			
		||||
            auth_server.time = lambda: 1
 | 
			
		||||
            auth_server.http_connect = fake_http_connect(201)
 | 
			
		||||
            cfaccount = self.controller.create_user('test', 'tester',
 | 
			
		||||
                            'testing').split('/')[-1]
 | 
			
		||||
            res = self.controller.handle_auth(Request.blank('/v1/test/auth',
 | 
			
		||||
                    environ={'REQUEST_METHOD': 'GET'},
 | 
			
		||||
                    headers={'X-Storage-User': 'tester',
 | 
			
		||||
                             'X-Storage-Pass': 'testing'}))
 | 
			
		||||
            token = res.headers['x-storage-token']
 | 
			
		||||
            ttl, _junk, _junk, _junk = self.controller.validate_token(token)
 | 
			
		||||
            self.assert_(ttl > 0, repr(ttl))
 | 
			
		||||
            auth_server.time = lambda: 1 + self.controller.token_life
 | 
			
		||||
            self.assertEquals(self.controller.validate_token(token), False)
 | 
			
		||||
        finally:
 | 
			
		||||
            auth_server.time = orig_time
 | 
			
		||||
 | 
			
		||||
    def test_create_user_no_new_account(self):
 | 
			
		||||
        auth_server.http_connect = fake_http_connect(201)
 | 
			
		||||
        result = self.controller.create_user('', 'tester', 'testing')
 | 
			
		||||
        self.assertFalse(result)
 | 
			
		||||
 | 
			
		||||
    def test_create_user_no_new_user(self):
 | 
			
		||||
        auth_server.http_connect = fake_http_connect(201)
 | 
			
		||||
        result = self.controller.create_user('test', '', 'testing')
 | 
			
		||||
        self.assertFalse(result)
 | 
			
		||||
 | 
			
		||||
    def test_create_user_no_new_password(self):
 | 
			
		||||
        auth_server.http_connect = fake_http_connect(201)
 | 
			
		||||
        result = self.controller.create_user('test', 'tester', '')
 | 
			
		||||
        self.assertFalse(result)
 | 
			
		||||
 | 
			
		||||
    def test_create_user_good(self):
 | 
			
		||||
        auth_server.http_connect = fake_http_connect(201)
 | 
			
		||||
        url = self.controller.create_user('test', 'tester', 'testing')
 | 
			
		||||
        self.assert_(url)
 | 
			
		||||
        self.assertEquals('/'.join(url.split('/')[:-1]),
 | 
			
		||||
            self.controller.default_cluster_url.rstrip('/'), repr(url))
 | 
			
		||||
 | 
			
		||||
    def test_recreate_accounts_none(self):
 | 
			
		||||
        auth_server.http_connect = fake_http_connect(201)
 | 
			
		||||
        rv = self.controller.recreate_accounts()
 | 
			
		||||
        self.assertEquals(rv.split()[0], '0', repr(rv))
 | 
			
		||||
        self.assertEquals(rv.split()[-1], '[]', repr(rv))
 | 
			
		||||
 | 
			
		||||
    def test_recreate_accounts_one(self):
 | 
			
		||||
        auth_server.http_connect = fake_http_connect(201)
 | 
			
		||||
        self.controller.create_user('test', 'tester', 'testing')
 | 
			
		||||
        auth_server.http_connect = fake_http_connect(201)
 | 
			
		||||
        rv = self.controller.recreate_accounts()
 | 
			
		||||
        self.assertEquals(rv.split()[0], '1', repr(rv))
 | 
			
		||||
        self.assertEquals(rv.split()[-1], '[]', repr(rv))
 | 
			
		||||
 | 
			
		||||
    def test_recreate_accounts_several(self):
 | 
			
		||||
        auth_server.http_connect = fake_http_connect(201)
 | 
			
		||||
        self.controller.create_user('test1', 'tester', 'testing')
 | 
			
		||||
        auth_server.http_connect = fake_http_connect(201)
 | 
			
		||||
        self.controller.create_user('test2', 'tester', 'testing')
 | 
			
		||||
        auth_server.http_connect = fake_http_connect(201)
 | 
			
		||||
        self.controller.create_user('test3', 'tester', 'testing')
 | 
			
		||||
        auth_server.http_connect = fake_http_connect(201)
 | 
			
		||||
        self.controller.create_user('test4', 'tester', 'testing')
 | 
			
		||||
        auth_server.http_connect = fake_http_connect(201, 201, 201, 201)
 | 
			
		||||
        rv = self.controller.recreate_accounts()
 | 
			
		||||
        self.assertEquals(rv.split()[0], '4', repr(rv))
 | 
			
		||||
        self.assertEquals(rv.split()[-1], '[]', repr(rv))
 | 
			
		||||
 | 
			
		||||
    def test_recreate_accounts_one_fail(self):
 | 
			
		||||
        auth_server.http_connect = fake_http_connect(201)
 | 
			
		||||
        url = self.controller.create_user('test', 'tester', 'testing')
 | 
			
		||||
        cfaccount = url.split('/')[-1]
 | 
			
		||||
        auth_server.http_connect = fake_http_connect(500)
 | 
			
		||||
        rv = self.controller.recreate_accounts()
 | 
			
		||||
        self.assertEquals(rv.split()[0], '1', repr(rv))
 | 
			
		||||
        self.assertEquals(rv.split()[-1], '[%s]' % repr(cfaccount),
 | 
			
		||||
                          repr(rv))
 | 
			
		||||
 | 
			
		||||
    def test_recreate_accounts_several_fail(self):
 | 
			
		||||
        auth_server.http_connect = fake_http_connect(201)
 | 
			
		||||
        url = self.controller.create_user('test1', 'tester', 'testing')
 | 
			
		||||
        cfaccounts = [url.split('/')[-1]]
 | 
			
		||||
        auth_server.http_connect = fake_http_connect(201)
 | 
			
		||||
        url = self.controller.create_user('test2', 'tester', 'testing')
 | 
			
		||||
        cfaccounts.append(url.split('/')[-1])
 | 
			
		||||
        auth_server.http_connect = fake_http_connect(201)
 | 
			
		||||
        url = self.controller.create_user('test3', 'tester', 'testing')
 | 
			
		||||
        cfaccounts.append(url.split('/')[-1])
 | 
			
		||||
        auth_server.http_connect = fake_http_connect(201)
 | 
			
		||||
        url = self.controller.create_user('test4', 'tester', 'testing')
 | 
			
		||||
        cfaccounts.append(url.split('/')[-1])
 | 
			
		||||
        auth_server.http_connect = fake_http_connect(500, 500, 500, 500)
 | 
			
		||||
        rv = self.controller.recreate_accounts()
 | 
			
		||||
        self.assertEquals(rv.split()[0], '4', repr(rv))
 | 
			
		||||
        failed = rv.split('[', 1)[-1][:-1].split(', ')
 | 
			
		||||
        self.assertEquals(set(failed), set(repr(a) for a in cfaccounts))
 | 
			
		||||
 | 
			
		||||
    def test_recreate_accounts_several_fail_some(self):
 | 
			
		||||
        auth_server.http_connect = fake_http_connect(201)
 | 
			
		||||
        url = self.controller.create_user('test1', 'tester', 'testing')
 | 
			
		||||
        cfaccounts = [url.split('/')[-1]]
 | 
			
		||||
        auth_server.http_connect = fake_http_connect(201)
 | 
			
		||||
        url = self.controller.create_user('test2', 'tester', 'testing')
 | 
			
		||||
        cfaccounts.append(url.split('/')[-1])
 | 
			
		||||
        auth_server.http_connect = fake_http_connect(201)
 | 
			
		||||
        url = self.controller.create_user('test3', 'tester', 'testing')
 | 
			
		||||
        cfaccounts.append(url.split('/')[-1])
 | 
			
		||||
        auth_server.http_connect = fake_http_connect(201)
 | 
			
		||||
        url = self.controller.create_user('test4', 'tester', 'testing')
 | 
			
		||||
        cfaccounts.append(url.split('/')[-1])
 | 
			
		||||
        auth_server.http_connect = fake_http_connect(500, 201, 500, 201)
 | 
			
		||||
        rv = self.controller.recreate_accounts()
 | 
			
		||||
        self.assertEquals(rv.split()[0], '4', repr(rv))
 | 
			
		||||
        failed = rv.split('[', 1)[-1][:-1].split(', ')
 | 
			
		||||
        self.assertEquals(
 | 
			
		||||
            len(set(repr(a) for a in cfaccounts) - set(failed)), 2)
 | 
			
		||||
 | 
			
		||||
    def test_auth_bad_path(self):
 | 
			
		||||
        res = self.controller.handle_auth(
 | 
			
		||||
            Request.blank('', environ={'REQUEST_METHOD': 'GET'}))
 | 
			
		||||
        self.assertEquals(res.status_int, 400)
 | 
			
		||||
        res = self.controller.handle_auth(Request.blank('/bad',
 | 
			
		||||
                environ={'REQUEST_METHOD': 'GET'}))
 | 
			
		||||
        self.assertEquals(res.status_int, 400)
 | 
			
		||||
 | 
			
		||||
    def test_auth_SOSO_missing_headers(self):
 | 
			
		||||
        auth_server.http_connect = fake_http_connect(201)
 | 
			
		||||
        cfaccount = self.controller.create_user(
 | 
			
		||||
            'test', 'tester', 'testing').split('/')[-1]
 | 
			
		||||
        res = self.controller.handle_auth(Request.blank('/v1/test/auth',
 | 
			
		||||
                environ={'REQUEST_METHOD': 'GET'},
 | 
			
		||||
                headers={'X-Storage-Pass': 'testing'}))
 | 
			
		||||
        self.assertEquals(res.status_int, 401)
 | 
			
		||||
        res = self.controller.handle_auth(Request.blank('/v1/test/auth',
 | 
			
		||||
                environ={'REQUEST_METHOD': 'GET'}))
 | 
			
		||||
        self.assertEquals(res.status_int, 401)
 | 
			
		||||
        res = self.controller.handle_auth(Request.blank('/v1/test/auth',
 | 
			
		||||
                environ={'REQUEST_METHOD': 'GET'},
 | 
			
		||||
                headers={'X-Storage-User': 'tester'}))
 | 
			
		||||
        self.assertEquals(res.status_int, 401)
 | 
			
		||||
 | 
			
		||||
    def test_auth_SOSO_bad_account(self):
 | 
			
		||||
        auth_server.http_connect = fake_http_connect(201)
 | 
			
		||||
        cfaccount = self.controller.create_user(
 | 
			
		||||
            'test', 'tester', 'testing').split('/')[-1]
 | 
			
		||||
        res = self.controller.handle_auth(Request.blank('/v1/testbad/auth',
 | 
			
		||||
                environ={'REQUEST_METHOD': 'GET'},
 | 
			
		||||
                headers={'X-Storage-User': 'tester',
 | 
			
		||||
                         'X-Storage-Pass': 'testing'}))
 | 
			
		||||
        self.assertEquals(res.status_int, 401)
 | 
			
		||||
        res = self.controller.handle_auth(Request.blank('/v1//auth',
 | 
			
		||||
                environ={'REQUEST_METHOD': 'GET'},
 | 
			
		||||
                headers={'X-Storage-User': 'tester',
 | 
			
		||||
                         'X-Storage-Pass': 'testing'}))
 | 
			
		||||
        self.assertEquals(res.status_int, 401)
 | 
			
		||||
 | 
			
		||||
    def test_auth_SOSO_bad_user(self):
 | 
			
		||||
        auth_server.http_connect = fake_http_connect(201)
 | 
			
		||||
        cfaccount = self.controller.create_user(
 | 
			
		||||
            'test', 'tester', 'testing').split('/')[-1]
 | 
			
		||||
        res = self.controller.handle_auth(Request.blank('/v1/test/auth',
 | 
			
		||||
                environ={'REQUEST_METHOD': 'GET'},
 | 
			
		||||
                headers={'X-Storage-User': 'testerbad',
 | 
			
		||||
                         'X-Storage-Pass': 'testing'}))
 | 
			
		||||
        self.assertEquals(res.status_int, 401)
 | 
			
		||||
        res = self.controller.handle_auth(Request.blank('/v1/test/auth',
 | 
			
		||||
                environ={'REQUEST_METHOD': 'GET'},
 | 
			
		||||
                headers={'X-Storage-User': '',
 | 
			
		||||
                         'X-Storage-Pass': 'testing'}))
 | 
			
		||||
        self.assertEquals(res.status_int, 401)
 | 
			
		||||
 | 
			
		||||
    def test_auth_SOSO_bad_password(self):
 | 
			
		||||
        auth_server.http_connect = fake_http_connect(201)
 | 
			
		||||
        cfaccount = self.controller.create_user(
 | 
			
		||||
            'test', 'tester', 'testing').split('/')[-1]
 | 
			
		||||
        res = self.controller.handle_auth(Request.blank('/v1/test/auth',
 | 
			
		||||
                environ={'REQUEST_METHOD': 'GET'},
 | 
			
		||||
                headers={'X-Storage-User': 'tester',
 | 
			
		||||
                         'X-Storage-Pass': 'testingbad'}))
 | 
			
		||||
        self.assertEquals(res.status_int, 401)
 | 
			
		||||
        res = self.controller.handle_auth(Request.blank('/v1/test/auth',
 | 
			
		||||
                environ={'REQUEST_METHOD': 'GET'},
 | 
			
		||||
                headers={'X-Storage-User': 'tester',
 | 
			
		||||
                         'X-Storage-Pass': ''}))
 | 
			
		||||
        self.assertEquals(res.status_int, 401)
 | 
			
		||||
 | 
			
		||||
    def test_auth_SOSO_good(self):
 | 
			
		||||
        auth_server.http_connect = fake_http_connect(201)
 | 
			
		||||
        cfaccount = self.controller.create_user(
 | 
			
		||||
            'test', 'tester', 'testing').split('/')[-1]
 | 
			
		||||
        res = self.controller.handle_auth(Request.blank('/v1/test/auth',
 | 
			
		||||
                environ={'REQUEST_METHOD': 'GET'},
 | 
			
		||||
                headers={'X-Storage-User': 'tester',
 | 
			
		||||
                         'X-Storage-Pass': 'testing'}))
 | 
			
		||||
        token = res.headers['x-storage-token']
 | 
			
		||||
        ttl, _junk, _junk, _junk = self.controller.validate_token(token)
 | 
			
		||||
        self.assert_(ttl > 0, repr(ttl))
 | 
			
		||||
 | 
			
		||||
    def test_auth_SOSO_good_Mosso_headers(self):
 | 
			
		||||
        auth_server.http_connect = fake_http_connect(201)
 | 
			
		||||
        cfaccount = self.controller.create_user(
 | 
			
		||||
            'test', 'tester', 'testing').split('/')[-1]
 | 
			
		||||
        res = self.controller.handle_auth(Request.blank('/v1/test/auth',
 | 
			
		||||
                environ={'REQUEST_METHOD': 'GET'},
 | 
			
		||||
                headers={'X-Auth-User': 'test:tester',
 | 
			
		||||
                         'X-Auth-Key': 'testing'}))
 | 
			
		||||
        token = res.headers['x-storage-token']
 | 
			
		||||
        ttl, _junk, _junk, _junk = self.controller.validate_token(token)
 | 
			
		||||
        self.assert_(ttl > 0, repr(ttl))
 | 
			
		||||
 | 
			
		||||
    def test_auth_SOSO_bad_Mosso_headers(self):
 | 
			
		||||
        auth_server.http_connect = fake_http_connect(201)
 | 
			
		||||
        cfaccount = self.controller.create_user(
 | 
			
		||||
            'test', 'tester', 'testing',).split('/')[-1]
 | 
			
		||||
        res = self.controller.handle_auth(Request.blank('/v1/test/auth',
 | 
			
		||||
                environ={'REQUEST_METHOD': 'GET'},
 | 
			
		||||
                headers={'X-Auth-User': 'test2:tester',
 | 
			
		||||
                         'X-Auth-Key': 'testing'}))
 | 
			
		||||
        self.assertEquals(res.status_int, 401)
 | 
			
		||||
        res = self.controller.handle_auth(Request.blank('/v1/test/auth',
 | 
			
		||||
                environ={'REQUEST_METHOD': 'GET'},
 | 
			
		||||
                headers={'X-Auth-User': ':tester',
 | 
			
		||||
                         'X-Auth-Key': 'testing'}))
 | 
			
		||||
        self.assertEquals(res.status_int, 401)
 | 
			
		||||
        res = self.controller.handle_auth(Request.blank('/v1/test/auth',
 | 
			
		||||
                environ={'REQUEST_METHOD': 'GET'},
 | 
			
		||||
                headers={'X-Auth-User': 'test:',
 | 
			
		||||
                         'X-Auth-Key': 'testing'}))
 | 
			
		||||
        self.assertEquals(res.status_int, 401)
 | 
			
		||||
 | 
			
		||||
    def test_auth_Mosso_missing_headers(self):
 | 
			
		||||
        auth_server.http_connect = fake_http_connect(201)
 | 
			
		||||
        cfaccount = self.controller.create_user(
 | 
			
		||||
            'test', 'tester', 'testing').split('/')[-1]
 | 
			
		||||
        res = self.controller.handle_auth(Request.blank('/auth',
 | 
			
		||||
                environ={'REQUEST_METHOD': 'GET'}))
 | 
			
		||||
        self.assertEquals(res.status_int, 401)
 | 
			
		||||
        res = self.controller.handle_auth(Request.blank('/auth',
 | 
			
		||||
                environ={'REQUEST_METHOD': 'GET'},
 | 
			
		||||
                headers={'X-Auth-Key': 'testing'}))
 | 
			
		||||
        self.assertEquals(res.status_int, 401)
 | 
			
		||||
        res = self.controller.handle_auth(Request.blank('/auth',
 | 
			
		||||
                environ={'REQUEST_METHOD': 'GET'},
 | 
			
		||||
                headers={'X-Auth-User': 'test:tester'}))
 | 
			
		||||
        self.assertEquals(res.status_int, 401)
 | 
			
		||||
 | 
			
		||||
    def test_auth_Mosso_bad_header_format(self):
 | 
			
		||||
        auth_server.http_connect = fake_http_connect(201)
 | 
			
		||||
        cfaccount = self.controller.create_user(
 | 
			
		||||
            'test', 'tester', 'testing').split('/')[-1]
 | 
			
		||||
        res = self.controller.handle_auth(Request.blank('/auth',
 | 
			
		||||
                environ={'REQUEST_METHOD': 'GET'},
 | 
			
		||||
                headers={'X-Auth-User': 'badformat',
 | 
			
		||||
                         'X-Auth-Key': 'testing'}))
 | 
			
		||||
        self.assertEquals(res.status_int, 401)
 | 
			
		||||
        res = self.controller.handle_auth(Request.blank('/auth',
 | 
			
		||||
                environ={'REQUEST_METHOD': 'GET'},
 | 
			
		||||
                headers={'X-Auth-User': '',
 | 
			
		||||
                         'X-Auth-Key': 'testing'}))
 | 
			
		||||
        self.assertEquals(res.status_int, 401)
 | 
			
		||||
 | 
			
		||||
    def test_auth_Mosso_bad_account(self):
 | 
			
		||||
        auth_server.http_connect = fake_http_connect(201)
 | 
			
		||||
        cfaccount = self.controller.create_user(
 | 
			
		||||
            'test', 'tester', 'testing').split('/')[-1]
 | 
			
		||||
        res = self.controller.handle_auth(Request.blank('/auth',
 | 
			
		||||
                environ={'REQUEST_METHOD': 'GET'},
 | 
			
		||||
                headers={'X-Auth-User': 'testbad:tester',
 | 
			
		||||
                         'X-Auth-Key': 'testing'}))
 | 
			
		||||
        self.assertEquals(res.status_int, 401)
 | 
			
		||||
        res = self.controller.handle_auth(Request.blank('/auth',
 | 
			
		||||
                environ={'REQUEST_METHOD': 'GET'},
 | 
			
		||||
                headers={'X-Auth-User': ':tester',
 | 
			
		||||
                         'X-Auth-Key': 'testing'}))
 | 
			
		||||
        self.assertEquals(res.status_int, 401)
 | 
			
		||||
 | 
			
		||||
    def test_auth_Mosso_bad_user(self):
 | 
			
		||||
        auth_server.http_connect = fake_http_connect(201)
 | 
			
		||||
        cfaccount = self.controller.create_user(
 | 
			
		||||
            'test', 'tester', 'testing').split('/')[-1]
 | 
			
		||||
        res = self.controller.handle_auth(Request.blank('/auth',
 | 
			
		||||
                environ={'REQUEST_METHOD': 'GET'},
 | 
			
		||||
                headers={'X-Auth-User': 'test:testerbad',
 | 
			
		||||
                         'X-Auth-Key': 'testing'}))
 | 
			
		||||
        self.assertEquals(res.status_int, 401)
 | 
			
		||||
        res = self.controller.handle_auth(Request.blank('/auth',
 | 
			
		||||
                environ={'REQUEST_METHOD': 'GET'},
 | 
			
		||||
                headers={'X-Auth-User': 'test:',
 | 
			
		||||
                         'X-Auth-Key': 'testing'}))
 | 
			
		||||
        self.assertEquals(res.status_int, 401)
 | 
			
		||||
 | 
			
		||||
    def test_auth_Mosso_bad_password(self):
 | 
			
		||||
        auth_server.http_connect = fake_http_connect(201)
 | 
			
		||||
        cfaccount = self.controller.create_user(
 | 
			
		||||
            'test', 'tester', 'testing').split('/')[-1]
 | 
			
		||||
        res = self.controller.handle_auth(Request.blank('/auth',
 | 
			
		||||
                environ={'REQUEST_METHOD': 'GET'},
 | 
			
		||||
                headers={'X-Auth-User': 'test:tester',
 | 
			
		||||
                         'X-Auth-Key': 'testingbad'}))
 | 
			
		||||
        self.assertEquals(res.status_int, 401)
 | 
			
		||||
        res = self.controller.handle_auth(Request.blank('/auth',
 | 
			
		||||
                environ={'REQUEST_METHOD': 'GET'},
 | 
			
		||||
                headers={'X-Auth-User': 'test:tester',
 | 
			
		||||
                         'X-Auth-Key': ''}))
 | 
			
		||||
        self.assertEquals(res.status_int, 401)
 | 
			
		||||
 | 
			
		||||
    def test_auth_Mosso_good(self):
 | 
			
		||||
        auth_server.http_connect = fake_http_connect(201)
 | 
			
		||||
        cfaccount = self.controller.create_user(
 | 
			
		||||
            'test', 'tester', 'testing').split('/')[-1]
 | 
			
		||||
        res = self.controller.handle_auth(Request.blank('/auth',
 | 
			
		||||
                environ={'REQUEST_METHOD': 'GET'},
 | 
			
		||||
                headers={'X-Auth-User': 'test:tester',
 | 
			
		||||
                         'X-Auth-Key': 'testing'}))
 | 
			
		||||
        token = res.headers['x-storage-token']
 | 
			
		||||
        ttl, _junk, _junk, _junk = self.controller.validate_token(token)
 | 
			
		||||
        self.assert_(ttl > 0, repr(ttl))
 | 
			
		||||
 | 
			
		||||
    def test_auth_Mosso_good_SOSO_header_names(self):
 | 
			
		||||
        auth_server.http_connect = fake_http_connect(201)
 | 
			
		||||
        cfaccount = self.controller.create_user(
 | 
			
		||||
            'test', 'tester', 'testing').split('/')[-1]
 | 
			
		||||
        res = self.controller.handle_auth(Request.blank('/auth',
 | 
			
		||||
                environ={'REQUEST_METHOD': 'GET'},
 | 
			
		||||
                headers={'X-Storage-User': 'test:tester',
 | 
			
		||||
                         'X-Storage-Pass': 'testing'}))
 | 
			
		||||
        token = res.headers['x-storage-token']
 | 
			
		||||
        ttl, _junk, _junk, _junk = self.controller.validate_token(token)
 | 
			
		||||
        self.assert_(ttl > 0, repr(ttl))
 | 
			
		||||
 | 
			
		||||
    def test_basic_logging(self):
 | 
			
		||||
        log = StringIO()
 | 
			
		||||
        log_handler = StreamHandler(log)
 | 
			
		||||
        logger = get_logger(self.conf, 'auth-server', log_route='auth-server')
 | 
			
		||||
        logger.logger.addHandler(log_handler)
 | 
			
		||||
        try:
 | 
			
		||||
            auth_server.http_connect = fake_http_connect(201)
 | 
			
		||||
            url = self.controller.create_user('test', 'tester', 'testing')
 | 
			
		||||
            self.assertEquals(log.getvalue().rsplit(' ', 1)[0],
 | 
			
		||||
                "SUCCESS create_user('test', 'tester', _, False, False) "
 | 
			
		||||
                "= %s" % repr(url))
 | 
			
		||||
            log.truncate(0)
 | 
			
		||||
            def start_response(*args):
 | 
			
		||||
                pass
 | 
			
		||||
            self.controller.handleREST({'REQUEST_METHOD': 'GET',
 | 
			
		||||
                                     'SCRIPT_NAME': '',
 | 
			
		||||
                                     'PATH_INFO': '/v1/test/auth',
 | 
			
		||||
                                     'QUERY_STRING': 'test=True',
 | 
			
		||||
                                     'SERVER_NAME': '127.0.0.1',
 | 
			
		||||
                                     'SERVER_PORT': '8080',
 | 
			
		||||
                                     'SERVER_PROTOCOL': 'HTTP/1.0',
 | 
			
		||||
                                     'CONTENT_LENGTH': '0',
 | 
			
		||||
                                     'wsgi.version': (1, 0),
 | 
			
		||||
                                     'wsgi.url_scheme': 'http',
 | 
			
		||||
                                     'wsgi.input': StringIO(),
 | 
			
		||||
                                     'wsgi.errors': StringIO(),
 | 
			
		||||
                                     'wsgi.multithread': False,
 | 
			
		||||
                                     'wsgi.multiprocess': False,
 | 
			
		||||
                                     'wsgi.run_once': False,
 | 
			
		||||
                                     'HTTP_X_FORWARDED_FOR': 'testhost',
 | 
			
		||||
                                     'HTTP_X_STORAGE_USER': 'tester',
 | 
			
		||||
                                     'HTTP_X_STORAGE_PASS': 'testing'},
 | 
			
		||||
                                    start_response)
 | 
			
		||||
            logsegs = log.getvalue().split(' [', 1)
 | 
			
		||||
            logsegs[1:] = logsegs[1].split('] ', 1)
 | 
			
		||||
            logsegs[1] = '[01/Jan/2001:01:02:03 +0000]'
 | 
			
		||||
            logsegs[2:] = logsegs[2].split(' ')
 | 
			
		||||
            logsegs[-1] = '0.1234'
 | 
			
		||||
            self.assertEquals(' '.join(logsegs), 'testhost - - '
 | 
			
		||||
                '[01/Jan/2001:01:02:03 +0000] "GET /v1/test/auth?test=True '
 | 
			
		||||
                'HTTP/1.0" 204 - "-" "-" - - - - - - - - - "-" "None" "-" '
 | 
			
		||||
                '0.1234')
 | 
			
		||||
            self.controller.log_headers = True
 | 
			
		||||
            log.truncate(0)
 | 
			
		||||
            self.controller.handleREST({'REQUEST_METHOD': 'GET',
 | 
			
		||||
                                     'SCRIPT_NAME': '',
 | 
			
		||||
                                     'PATH_INFO': '/v1/test/auth',
 | 
			
		||||
                                     'SERVER_NAME': '127.0.0.1',
 | 
			
		||||
                                     'SERVER_PORT': '8080',
 | 
			
		||||
                                     'SERVER_PROTOCOL': 'HTTP/1.0',
 | 
			
		||||
                                     'CONTENT_LENGTH': '0',
 | 
			
		||||
                                     'wsgi.version': (1, 0),
 | 
			
		||||
                                     'wsgi.url_scheme': 'http',
 | 
			
		||||
                                     'wsgi.input': StringIO(),
 | 
			
		||||
                                     'wsgi.errors': StringIO(),
 | 
			
		||||
                                     'wsgi.multithread': False,
 | 
			
		||||
                                     'wsgi.multiprocess': False,
 | 
			
		||||
                                     'wsgi.run_once': False,
 | 
			
		||||
                                     'HTTP_X_STORAGE_USER': 'tester',
 | 
			
		||||
                                     'HTTP_X_STORAGE_PASS': 'testing'},
 | 
			
		||||
                                    start_response)
 | 
			
		||||
            logsegs = log.getvalue().split(' [', 1)
 | 
			
		||||
            logsegs[1:] = logsegs[1].split('] ', 1)
 | 
			
		||||
            logsegs[1] = '[01/Jan/2001:01:02:03 +0000]'
 | 
			
		||||
            logsegs[2:] = logsegs[2].split(' ')
 | 
			
		||||
            logsegs[-1] = '0.1234'
 | 
			
		||||
            self.assertEquals(' '.join(logsegs), 'None - - [01/Jan/2001:'
 | 
			
		||||
                '01:02:03 +0000] "GET /v1/test/auth HTTP/1.0" 204 - "-" "-" - '
 | 
			
		||||
                '- - - - - - - - "-" "None" "Content-Length: 0\n'
 | 
			
		||||
                'X-Storage-User: tester\nX-Storage-Pass: testing" 0.1234')
 | 
			
		||||
        finally:
 | 
			
		||||
            logger.logger.handlers.remove(log_handler)
 | 
			
		||||
 | 
			
		||||
    def test_unhandled_exceptions(self):
 | 
			
		||||
        def request_causing_exception(*args, **kwargs):
 | 
			
		||||
            pass
 | 
			
		||||
        def start_response(*args):
 | 
			
		||||
            pass
 | 
			
		||||
        orig_Request = auth_server.Request
 | 
			
		||||
        log = StringIO()
 | 
			
		||||
        log_handler = StreamHandler(log)
 | 
			
		||||
        logger = get_logger(self.conf, 'auth-server', log_route='auth-server')
 | 
			
		||||
        logger.logger.addHandler(log_handler)
 | 
			
		||||
        try:
 | 
			
		||||
            auth_server.Request = request_causing_exception
 | 
			
		||||
            self.controller.handleREST({'REQUEST_METHOD': 'GET',
 | 
			
		||||
                                     'SCRIPT_NAME': '',
 | 
			
		||||
                                     'PATH_INFO': '/v1/test/auth',
 | 
			
		||||
                                     'SERVER_NAME': '127.0.0.1',
 | 
			
		||||
                                     'SERVER_PORT': '8080',
 | 
			
		||||
                                     'SERVER_PROTOCOL': 'HTTP/1.0',
 | 
			
		||||
                                     'CONTENT_LENGTH': '0',
 | 
			
		||||
                                     'wsgi.version': (1, 0),
 | 
			
		||||
                                     'wsgi.url_scheme': 'http',
 | 
			
		||||
                                     'wsgi.input': StringIO(),
 | 
			
		||||
                                     'wsgi.errors': StringIO(),
 | 
			
		||||
                                     'wsgi.multithread': False,
 | 
			
		||||
                                     'wsgi.multiprocess': False,
 | 
			
		||||
                                     'wsgi.run_once': False,
 | 
			
		||||
                                     'HTTP_X_STORAGE_USER': 'tester',
 | 
			
		||||
                                     'HTTP_X_STORAGE_PASS': 'testing'},
 | 
			
		||||
                                    start_response)
 | 
			
		||||
            self.assert_(log.getvalue().startswith(
 | 
			
		||||
                'ERROR Unhandled exception in ReST request'),
 | 
			
		||||
                log.getvalue())
 | 
			
		||||
            log.truncate(0)
 | 
			
		||||
        finally:
 | 
			
		||||
            auth_server.Request = orig_Request
 | 
			
		||||
            logger.logger.handlers.remove(log_handler)
 | 
			
		||||
 | 
			
		||||
    def test_upgrading_from_db1(self):
 | 
			
		||||
        swift_dir = '/tmp/swift_test_auth_%s' % uuid4().hex
 | 
			
		||||
        os.mkdir(swift_dir)
 | 
			
		||||
        try:
 | 
			
		||||
            # Create db1
 | 
			
		||||
            db_file = os.path.join(swift_dir, 'auth.db')
 | 
			
		||||
            conn = get_db_connection(db_file, okay_to_create=True)
 | 
			
		||||
            conn.execute('''CREATE TABLE IF NOT EXISTS account (
 | 
			
		||||
                            account TEXT, url TEXT, cfaccount TEXT,
 | 
			
		||||
                            user TEXT, password TEXT)''')
 | 
			
		||||
            conn.execute('''CREATE INDEX IF NOT EXISTS ix_account_account
 | 
			
		||||
                            ON account (account)''')
 | 
			
		||||
            conn.execute('''CREATE TABLE IF NOT EXISTS token (
 | 
			
		||||
                            cfaccount TEXT, token TEXT, created FLOAT)''')
 | 
			
		||||
            conn.execute('''CREATE INDEX IF NOT EXISTS ix_token_cfaccount
 | 
			
		||||
                            ON token (cfaccount)''')
 | 
			
		||||
            conn.execute('''CREATE INDEX IF NOT EXISTS ix_token_created
 | 
			
		||||
                            ON token (created)''')
 | 
			
		||||
            conn.execute('''INSERT INTO account
 | 
			
		||||
                            (account, url, cfaccount, user, password)
 | 
			
		||||
                            VALUES ('act', 'url', 'cfa', 'usr', 'pas')''')
 | 
			
		||||
            conn.execute('''INSERT INTO token (cfaccount, token, created)
 | 
			
		||||
                            VALUES ('cfa', 'tok', '1')''')
 | 
			
		||||
            conn.commit()
 | 
			
		||||
            conn.close()
 | 
			
		||||
            # Upgrade to current db
 | 
			
		||||
            conf = {'swift_dir': swift_dir, 'super_admin_key': 'testkey'}
 | 
			
		||||
            exc = None
 | 
			
		||||
            try:
 | 
			
		||||
                auth_server.AuthController(conf)
 | 
			
		||||
            except Exception, err:
 | 
			
		||||
                exc = err
 | 
			
		||||
            self.assert_(str(err).strip().startswith('THERE ARE ACCOUNTS IN '
 | 
			
		||||
                'YOUR auth.db THAT DO NOT BEGIN WITH YOUR NEW RESELLER'), err)
 | 
			
		||||
            # Check new items exist and are correct
 | 
			
		||||
            conn = get_db_connection(db_file)
 | 
			
		||||
            row = conn.execute('SELECT admin FROM account').fetchone()
 | 
			
		||||
            self.assertEquals(row[0], 't')
 | 
			
		||||
            row = conn.execute('SELECT user FROM token').fetchone()
 | 
			
		||||
            self.assert_(not row)
 | 
			
		||||
        finally:
 | 
			
		||||
            rmtree(swift_dir)
 | 
			
		||||
 | 
			
		||||
    def test_upgrading_from_db2(self):
 | 
			
		||||
        swift_dir = '/tmp/swift_test_auth_%s' % uuid4().hex
 | 
			
		||||
        os.mkdir(swift_dir)
 | 
			
		||||
        try:
 | 
			
		||||
            # Create db1
 | 
			
		||||
            db_file = os.path.join(swift_dir, 'auth.db')
 | 
			
		||||
            conn = get_db_connection(db_file, okay_to_create=True)
 | 
			
		||||
            conn.execute('''CREATE TABLE IF NOT EXISTS account (
 | 
			
		||||
                               account TEXT, url TEXT, cfaccount TEXT,
 | 
			
		||||
                               user TEXT, password TEXT, admin TEXT)''')
 | 
			
		||||
            conn.execute('''CREATE INDEX IF NOT EXISTS ix_account_account
 | 
			
		||||
                            ON account (account)''')
 | 
			
		||||
            conn.execute('''CREATE TABLE IF NOT EXISTS token (
 | 
			
		||||
                               token TEXT, created FLOAT,
 | 
			
		||||
                               account TEXT, user TEXT, cfaccount TEXT)''')
 | 
			
		||||
            conn.execute('''CREATE INDEX IF NOT EXISTS ix_token_token
 | 
			
		||||
                            ON token (token)''')
 | 
			
		||||
            conn.execute('''CREATE INDEX IF NOT EXISTS ix_token_created
 | 
			
		||||
                            ON token (created)''')
 | 
			
		||||
            conn.execute('''CREATE INDEX IF NOT EXISTS ix_token_account
 | 
			
		||||
                            ON token (account)''')
 | 
			
		||||
            conn.execute('''INSERT INTO account
 | 
			
		||||
                            (account, url, cfaccount, user, password, admin)
 | 
			
		||||
                            VALUES ('act', 'url', 'cfa', 'us1', 'pas', '')''')
 | 
			
		||||
            conn.execute('''INSERT INTO account
 | 
			
		||||
                            (account, url, cfaccount, user, password, admin)
 | 
			
		||||
                            VALUES ('act', 'url', 'cfa', 'us2', 'pas', 't')''')
 | 
			
		||||
            conn.execute('''INSERT INTO token
 | 
			
		||||
                            (token, created, account, user, cfaccount)
 | 
			
		||||
                            VALUES ('tok', '1', 'act', 'us1', 'cfa')''')
 | 
			
		||||
            conn.commit()
 | 
			
		||||
            conn.close()
 | 
			
		||||
            # Upgrade to current db
 | 
			
		||||
            conf = {'swift_dir': swift_dir, 'super_admin_key': 'testkey'}
 | 
			
		||||
            exc = None
 | 
			
		||||
            try:
 | 
			
		||||
                auth_server.AuthController(conf)
 | 
			
		||||
            except Exception, err:
 | 
			
		||||
                exc = err
 | 
			
		||||
            self.assert_(str(err).strip().startswith('THERE ARE ACCOUNTS IN '
 | 
			
		||||
                'YOUR auth.db THAT DO NOT BEGIN WITH YOUR NEW RESELLER'), err)
 | 
			
		||||
            # Check new items exist and are correct
 | 
			
		||||
            conn = get_db_connection(db_file)
 | 
			
		||||
            row = conn.execute('''SELECT admin, reseller_admin
 | 
			
		||||
                                FROM account WHERE user = 'us1' ''').fetchone()
 | 
			
		||||
            self.assert_(not row[0], row[0])
 | 
			
		||||
            self.assert_(not row[1], row[1])
 | 
			
		||||
            row = conn.execute('''SELECT admin, reseller_admin
 | 
			
		||||
                                FROM account WHERE user = 'us2' ''').fetchone()
 | 
			
		||||
            self.assertEquals(row[0], 't')
 | 
			
		||||
            self.assert_(not row[1], row[1])
 | 
			
		||||
            row = conn.execute('SELECT user FROM token').fetchone()
 | 
			
		||||
            self.assert_(row)
 | 
			
		||||
        finally:
 | 
			
		||||
            rmtree(swift_dir)
 | 
			
		||||
 | 
			
		||||
    def test_create_user_twice(self):
 | 
			
		||||
        auth_server.http_connect = fake_http_connect(201)
 | 
			
		||||
        self.controller.create_user('test', 'tester', 'testing')
 | 
			
		||||
        auth_server.http_connect = fake_http_connect(201)
 | 
			
		||||
        self.assertEquals(
 | 
			
		||||
            self.controller.create_user('test', 'tester', 'testing'),
 | 
			
		||||
            'already exists')
 | 
			
		||||
        req = Request.blank('/account/test/tester',
 | 
			
		||||
                headers={'X-Auth-User-Key': 'testing'})
 | 
			
		||||
        resp = self.controller.handle_add_user(req)
 | 
			
		||||
        self.assertEquals(resp.status_int, 409)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    def test_create_2users_1account(self):
 | 
			
		||||
        auth_server.http_connect = fake_http_connect(201)
 | 
			
		||||
        url = self.controller.create_user('test', 'tester', 'testing')
 | 
			
		||||
        auth_server.http_connect = fake_http_connect(201)
 | 
			
		||||
        url2 = self.controller.create_user('test', 'tester2', 'testing2')
 | 
			
		||||
        self.assertEquals(url, url2)
 | 
			
		||||
 | 
			
		||||
    def test_no_super_admin_key(self):
 | 
			
		||||
        conf = {'swift_dir': self.testdir, 'log_name': 'auth'}
 | 
			
		||||
        self.assertRaises(ValueError, auth_server.AuthController, conf)
 | 
			
		||||
        conf['super_admin_key'] = 'testkey'
 | 
			
		||||
        controller = auth_server.AuthController(conf)
 | 
			
		||||
        self.assertEquals(controller.super_admin_key, conf['super_admin_key'])
 | 
			
		||||
 | 
			
		||||
    def test_add_storage_account(self):
 | 
			
		||||
        auth_server.http_connect = fake_http_connect(201)
 | 
			
		||||
        stgact = self.controller.add_storage_account()
 | 
			
		||||
        self.assert_(stgact.startswith(self.controller.reseller_prefix),
 | 
			
		||||
                     stgact)
 | 
			
		||||
        # Make sure token given is the expected single use token
 | 
			
		||||
        token = auth_server.http_connect.last_args[-1]['X-Auth-Token']
 | 
			
		||||
        self.assert_(self.controller.validate_token(token))
 | 
			
		||||
        self.assert_(not self.controller.validate_token(token))
 | 
			
		||||
        auth_server.http_connect = fake_http_connect(201)
 | 
			
		||||
        stgact = self.controller.add_storage_account('bob')
 | 
			
		||||
        self.assertEquals(stgact, 'bob')
 | 
			
		||||
        # Make sure token given is the expected single use token
 | 
			
		||||
        token = auth_server.http_connect.last_args[-1]['X-Auth-Token']
 | 
			
		||||
        self.assert_(self.controller.validate_token(token))
 | 
			
		||||
        self.assert_(not self.controller.validate_token(token))
 | 
			
		||||
 | 
			
		||||
    def test_regular_user(self):
 | 
			
		||||
        auth_server.http_connect = fake_http_connect(201)
 | 
			
		||||
        self.controller.create_user('act', 'usr', 'pas').split('/')[-1]
 | 
			
		||||
        res = self.controller.handle_auth(Request.blank('/v1.0',
 | 
			
		||||
                environ={'REQUEST_METHOD': 'GET'},
 | 
			
		||||
                headers={'X-Auth-User': 'act:usr', 'X-Auth-Key': 'pas'}))
 | 
			
		||||
        _junk, _junk, _junk, stgact = \
 | 
			
		||||
            self.controller.validate_token(res.headers['x-auth-token'])
 | 
			
		||||
        self.assertEquals(stgact, '')
 | 
			
		||||
 | 
			
		||||
    def test_account_admin(self):
 | 
			
		||||
        auth_server.http_connect = fake_http_connect(201)
 | 
			
		||||
        stgact = self.controller.create_user(
 | 
			
		||||
            'act', 'usr', 'pas', admin=True).split('/')[-1]
 | 
			
		||||
        res = self.controller.handle_auth(Request.blank('/v1.0',
 | 
			
		||||
                environ={'REQUEST_METHOD': 'GET'},
 | 
			
		||||
                headers={'X-Auth-User': 'act:usr', 'X-Auth-Key': 'pas'}))
 | 
			
		||||
        _junk, _junk, _junk, vstgact = \
 | 
			
		||||
            self.controller.validate_token(res.headers['x-auth-token'])
 | 
			
		||||
        self.assertEquals(stgact, vstgact)
 | 
			
		||||
 | 
			
		||||
    def test_reseller_admin(self):
 | 
			
		||||
        auth_server.http_connect = fake_http_connect(201)
 | 
			
		||||
        self.controller.create_user(
 | 
			
		||||
            'act', 'usr', 'pas', reseller_admin=True).split('/')[-1]
 | 
			
		||||
        res = self.controller.handle_auth(Request.blank('/v1.0',
 | 
			
		||||
                environ={'REQUEST_METHOD': 'GET'},
 | 
			
		||||
                headers={'X-Auth-User': 'act:usr', 'X-Auth-Key': 'pas'}))
 | 
			
		||||
        _junk, _junk, _junk, stgact = \
 | 
			
		||||
            self.controller.validate_token(res.headers['x-auth-token'])
 | 
			
		||||
        self.assertEquals(stgact, '.reseller_admin')
 | 
			
		||||
 | 
			
		||||
    def test_is_account_admin(self):
 | 
			
		||||
        req = Request.blank('/', headers={'X-Auth-Admin-User': '.super_admin',
 | 
			
		||||
                                          'X-Auth-Admin-Key': 'testkey'})
 | 
			
		||||
        self.assert_(self.controller.is_account_admin(req, 'any'))
 | 
			
		||||
        req = Request.blank('/', headers={'X-Auth-Admin-User': '.super_admin',
 | 
			
		||||
                                          'X-Auth-Admin-Key': 'testkey2'})
 | 
			
		||||
        self.assert_(not self.controller.is_account_admin(req, 'any'))
 | 
			
		||||
        req = Request.blank('/', headers={'X-Auth-Admin-User': '.super_admi',
 | 
			
		||||
                                          'X-Auth-Admin-Key': 'testkey'})
 | 
			
		||||
        self.assert_(not self.controller.is_account_admin(req, 'any'))
 | 
			
		||||
 | 
			
		||||
        auth_server.http_connect = fake_http_connect(201, 201)
 | 
			
		||||
        self.controller.create_user(
 | 
			
		||||
            'act1', 'resadmin', 'pas', reseller_admin=True).split('/')[-1]
 | 
			
		||||
        self.controller.create_user('act1', 'usr', 'pas').split('/')[-1]
 | 
			
		||||
        self.controller.create_user(
 | 
			
		||||
            'act2', 'actadmin', 'pas', admin=True).split('/')[-1]
 | 
			
		||||
 | 
			
		||||
        req = Request.blank('/', headers={'X-Auth-Admin-User': 'act1:resadmin',
 | 
			
		||||
                                          'X-Auth-Admin-Key': 'pas'})
 | 
			
		||||
        self.assert_(self.controller.is_account_admin(req, 'any'))
 | 
			
		||||
        self.assert_(self.controller.is_account_admin(req, 'act1'))
 | 
			
		||||
        self.assert_(self.controller.is_account_admin(req, 'act2'))
 | 
			
		||||
 | 
			
		||||
        req = Request.blank('/', headers={'X-Auth-Admin-User': 'act1:usr',
 | 
			
		||||
                                          'X-Auth-Admin-Key': 'pas'})
 | 
			
		||||
        self.assert_(not self.controller.is_account_admin(req, 'any'))
 | 
			
		||||
        self.assert_(not self.controller.is_account_admin(req, 'act1'))
 | 
			
		||||
        self.assert_(not self.controller.is_account_admin(req, 'act2'))
 | 
			
		||||
 | 
			
		||||
        req = Request.blank('/', headers={'X-Auth-Admin-User': 'act2:actadmin',
 | 
			
		||||
                                          'X-Auth-Admin-Key': 'pas'})
 | 
			
		||||
        self.assert_(not self.controller.is_account_admin(req, 'any'))
 | 
			
		||||
        self.assert_(not self.controller.is_account_admin(req, 'act1'))
 | 
			
		||||
        self.assert_(self.controller.is_account_admin(req, 'act2'))
 | 
			
		||||
 | 
			
		||||
    def test_handle_add_user_create_reseller_admin(self):
 | 
			
		||||
        auth_server.http_connect = fake_http_connect(201)
 | 
			
		||||
        self.controller.create_user('act', 'usr', 'pas')
 | 
			
		||||
        self.controller.create_user('act', 'actadmin', 'pas', admin=True)
 | 
			
		||||
        self.controller.create_user('act', 'resadmin', 'pas',
 | 
			
		||||
                                    reseller_admin=True)
 | 
			
		||||
 | 
			
		||||
        req = Request.blank('/account/act/resadmin2',
 | 
			
		||||
                headers={'X-Auth-User-Key': 'pas',
 | 
			
		||||
                         'X-Auth-User-Reseller-Admin': 'true'})
 | 
			
		||||
        resp = self.controller.handle_add_user(req)
 | 
			
		||||
        self.assert_(resp.status_int // 100 == 4, resp.status_int)
 | 
			
		||||
 | 
			
		||||
        req = Request.blank('/account/act/resadmin2',
 | 
			
		||||
                headers={'X-Auth-User-Key': 'pas',
 | 
			
		||||
                         'X-Auth-User-Reseller-Admin': 'true',
 | 
			
		||||
                         'X-Auth-Admin-User': 'act:usr',
 | 
			
		||||
                         'X-Auth-Admin-Key': 'pas'})
 | 
			
		||||
        resp = self.controller.handle_add_user(req)
 | 
			
		||||
        self.assert_(resp.status_int // 100 == 4, resp.status_int)
 | 
			
		||||
 | 
			
		||||
        req = Request.blank('/account/act/resadmin2',
 | 
			
		||||
                headers={'X-Auth-User-Key': 'pas',
 | 
			
		||||
                         'X-Auth-User-Reseller-Admin': 'true',
 | 
			
		||||
                         'X-Auth-Admin-User': 'act:actadmin',
 | 
			
		||||
                         'X-Auth-Admin-Key': 'pas'})
 | 
			
		||||
        resp = self.controller.handle_add_user(req)
 | 
			
		||||
        self.assert_(resp.status_int // 100 == 4, resp.status_int)
 | 
			
		||||
 | 
			
		||||
        req = Request.blank('/account/act/resadmin2',
 | 
			
		||||
                headers={'X-Auth-User-Key': 'pas',
 | 
			
		||||
                         'X-Auth-User-Reseller-Admin': 'true',
 | 
			
		||||
                         'X-Auth-Admin-User': 'act:resadmin',
 | 
			
		||||
                         'X-Auth-Admin-Key': 'pas'})
 | 
			
		||||
        resp = self.controller.handle_add_user(req)
 | 
			
		||||
        self.assert_(resp.status_int // 100 == 4, resp.status_int)
 | 
			
		||||
 | 
			
		||||
        req = Request.blank('/account/act/resadmin2',
 | 
			
		||||
                headers={'X-Auth-User-Key': 'pas',
 | 
			
		||||
                         'X-Auth-User-Reseller-Admin': 'true',
 | 
			
		||||
                         'X-Auth-Admin-User': '.super_admin',
 | 
			
		||||
                         'X-Auth-Admin-Key': 'testkey'})
 | 
			
		||||
        resp = self.controller.handle_add_user(req)
 | 
			
		||||
        self.assert_(resp.status_int // 100 == 2, resp.status_int)
 | 
			
		||||
 | 
			
		||||
    def test_handle_add_user_create_account_admin(self):
 | 
			
		||||
        auth_server.http_connect = fake_http_connect(201, 201)
 | 
			
		||||
        self.controller.create_user('act', 'usr', 'pas')
 | 
			
		||||
        self.controller.create_user('act', 'actadmin', 'pas', admin=True)
 | 
			
		||||
        self.controller.create_user('act2', 'actadmin', 'pas', admin=True)
 | 
			
		||||
        self.controller.create_user('act2', 'resadmin', 'pas',
 | 
			
		||||
                                    reseller_admin=True)
 | 
			
		||||
 | 
			
		||||
        req = Request.blank('/account/act/actadmin2',
 | 
			
		||||
                headers={'X-Auth-User-Key': 'pas',
 | 
			
		||||
                         'X-Auth-User-Admin': 'true'})
 | 
			
		||||
        resp = self.controller.handle_add_user(req)
 | 
			
		||||
        self.assert_(resp.status_int // 100 == 4, resp.status_int)
 | 
			
		||||
 | 
			
		||||
        req = Request.blank('/account/act/actadmin2',
 | 
			
		||||
                headers={'X-Auth-User-Key': 'pas',
 | 
			
		||||
                         'X-Auth-User-Admin': 'true',
 | 
			
		||||
                         'X-Auth-Admin-User': 'act:usr',
 | 
			
		||||
                         'X-Auth-Admin-Key': 'pas'})
 | 
			
		||||
        resp = self.controller.handle_add_user(req)
 | 
			
		||||
        self.assert_(resp.status_int // 100 == 4, resp.status_int)
 | 
			
		||||
 | 
			
		||||
        req = Request.blank('/account/act/actadmin2',
 | 
			
		||||
                headers={'X-Auth-User-Key': 'pas',
 | 
			
		||||
                         'X-Auth-User-Admin': 'true',
 | 
			
		||||
                         'X-Auth-Admin-User': 'act2:actadmin',
 | 
			
		||||
                         'X-Auth-Admin-Key': 'pas'})
 | 
			
		||||
        resp = self.controller.handle_add_user(req)
 | 
			
		||||
        self.assert_(resp.status_int // 100 == 4, resp.status_int)
 | 
			
		||||
 | 
			
		||||
        req = Request.blank('/account/act/actadmin2',
 | 
			
		||||
                headers={'X-Auth-User-Key': 'pas',
 | 
			
		||||
                         'X-Auth-User-Admin': 'true',
 | 
			
		||||
                         'X-Auth-Admin-User': 'act:actadmin',
 | 
			
		||||
                         'X-Auth-Admin-Key': 'pas'})
 | 
			
		||||
        resp = self.controller.handle_add_user(req)
 | 
			
		||||
        self.assert_(resp.status_int // 100 == 2, resp.status_int)
 | 
			
		||||
 | 
			
		||||
        req = Request.blank('/account/act/actadmin3',
 | 
			
		||||
                headers={'X-Auth-User-Key': 'pas',
 | 
			
		||||
                         'X-Auth-User-Admin': 'true',
 | 
			
		||||
                         'X-Auth-Admin-User': 'act2:resadmin',
 | 
			
		||||
                         'X-Auth-Admin-Key': 'pas'})
 | 
			
		||||
        resp = self.controller.handle_add_user(req)
 | 
			
		||||
        self.assert_(resp.status_int // 100 == 2, resp.status_int)
 | 
			
		||||
 | 
			
		||||
        req = Request.blank('/account/act/actadmin4',
 | 
			
		||||
                headers={'X-Auth-User-Key': 'pas',
 | 
			
		||||
                         'X-Auth-User-Admin': 'true',
 | 
			
		||||
                         'X-Auth-Admin-User': '.super_admin',
 | 
			
		||||
                         'X-Auth-Admin-Key': 'testkey'})
 | 
			
		||||
        resp = self.controller.handle_add_user(req)
 | 
			
		||||
        self.assert_(resp.status_int // 100 == 2, resp.status_int)
 | 
			
		||||
 | 
			
		||||
    def test_handle_add_user_create_normal_user(self):
 | 
			
		||||
        auth_server.http_connect = fake_http_connect(201, 201)
 | 
			
		||||
        self.controller.create_user('act', 'usr', 'pas')
 | 
			
		||||
        self.controller.create_user('act', 'actadmin', 'pas', admin=True)
 | 
			
		||||
        self.controller.create_user('act2', 'actadmin', 'pas', admin=True)
 | 
			
		||||
        self.controller.create_user('act2', 'resadmin', 'pas',
 | 
			
		||||
                                    reseller_admin=True)
 | 
			
		||||
 | 
			
		||||
        req = Request.blank('/account/act/usr2',
 | 
			
		||||
                headers={'X-Auth-User-Key': 'pas',
 | 
			
		||||
                         'X-Auth-User-Admin': 'true'})
 | 
			
		||||
        resp = self.controller.handle_add_user(req)
 | 
			
		||||
        self.assert_(resp.status_int // 100 == 4, resp.status_int)
 | 
			
		||||
 | 
			
		||||
        req = Request.blank('/account/act/usr2',
 | 
			
		||||
                headers={'X-Auth-User-Key': 'pas',
 | 
			
		||||
                         'X-Auth-User-Admin': 'true',
 | 
			
		||||
                         'X-Auth-Admin-User': 'act:usr',
 | 
			
		||||
                         'X-Auth-Admin-Key': 'pas'})
 | 
			
		||||
        resp = self.controller.handle_add_user(req)
 | 
			
		||||
        self.assert_(resp.status_int // 100 == 4, resp.status_int)
 | 
			
		||||
 | 
			
		||||
        req = Request.blank('/account/act/usr2',
 | 
			
		||||
                headers={'X-Auth-User-Key': 'pas',
 | 
			
		||||
                         'X-Auth-User-Admin': 'true',
 | 
			
		||||
                         'X-Auth-Admin-User': 'act2:actadmin',
 | 
			
		||||
                         'X-Auth-Admin-Key': 'pas'})
 | 
			
		||||
        resp = self.controller.handle_add_user(req)
 | 
			
		||||
        self.assert_(resp.status_int // 100 == 4, resp.status_int)
 | 
			
		||||
 | 
			
		||||
        req = Request.blank('/account/act/usr2',
 | 
			
		||||
                headers={'X-Auth-User-Key': 'pas',
 | 
			
		||||
                         'X-Auth-User-Admin': 'true',
 | 
			
		||||
                         'X-Auth-Admin-User': 'act:actadmin',
 | 
			
		||||
                         'X-Auth-Admin-Key': 'pas'})
 | 
			
		||||
        resp = self.controller.handle_add_user(req)
 | 
			
		||||
        self.assert_(resp.status_int // 100 == 2, resp.status_int)
 | 
			
		||||
 | 
			
		||||
        req = Request.blank('/account/act/usr3',
 | 
			
		||||
                headers={'X-Auth-User-Key': 'pas',
 | 
			
		||||
                         'X-Auth-User-Admin': 'true',
 | 
			
		||||
                         'X-Auth-Admin-User': 'act2:resadmin',
 | 
			
		||||
                         'X-Auth-Admin-Key': 'pas'})
 | 
			
		||||
        resp = self.controller.handle_add_user(req)
 | 
			
		||||
        self.assert_(resp.status_int // 100 == 2, resp.status_int)
 | 
			
		||||
 | 
			
		||||
        req = Request.blank('/account/act/usr4',
 | 
			
		||||
                headers={'X-Auth-User-Key': 'pas',
 | 
			
		||||
                         'X-Auth-User-Admin': 'true',
 | 
			
		||||
                         'X-Auth-Admin-User': '.super_admin',
 | 
			
		||||
                         'X-Auth-Admin-Key': 'testkey'})
 | 
			
		||||
        resp = self.controller.handle_add_user(req)
 | 
			
		||||
        self.assert_(resp.status_int // 100 == 2, resp.status_int)
 | 
			
		||||
 | 
			
		||||
    def test_handle_account_recreate_permissions(self):
 | 
			
		||||
        auth_server.http_connect = fake_http_connect(201, 201)
 | 
			
		||||
        self.controller.create_user('act', 'usr', 'pas')
 | 
			
		||||
        self.controller.create_user('act', 'actadmin', 'pas', admin=True)
 | 
			
		||||
        self.controller.create_user('act', 'resadmin', 'pas',
 | 
			
		||||
                                    reseller_admin=True)
 | 
			
		||||
 | 
			
		||||
        req = Request.blank('/recreate_accounts',
 | 
			
		||||
                headers={'X-Auth-User-Key': 'pas',
 | 
			
		||||
                         'X-Auth-User-Admin': 'true'})
 | 
			
		||||
        resp = self.controller.handle_account_recreate(req)
 | 
			
		||||
        self.assert_(resp.status_int // 100 == 4, resp.status_int)
 | 
			
		||||
 | 
			
		||||
        req = Request.blank('/recreate_accounts',
 | 
			
		||||
                headers={'X-Auth-User-Key': 'pas',
 | 
			
		||||
                         'X-Auth-User-Admin': 'true',
 | 
			
		||||
                         'X-Auth-Admin-User': 'act:usr',
 | 
			
		||||
                         'X-Auth-Admin-Key': 'pas'})
 | 
			
		||||
        resp = self.controller.handle_account_recreate(req)
 | 
			
		||||
        self.assert_(resp.status_int // 100 == 4, resp.status_int)
 | 
			
		||||
 | 
			
		||||
        req = Request.blank('/recreate_accounts',
 | 
			
		||||
                headers={'X-Auth-User-Key': 'pas',
 | 
			
		||||
                         'X-Auth-User-Admin': 'true',
 | 
			
		||||
                         'X-Auth-Admin-User': 'act:actadmin',
 | 
			
		||||
                         'X-Auth-Admin-Key': 'pas'})
 | 
			
		||||
        resp = self.controller.handle_account_recreate(req)
 | 
			
		||||
        self.assert_(resp.status_int // 100 == 4, resp.status_int)
 | 
			
		||||
 | 
			
		||||
        req = Request.blank('/recreate_accounts',
 | 
			
		||||
                headers={'X-Auth-User-Key': 'pas',
 | 
			
		||||
                         'X-Auth-User-Admin': 'true',
 | 
			
		||||
                         'X-Auth-Admin-User': 'act:resadmin',
 | 
			
		||||
                         'X-Auth-Admin-Key': 'pas'})
 | 
			
		||||
        resp = self.controller.handle_account_recreate(req)
 | 
			
		||||
        self.assert_(resp.status_int // 100 == 4, resp.status_int)
 | 
			
		||||
 | 
			
		||||
        req = Request.blank('/recreate_accounts',
 | 
			
		||||
                headers={'X-Auth-User-Key': 'pas',
 | 
			
		||||
                         'X-Auth-User-Admin': 'true',
 | 
			
		||||
                         'X-Auth-Admin-User': '.super_admin',
 | 
			
		||||
                         'X-Auth-Admin-Key': 'testkey'})
 | 
			
		||||
        resp = self.controller.handle_account_recreate(req)
 | 
			
		||||
        self.assert_(resp.status_int // 100 == 2, resp.status_int)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
if __name__ == '__main__':
 | 
			
		||||
    unittest.main()
 | 
			
		||||
@@ -1,471 +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.
 | 
			
		||||
 | 
			
		||||
from __future__ import with_statement
 | 
			
		||||
import logging
 | 
			
		||||
import os
 | 
			
		||||
import sys
 | 
			
		||||
import unittest
 | 
			
		||||
from contextlib import contextmanager
 | 
			
		||||
 | 
			
		||||
import eventlet
 | 
			
		||||
from webob import Request
 | 
			
		||||
 | 
			
		||||
from swift.common.middleware import auth
 | 
			
		||||
 | 
			
		||||
# mocks
 | 
			
		||||
logging.getLogger().addHandler(logging.StreamHandler(sys.stdout))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class FakeMemcache(object):
 | 
			
		||||
    def __init__(self):
 | 
			
		||||
        self.store = {}
 | 
			
		||||
 | 
			
		||||
    def get(self, key):
 | 
			
		||||
        return self.store.get(key)
 | 
			
		||||
 | 
			
		||||
    def set(self, key, value, timeout=0):
 | 
			
		||||
        self.store[key] = value
 | 
			
		||||
        return True
 | 
			
		||||
 | 
			
		||||
    def incr(self, key, timeout=0):
 | 
			
		||||
        self.store[key] = self.store.setdefault(key, 0) + 1
 | 
			
		||||
        return self.store[key]
 | 
			
		||||
 | 
			
		||||
    @contextmanager
 | 
			
		||||
    def soft_lock(self, key, timeout=0, retries=5):
 | 
			
		||||
        yield True
 | 
			
		||||
 | 
			
		||||
    def delete(self, key):
 | 
			
		||||
        try:
 | 
			
		||||
            del self.store[key]
 | 
			
		||||
        except Exception:
 | 
			
		||||
            pass
 | 
			
		||||
        return True
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def mock_http_connect(response, headers=None, with_exc=False):
 | 
			
		||||
    class FakeConn(object):
 | 
			
		||||
        def __init__(self, status, headers, with_exc):
 | 
			
		||||
            self.status = status
 | 
			
		||||
            self.reason = 'Fake'
 | 
			
		||||
            self.host = '1.2.3.4'
 | 
			
		||||
            self.port = '1234'
 | 
			
		||||
            self.with_exc = with_exc
 | 
			
		||||
            self.headers = headers
 | 
			
		||||
            if self.headers is None:
 | 
			
		||||
                self.headers = {}
 | 
			
		||||
 | 
			
		||||
        def getresponse(self):
 | 
			
		||||
            if self.with_exc:
 | 
			
		||||
                raise Exception('test')
 | 
			
		||||
            return self
 | 
			
		||||
 | 
			
		||||
        def getheader(self, header):
 | 
			
		||||
            return self.headers[header]
 | 
			
		||||
 | 
			
		||||
        def read(self, amt=None):
 | 
			
		||||
            return ''
 | 
			
		||||
 | 
			
		||||
        def close(self):
 | 
			
		||||
            return
 | 
			
		||||
 | 
			
		||||
    return lambda *args, **kwargs: FakeConn(response, headers, with_exc)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Logger(object):
 | 
			
		||||
 | 
			
		||||
    def __init__(self):
 | 
			
		||||
        self.error_value = None
 | 
			
		||||
        self.exception_value = None
 | 
			
		||||
 | 
			
		||||
    def error(self, msg, *args, **kwargs):
 | 
			
		||||
        self.error_value = (msg, args, kwargs)
 | 
			
		||||
 | 
			
		||||
    def exception(self, msg, *args, **kwargs):
 | 
			
		||||
        _junk, exc, _junk = sys.exc_info()
 | 
			
		||||
        self.exception_value = (msg,
 | 
			
		||||
            '%s %s' % (exc.__class__.__name__, str(exc)), args, kwargs)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class FakeApp(object):
 | 
			
		||||
 | 
			
		||||
    def __init__(self):
 | 
			
		||||
        self.i_was_called = False
 | 
			
		||||
 | 
			
		||||
    def __call__(self, env, start_response):
 | 
			
		||||
        self.i_was_called = True
 | 
			
		||||
        req = Request.blank('', environ=env)
 | 
			
		||||
        if 'swift.authorize' in env:
 | 
			
		||||
            resp = env['swift.authorize'](req)
 | 
			
		||||
            if resp:
 | 
			
		||||
                return resp(env, start_response)
 | 
			
		||||
        return ['204 No Content']
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def start_response(*args):
 | 
			
		||||
    pass
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TestAuth(unittest.TestCase):
 | 
			
		||||
 | 
			
		||||
    def setUp(self):
 | 
			
		||||
        self.test_auth = auth.filter_factory({})(FakeApp())
 | 
			
		||||
 | 
			
		||||
    def test_auth_deny_non_reseller_prefix(self):
 | 
			
		||||
        old_http_connect = auth.http_connect
 | 
			
		||||
        try:
 | 
			
		||||
            auth.http_connect = mock_http_connect(204,
 | 
			
		||||
               {'x-auth-ttl': '1234', 'x-auth-groups': 'act:usr,act,AUTH_cfa'})
 | 
			
		||||
            reqenv = {'REQUEST_METHOD': 'GET', 'PATH_INFO': '/v1/BLAH_account',
 | 
			
		||||
                'HTTP_X_AUTH_TOKEN': 'BLAH_t', 'swift.cache': FakeMemcache()}
 | 
			
		||||
            result = ''.join(self.test_auth(reqenv, lambda x, y: None))
 | 
			
		||||
            self.assert_(result.startswith('401'), result)
 | 
			
		||||
            self.assertEquals(reqenv['swift.authorize'],
 | 
			
		||||
                              self.test_auth.denied_response)
 | 
			
		||||
        finally:
 | 
			
		||||
            auth.http_connect = old_http_connect
 | 
			
		||||
 | 
			
		||||
    def test_auth_deny_non_reseller_prefix_no_override(self):
 | 
			
		||||
        old_http_connect = auth.http_connect
 | 
			
		||||
        try:
 | 
			
		||||
            auth.http_connect = mock_http_connect(204,
 | 
			
		||||
               {'x-auth-ttl': '1234', 'x-auth-groups': 'act:usr,act,AUTH_cfa'})
 | 
			
		||||
            fake_authorize = lambda x: lambda x, y: ['500 Fake']
 | 
			
		||||
            reqenv = {'REQUEST_METHOD': 'GET', 'PATH_INFO': '/v1/BLAH_account',
 | 
			
		||||
                'HTTP_X_AUTH_TOKEN': 'BLAH_t', 'swift.cache': FakeMemcache(),
 | 
			
		||||
                'swift.authorize': fake_authorize}
 | 
			
		||||
            result = ''.join(self.test_auth(reqenv, lambda x, y: None))
 | 
			
		||||
            self.assert_(result.startswith('500 Fake'), result)
 | 
			
		||||
            self.assertEquals(reqenv['swift.authorize'], fake_authorize)
 | 
			
		||||
        finally:
 | 
			
		||||
            auth.http_connect = old_http_connect
 | 
			
		||||
 | 
			
		||||
    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.
 | 
			
		||||
        old_http_connect = auth.http_connect
 | 
			
		||||
        try:
 | 
			
		||||
            local_app = FakeApp()
 | 
			
		||||
            local_auth = \
 | 
			
		||||
                auth.filter_factory({'reseller_prefix': ''})(local_app)
 | 
			
		||||
            auth.http_connect = mock_http_connect(404)
 | 
			
		||||
            reqenv = {'REQUEST_METHOD': 'GET', 'PATH_INFO': '/v1/account',
 | 
			
		||||
                'HTTP_X_AUTH_TOKEN': 't', 'swift.cache': FakeMemcache()}
 | 
			
		||||
            result = ''.join(local_auth(reqenv, lambda x, y: None))
 | 
			
		||||
            self.assert_(result.startswith('401'), result)
 | 
			
		||||
            self.assert_(local_app.i_was_called)
 | 
			
		||||
            self.assertEquals(reqenv['swift.authorize'],
 | 
			
		||||
                              local_auth.denied_response)
 | 
			
		||||
        finally:
 | 
			
		||||
            auth.http_connect = old_http_connect
 | 
			
		||||
 | 
			
		||||
    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
 | 
			
		||||
        old_http_connect = auth.http_connect
 | 
			
		||||
        try:
 | 
			
		||||
            local_app = FakeApp()
 | 
			
		||||
            local_auth = \
 | 
			
		||||
                auth.filter_factory({'reseller_prefix': ''})(local_app)
 | 
			
		||||
            auth.http_connect = mock_http_connect(204,
 | 
			
		||||
               {'x-auth-ttl': '1234', 'x-auth-groups': 'act:usr,act,AUTH_cfa'})
 | 
			
		||||
            reqenv = {'REQUEST_METHOD': 'GET', 'PATH_INFO': '/v1/act',
 | 
			
		||||
                'HTTP_X_AUTH_TOKEN': 't', 'swift.cache': None}
 | 
			
		||||
            result = ''.join(local_auth(reqenv, lambda x, y: None))
 | 
			
		||||
            self.assert_(result.startswith('204'), result)
 | 
			
		||||
            self.assert_(local_app.i_was_called)
 | 
			
		||||
            self.assertEquals(reqenv['swift.authorize'],
 | 
			
		||||
                              local_auth.authorize)
 | 
			
		||||
        finally:
 | 
			
		||||
            auth.http_connect = old_http_connect
 | 
			
		||||
 | 
			
		||||
    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({'reseller_prefix': ''})(FakeApp())
 | 
			
		||||
        reqenv = {'REQUEST_METHOD': 'GET', 'PATH_INFO': '/v1/account',
 | 
			
		||||
                  'swift.cache': FakeMemcache()}
 | 
			
		||||
        result = ''.join(local_auth(reqenv, lambda x, y: None))
 | 
			
		||||
        self.assert_(result.startswith('401'), result)
 | 
			
		||||
        self.assertEquals(reqenv['swift.authorize'], local_auth.authorize)
 | 
			
		||||
        # Now make sure we don't override an existing swift.authorize when we
 | 
			
		||||
        # have no reseller prefix.
 | 
			
		||||
        local_authorize = lambda req: None
 | 
			
		||||
        reqenv['swift.authorize'] = local_authorize
 | 
			
		||||
        result = ''.join(local_auth(reqenv, lambda x, y: None))
 | 
			
		||||
        self.assert_(result.startswith('204'), result)
 | 
			
		||||
        self.assertEquals(reqenv['swift.authorize'], local_authorize)
 | 
			
		||||
 | 
			
		||||
    def test_auth_fail(self):
 | 
			
		||||
        old_http_connect = auth.http_connect
 | 
			
		||||
        try:
 | 
			
		||||
            auth.http_connect = mock_http_connect(404)
 | 
			
		||||
            result = ''.join(self.test_auth({'REQUEST_METHOD': 'GET',
 | 
			
		||||
                'HTTP_X_AUTH_TOKEN': 'AUTH_t', 'swift.cache': FakeMemcache()},
 | 
			
		||||
                lambda x, y: None))
 | 
			
		||||
            self.assert_(result.startswith('401'), result)
 | 
			
		||||
        finally:
 | 
			
		||||
            auth.http_connect = old_http_connect
 | 
			
		||||
 | 
			
		||||
    def test_auth_success(self):
 | 
			
		||||
        old_http_connect = auth.http_connect
 | 
			
		||||
        try:
 | 
			
		||||
            auth.http_connect = mock_http_connect(204,
 | 
			
		||||
               {'x-auth-ttl': '1234', 'x-auth-groups': 'act:usr,act,AUTH_cfa'})
 | 
			
		||||
            result = ''.join(self.test_auth({'REQUEST_METHOD': 'GET',
 | 
			
		||||
                'PATH_INFO': '/v/AUTH_cfa', 'HTTP_X_AUTH_TOKEN': 'AUTH_t',
 | 
			
		||||
                'swift.cache': FakeMemcache()}, lambda x, y: None))
 | 
			
		||||
            self.assert_(result.startswith('204'), result)
 | 
			
		||||
        finally:
 | 
			
		||||
            auth.http_connect = old_http_connect
 | 
			
		||||
 | 
			
		||||
    def test_auth_memcache(self):
 | 
			
		||||
        old_http_connect = auth.http_connect
 | 
			
		||||
        try:
 | 
			
		||||
            fake_memcache = FakeMemcache()
 | 
			
		||||
            auth.http_connect = mock_http_connect(204,
 | 
			
		||||
               {'x-auth-ttl': '1234', 'x-auth-groups': 'act:usr,act,AUTH_cfa'})
 | 
			
		||||
            result = ''.join(self.test_auth({'REQUEST_METHOD': 'GET',
 | 
			
		||||
                'PATH_INFO': '/v/AUTH_cfa', 'HTTP_X_AUTH_TOKEN': 'AUTH_t',
 | 
			
		||||
                'swift.cache': fake_memcache}, lambda x, y: None))
 | 
			
		||||
            self.assert_(result.startswith('204'), result)
 | 
			
		||||
            auth.http_connect = mock_http_connect(404)
 | 
			
		||||
            # Should still be in memcache
 | 
			
		||||
            result = ''.join(self.test_auth({'REQUEST_METHOD': 'GET',
 | 
			
		||||
                'PATH_INFO': '/v/AUTH_cfa', 'HTTP_X_AUTH_TOKEN': 'AUTH_t',
 | 
			
		||||
                'swift.cache': fake_memcache}, lambda x, y: None))
 | 
			
		||||
            self.assert_(result.startswith('204'), result)
 | 
			
		||||
        finally:
 | 
			
		||||
            auth.http_connect = old_http_connect
 | 
			
		||||
 | 
			
		||||
    def test_auth_just_expired(self):
 | 
			
		||||
        old_http_connect = auth.http_connect
 | 
			
		||||
        try:
 | 
			
		||||
            fake_memcache = FakeMemcache()
 | 
			
		||||
            auth.http_connect = mock_http_connect(204,
 | 
			
		||||
                {'x-auth-ttl': '0', 'x-auth-groups': 'act:usr,act,AUTH_cfa'})
 | 
			
		||||
            result = ''.join(self.test_auth({'REQUEST_METHOD': 'GET',
 | 
			
		||||
                'PATH_INFO': '/v/AUTH_cfa', 'HTTP_X_AUTH_TOKEN': 'AUTH_t',
 | 
			
		||||
                'swift.cache': fake_memcache}, lambda x, y: None))
 | 
			
		||||
            self.assert_(result.startswith('204'), result)
 | 
			
		||||
            auth.http_connect = mock_http_connect(404)
 | 
			
		||||
            # Should still be in memcache, but expired
 | 
			
		||||
            result = ''.join(self.test_auth({'REQUEST_METHOD': 'GET',
 | 
			
		||||
                'HTTP_X_AUTH_TOKEN': 'AUTH_t', 'swift.cache': fake_memcache},
 | 
			
		||||
                lambda x, y: None))
 | 
			
		||||
            self.assert_(result.startswith('401'), result)
 | 
			
		||||
        finally:
 | 
			
		||||
            auth.http_connect = old_http_connect
 | 
			
		||||
 | 
			
		||||
    def test_middleware_success(self):
 | 
			
		||||
        old_http_connect = auth.http_connect
 | 
			
		||||
        try:
 | 
			
		||||
            auth.http_connect = mock_http_connect(204,
 | 
			
		||||
               {'x-auth-ttl': '1234', 'x-auth-groups': 'act:usr,act,AUTH_cfa'})
 | 
			
		||||
            req = Request.blank('/v/AUTH_cfa/c/o',
 | 
			
		||||
                                headers={'x-auth-token': 'AUTH_t'})
 | 
			
		||||
            req.environ['swift.cache'] = FakeMemcache()
 | 
			
		||||
            result = ''.join(self.test_auth(req.environ, start_response))
 | 
			
		||||
            self.assert_(result.startswith('204'), result)
 | 
			
		||||
            self.assertEquals(req.remote_user, 'act:usr,act,AUTH_cfa')
 | 
			
		||||
        finally:
 | 
			
		||||
            auth.http_connect = old_http_connect
 | 
			
		||||
 | 
			
		||||
    def test_middleware_no_header(self):
 | 
			
		||||
        old_http_connect = auth.http_connect
 | 
			
		||||
        try:
 | 
			
		||||
            auth.http_connect = mock_http_connect(204,
 | 
			
		||||
               {'x-auth-ttl': '1234', 'x-auth-groups': 'act:usr,act,AUTH_cfa'})
 | 
			
		||||
            req = Request.blank('/v/AUTH_cfa/c/o')
 | 
			
		||||
            req.environ['swift.cache'] = FakeMemcache()
 | 
			
		||||
            result = ''.join(self.test_auth(req.environ, start_response))
 | 
			
		||||
            self.assert_(result.startswith('401'), result)
 | 
			
		||||
            self.assert_(not req.remote_user, req.remote_user)
 | 
			
		||||
        finally:
 | 
			
		||||
            auth.http_connect = old_http_connect
 | 
			
		||||
 | 
			
		||||
    def test_middleware_storage_token(self):
 | 
			
		||||
        old_http_connect = auth.http_connect
 | 
			
		||||
        try:
 | 
			
		||||
            auth.http_connect = mock_http_connect(204,
 | 
			
		||||
               {'x-auth-ttl': '1234', 'x-auth-groups': 'act:usr,act,AUTH_cfa'})
 | 
			
		||||
            req = Request.blank('/v/AUTH_cfa/c/o',
 | 
			
		||||
                                headers={'x-storage-token': 'AUTH_t'})
 | 
			
		||||
            req.environ['swift.cache'] = FakeMemcache()
 | 
			
		||||
            result = ''.join(self.test_auth(req.environ, start_response))
 | 
			
		||||
            self.assert_(result.startswith('204'), result)
 | 
			
		||||
            self.assertEquals(req.remote_user, 'act:usr,act,AUTH_cfa')
 | 
			
		||||
        finally:
 | 
			
		||||
            auth.http_connect = old_http_connect
 | 
			
		||||
 | 
			
		||||
    def test_authorize_bad_path(self):
 | 
			
		||||
        req = Request.blank('/badpath')
 | 
			
		||||
        resp = self.test_auth.authorize(req)
 | 
			
		||||
        self.assertEquals(resp and resp.status_int, 401)
 | 
			
		||||
        req = Request.blank('/badpath')
 | 
			
		||||
        req.remote_user = 'act:usr,act,AUTH_cfa'
 | 
			
		||||
        resp = self.test_auth.authorize(req)
 | 
			
		||||
        self.assertEquals(resp and resp.status_int, 403)
 | 
			
		||||
        req = Request.blank('')
 | 
			
		||||
        resp = self.test_auth.authorize(req)
 | 
			
		||||
        self.assertEquals(resp and resp.status_int, 404)
 | 
			
		||||
        req = Request.blank('')
 | 
			
		||||
        req.environ['swift.cache'] = FakeMemcache()
 | 
			
		||||
        result = ''.join(self.test_auth(req.environ, lambda x, y: None))
 | 
			
		||||
        self.assert_(result.startswith('404'), result)
 | 
			
		||||
 | 
			
		||||
    def test_authorize_account_access(self):
 | 
			
		||||
        req = Request.blank('/v1/AUTH_cfa')
 | 
			
		||||
        req.remote_user = 'act:usr,act,AUTH_cfa'
 | 
			
		||||
        self.assertEquals(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.assertEquals(resp and 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.assertEquals(resp and resp.status_int, 403)
 | 
			
		||||
        req = Request.blank('/v1/AUTH_cfa')
 | 
			
		||||
        req.remote_user = 'act:usr,act'
 | 
			
		||||
        req.acl = 'act'
 | 
			
		||||
        self.assertEquals(self.test_auth.authorize(req), None)
 | 
			
		||||
        req = Request.blank('/v1/AUTH_cfa')
 | 
			
		||||
        req.remote_user = 'act:usr,act'
 | 
			
		||||
        req.acl = 'act:usr'
 | 
			
		||||
        self.assertEquals(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.assertEquals(resp and 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.assertEquals(resp and 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.assertEquals(resp and resp.status_int, 403)
 | 
			
		||||
 | 
			
		||||
    def test_authorize_acl_referrer_access(self):
 | 
			
		||||
        req = Request.blank('/v1/AUTH_cfa')
 | 
			
		||||
        req.remote_user = 'act:usr,act'
 | 
			
		||||
        resp = self.test_auth.authorize(req)
 | 
			
		||||
        self.assertEquals(resp and resp.status_int, 403)
 | 
			
		||||
        req = Request.blank('/v1/AUTH_cfa')
 | 
			
		||||
        req.remote_user = 'act:usr,act'
 | 
			
		||||
        req.acl = '.r:*'
 | 
			
		||||
        self.assertEquals(self.test_auth.authorize(req), None)
 | 
			
		||||
        req = Request.blank('/v1/AUTH_cfa')
 | 
			
		||||
        req.remote_user = 'act:usr,act'
 | 
			
		||||
        req.acl = '.r:.example.com'
 | 
			
		||||
        resp = self.test_auth.authorize(req)
 | 
			
		||||
        self.assertEquals(resp and resp.status_int, 403)
 | 
			
		||||
        req = Request.blank('/v1/AUTH_cfa')
 | 
			
		||||
        req.remote_user = 'act:usr,act'
 | 
			
		||||
        req.referer = 'http://www.example.com/index.html'
 | 
			
		||||
        req.acl = '.r:.example.com'
 | 
			
		||||
        self.assertEquals(self.test_auth.authorize(req), None)
 | 
			
		||||
        req = Request.blank('/v1/AUTH_cfa')
 | 
			
		||||
        resp = self.test_auth.authorize(req)
 | 
			
		||||
        self.assertEquals(resp and resp.status_int, 401)
 | 
			
		||||
        req = Request.blank('/v1/AUTH_cfa')
 | 
			
		||||
        req.acl = '.r:*'
 | 
			
		||||
        self.assertEquals(self.test_auth.authorize(req), None)
 | 
			
		||||
        req = Request.blank('/v1/AUTH_cfa')
 | 
			
		||||
        req.acl = '.r:.example.com'
 | 
			
		||||
        resp = self.test_auth.authorize(req)
 | 
			
		||||
        self.assertEquals(resp and resp.status_int, 401)
 | 
			
		||||
        req = Request.blank('/v1/AUTH_cfa')
 | 
			
		||||
        req.referer = 'http://www.example.com/index.html'
 | 
			
		||||
        req.acl = '.r:.example.com'
 | 
			
		||||
        self.assertEquals(self.test_auth.authorize(req), None)
 | 
			
		||||
 | 
			
		||||
    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.assertEquals(resp and 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.assertEquals(resp and 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.assertEquals(resp and 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.assertEquals(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.assertEquals(resp and 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.assertEquals(resp and 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.assertEquals(resp and 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.assertEquals(resp and 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.assertEquals(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)
 | 
			
		||||
        self.assertEquals(resp and resp.status_int, 403)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
if __name__ == '__main__':
 | 
			
		||||
    unittest.main()
 | 
			
		||||
		Reference in New Issue
	
	Block a user