summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorYogeshwar Srikrishnan <yoga80@yahoo.com>2011-05-23 11:51:01 -0500
committerYogeshwar Srikrishnan <yoga80@yahoo.com>2011-05-23 11:51:01 -0500
commit3d855a4cec46ecc8d8b98090de95bab88864a643 (patch)
tree00f9698c29278df28856915701aa6c5197a8d7b5
parent7170c6219b2c635856218290e5fea779eb595642 (diff)
parent7211ce8ecb303c3da561f530df7aaad2ea1d68b4 (diff)
Merge remote branch 'khussein/master'
-rw-r--r--README.md142
-rwxr-xr-xbin/keystone233
-rwxr-xr-xbin/keystone-admin65
-rwxr-xr-xbin/keystone-auth31
-rwxr-xr-xbin/keystone-control225
-rwxr-xr-xbin/keystone-manage280
-rwxr-xr-xbin/sampledata.sh51
-rw-r--r--docs/nova-api-paste.ini114
-rw-r--r--echo/echo/echo.ini10
-rw-r--r--echo/echo/echo_basic.ini10
-rw-r--r--echo/echo/echo_remote.ini4
-rwxr-xr-x[-rw-r--r--]echo/echo_client.py2
-rw-r--r--etc/keystone.conf31
-rw-r--r--keystone/__init__.py32
-rw-r--r--keystone/auth_protocols/auth_token.ini2
-rw-r--r--keystone/auth_protocols/auth_token.py13
-rw-r--r--keystone/auth_protocols/nova_auth_token.py363
-rw-r--r--keystone/common/config.py98
-rwxr-xr-xkeystone/common/exception.py2
-rw-r--r--keystone/common/template.py20
-rwxr-xr-xkeystone/common/wsgi.py36
-rw-r--r--keystone/db/sqlalchemy/api.py181
-rw-r--r--keystone/db/sqlalchemy/models.py61
-rw-r--r--keystone/keystone.ini16
-rw-r--r--keystone/logic/service.py56
-rw-r--r--keystone/logic/types/auth.py23
-rw-r--r--keystone/logic/types/user.py25
-rw-r--r--[-rwxr-xr-x]keystone/server.py128
-rw-r--r--keystone/utils.py35
-rwxr-xr-xkeystone/version.py33
-rwxr-xr-x[-rw-r--r--]management/delgroup.py96
-rwxr-xr-x[-rw-r--r--]management/getgroup.py92
-rwxr-xr-x[-rw-r--r--]management/getgroups.py86
-rwxr-xr-x[-rw-r--r--]management/getgroupusers.py90
-rwxr-xr-x[-rw-r--r--]management/getuser.py86
-rwxr-xr-x[-rw-r--r--]management/getusergroups.py88
-rw-r--r--management/getusers.py38
-rwxr-xr-x[-rw-r--r--]management/groupadd.py92
-rwxr-xr-x[-rw-r--r--]management/setuserlock.py110
-rwxr-xr-x[-rw-r--r--]management/setuserpswd.py94
-rwxr-xr-x[-rw-r--r--]management/updategroup.py96
-rw-r--r--management/useradd.py42
-rwxr-xr-x[-rw-r--r--]management/userdel.py90
-rwxr-xr-x[-rw-r--r--]management/userupdate.py94
-rw-r--r--setup.py5
-rw-r--r--test/unit/test_authentication.py9
-rw-r--r--test/unit/test_common.py38
-rw-r--r--test/unit/test_exthandler.py16
-rw-r--r--tools/__init__.py0
-rw-r--r--tools/tracer.py108
50 files changed, 2381 insertions, 1311 deletions
diff --git a/README.md b/README.md
index 6257536f..aefd2e61 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,5 @@
-Keystone: Identity Service
-==========================
+Keystone: OpenStack Identity Service
+====================================
Keystone is a proposed independent authentication service for [OpenStack](http://www.openstack.org).
@@ -18,80 +18,47 @@ SERVICES:
Also included:
+* Keystone - Service and Admin API are available separately. Admin API allows management of tenants, roles, and users as well.
* Auth_Basic - Stub for WSGI middleware that will be used to handle basic auth
* Auth_OpenID - Stub for WSGI middleware that will be used to handle openid auth protocol
* RemoteAuth - WSGI middleware that can be used in services (like Swift, Nova, and Glance) when Auth middleware is running remotely
-ENVIRONMENT & DEPENDENCIES:
----------------------------
-See pip-requires for dependency list
-
-Setup:
-Install http://pypi.python.org/pypi/setuptools
- sudo easy_install pip
- sudo pip install -r pip-requires
-
-Configuration:
-Keystone gets its configuration from command-line parameters or a .conf file. The file can be provided explicitely
-on the command line otherwise the following logic applies (the conf file in use will be output to help
-in troubleshooting:
-
-1. config.py takes the config file from <topdir>/etc/keystone.conf
-2. If the keystone package is also intalled on the system,
- /etc/keystone.conf or /etc/keystone/keystone.conf have higher priority than <top_dir>/etc/keystone.conf.
-
-If you are also doing development on a system that has keystone.conf installed in /etc you may need to disambiguate it by providing the conf file in the command-line
-
- $ bin/keystone-control --confg-file etc/keystone.conf --pid-file <pidfile> auth <start|stop|restart>
-
-Path:
-keystone-control calls keystone-auth and it needs to be in the PATH
-
- $ export PATH=<top_dir>/bin:$PATH
-
-
RUNNING KEYSTONE:
-----------------
- $ cd bin
- $ ./keystone-auth
+Starting both Admin and Service API endpoints:
+ $ cd bin
+ $ ./keystone
-RUNNING KEYSTONE FOR DEVELOPMENT (HACKING):
-------------------------------
-
-During development, you can simply run as user (root not needed)
-
-From the top Keystone directory (<topdir>)
+Starting the auth server only (exposes the Service API):
- $ bin/keystone=auth
+ $ cd bin
+ $ ./keystone-auth
-It dumps stdout and stderr onto the terminal.
+Starting the admin server only (exposes the Admin API):
-If you want to specify additional parameters (optional):
+ $ cd bin
+ $ ./keystone-admin
- $ bin/keystone-control --pid-file <pidfile> --config-file etc/keystone.conf auth <start|stop|restart>
+All above files take parameters from etc/keystone.conf file under the Keystone root folder by default
-RUNNING KEYSTONE AS ROOT IN PRODUCTION
---------------------------------------
-In production, stdout and stderr need to be closed and all the output needs to be redirected to a log file.
-Once the package is installed through setup tools, RPM, deb, or ebuild keystone-control is installed as /usr/sbin/keystone-control. Typically, it will be started a script in /etc/init.d/keystoned
-keystone-control can invoke keystone-auth and start the keystone daemon with
- $ /usr/sbin/keystone-control auth start
+DEPENDENCIES:
+-------------
+See pip-requires for dependency list. The list of dependencies should not add to what already is needed to run other OpenStack services.
-It writes the process id of the daemon into /var/run/keystone/keystine-auth.pid.
-The daemon can be stopped with
-
- $ /usr/sbin/keystone-control auth stop
+Setup:
-keystone-control has the infrastructure to start and stop multiple servers keystone-xxx
+ # Install http://pypi.python.org/pypi/setuptools
+ sudo easy_install pip
+ sudo pip install -r pip-requires
-RUNNING TEST SERVICE:
----------------------
+RUNNING THE TEST SERVICE (Echo.py):
+----------------------------------
Standalone stack (with Auth_Token)
$ cd echo/bin
@@ -103,39 +70,43 @@ RUNNING TEST SERVICE:
in separate session
$ cd keystone/auth_protocols
- $ python auth_token.py --remote
+ $ python auth_token.py
DEMO CLIENT:
----------------------
+------------
+A sample client that gets a token from Keystone and then uses it to call Echo (and a few other example calls):
+
$ cd echo/echo
$ python echo_client.py
- Note: this requires tests data. See section TESTING for initializing data
+ Note: this requires test data. See section TESTING for initializing data
-TESTING
--------
-After starting keystone a keystone.db sqlite database should be created in the keystone folder.
+TESTING:
+--------
+A set of sample data can be added by running a shell script:
-Add test data to the database:
+ $ ./bin/sampledata.sh
- $ sqlite3 bin/keystone.db < test/test_setup.sql
+The script calls keystone-manage to create the sample data.
-To clean the test database
+After starting keystone or running keystone-manage a keystone.db sqlite database should be created in the keystone folder.
- $ sqlite3 bin/keystone.db < test/kill.sql
To run client demo (with all auth middleware running locally on sample service):
$ ./echo/bin/echod
$ python echo/echo/echo_client.py
+NOTE: NOT ALL TESTS CONVERTED TO NEW MODEL YET. MANY FAIL. THIS WILL BE ADDRESSED SOON.
+
To run unit tests:
+
* go to unit test/unit directory
* run tests: python test_keystone
-There are 8 groups of tests. They can be run individually or as an entire colection. To run the entire test suite run
+There are 10 groups of tests. They can be run individually or as an entire colection. To run the entire test suite run
$ python test_keystone.py
@@ -150,12 +121,49 @@ For more on unit testing please refer
To perform contract validation and load testing, use SoapUI (for now).
+
Using SOAPUI:
-Download [SOAPUI](http://sourceforge.net/projects/soapui/files/):
+First, download [SOAPUI](http://sourceforge.net/projects/soapui/files/):
To Test Keystone Service:
* File->Import Project
* Select tests/IdentitySOAPUI.xml
* Double click on "Keystone Tests" and press the green play (>) button
+
+
+ADDITIONAL INFORMATION:
+-----------------------
+
+Configuration:
+Keystone gets its configuration from command-line parameters or a .conf file. The file can be provided explicitely
+on the command line otherwise the following logic applies (the conf file in use will be output to help
+in troubleshooting:
+
+1. config.py takes the config file from <topdir>/etc/keystone.conf
+2. If the keystone package is also intalled on the system,
+ /etc/keystone.conf or /etc/keystone/keystone.conf have higher priority than <top_dir>/etc/keystone.conf.
+
+CURL commands:
+
+curl -d '{"passwordCredentials": {"username": "joeuser", "password": "secrete"}}' -H "Content-type: application/json" http://localhost:8081/v2.0/token
+curl -d '{"passwordCredentials": {"username": "joeuser", "password": "secrete", "tenant": "1234"}}' -H "Content-type: application/json" http://localhost:8081/v2.0/token
+
+NOVA Integration:
+-----------------
+
+Initial support for using keystone as nova's identity component has been started.
+
+ # clone projects
+ bzr clone lp:nova
+ git clone git://github.com/khussein/keystone.git
+
+ # copy keystone librarys into nova
+ cp keystone/keystone/common/bufferedhttp.py nova/nova/auth/
+ cp keystone/keystone/auth_protocols/nova_auth_token.py nova/nova/auth/
+
+ # copy paste config to use nova_auth_token.py
+ cp keystone/docs/nova-api-paste.ini nova/etc/nova/api-paste.ini
+
+Assuming you added the test_sql, you can then use joeuser/secrete
diff --git a/bin/keystone b/bin/keystone
index 45693a3f..1afebe8b 100755
--- a/bin/keystone
+++ b/bin/keystone
@@ -1,196 +1,83 @@
#!/usr/bin/env python
# vim: tabstop=4 shiftwidth=4 softtabstop=4
-# Copyright (c) 2011 OpenStack, LLC.
+# Copyright 2010 United States Government as represented by the
+# Administrator of the National Aeronautics and Space Administration.
+# Copyright 2011 OpenStack LLC.
+# All Rights Reserved.
#
-# 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
+# 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
+# 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.
+# 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.
"""
-Helper script for starting/stopping/reloading OpenStack server programs.
-Code copied from swift. Should eventually come from common.
+Keystone Identity Server - Admin and Service API
"""
-from __future__ import with_statement
-
-import errno
-import os
import optparse
-import resource
-import signal
+import os
import sys
-import time
+import tools.tracer #load this first
+# If ../../keystone/__init__.py exists, add ../ to Python search path, so that
+# it will override what happens to be installed in /usr/(local/)lib/python...
possible_topdir = os.path.normpath(os.path.join(os.path.abspath(sys.argv[0]),
os.pardir,
- os.pardir,
os.pardir))
if os.path.exists(os.path.join(possible_topdir, 'keystone', '__init__.py')):
sys.path.insert(0, possible_topdir)
-from keystone import version
+import keystone
from keystone.common import config
-ALL_COMMANDS = ['start', 'stop', 'restart']
-
-MAX_DESCRIPTORS = 32768
-MAX_MEMORY = (1024 * 1024 * 1024) * 1 # 2 GB
-USAGE = """%prog [options] keystone <COMMAND> [CONFPATH]
-
-And command is one of:
-
- start, stop, restart
-
-And CONFPATH is the optional configuration file to use."""
-
-
-def pid_files(server, options):
- pid_files = []
- if options['pid_file']:
- if os.path.exists(os.path.abspath(options['pid_file'])):
- pid_files = [os.path.abspath(options['pid_file'])]
- else:
- if os.path.exists('/var/run/keystone/%s.pid' % server):
- pid_files = ['/var/run/keystone/%s.pid' % server]
- for pid_file in pid_files:
- pid = int(open(pid_file).read().strip())
- yield pid_file, pid
-
-
-def do_start(server, options, args):
- for pid_file, pid in pid_files(server, options):
- if os.path.exists('/proc/%s' % pid):
- print "%s appears to already be running: %s" % (server, pid_file)
- return
- else:
- print "Removing stale pid file %s" % pid_file
- os.unlink(pid_file)
-
- try:
- resource.setrlimit(resource.RLIMIT_NOFILE,
- (MAX_DESCRIPTORS, MAX_DESCRIPTORS))
- resource.setrlimit(resource.RLIMIT_DATA,
- (MAX_MEMORY, MAX_MEMORY))
- except ValueError:
- print "Unable to increase file descriptor limit. Running as non-root?"
- os.environ['PYTHON_EGG_CACHE'] = '/tmp'
-
- def write_pid_file(pid_file, pid):
- dir, file = os.path.split(pid_file)
- if not os.path.exists(dir):
- try:
- os.makedirs(dir)
- except OSError, err:
- if err.errno == errno.EACCES:
- sys.exit('Unable to create %s. Running as non-root?'
- % dir)
- fp = open(pid_file, 'w')
- fp.write('%d\n' % pid)
- fp.close()
-
- def launch(ini_file, pid_file):
- args = [server, ini_file]
- print 'Starting %s with %s' % (server, ini_file)
-
- pid = os.fork()
- if pid == 0:
- os.setsid()
- with open(os.devnull, 'r+b') as nullfile:
- for desc in (0, 1, 2): # close stdio
- try:
- os.dup2(nullfile.fileno(), desc)
- except OSError:
- pass
- try:
- os.execlp('%s' % server, server, ini_file)
- except OSError, e:
- sys.exit('unable to launch %s. Got error: %s'
- % (server, str(e)))
- sys.exit(0)
- else:
- write_pid_file(pid_file, pid)
-
- if not options['pid_file']:
- pid_file = '/var/run/keystone/%s.pid' % server
- else:
- pid_file = os.path.abspath(options['pid_file'])
- conf_file = config.find_config_file(options, args)
- if not conf_file:
- sys.exit("Could not find any configuration file to use!")
- launch_args = [(conf_file, pid_file)]
-
- # start all servers
- for conf_file, pid_file in launch_args:
- launch(conf_file, pid_file)
-
-
-def do_stop(server, options, args, graceful=False):
- if graceful and server in GRACEFUL_SHUTDOWN_SERVERS:
- sig = signal.SIGHUP
- else:
- sig = signal.SIGTERM
-
- did_anything = False
- pfiles = pid_files(server, options)
- for pid_file, pid in pfiles:
- did_anything = True
- try:
- print 'Stopping %s pid: %s signal: %s' % (server, pid, sig)
- os.kill(pid, sig)
- except OSError:
- print "Process %d not running" % pid
- try:
- os.unlink(pid_file)
- except OSError:
- pass
- for pid_file, pid in pfiles:
- for _junk in xrange(150): # 15 seconds
- if not os.path.exists('/proc/%s' % pid):
- break
- time.sleep(0.1)
- else:
- print 'Waited 15 seconds for pid %s (%s) to die; giving up' % \
- (pid, pid_file)
- if not did_anything:
- print 'No %s running' % server
+from keystone.common import wsgi
if __name__ == '__main__':
- oparser = optparse.OptionParser(usage=USAGE, version='%%prog %s'
- % version.version_string())
- oparser.add_option('--pid-file', default=None, metavar="PATH",
- help="File to use as pid file. Default: "
- "/var/run/keysonte/keystone.pid")
- config.add_common_options(oparser)
- (options, args) = config.parse_options(oparser)
-
- if len(args) < 1:
- oparser.print_usage()
- sys.exit(1)
-
- server = 'keystone'
- command = args.pop(0).lower()
- if command not in ALL_COMMANDS:
- command_list = ", ".join(ALL_COMMANDS)
- msg = ("Unknown command %(command)s specified. Please specify a "
- "command in this list: %(command_list)s" % locals())
- sys.exit(msg)
-
- if command == 'start':
- do_start(server, options, args)
-
- if command == 'stop':
- do_stop(server, options, args)
-
- if command == 'restart':
- do_stop(server, options, args)
- do_start(server, options, args)
+ # Initialize a parser for our configuration paramaters
+ parser = optparse.OptionParser(version='%%prog %s' % keystone.version)
+ common_group = config.add_common_options(parser)
+ config.add_log_options(parser)
+
+ # Handle a special argument to support starting two endpoints
+ common_group.add_option('-a', '--admin-port', default=8081,
+ dest="admin_port", metavar="PORT",
+ help = "specifies port for Admin API to listen"
+ "on (default is 8080)")
+
+ # Parse arguments and load config
+ (options, args) = config.parse_options(parser)
+ config_file = config.find_config_file(options, args)
+ print "Using config file:", config_file
+
+ # Start services
+ try:
+ # Load Service API server
+ conf, app = config.load_paste_app('server', options, args)
+ server = wsgi.Server()
+ server.start(app, int(conf['bind_port']), conf['bind_host'])
+ print "Service API listening on %s:%s" % (conf['bind_host'],
+ conf['bind_port'])
+
+ # Load Admin API server
+ admin_conf, admin_app = config.load_paste_app('admin', options, args)
+ admin_server = wsgi.Server()
+ admin_bind = options.get('admin_port') or admin_conf.get('bind_port')
+ if conf['bind_port'] == admin_bind:
+ admin_bind += 1
+ admin_server.start(admin_app, int(admin_bind),
+ admin_conf['bind_host'])
+ print "Admin API listening on %s:%s" % (admin_conf['bind_host'],
+ admin_bind)
+
+ # Wait until done
+ server.wait()
+ except RuntimeError, e:
+ sys.exit("ERROR: %s" % e)
diff --git a/bin/keystone-admin b/bin/keystone-admin
new file mode 100755
index 00000000..11442673
--- /dev/null
+++ b/bin/keystone-admin
@@ -0,0 +1,65 @@
+#!/usr/bin/env python
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2010 United States Government as represented by the
+# Administrator of the National Aeronautics and Space Administration.
+# Copyright 2011 OpenStack LLC.
+# All Rights Reserved.
+#
+# 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.
+
+"""
+Keystone Identity Server - Admin API
+"""
+
+import optparse
+import os
+import sys
+import tools.tracer #load this first
+
+# If ../../keystone/__init__.py exists, add ../ to Python search path, so that
+# it will override what happens to be installed in /usr/(local/)lib/python...
+possible_topdir = os.path.normpath(os.path.join(os.path.abspath(sys.argv[0]),
+ os.pardir,
+ os.pardir))
+if os.path.exists(os.path.join(possible_topdir, 'keystone', '__init__.py')):
+ sys.path.insert(0, possible_topdir)
+
+import keystone
+from keystone.common import config
+from keystone.common import wsgi
+
+
+if __name__ == '__main__':
+ # Initialize a parser for our configuration paramaters
+ parser = optparse.OptionParser(version='%%prog %s' % keystone.version)
+ common_group = config.add_common_options(parser)
+ config.add_log_options(parser)
+
+ # Parse arguments and load config
+ (options, args) = config.parse_options(parser)
+ config_file = config.find_config_file(options, args)
+ print "Using config file:", config_file
+
+ # Start services
+ try:
+ # Load Admin API server
+ conf, app = config.load_paste_app('admin', options, args)
+ server = wsgi.Server()
+ server.start(app, int(conf['bind_port']), conf['bind_host'])
+ print "Admin API listening on %s:%s" % (conf['bind_host'],
+ conf['bind_port'])
+
+ server.wait()
+ except RuntimeError, e:
+ sys.exit("ERROR: %s" % e)
diff --git a/bin/keystone-auth b/bin/keystone-auth
index 8dc71aaa..3e59d3e8 100755
--- a/bin/keystone-auth
+++ b/bin/keystone-auth
@@ -19,12 +19,13 @@
# under the License.
"""
-Keystone API Server
+Keystone Identity Server - Service API
"""
import optparse
import os
import sys
+import tools.tracer #load this first
# If ../../keystone/__init__.py exists, add ../ to Python search path, so that
# it will override what happens to be installed in /usr/(local/)lib/python...
@@ -34,33 +35,29 @@ possible_topdir = os.path.normpath(os.path.join(os.path.abspath(sys.argv[0]),
if os.path.exists(os.path.join(possible_topdir, 'keystone', '__init__.py')):
sys.path.insert(0, possible_topdir)
-from keystone import version
+import keystone
from keystone.common import config
from keystone.common import wsgi
-def create_options(parser):
- """
- Sets up the CLI and config-file options that may be
- parsed and program commands.
-
- :param parser: The option parser
- """
- config.add_common_options(parser)
+if __name__ == '__main__':
+ # Initialize a parser for our configuration paramaters
+ parser = optparse.OptionParser(version='%%prog %s' % keystone.version)
+ common_group = config.add_common_options(parser)
config.add_log_options(parser)
+ # Parse arguments and load config
+ (options, args) = config.parse_options(parser)
+ config_file = config.find_config_file(options, args)
+ print "Using config file:", config_file
-if __name__ == '__main__':
- oparser = optparse.OptionParser(version='%%prog %s'
- % version.version_string())
- create_options(oparser)
- (options, args) = config.parse_options(oparser)
-
+ # Start services
try:
+ # Load Service API server
conf, app = config.load_paste_app('server', options, args)
server = wsgi.Server()
server.start(app, int(conf['bind_port']), conf['bind_host'])
- print "Server listening on %s:%s" % (conf['bind_host'],
+ print "Service API listening on %s:%s" % (conf['bind_host'],
conf['bind_port'])
server.wait()
except RuntimeError, e:
diff --git a/bin/keystone-control b/bin/keystone-control
deleted file mode 100755
index 3061f0c8..00000000
--- a/bin/keystone-control
+++ /dev/null
@@ -1,225 +0,0 @@
-#!/usr/bin/env python
-# vim: tabstop=4 shiftwidth=4 softtabstop=4
-
-# Copyright (c) 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.
-
-"""
-Helper script for starting/stopping/reloading Keystone server programs.
-Thanks for some of the code, Swifties ;)
-"""
-
-from __future__ import with_statement
-
-import errno
-import os
-import optparse
-import resource
-import signal
-import sys
-import time
-
-# If ../keystone/__init__.py exists, add ../ to Python search path, so that
-# it will override what happens to be installed in /usr/(local/)lib/python...
-possible_topdir = os.path.normpath(os.path.join(os.path.abspath(sys.argv[0]),
- os.pardir,
- os.pardir))
-if os.path.exists(os.path.join(possible_topdir, 'keystone', '__init__.py')):
- sys.path.insert(0, possible_topdir)
-
-from keystone import version
-from keystone.common import config
-
-ALL_COMMANDS = ['start', 'stop', 'shutdown', 'restart',
- 'reload', 'force-reload']
-ALL_SERVERS = ['keystone-auth']
-GRACEFUL_SHUTDOWN_SERVERS = ['keystone-auth']
-MAX_DESCRIPTORS = 32768
-MAX_MEMORY = (1024 * 1024 * 1024) * 2 # 2 GB
-USAGE = """%prog [options] <SERVER> <COMMAND> [CONFPATH]
-
-Where <SERVER> is one of:
-
- all, auth
-
-And command is one of:
-
- start, stop, shutdown, restart, reload, force-reload
-
-And CONFPATH is the optional configuration file to use."""
-
-
-def pid_files(server, options):
- pid_files = []
- if options['pid_file']:
- if os.path.exists(os.path.abspath(options['pid_file'])):
- pid_files = [os.path.abspath(options['pid_file'])]
- else:
- if os.path.exists('/var/run/keystone/%s.pid' % server):
- pid_files = ['/var/run/keystone/%s.pid' % server]
- for pid_file in pid_files:
- pid = int(open(pid_file).read().strip())
- yield pid_file, pid
-
-
-def do_start(server, options, args):
- server_type = '-'.join(server.split('-')[:-1])
-
- for pid_file, pid in pid_files(server, options):
- if os.path.exists('/proc/%s' % pid):
- print "%s appears to already be running: %s" % (server, pid_file)
- return
- else:
- print "Removing stale pid file %s" % pid_file
- os.unlink(pid_file)
-
- os.environ['PYTHON_EGG_CACHE'] = '/tmp'
-
- def write_pid_file(pid_file, pid):
- dir, file = os.path.split(pid_file)
- if not os.path.exists(dir):
- try:
- os.makedirs(dir)
- except OSError, err:
- if err.errno == errno.EACCES:
- sys.exit('Unable to create %s. Running as non-root?'
- % dir)
- fp = open(pid_file, 'w')
- fp.write('%d\n' % pid)
- fp.close()
-
- def launch(ini_file, pid_file):
- args = [server, ini_file]
- print 'Starting %s with %s' % (server, ini_file)
-
- pid = os.fork()
- if pid == 0:
- os.setsid()
- with open(os.devnull, 'r+b') as nullfile:
- for desc in (0, 1, 2): # close stdio
- try:
- os.dup2(nullfile.fileno(), desc)
- except OSError:
- pass
- try:
- os.execlp('%s' % server, server, ini_file)
- except OSError, e:
- sys.exit('unable to launch %s. Got error: %s'
- % (server, str(e)))
- sys.exit(0)
- else:
- write_pid_file(pid_file, pid)
-
- if not options['pid_file']:
- pid_file = '/var/run/keystone/%s.pid' % server
- else:
- pid_file = os.path.abspath(options['pid_file'])
- conf_file = config.find_config_file(options, args)
- if not conf_file:
- sys.exit("Could not find any configuration file to use!")
- launch_args = [(conf_file, pid_file)]
-
- # start all servers
- for conf_file, pid_file in launch_args:
- launch(conf_file, pid_file)
-
-
-def do_stop(server, options, args, graceful=False):
- if graceful and server in GRACEFUL_SHUTDOWN_SERVERS:
- sig = signal.SIGHUP
- else:
- sig = signal.SIGTERM
-
- did_anything = False
- pfiles = pid_files(server, options)
- for pid_file, pid in pfiles:
- did_anything = True
- try:
- print 'Stopping %s pid: %s signal: %s' % (server, pid, sig)
- os.kill(pid, sig)
- except OSError:
- print "Process %d not running" % pid
- try:
- os.unlink(pid_file)
- except OSError:
- pass
- for pid_file, pid in pfiles:
- for _junk in xrange(150): # 15 seconds
- if not os.path.exists('/proc/%s' % pid):
- break
- time.sleep(0.1)
- else:
- print 'Waited 15 seconds for pid %s (%s) to die; giving up' % \
- (pid, pid_file)
- if not did_anything:
- print 'No %s running' % server
-
-
-if __name__ == '__main__':
- oparser = optparse.OptionParser(usage=USAGE, version='%%prog %s'
- % version.version_string())
- oparser.add_option('--pid-file', default=None, metavar="PATH",
- help="File to use as pid file. Default: "
- "/var/run/keystone/$server.pid")
- config.add_common_options(oparser)
- (options, args) = config.parse_options(oparser)
-
- if len(args) < 2:
- oparser.print_usage()
- sys.exit(1)
-
- server = args.pop(0).lower()
- if server == 'all':
- servers = ALL_SERVERS
- else:
- if not server.startswith('keystone-'):
- server = 'keystone-%s' % server
- if server not in ALL_SERVERS:
- server_list = ", ".join([s.replace('keystone-', '')
- for s in ALL_SERVERS])
- msg = ("Unknown server '%(server)s' specified. Please specify "
- "all, or one of the servers: %(server_list)s" % locals())
- sys.exit(msg)
- servers = [server]
-
- command = args.pop(0).lower()
- if command not in ALL_COMMANDS:
- command_list = ", ".join(ALL_COMMANDS)
- msg = ("Unknown command %(command)s specified. Please specify a "
- "command in this list: %(command_list)s" % locals())
- sys.exit(msg)
-
- if command == 'start':
- for server in servers:
- do_start(server, options, args)
-
- if command == 'stop':
- for server in servers:
- do_stop(server, options, args)
-
- if command == 'shutdown':
- for server in servers:
- do_stop(server, options, args, graceful=True)
-
- if command == 'restart':
- for server in servers:
- do_stop(server, options, args)
- for server in servers:
- do_start(server, options, args)
-
- if command == 'reload' or command == 'force-reload':
- for server in servers:
- do_stop(server, options, args, graceful=True)
- do_start(server, options, args)
diff --git a/bin/keystone-manage b/bin/keystone-manage
new file mode 100755
index 00000000..2d5aecea
--- /dev/null
+++ b/bin/keystone-manage
@@ -0,0 +1,280 @@
+#!/usr/bin/env python
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2010 United States Government as represented by the
+# Administrator of the National Aeronautics and Space Administration.
+# Copyright 2011 OpenStack LLC.
+# All Rights Reserved.
+#
+# 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.
+
+"""
+Keystone Identity Server - CLI Management Interface
+"""
+
+import datetime
+import optparse
+import os
+import sys
+import tools.tracer
+
+# If ../../keystone/__init__.py exists, add ../ to Python search path, so that
+# it will override what happens to be installed in /usr/(local/)lib/python...
+possible_topdir = os.path.normpath(os.path.join(os.path.abspath(sys.argv[0]),
+ os.pardir,
+ os.pardir))
+if os.path.exists(os.path.join(possible_topdir, 'keystone', '__init__.py')):
+ sys.path.insert(0, possible_topdir)
+
+import keystone
+from keystone.common import config
+import keystone.db.sqlalchemy.api as db_api
+import keystone.db.sqlalchemy.models as db_models
+
+
+def Main():
+ """
+ Usage: keystone-manage [options] type command [id [attributes]]
+ type : role, tenant, user, token (more to come)
+ command : add, list, disable (more to come)
+ id : name or id
+ attributes : depending on type...
+ users : password, tenant
+ tokens : user, tenant, expiration
+
+ options
+ -c | --config-file : config file to use
+ -d | --debug : debug mode
+
+ Example: keystone-manage add user Admin P@ssw0rd
+ """
+ usage = "usage: %prog [options] type command [id [attributes]]"
+
+ # Initialize a parser for our configuration paramaters
+ parser = optparse.OptionParser(usage, version='%%prog %s'
+ % keystone.version())
+ common_group = config.add_common_options(parser)
+ config.add_log_options(parser)
+
+ # Parse command-line and load config
+ (options, args) = config.parse_options(parser)
+ config_file, conf = config.load_paste_config('admin', options, args)
+
+ # Check arguments
+ if len(args) == 0:
+ parser.error('No object type specified for first argument')
+
+ object_type = args[0]
+ if object_type in ['user', 'tenant', 'role', 'token']:
+ pass
+ else:
+ parser.error('%s is not a supported object type' % object_type)
+
+ if len(args) == 1:
+ parser.error('No command specified for second argument')
+ command = args[1]
+ if command in ['add', 'list', 'disable', 'delete', 'grant', 'revoke']:
+ pass
+ else:
+ parser.error('add, disable, delete, and list are the only supported"\
+ " commands (right now)')
+
+ if len(args) == 2:
+ if command != 'list':
+ parser.error('No id specified for third argument')
+ if len(args) > 2:
+ object_id = args[2]
+
+ # Set things up to run the command
+ print "Config file in use: %s" % config_file
+ config.setup_logging(options, conf)
+
+ db_api.configure_db(conf)
+
+ if object_type == "user":
+ if command == "add":
+ if len(args) < 4:
+ parser.error('No password specified for fourth argument')
+ password = args[3]
+
+ try:
+ object = db_models.User()
+ object.id = object_id
+ object.password = password
+ object.enabled = True
+ if len(args) > 4:
+ tenant = args[4]
+ object.tenant_id = tenant
+ db_api.user_create(object)
+ print "User created successfully. ID=%s" % object.id
+ except Exception as exc:
+ print "ERROR: Failed to create user: %s" % exc
+ return
+ elif command == "disable":
+ try:
+ object = db_api.user_get(object_id)
+ if object == None:
+ raise IndexError("User %s not found" % object_id)
+ object.enabled = False
+ db_api.user_update(object_id, object)
+ print "User disabled: %s" % object.id
+ except Exception as exc:
+ print "ERROR: Failed to disable user: %s" % exc
+ return
+ elif command == "list":
+ try:
+ if len(args) > 2:
+ tenant = args[2]
+ objects = db_api.user_get_by_tenant(tenant)
+ if objects == None:
+ raise IndexError("Users not found")
+ print 'id', 'enabled'
+ print '-' * 20
+ for row in objects:
+ print row.id, row.enabled
+ else:
+ objects = db_api.user_get_all()
+ if objects == None:
+ raise IndexError("Users not found")
+ print 'id', 'enabled', 'tenant'
+ print '-' * 20
+ for row in objects:
+ print row.id, row.enabled, row.tenant_id
+ except Exception, e:
+ print 'Error getting all users:', str(e)
+ return
+ elif object_type == "tenant":
+ if command == "add":
+ try:
+ object = db_models.Tenant()
+ object.id = object_id
+ object.enabled = True
+ db_api.tenant_create(object)
+ print "Tenant created successfully. ID=%s" % object.id
+ return
+ except Exception as exc:
+ print "ERROR: Failed to create tenant: %s" % exc
+ return
+ elif command == "list":
+ try:
+ objects = db_api.tenant_get_all()
+ if objects == None:
+ raise IndexError("Tenants not found")
+ print 'tenant', 'enabled'
+ print '-' * 20
+ for row in objects:
+ print row.id, row.enabled
+ except Exception, e:
+ print 'Error getting all users:', str(e)
+ return
+ elif command == "disable":
+ try:
+ object = db_api.tenant_get(object_id)
+ if object == None:
+ raise IndexError("Tenant %s not found" % object_id)
+ object.enabled = False
+ db_api.tenant_update(object_id, object)
+ print "Tenant disabled: %s" % object.id
+ except Exception as exc:
+ print "ERROR: Failed to disable tenant: %s" % exc
+ return
+ elif object_type == "role":
+ if command == "add":
+ try:
+ object = db_models.Role()
+ object.id = object_id
+ db_api.role_create(object)
+ print "Role created successfully. ID=%s" % object.id
+ return
+ except Exception as exc:
+ print "ERROR: Failed to create role: %s" % exc
+ return
+ elif command == "list":
+ try:
+ objects = db_api.role_get_all()
+ if objects == None:
+ raise IndexError("Roles not found")
+ print 'role'
+ print '-' * 20
+ for row in objects:
+ print row.id
+ except Exception, e:
+ print 'Error getting all roles:', str(e)
+ return
+ elif command == "grant":
+ if len(args) < 5:
+ parser.error("Missing arguments: role grant 'role' 'user'"\
+ "'tenant'")
+ user = args[3]
+ tenant = args[4]
+ try:
+ object = db_models.UserRoleAssociation()
+ object.role_id = object_id
+ object.user_id = user
+ object.tenant_id = tenant
+ db_api.user_role_add(object)
+ print "Successfully granted %s the %s role on %s" %\
+ (object.user_id, object.role_id, object.tenant_id)
+ except Exception as exc:
+ print "ERROR: Failed to grant role: %s" % exc
+ return
+ elif object_type == "token":
+ if command == "add":
+ if len(args) < 6:
+ parser.error('Creating a token requires a token id, user'\
+ ', tenant, and expiration')
+ try:
+ object = db_models.Token()
+ object.token_id = object_id
+ object.user_id = args[3]
+ object.tenant_id = args[4]
+ tuple_time = datetime.datetime.strptime(args[5]
+ .replace("-", ""),
+ "%Y%m%dT%H:%M")
+ object.expires = tuple_time
+ db_api.token_create(object)
+ print "Token created successfully. ID=%s" % object.token_id
+ return
+ except Exception as exc:
+ print "ERROR: Failed to create token: %s" % exc
+ return
+ elif command == "list":
+ try:
+ objects = db_api.token_get_all()
+ if objects == None:
+ raise IndexError("Tokens not found")
+ print 'token', 'user', 'expiration', 'tenant'
+ print '-' * 20
+ for row in objects:
+ print row.token_id, row.user_id, row.expires, row.tenant_id
+ except Exception, e:
+ print 'Error getting all tokens:', str(e)
+ return
+ elif command == "delete":
+ try:
+ object = db_api.token_get(object_id)
+ if object == None:
+ raise IndexError("Token %s not found" % object_id)
+ else:
+ db_api.token_delete(object_id)
+ print 'Token %s deleted' % object_id
+ except Exception, e:
+ print 'Error deleting token %s:' % object_id, str(e)
+ return
+
+ # Command not handled
+ print ("Fail: %s %s not yet supported" % (object_type, command))
+
+
+if __name__ == '__main__':
+ Main()
diff --git a/bin/sampledata.sh b/bin/sampledata.sh
new file mode 100755
index 00000000..c4102332
--- /dev/null
+++ b/bin/sampledata.sh
@@ -0,0 +1,51 @@
+#!/bin/bash
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2010 United States Government as represented by the
+# Administrator of the National Aeronautics and Space Administration.
+# Copyright 2011 OpenStack LLC.
+# All Rights Reserved.
+#
+# 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.
+
+# Tenants
+./keystone-manage tenant add 1234
+./keystone-manage tenant add 0000
+./keystone-manage tenant disable 0000
+
+# Users
+./keystone-manage user add joeuser secrete 1234
+./keystone-manage user add admin secrete 1234
+./keystone-manage user add disabled secrete 1234
+./keystone-manage user disable disabled
+
+# Roles
+./keystone-manage role add Admin
+./keystone-manage role add Admin admin 1234
+
+
+# Groups
+#./keystone-manage group add Admin 1234
+#./keystone-manage group add Default 1234
+#./keystone-manage group add Empty 0000
+
+# User Group Associations
+#./keystone-manage user joeuser join Default
+#./keystone-manage user disabled join Default
+#./keystone-manage user admin join Admin
+
+# Tokens
+./keystone-manage token add 887665443383838 joeuser 1234 2012-02-05T00:00
+./keystone-manage token add 999888777666 admin 1234 2015-02-05T00:00
+./keystone-manage token add 000999 admin 1234 2010-02-05T00:00
+./keystone-manage token add 999888777 disabled 1234 2015-02-05T00:00
diff --git a/docs/nova-api-paste.ini b/docs/nova-api-paste.ini
new file mode 100644
index 00000000..e1c26a24
--- /dev/null
+++ b/docs/nova-api-paste.ini
@@ -0,0 +1,114 @@
+#######
+# EC2 #
+#######
+
+[composite:ec2]
+use = egg:Paste#urlmap
+/: ec2versions
+/services/Cloud: ec2cloud
+/services/Admin: ec2admin
+/latest: ec2metadata
+/2007-01-19: ec2metadata
+/2007-03-01: ec2metadata
+/2007-08-29: ec2metadata
+/2007-10-10: ec2metadata
+/2007-12-15: ec2metadata
+/2008-02-01: ec2metadata
+/2008-09-01: ec2metadata
+/2009-04-04: ec2metadata
+/1.0: ec2metadata
+
+[pipeline:ec2cloud]
+pipeline = logrequest authenticate cloudrequest authorizer ec2executor
+#pipeline = logrequest ec2lockout authenticate cloudrequest authorizer ec2executor
+
+[pipeline:ec2admin]
+pipeline = logrequest authenticate adminrequest authorizer ec2executor
+
+[pipeline:ec2metadata]
+pipeline = logrequest ec2md
+
+[pipeline:ec2versions]
+pipeline = logrequest ec2ver
+
+[filter:logrequest]
+paste.filter_factory = nova.api.ec2:RequestLogging.factory
+
+[filter:ec2lockout]
+paste.filter_factory = nova.api.ec2:Lockout.factory
+
+[filter:authenticate]
+paste.filter_factory = nova.api.ec2:Authenticate.factory
+
+[filter:cloudrequest]
+controller = nova.api.ec2.cloud.CloudController
+paste.filter_factory = nova.api.ec2:Requestify.factory
+
+[filter:adminrequest]
+controller = nova.api.ec2.admin.AdminController
+paste.filter_factory = nova.api.ec2:Requestify.factory
+
+[filter:authorizer]
+paste.filter_factory = nova.api.ec2:Authorizer.factory
+
+[app:ec2executor]
+paste.app_factory = nova.api.ec2:Executor.factory
+
+[app:ec2ver]
+paste.app_factory = nova.api.ec2:Versions.factory
+
+[app:ec2md]
+paste.app_factory = nova.api.ec2.metadatarequesthandler:MetadataRequestHandler.factory
+
+#############
+# Openstack #
+#############
+
+[composite:osapi]
+use = egg:Paste#urlmap
+/: osversions
+/v1.0: openstackapi10
+/v1.1: openstackapi11
+
+[pipeline:openstackapi10]
+pipeline = faultwrap tokenauth auth_shim ratelimit osapiapp10
+
+[pipeline:openstackapi11]
+pipeline = faultwrap tokenauth auth_shim ratelimit extensions osapiapp11
+
+[filter:faultwrap]
+paste.filter_factory = nova.api.openstack:FaultWrapper.factory
+
+[filter:auth]
+paste.filter_factory = nova.api.openstack.auth:AuthMiddleware.factory
+
+[filter:tokenauth]
+paste.filter_factory = nova.auth.nova_auth_token:filter_factory
+service_protocol = http
+service_host = 127.0.0.1
+service_port = 808
+auth_host = 127.0.0.1
+auth_port = 8081
+auth_protocol = http
+admin_token = 999888777666
+
+[filter:auth_shim]
+paste.filter_factory = nova.auth.nova_auth_token:KeystoneAuthShim.factory
+
+[filter:ratelimit]
+paste.filter_factory = nova.api.openstack.limits:RateLimitingMiddleware.factory
+
+[filter:extensions]
+paste.filter_factory = nova.api.openstack.extensions:ExtensionMiddleware.factory
+
+[app:osapiapp10]
+paste.app_factory = nova.api.openstack:APIRouterV10.factory
+
+[app:osapiapp11]
+paste.app_factory = nova.api.openstack:APIRouterV11.factory
+
+[pipeline:osversions]
+pipeline = faultwrap osversionapp
+
+[app:osversionapp]
+paste.app_factory = nova.api.openstack.versions:Versions.factory
diff --git a/echo/echo/echo.ini b/echo/echo/echo.ini
index b81174d0..a7b95e9e 100644
--- a/echo/echo/echo.ini
+++ b/echo/echo/echo.ini
@@ -6,7 +6,7 @@ delay_auth_decision = 0
;where to find the OpenStack service (if not in local WSGI chain)
service_protocol = http
service_host = 127.0.0.1
-service_port = 8090
+service_port = 8081
;used to verify this component with the OpenStack service (or PAPIAuth)
service_pass = dTpw
@@ -20,17 +20,17 @@ pipeline =
echo
[filter:tokenauth]
-paste.filter_factory = keystone:tokenauth_factory
+paste.filter_factory = keystone.auth_protocols.auth_token:filter_factory
;where to find the token auth service
auth_host = 127.0.0.1
-auth_port = 8080
+auth_port = 8081
auth_protocol = http
;how to authenticate to the auth service for priviledged operations
;like validate token
admin_token = 999888777666
[filter:basicauth]
-paste.filter_factory = keystone:basicauth_factory
+paste.filter_factory = keystone.auth_protocols.auth_basic:filter_factory
[filter:openidauth]
-paste.filter_factory = keystone:openidauth_factory
+paste.filter_factory = keystone.auth_protocols.auth_openid:filter_factory
diff --git a/echo/echo/echo_basic.ini b/echo/echo/echo_basic.ini
index 38da0c66..65c85357 100644
--- a/echo/echo/echo_basic.ini
+++ b/echo/echo/echo_basic.ini
@@ -6,7 +6,7 @@ delay_auth_decision = 0
;where to find the OpenStack service (if not in local WSGI chain)
service_protocol = http
service_host = 127.0.0.1
-service_port = 8090
+service_port = 8081
;used to verify this component with the OpenStack service (or PAPIAuth)
service_pass = dTpw
@@ -20,17 +20,17 @@ pipeline =
echo
[filter:tokenauth]
-paste.filter_factory = keystone:tokenauth_factory
+paste.filter_factory = keystone.auth_protocols.auth_token:filter_factory
;where to find the token auth service
auth_host = 127.0.0.1
-auth_port = 8080
+auth_port = 8081
auth_protocol = http
;how to authenticate to the auth service for priviledged operations
;like validate token
admin_token = 999888777666
[filter:basicauth]
-paste.filter_factory = keystone:basicauth_factory
+paste.filter_factory = keystone.auth_protocols.auth_basic:filter_factory
[filter:openidauth]
-paste.filter_factory = keystone:openidauth_factory
+paste.filter_factory = keystone.auth_protocols.auth_openid:filter_factory
diff --git a/echo/echo/echo_remote.ini b/echo/echo/echo_remote.ini
index c27b2365..d225f756 100644
--- a/echo/echo/echo_remote.ini
+++ b/echo/echo/echo_remote.ini
@@ -9,11 +9,11 @@ pipeline =
echo
[filter:remoteauth]
-paste.filter_factory = keystone:remoteauth_factory
+paste.filter_factory = keystone.middleware.remoteauth:filter_factory
;password which will tell us call is coming from a trusted auth proxy
; (otherwise we redirect call)
remote_auth_pass = dTpw
;where to redirect untrusted calls to
-auth_location = http://127.0.0.1:8080/
+auth_location = http://127.0.0.1:8081/
diff --git a/echo/echo_client.py b/echo/echo_client.py
index a70d2a4a..ba835417 100644..100755
--- a/echo/echo_client.py
+++ b/echo/echo_client.py
@@ -1,3 +1,4 @@
+#!/usr/bin/env python
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright (c) 2010-2011 OpenStack, LLC.
#
@@ -31,6 +32,7 @@ def get_auth_token(username, password, tenant):
conn.request("POST", "/v2.0/token", json.dumps(params), headers=headers)
response = conn.getresponse()
data = response.read()
+ print data
ret = data
return ret
diff --git a/etc/keystone.conf b/etc/keystone.conf
index 117b3eca..e9c59046 100644
--- a/etc/keystone.conf
+++ b/etc/keystone.conf
@@ -1,24 +1,15 @@
[DEFAULT]
# Show more verbose log output (sets INFO log level output)
-verbose = True
+verbose = False
# Show debugging output in logs (sets DEBUG log level output)
-debug = True
-
-[app:server]
-paste.app_factory = keystone.server:app_factory
+debug = False
# Which backend store should Keystone use by default.
# Default: 'sqlite'
# Available choices are 'sqlite' [future will include LDAP, PAM, etc]
default_store = sqlite
-# Address to bind the API server
-bind_host = 0.0.0.0
-
-# Port the bind the API server to
-bind_port = 8080
-
# Log to this file. Make sure you do not set the same log
# file for both the API and registry servers!
#
@@ -35,3 +26,21 @@ sql_connection = sqlite:///../keystone/keystone.db
#
sql_idle_timeout = 30
+[app:admin]
+paste.app_factory = keystone.server:admin_app_factory
+
+# Address to bind the Admin API server
+bind_host = 0.0.0.0
+
+# Port the bind the Admin API server to
+bind_port = 8081
+
+
+[app:server]
+paste.app_factory = keystone.server:app_factory
+
+# Address to bind the API server
+bind_host = 0.0.0.0
+
+# Port the bind the API server to
+bind_port = 8080
diff --git a/keystone/__init__.py b/keystone/__init__.py
index aec9d44f..b70c0009 100644
--- a/keystone/__init__.py
+++ b/keystone/__init__.py
@@ -1,3 +1,34 @@
+# Copyright (C) 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.
+
+'''Main module for Keystone service. This installs the gettext function
+for all sub modules and packages.'''
+
+import gettext
+
+__version__ = '0.9'
+
+# This installs the _(...) function as a built-in so all other modules
+# don't need to.
+gettext.install('keystone')
+
+
+def version():
+ return __version__
+
+'''
+TODO(Ziad): Commenting out so we don't load this always - remove eventually
#TOKEN AUTH
from auth_protocols.auth_token \
import filter_factory as tokenauth_factory
@@ -13,3 +44,4 @@ from auth_protocols.auth_openid \
#Remote Auth handler
from middleware.remoteauth \
import filter_factory as remoteauth_factory
+''' \ No newline at end of file
diff --git a/keystone/auth_protocols/auth_token.ini b/keystone/auth_protocols/auth_token.ini
index 3154fc69..25f2cbbf 100644
--- a/keystone/auth_protocols/auth_token.ini
+++ b/keystone/auth_protocols/auth_token.ini
@@ -5,7 +5,7 @@ paste.app_factory = auth_token:app_factory
auth_protocol = http
auth_host = 127.0.0.1
-auth_port = 8080
+auth_port = 8081
admin_token = 999888777666
delay_auth_decision = 0
diff --git a/keystone/auth_protocols/auth_token.py b/keystone/auth_protocols/auth_token.py
index 8fe519ca..4de54a55 100644
--- a/keystone/auth_protocols/auth_token.py
+++ b/keystone/auth_protocols/auth_token.py
@@ -165,8 +165,8 @@ class AuthProtocol(object):
"Proxy %s" % claims['user'])
self._decorate_request('X_TENANT',
claims['tenant'])
- self._decorate_request('X_GROUP',
- claims['group'])
+ if 'group' in claims:
+ self._decorate_request('X_GROUP', claims['group'])
self.expanded = True
#Send request downstream
@@ -262,11 +262,12 @@ class AuthProtocol(object):
token_info = json.loads(data)
#TODO(Ziad): make this more robust
- first_group = token_info['auth']['user']['groups']['group'][0]
+ #first_group = token_info['auth']['user']['groups']['group'][0]
verified_claims = {'user': token_info['auth']['user']['username'],
- 'tenant': token_info['auth']['user']['tenantId'],
- 'group': '%s/%s' % (first_group['id'],
- first_group['tenantId'])}
+ 'tenant': token_info['auth']['user']['tenantId']}
+ # TODO(Ziad): removed groups for now
+ # ,'group': '%s/%s' % (first_group['id'],
+ # first_group['tenantId'])}
return verified_claims
def _decorate_request(self, index, value):
diff --git a/keystone/auth_protocols/nova_auth_token.py b/keystone/auth_protocols/nova_auth_token.py
new file mode 100644
index 00000000..8cc51206
--- /dev/null
+++ b/keystone/auth_protocols/nova_auth_token.py
@@ -0,0 +1,363 @@
+#!/usr/bin/env python
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+#
+# 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.
+# Not Yet PEP8 standardized
+
+
+# FIXME(ja): fix "copy & paste ware"!
+# determine how to integrate nova, dash, keystone
+
+"""
+TOKEN-BASED AUTH MIDDLEWARE
+
+This WSGI component performs multiple jobs:
+- it verifies that incoming client requests have valid tokens by verifying
+ tokens with the auth service.
+- it will reject unauthenticated requests UNLESS it is in 'delay_auth_decision'
+ mode, which means the final decision is delegated to the downstream WSGI
+ component (usually the OpenStack service)
+- it will collect and forward identity information from a valid token
+ such as user name, groups, etc...
+
+Refer to: http://wiki.openstack.org/openstack-authn
+
+
+HEADERS
+-------
+Headers starting with HTTP_ is a standard http header
+Headers starting with HTTP_X is an extended http header
+
+> Coming in from initial call from client or customer
+HTTP_X_AUTH_TOKEN : the client token being passed in
+HTTP_X_STORAGE_TOKEN: the client token being passed in (legacy Rackspace use)
+ to support cloud files
+> Used for communication between components
+www-authenticate : only used if this component is being used remotely
+HTTP_AUTHORIZATION : basic auth password used to validate the connection
+
+> What we add to the request for use by the OpenStack service
+HTTP_X_AUTHORIZATION: the client identity being passed in
+
+"""
+
+from bufferedhttp import http_connect_raw as http_connect
+import eventlet
+import httplib
+import json
+from nova import auth
+from nova import context
+from nova import flags
+from nova import utils
+from nova import wsgi
+import os
+from paste.deploy import loadapp
+import sys
+from urlparse import urlparse
+import webob.exc
+import webob.dec
+from webob.exc import HTTPUnauthorized, HTTPUseProxy
+from webob.exc import Request, Response
+
+FLAGS = flags.FLAGS
+
+PROTOCOL_NAME = "Token Authentication"
+
+
+class AuthProtocol(object):
+ """Auth Middleware that handles authenticating client calls"""
+
+ def _init_protocol_common(self, app, conf):
+ """ Common initialization code"""
+ print "Starting the %s component" % PROTOCOL_NAME
+
+ self.conf = conf
+ self.app = app
+ #if app is set, then we are in a WSGI pipeline and requests get passed
+ # on to app. If it is not set, this component should forward requests
+
+ # where to find the OpenStack service (if not in local WSGI chain)
+ # these settings are only used if this component is acting as a proxy
+ # and the OpenSTack service is running remotely
+ self.service_protocol = conf.get('service_protocol', 'https')
+ self.service_host = conf.get('service_host')
+ self.service_port = int(conf.get('service_port'))
+ self.service_url = '%s://%s:%s' % (self.service_protocol,
+ self.service_host,
+ self.service_port)
+ # used to verify this component with the OpenStack service or PAPIAuth
+ self.service_pass = conf.get('service_pass')
+
+ # delay_auth_decision means we still allow unauthenticated requests
+ # through and we let the downstream service make the final decision
+ self.delay_auth_decision = int(conf.get('delay_auth_decision', 0))
+
+ def _init_protocol(self, app, conf):
+ """ Protocol specific initialization """
+
+ # where to find the auth service (we use this to validate tokens)
+ self.auth_host = conf.get('auth_host')
+ self.auth_port = int(conf.get('auth_port'))
+ self.auth_protocol = conf.get('auth_protocol', 'https')
+ self.auth_location = "%s://%s:%s" % (self.auth_protocol,
+ self.auth_host,
+ self.auth_port)
+
+ # Credentials used to verify this component with the Auth service since
+ # validating tokens is a priviledged call
+ self.admin_token = conf.get('admin_token')
+
+ def __init__(self, app, conf):
+ """ Common initialization code """
+
+ #TODO(ziad): maybe we rafactor this into a superclass
+ self._init_protocol_common(app, conf) # Applies to all protocols
+ self._init_protocol(app, conf) # Specific to this protocol
+
+ def __call__(self, env, start_response):
+ """ Handle incoming request. Authenticate. And send downstream. """
+
+ self.start_response = start_response
+ self.env = env
+
+ #Prep headers to forward request to local or remote downstream service
+ self.proxy_headers = env.copy()
+ for header in self.proxy_headers.iterkeys():
+ if header[0:5] == 'HTTP_':
+ self.proxy_headers[header[5:]] = self.proxy_headers[header]
+ del self.proxy_headers[header]
+
+ #Look for authentication claims
+ self.claims = self._get_claims(env)
+ if not self.claims:
+ #No claim(s) provided
+ if self.delay_auth_decision:
+ #Configured to allow downstream service to make final decision.
+ #So mark status as Invalid and forward the request downstream
+ self._decorate_request("X_IDENTITY_STATUS", "Invalid")
+ else:
+ #Respond to client as appropriate for this auth protocol
+ return self._reject_request()
+ else:
+ # this request is presenting claims. Let's validate them
+ valid = self._validate_claims(self.claims)
+ if not valid:
+ # Keystone rejected claim
+ if self.delay_auth_decision:
+ # Downstream service will receive call still and decide
+ self._decorate_request("X_IDENTITY_STATUS", "Invalid")
+ else:
+ #Respond to client as appropriate for this auth protocol
+ return self._reject_claims()
+ else:
+ self._decorate_request("X_IDENTITY_STATUS", "Confirmed")
+
+ #Collect information about valid claims
+ if valid:
+ claims = self._expound_claims()
+ if claims:
+ # TODO(Ziad): add additional details we may need,
+ # like tenant and group info
+ self._decorate_request('X_AUTHORIZATION',
+ claims['user'])
+ self._decorate_request('X_TENANT',
+ claims['tenant'])
+ self._decorate_request('X_GROUP',
+ claims['group'])
+ self.expanded = True
+
+ #Send request downstream
+ return self._forward_request()
+
+ def get_admin_auth_token(self, username, password, tenant):
+ """
+ This function gets an admin auth token to be used by this service to
+ validate a user's token. Validate_token is a priviledged call so
+ it needs to be authenticated by a service that is calling it
+ """
+ headers = {"Content-type": "application/json", "Accept": "text/json"}
+ params = {"passwordCredentials": {"username": username,
+ "password": password,
+ "tenantId": "1"}}
+ conn = httplib.HTTPConnection("%s:%s" \
+ % (self.auth_host, self.auth_port))
+ conn.request("POST", "/v2.0/token", json.dumps(params), \
+ headers=headers)
+ response = conn.getresponse()
+ data = response.read()
+ return data
+
+ def _get_claims(self, env):
+ claims = env.get('HTTP_X_AUTH_TOKEN', env.get('HTTP_X_STORAGE_TOKEN'))
+ return claims
+
+ def _reject_request(self):
+ # Redirect client to auth server
+ return HTTPUseProxy(location=self.auth_location)(self.env,
+ self.start_response)
+
+ def _reject_claims(self):
+ # Client sent bad claims
+ return HTTPUnauthorized()(self.env,
+ self.start_response)
+
+ def _validate_claims(self, claims):
+ """Validate claims, and provide identity information isf applicable """
+
+ # Step 1: We need to auth with the keystone service, so get an
+ # admin token
+ #TODO(ziad): Need to properly implement this, where to store creds
+ # for now using token from ini
+ #auth = self.get_admin_auth_token("admin", "secrete", "1")
+ #admin_token = json.loads(auth)["auth"]["token"]["id"]
+
+ # Step 2: validate the user's token with the auth service
+ # since this is a priviledged op,m we need to auth ourselves
+ # by using an admin token
+ headers = {"Content-type": "application/json",
+ "Accept": "text/json",
+ "X-Auth-Token": self.admin_token}
+ ##TODO(ziad):we need to figure out how to auth to keystone
+ #since validate_token is a priviledged call
+ #Khaled's version uses creds to get a token
+ # "X-Auth-Token": admin_token}
+ # we're using a test token from the ini file for now
+ conn = http_connect(self.auth_host, self.auth_port, 'GET',
+ '/v2.0/token/%s' % claims, headers=headers)
+ resp = conn.getresponse()
+ data = resp.read()
+ conn.close()
+
+ if not str(resp.status).startswith('20'):
+ # Keystone rejected claim
+ return False
+ else:
+ #TODO(Ziad): there is an optimization we can do here. We have just
+ #received data from Keystone that we can use instead of making
+ #another call in _expound_claims
+ return True
+
+ def _expound_claims(self):
+ # Valid token. Get user data and put it in to the call
+ # so the downstream service can use it
+ headers = {"Content-type": "application/json",
+ "Accept": "text/json",
+ "X-Auth-Token": self.admin_token}
+ ##TODO(ziad):we need to figure out how to auth to keystone
+ #since validate_token is a priviledged call
+ #Khaled's version uses creds to get a token
+ # "X-Auth-Token": admin_token}
+ # we're using a test token from the ini file for now
+ conn = http_connect(self.auth_host, self.auth_port, 'GET',
+ '/v2.0/token/%s' % self.claims, headers=headers)
+ resp = conn.getresponse()
+ data = resp.read()
+ conn.close()
+
+ if not str(resp.status).startswith('20'):
+ raise LookupError('Unable to locate claims: %s' % resp.status)
+
+ token_info = json.loads(data)
+ #TODO(Ziad): make this more robust
+ first_group = token_info['auth']['user']['groups']['group'][0]
+ verified_claims = {'user': token_info['auth']['user']['username'],
+ 'tenant': token_info['auth']['user']['tenantId'],
+ 'group': '%s/%s' % (first_group['id'],
+ first_group['tenantId'])}
+ return verified_claims
+
+ def _decorate_request(self, index, value):
+ self.proxy_headers[index] = value
+ self.env["HTTP_%s" % index] = value
+
+ def _forward_request(self):
+ #Token/Auth processed & claims added to headers
+ self._decorate_request('AUTHORIZATION',
+ "Basic %s" % self.service_pass)
+ #now decide how to pass on the call
+ if self.app:
+ # Pass to downstream WSGI component
+ return self.app(self.env, self.start_response)
+ #.custom_start_response)
+ else:
+ # We are forwarding to a remote service (no downstream WSGI app)
+ req = Request(self.proxy_headers)
+ parsed = urlparse(req.url)
+ conn = http_connect(self.service_host,
+ self.service_port,
+ req.method,
+ parsed.path,
+ self.proxy_headers,
+ ssl=(self.service_protocol == 'https'))
+ resp = conn.getresponse()
+ data = resp.read()
+ #TODO(ziad): use a more sophisticated proxy
+ # we are rewriting the headers now
+ return Response(status=resp.status, body=data)(self.proxy_headers,
+ self.start_response)
+
+
+class KeystoneAuthShim(wsgi.Middleware):
+ """Lazy provisioning nova project/users from keystone tenant/user"""
+
+ def __init__(self, application, db_driver=None):
+ if not db_driver:
+ db_driver = FLAGS.db_driver
+ self.db = utils.import_object(db_driver)
+ self.auth = auth.manager.AuthManager()
+ super(KeystoneAuthShim, self).__init__(application)
+
+ @webob.dec.wsgify(RequestClass=wsgi.Request)
+ def __call__(self, req):
+ user_id = req.headers['X_AUTHORIZATION']
+ try:
+ user_ref = self.auth.get_user(user_id)
+ except:
+ user_ref = self.auth.create_user(user_id)
+ project_id = req.headers['X_TENANT']
+ try:
+ project_ref = self.auth.get_project(project_id)
+ except:
+ project_ref = self.auth.create_project(project_id, user_id)
+
+ if not self.auth.is_project_member(user_id, project_id):
+ self.auth.add_to_project(user_id, project_id)
+
+ # groups = req.headers['X_GROUP']
+
+ req.environ['nova.context'] = context.RequestContext(user_ref, project_ref)
+ return self.application
+
+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 AuthProtocol(app, conf)
+ return auth_filter
+
+
+def app_factory(global_conf, **local_conf):
+ conf = global_conf.copy()
+ conf.update(local_conf)
+ return AuthProtocol(None, conf)
+
+if __name__ == "__main__":
+ app = loadapp("config:" + \
+ os.path.join(os.path.abspath(os.path.dirname(__file__)),
+ "auth_token.ini"), global_conf={"log_name": "auth_token.log"})
+ wsgi.server(eventlet.listen(('', 8090)), app)
diff --git a/keystone/common/config.py b/keystone/common/config.py
index e202bf89..708213c9 100644
--- a/keystone/common/config.py
+++ b/keystone/common/config.py
@@ -75,14 +75,28 @@ def add_common_options(parser):
help="Print more verbose output")
group.add_option('-d', '--debug', default=False, dest="debug",
action="store_true",
- help="Print debugging output")
- group.add_option('--config-file', default=None, metavar="PATH",
- help="Path to the config file to use. When not specified "
- "(the default), we generally look at the first "
- "argument specified to be a config file, and if "
- "that is also missing, we search standard "
+ help="Print debugging output to console")
+ group.add_option('-c', '--config-file', default=None, metavar="PATH",
+ help="Path to the config file to use. When not specified "\
+ "(the default), we generally look at the first "\
+ "argument specified to be a config file, and if "\
+ "that is also missing, we search standard "\
"directories for a config file.")
+ group.add_option('-p', '--port', '--bind-port', default=8080,
+ dest="bind_port",
+ help="specifies port to listen on (default is 8080)")
+ group.add_option('--host', '--bind-host',
+ default="0.0.0.0", dest="bind_host",
+ help="specifies host address to listen on "\
+ "(default is all or 0.0.0.0)")
+ # This one is handled by tools/tracer.py (if loaded)
+ group.add_option('-t', '--trace-calls', default=False,
+ dest="trace_calls",
+ action="store_true",
+ help="Turns on call tracing for troubleshooting")
+
parser.add_option_group(group)
+ return group
def add_log_options(parser):
@@ -97,22 +111,51 @@ def add_log_options(parser):
group = optparse.OptionGroup(parser, "Logging Options", help_text)
group.add_option('--log-config', default=None, metavar="PATH",
- help="If this option is specified, the logging "
- "configuration file specified is used and overrides "
- "any other logging options specified. Please see "
- "the Python logging module documentation for "
+ help="If this option is specified, the logging "\
+ "configuration file specified is used and overrides "\
+ "any other logging options specified. Please see "\
+ "the Python logging module documentation for "\
"details on logging configuration files.")
group.add_option('--log-date-format', metavar="FORMAT",
default=DEFAULT_LOG_DATE_FORMAT,
- help="Format string for %(asctime)s in log records. "
+ help="Format string for %(asctime)s in log records. "\
"Default: %default")
group.add_option('--log-file', default=None, metavar="PATH",
- help="(Optional) Name of log file to output to. "
+ help="(Optional) Name of log file to output to. "\
"If not set, logging will go to stdout.")
group.add_option("--log-dir", default=None,
- help="(Optional) The directory to keep log files in "
+ help="(Optional) The directory to keep log files in "\
"(will be prepended to --logfile)")
+
parser.add_option_group(group)
+ return group
+
+
+def add_console_handler(logger, level=logging.INFO):
+ """
+ Add a Handler which writes log messages to sys.stderr
+ (which is often the console)
+ There is a copy of this function in wsgi.py (TODO(Ziad): Make it one copy)
+ """
+ console = None
+ for console in logger.handlers:
+ if isinstance(console, logging.StreamHandler):
+ break
+
+ if not console:
+ console = logging.StreamHandler()
+ console.setLevel(level)
+ # set a format which is simpler for console use
+ formatter = logging.Formatter("%(name)-12s: "\
+ "%(levelname)-8s %(message)s")
+ # tell the handler to use this format
+ console.setFormatter(formatter)
+ # add the handler to the root logger
+ logger.addHandler(console)
+ else:
+ if console.level != level:
+ console.setLevel(level)
+ return console
def setup_logging(options, conf):
@@ -128,7 +171,7 @@ def setup_logging(options, conf):
logging.config.fileConfig(options['log_config'])
return
else:
- raise RuntimeError("Unable to locate specified logging "
+ raise RuntimeError("Unable to locate specified logging "\
"config file: %s" % options['log_config'])
# If either the CLI option or the conf value
@@ -163,8 +206,12 @@ def setup_logging(options, conf):
logfile = os.path.join(logdir, logfile)
logfile = logging.FileHandler(logfile)
logfile.setFormatter(formatter)
- logfile.setFormatter(formatter)
root_logger.addHandler(logfile)
+ # Mirror to console if verbose or debug
+ if debug:
+ add_console_handler(root_logger, logging.INFO) #debug too noisy
+ elif verbose:
+ add_console_handler(root_logger, logging.INFO)
else:
handler = logging.StreamHandler(sys.stdout)
handler.setFormatter(formatter)
@@ -217,10 +264,7 @@ def find_config_file(options, args):
# For debug only
config_file = os.path.join(POSSIBLE_TOPDIR, 'etc', \
'keystone.conf')
- print "Running server from %s " % config_file
-
- return os.path.join(POSSIBLE_TOPDIR, 'etc', \
- 'keystone.conf')
+ return config_file
def load_paste_config(app_name, options, args):
@@ -250,7 +294,7 @@ def load_paste_config(app_name, options, args):
"""
conf_file = find_config_file(options, args)
if not conf_file:
- raise RuntimeError("Unable to locate any configuration file. "
+ raise RuntimeError("Unable to locate any configuration file. "\
"Cannot load application %s" % app_name)
try:
conf = deploy.appconfig("config:%s" % conf_file, name=app_name)
@@ -274,7 +318,7 @@ def load_paste_app(app_name, options, args):
* /etc/keystone
* /etc
- :param app_name: Name of the application to load
+ :param app_name: Name of the application to load (server, admin, proxy, ...)
:param options: Set of typed options returned from parse_options()
:param args: Command line arguments from argv[1:]
@@ -298,15 +342,15 @@ def load_paste_app(app_name, options, args):
# Log the options used when starting if we're in debug mode...
if debug:
logger = logging.getLogger(app_name)
- logger.debug("*" * 80)
- logger.debug("Configuration options gathered from config file:")
- logger.debug(conf_file)
- logger.debug("================================================")
+ logger.info("*" * 50)
+ logger.info("Configuration options gathered from config file:")
+ logger.info(conf_file)
+ logger.info("================================================")
items = dict([(k, v) for k, v in conf.items()
if k not in ('__file__', 'here')])
for key, value in sorted(items.items()):
- logger.debug("%(key)-30s %(value)s" % locals())
- logger.debug("*" * 80)
+ logger.info("%(key)-20s %(value)s" % locals())
+ logger.info("*" * 50)
app = deploy.loadapp("config:%s" % conf_file, name=app_name)
except (LookupError, ImportError), e:
raise RuntimeError("Unable to load %(app_name)s from "
diff --git a/keystone/common/exception.py b/keystone/common/exception.py
index 1f793c7c..fb64220d 100755
--- a/keystone/common/exception.py
+++ b/keystone/common/exception.py
@@ -18,7 +18,7 @@
"""
OpenStack base exception handling, including decorator for re-raising
-OpenSTack-type exceptions. SHOULD include dedicated exception logging.
+OpenStack-type exceptions. SHOULD include dedicated exception logging.
"""
import logging
diff --git a/keystone/common/template.py b/keystone/common/template.py
index 58c8bd6e..f8412d62 100644
--- a/keystone/common/template.py
+++ b/keystone/common/template.py
@@ -1,3 +1,22 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2010 United States Government as represented by the
+# Administrator of the National Aeronautics and Space Administration.
+# All Rights Reserved.
+#
+# 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.
+#
+# Template library copied from bottle: http://bottlepy.org/
#
# Copyright (c) 2011, Marcel Hellkamp.
#
@@ -19,7 +38,6 @@
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
#
-# original code copied from bottle.py
import cgi
diff --git a/keystone/common/wsgi.py b/keystone/common/wsgi.py
index 32de27c9..1f48137c 100755
--- a/keystone/common/wsgi.py
+++ b/keystone/common/wsgi.py
@@ -34,13 +34,41 @@ import routes.middleware
import webob.dec
import webob.exc
+def add_console_handler(logger, level):
+ """
+ Add a Handler which writes messages to sys.stderr which is often the
+ console.
+ There is a copy of this function in config.py (TODO(Ziad): Make it one copy)
+ """
+ console = None
+ for console in logger.handlers:
+ if isinstance(console, logging.StreamHandler):
+ break
+
+ if not console:
+ console = logging.StreamHandler()
+ console.setLevel(level)
+ # set a format which is simpler for console use
+ formatter = logging.Formatter('%(name)-12s: %(levelname)-8s %(message)s')
+ # tell the handler to use this format
+ console.setFormatter(formatter)
+ # add the handler to the root logger
+ logger.addHandler(console)
+ elif console.level != level:
+ console.setLevel(level)
+ return console
+
class WritableLogger(object):
"""A thin wrapper that responds to `write` and logs."""
- def __init__(self, logger, level=logging.DEBUG):
+ def __init__(self, logger, level=logging.INFO):
self.logger = logger
self.level = level
+ # TODO(Ziad): figure out why root logger is not set to same level as
+ # caller. Maybe something to do with paste?
+ if level == logging.DEBUG:
+ add_console_handler(logger, level)
def write(self, msg):
self.logger.log(self.level, msg.strip("\n"))
@@ -73,8 +101,10 @@ class Server(object):
def _run(self, application, socket):
"""Start a WSGI server in a new green thread."""
logger = logging.getLogger('eventlet.wsgi.server')
+ # TODO(Ziad): figure out why root logger is not set to same level as
+ # caller. Maybe something to do with paste?
eventlet.wsgi.server(socket, application, custom_pool=self.pool,
- log=WritableLogger(logger))
+ log=WritableLogger(logger, logging.root.level))
class Middleware(object):
@@ -176,7 +206,7 @@ class Router(object):
# Pointing to an arbitrary WSGI app. You can specify the
# {path_info:.*} parameter so the target app can be handed just that
# section of the URL.
- mapper.connect(None, "/v2.0/{path_info:.*}", controller=BlogApp())
+ mapper.connect(None, "/v2.0/{path_info:.*}", controller=TheApp())
"""
self.map = mapper
self._router = routes.middleware.RoutesMiddleware(self._dispatch,
diff --git a/keystone/db/sqlalchemy/api.py b/keystone/db/sqlalchemy/api.py
index e479a4aa..95fbb9ee 100644
--- a/keystone/db/sqlalchemy/api.py
+++ b/keystone/db/sqlalchemy/api.py
@@ -28,6 +28,7 @@ _ENGINE = None
_MAKER = None
BASE = models.Base
+
def configure_db(options):
"""
Establish the database, create an engine if needed, and
@@ -77,11 +78,33 @@ def unregister_models():
assert _ENGINE
BASE.metadata.drop_all(_ENGINE)
+
#
-# Tenant API operations
+# Role API operations
#
+def role_create(values):
+ role_ref = models.Role()
+ role_ref.update(values)
+ role_ref.save()
+ return role_ref
+
+
+def role_get(id, session=None):
+ if not session:
+ session = get_session()
+ result = session.query(models.Role).filter_by(id=id).first()
+ return result
+
+
+def role_get_all(session=None):
+ if not session:
+ session = get_session()
+ return session.query(models.Role).all()
+#
+# Tenant API operations
+#
def tenant_create(values):
tenant_ref = models.Tenant()
tenant_ref.update(values)
@@ -182,11 +205,11 @@ def tenant_delete(id, session=None):
tenant_ref = tenant_get(id, session)
session.delete(tenant_ref)
+
+
#
# Tenant Group Operations API
#
-
-
def tenant_group_create(values):
group_ref = models.Group()
group_ref.update(values)
@@ -288,6 +311,16 @@ def tenant_group_delete(id, tenant_id, session=None):
session.delete(tenantgroup_ref)
+#
+# User Operations
+#
+def user_get_all(session=None):
+ if not session:
+ session = get_session()
+ result = session.query(models.User)
+ return result
+
+
def get_user_by_group(user_id, group_id, session=None):
if not session:
session = get_session()
@@ -310,10 +343,6 @@ def user_tenant_group_delete(id, group_id, session=None):
usertenantgroup_ref = get_user_by_group(id, group_id, session)
session.delete(usertenantgroup_ref)
-#
-# User Operations
-#
-
def user_create(values):
user_ref = models.User()
@@ -325,8 +354,10 @@ def user_create(values):
def user_get(id, session=None):
if not session:
session = get_session()
- result = session.query(models.User).options(joinedload('groups')).options(\
- joinedload('tenants')).filter_by(id=id).first()
+ #TODO(Ziad): finish cleaning up model
+ # result = session.query(models.User).options(joinedload('groups')).options(\
+ # joinedload('tenants')).filter_by(id=id).first()
+ result = session.query(models.User).filter_by(id=id).first()
return result
@@ -345,6 +376,14 @@ def user_groups(id, session=None):
return result
+def user_roles_by_tenant(user_id, tenant_id, session=None):
+ if not session:
+ session = get_session()
+ result = session.query(models.UserRoleAssociation).filter_by(\
+ user_id=user_id, tenant_id=tenant_id).options(joinedload('roles'))
+ return result
+
+
def user_update(id, values, session=None):
if not session:
session = get_session()
@@ -419,6 +458,64 @@ def users_tenant_group_get_page_markers(group_id, marker, limit, session=None):
return (prev, next)
+def user_delete(id, session=None):
+ if not session:
+ session = get_session()
+ with session.begin():
+ user_ref = user_get(id, session)
+ session.delete(user_ref)
+
+
+def user_get_by_tenant(id, tenant_id, session=None):
+ if not session:
+ session = get_session()
+ user_tenant = session.query(models.UserTenantAssociation).filter_by(\
+ tenant_id=tenant_id, user_id=id).first()
+ if user_tenant:
+ return user_get(id, session)
+ else:
+ return None
+
+
+def user_get_by_group(id, session=None):
+ if not session:
+ session = get_session()
+ user_group = session.query(models.Group).filter_by(tenant_id=id).all()
+ return user_group
+
+
+def user_delete_tenant(id, tenant_id, session=None):
+ if not session:
+ session = get_session()
+ with session.begin():
+ user_tenant_ref = user_get_by_tenant(id, tenant_id, session)
+
+ session.delete(user_tenant_ref)
+ user_group_ref = user_get_by_group(tenant_id, session)
+
+ if user_group_ref is not None:
+ for user_group in user_group_ref:
+ group_users = session.query(models.UserGroupAssociation)\
+ .filter_by(user_id=id,
+ group_id=user_group.id).all()
+ for group_user in group_users:
+ session.delete(group_user)
+ user_tenant_ref = session.query(models.UserTenantAssociation)\
+ .filter_by(user_id=id).first()
+ if user_tenant_ref is None:
+ user_ref = user_get(id, session)
+ session.delete(user_ref)
+
+def user_get_by_tenant(user_id, tenant_id, session=None):
+ if not session:
+ session = get_session()
+ result = session.query(models.User).filter_by(id=user_id,
+ tenant_id=tenant_id).first()
+ return result
+
+#
+# Group Operations
+#
def group_get(id, session=None):
if not session:
session = get_session()
@@ -499,11 +596,10 @@ def group_delete(id, session=None):
group_ref = group_get(id, session)
session.delete(group_ref)
+
#
# Token Operations
#
-
-
def token_create(values):
token_ref = models.Token()
token_ref.update(values)
@@ -542,6 +638,22 @@ def token_for_user_tenant(user_id, tenant_id, session=None):
return result
+def token_get_all(session=None):
+ if not session:
+ session = get_session()
+ return session.query(models.Token).all()
+
+
+#
+# Unsorted operations
+#
+def user_role_add(values):
+ user_role_ref = models.UserRoleAssociation()
+ user_role_ref.update(values)
+ user_role_ref.save()
+ return user_role_ref
+
+
def user_tenant_create(values):
user_tenant_ref = models.UserTenantAssociation()
user_tenant_ref.update(values)
@@ -555,6 +667,7 @@ def user_get_update(id, session=None):
result = session.query(models.User).filter_by(id=id).first()
return result
+
def users_get_by_tenant_get_page(tenant_id, marker, limit, session=None):
if not session:
session = get_session()
@@ -708,49 +821,3 @@ def groups_get_by_user_get_page_markers(user_id, marker, limit, session=None):
else:
next = next.id
return (prev, next)
-
-
-def user_delete(id, session=None):
- if not session:
- session = get_session()
- with session.begin():
- user_ref = user_get(id, session)
- session.delete(user_ref)
-
-
-def user_get_by_tenant(id, tenant_id, session=None):
- if not session:
- session = get_session()
- user_tenant = session.query(models.UserTenantAssociation).filter_by(\
- tenant_id=tenant_id, user_id=id).first()
- return user_tenant
-
-
-def user_get_by_group(id, session=None):
- if not session:
- session = get_session()
- user_group = session.query(models.Group).filter_by(tenant_id=id).all()
- return user_group
-
-
-def user_delete_tenant(id, tenant_id, session=None):
- if not session:
- session = get_session()
- with session.begin():
- user_tenant_ref = user_get_by_tenant(id, tenant_id, session)
-
- session.delete(user_tenant_ref)
- user_group_ref = user_get_by_group(tenant_id, session)
-
- if user_group_ref is not None:
- for user_group in user_group_ref:
- group_users = session.query(models.UserGroupAssociation)\
- .filter_by(user_id=id,
- group_id=user_group.id).all()
- for group_user in group_users:
- session.delete(group_user)
- user_tenant_ref = session.query(models.UserTenantAssociation)\
- .filter_by(user_id=id).first()
- if user_tenant_ref is None:
- user_ref = user_get(id, session)
- session.delete(user_ref)
diff --git a/keystone/db/sqlalchemy/models.py b/keystone/db/sqlalchemy/models.py
index bdcd5015..71511653 100644
--- a/keystone/db/sqlalchemy/models.py
+++ b/keystone/db/sqlalchemy/models.py
@@ -74,18 +74,38 @@ class KeystoneBase(object):
return local.iteritems()
-class UserTenantAssociation(Base, KeystoneBase):
- __tablename__ = 'user_tenant_association'
+# Define associations firest
+class UserGroupAssociation(Base, KeystoneBase):
+ __tablename__ = 'user_group_association'
user_id = Column(String(255), ForeignKey('users.id'), primary_key=True)
- tenant_id = Column(String(255), ForeignKey('tenants.id'), primary_key=True)
+ group_id = Column(String(255), ForeignKey('groups.id'), primary_key=True)
-class UserGroupAssociation(Base, KeystoneBase):
- __tablename__ = 'user_group_association'
+class UserRoleAssociation(Base, KeystoneBase):
+ __tablename__ = 'user_roles'
user_id = Column(String(255), ForeignKey('users.id'), primary_key=True)
- group_id = Column(String(255), ForeignKey('groups.id'), primary_key=True)
+ role_id = Column(String(255), ForeignKey('roles.id'), primary_key=True)
+ tenant_id = Column(String(255), ForeignKey('tenants.id'), primary_key=True)
+
+
+# Define objects
+class Role(Base, KeystoneBase):
+ __tablename__ = 'roles'
+
+ id = Column(String(255), primary_key=True, unique=True)
+ desc = Column(String(255))
+
+
+class Tenant(Base, KeystoneBase):
+ __tablename__ = 'tenants'
+
+ id = Column(String(255), primary_key=True, unique=True)
+ desc = Column(String(255))
+ enabled = Column(Integer)
+
+ groups = relationship('Group', backref='tenants')
class User(Base, KeystoneBase):
@@ -95,17 +115,27 @@ class User(Base, KeystoneBase):
password = Column(String(255))
email = Column(String(255))
enabled = Column(Integer)
+ tenant_id = Column(String(255), ForeignKey('tenants.id'))
+
groups = relationship(UserGroupAssociation, backref='users')
- tenants = relationship(UserTenantAssociation, backref='user')
+ roles = relationship(UserRoleAssociation)
+class Credentials(Base, KeystoneBase):
+ __tablename__ = 'credentials'
-class Tenant(Base, KeystoneBase):
- __tablename__ = 'tenants'
+ user_id = Column(String(255), ForeignKey('users.id'), primary_key=True)
+ type = Column(String(20)) #('Password','APIKey','EC2')
+ key = Column(String(255))
+ secret = Column(String(255))
+
+
+class Group(Base, KeystoneBase):
+ __tablename__ = 'groups'
id = Column(String(255), primary_key=True, unique=True)
desc = Column(String(255))
- enabled = Column(Integer)
- groups = relationship('Group', backref='tenants')
+ tenant_id = Column(String(255), ForeignKey('tenants.id'))
+
class Token(Base, KeystoneBase):
__tablename__ = 'token'
@@ -115,11 +145,10 @@ class Token(Base, KeystoneBase):
tenant_id = Column(String(255))
expires = Column(DateTime)
-class Group(Base, KeystoneBase):
- __tablename__ = 'groups'
+
+class Endpoints(Base, KeystoneBase):
+ __tablename__ = 'endpoints'
id = Column(String(255), primary_key=True, unique=True)
+ service = Column(String(255))
desc = Column(String(255))
- tenant_id = Column(String(255), ForeignKey('tenants.id'))
-
-
diff --git a/keystone/keystone.ini b/keystone/keystone.ini
deleted file mode 100644
index 626ca1ed..00000000
--- a/keystone/keystone.ini
+++ /dev/null
@@ -1,16 +0,0 @@
-;
-; This file not used
-;
-[DEFAULT]
-
-[composite:main]
-use = egg:Paste#urlmap
-/ = home
-/tenants = admin
-
-[app:home]
-use = egg:keystone
-
-[app:admin]
-use = egg:keystone
-
diff --git a/keystone/logic/service.py b/keystone/logic/service.py
index 8c833df3..c9fc0e2d 100644
--- a/keystone/logic/service.py
+++ b/keystone/logic/service.py
@@ -34,12 +34,19 @@ class IdentityService(object):
#
def authenticate(self, credentials):
+ # Check credentials
if not isinstance(credentials, auth.PasswordCredentials):
raise fault.BadRequestFault("Expecting Password Credentials!")
- duser = db_api.user_get(credentials.username)
- if duser == None:
- raise fault.UnauthorizedFault("Unauthorized")
+ if not credentials.tenant_id:
+ duser = db_api.user_get(credentials.username)
+ if duser == None:
+ raise fault.UnauthorizedFault("Unauthorized")
+ else:
+ duser = db_api.user_get_by_tenant(credentials.username, credentials.tenant_id)
+ if duser == None:
+ raise fault.UnauthorizedFault("Unauthorized on this tenant")
+
if not duser.enabled:
raise fault.UserDisabledFault("Your account has been disabled")
if duser.password != credentials.password:
@@ -55,23 +62,12 @@ class IdentityService(object):
dtoken = db_api.token_for_user_tenant(duser.id,
credentials.tenant_id)
if not dtoken or dtoken.expires < datetime.now():
+ # Create new token
dtoken = db_models.Token()
dtoken.token_id = str(uuid.uuid4())
dtoken.user_id = duser.id
-
- if not duser.tenants:
- raise fault.IdentityFault("Strange: user %s is not associated "
- "with a tenant!" % duser.id)
- user = db_api.user_get_by_tenant(duser.id, credentials.tenant_id)
-
- if not credentials.tenant_id or not user:
- raise fault.ForbiddenFault("Error: user %s is "
- "not associated "
- "with a tenant! %s" % (duser.id,
- credentials.tenant_id))
+ if credentials.tenant_id:
dtoken.tenant_id = credentials.tenant_id
- else:
- dtoken.tenant_id = duser.tenants[0].tenant_id
dtoken.expires = datetime.now() + timedelta(days=1)
db_api.token_create(dtoken)
@@ -833,21 +829,18 @@ class IdentityService(object):
token = auth.Token(dtoken.expires, dtoken.token_id)
- gs = []
+ """gs = []
for ug in duser.groups:
dgroup = db_api.group_get(ug.group_id)
- gs.append(auth.Group(dgroup.id, dgroup.tenant_id))
- groups = auth.Groups(gs, [])
- if len(duser.tenants) == 0:
- raise fault.IdentityFault("Strange: user %s is not associated "
- "with a tenant!" % duser.id)
- if not dtoken.tenant_id and \
- db_api.user_get_by_tenant(duser.id, dtoken.tenant_id):
- raise fault.IdentityFault("Error: user %s is not associated "
- "with a tenant! %s" % (duser.id,
- dtoken.tenant_id))
-
- user = auth.User(duser.id, dtoken.tenant_id, groups)
+ if dtoken.tenant_id:
+ if dgroup.tenant_id == dtoken.tenant_id:
+ gs.append(auth.Group(dgroup.id, dgroup.tenant_id))
+ else:
+ if dgroup.tenant_id == None:
+ gs.append(auth.Group(dgroup.id))
+ user = auth.User(duser.id, dtoken.tenant_id, gs)
+ """
+ user = auth.User(duser.id, dtoken.tenant_id, None)
return auth.AuthData(token, user)
def __validate_token(self, token_id, admin=True):
@@ -862,11 +855,12 @@ class IdentityService(object):
if not user.enabled:
raise fault.UserDisabledFault("The user %s has been disabled!"
% user.id)
+ '''TODO(Ziad): return roles
if admin:
- for ug in user.groups:
+ for roles in user.roles:
if ug.group_id == "Admin":
return (token, user)
raise fault.UnauthorizedFault("You are not authorized "
"to make this call")
-
+ '''
return (token, user)
diff --git a/keystone/logic/types/auth.py b/keystone/logic/types/auth.py
index c822ea8c..d80e158e 100644
--- a/keystone/logic/types/auth.py
+++ b/keystone/logic/types/auth.py
@@ -21,11 +21,9 @@ from lxml import etree
import keystone.logic.types.fault as fault
-
class PasswordCredentials(object):
"""Credentials based on username, password, and (optional) tenant_id.
- To handle multiple token for a user depending on tenants,
- tenant_id is mandatory.
+ To handle multiple token for a user depending on tenants.
"""
def __init__(self, username, password, tenant_id):
@@ -49,12 +47,6 @@ class PasswordCredentials(object):
if password == None:
raise fault.BadRequestFault("Expecting a password")
tenant_id = root.get("tenantId")
-
- #--for multi-token handling--
- if tenant_id == None:
- raise fault.BadRequestFault("Expecting tenant")
- # ----
-
return PasswordCredentials(username, password, tenant_id)
except etree.LxmlError as e:
raise fault.BadRequestFault("Cannot parse password credentials",
@@ -76,10 +68,7 @@ class PasswordCredentials(object):
if "tenantId" in cred:
tenant_id = cred["tenantId"]
else:
- #--for multi-token handling--
- if tenant_id == None:
- raise fault.BadRequestFault("Expecting a tenant")
- # ---
+ tenant_id = None
return PasswordCredentials(username, password, tenant_id)
except (ValueError, TypeError) as e:
raise fault.BadRequestFault("Cannot parse password credentials",
@@ -128,20 +117,21 @@ class AuthData(object):
def to_xml(self):
dom = etree.Element("auth",
- xmlns="http://docs.openstack.org/identity/api/v2.0")
+ xmlns="http://docs.openstack.org/identity/api/v2.0")
token = etree.Element("token",
expires=self.token.expires.isoformat())
token.set("id", self.token.token_id)
user = etree.Element("user",
username=self.user.username,
tenantId=str(self.user.tenant_id))
- groups = etree.Element("groups")
+ """groups = etree.Element("groups")
for group in self.user.groups.values:
g = etree.Element("group",
tenantId=group.tenant_id)
g.set("id", group.group_id)
groups.append(g)
user.append(groups)
+ """
dom.append(token)
dom.append(user)
return etree.tostring(dom)
@@ -153,7 +143,7 @@ class AuthData(object):
user = {}
user["username"] = self.user.username
user["tenantId"] = self.user.tenant_id
- group = []
+ """group = []
for g in self.user.groups.values:
grp = {}
grp["tenantId"] = g.tenant_id
@@ -162,6 +152,7 @@ class AuthData(object):
groups = {}
groups["group"] = group
user["groups"] = groups
+ """
auth = {}
auth["token"] = token
auth["user"] = user
diff --git a/keystone/logic/types/user.py b/keystone/logic/types/user.py
index 6f6e619e..43e7e986 100644
--- a/keystone/logic/types/user.py
+++ b/keystone/logic/types/user.py
@@ -19,6 +19,7 @@ import string
import keystone.logic.types.fault as fault
+
class User(object):
def __init__(self, password, user_id, tenant_id, email, enabled):
@@ -33,7 +34,8 @@ class User(object):
try:
dom = etree.Element("root")
dom.append(etree.fromstring(xml_str))
- root = dom.find("{http://docs.openstack.org/identity/api/v2.0}user")
+ root = dom.find("{http://docs.openstack.org/identity/api/v2.0}" \
+ "user")
if root == None:
raise fault.BadRequestFault("Expecting User")
user_id = root.get("id")
@@ -43,8 +45,6 @@ class User(object):
enabled = root.get("enabled")
if user_id == None:
raise fault.BadRequestFault("Expecting User")
- elif tenant_id == None:
- raise fault.BadRequestFault("Expecting User tenant")
elif password == None:
raise fault.BadRequestFault("Expecting User password")
elif email == None:
@@ -75,9 +75,10 @@ class User(object):
if not "password" in user:
raise fault.BadRequestFault("Expecting User Password")
password = user["password"]
- if not "tenantId" in user:
- raise fault.BadRequestFault("Expecting User Tenant")
- tenant_id = user["tenantId"]
+ if "tenantId" in user:
+ tenant_id = user["tenantId"]
+ else:
+ tenant_id = None
if not "email" in user:
raise fault.BadRequestFault("Expecting User Email")
email = user["email"]
@@ -87,15 +88,13 @@ class User(object):
raise fault.BadRequestFault("Bad enabled attribute!")
else:
set_enabled = True
- if password == '':
- password = user_id
return User(password, user_id, tenant_id, email, set_enabled)
except (ValueError, TypeError) as e:
raise fault.BadRequestFault("Cannot parse Tenant", str(e))
def to_dom(self):
dom = etree.Element("user",
- xmlns="http://docs.openstack.org/identity/api/v2.0")
+ xmlns="http://docs.openstack.org/identity/api/v2.0")
if self.email:
dom.set("email", self.email)
if self.tenant_id:
@@ -116,7 +115,8 @@ class User(object):
if self.user_id:
user["id"] = self.user_id
- user["tenantId"] = self.tenant_id
+ if self.tenant_id:
+ user["tenantId"] = self.tenant_id
if self.password:
user["password"] = self.password
user["email"] = self.email
@@ -143,7 +143,8 @@ class User_Update(object):
try:
dom = etree.Element("root")
dom.append(etree.fromstring(xml_str))
- root = dom.find("{http://docs.openstack.org/identity/api/v2.0}user")
+ root = dom.find("{http://docs.openstack.org/identity/api/v2.0}" \
+ "user")
if root == None:
raise fault.BadRequestFault("Expecting User")
user_id = root.get("id")
@@ -200,7 +201,7 @@ class User_Update(object):
def to_dom(self):
dom = etree.Element("user",
- xmlns="http://docs.openstack.org/identity/api/v2.0")
+ xmlns="http://docs.openstack.org/identity/api/v2.0")
if self.email:
dom.set("email", self.email)
if self.tenant_id:
diff --git a/keystone/server.py b/keystone/server.py
index 91526c9a..59a3f8e3 100755..100644
--- a/keystone/server.py
+++ b/keystone/server.py
@@ -173,6 +173,37 @@ class AuthController(wsgi.Controller):
service.revoke_token(utils.get_auth_token(req), token_id))
+class LegacyAuthController(wsgi.Controller):
+ """
+ Auth Controller for v1.x -
+ Controller for token related operations
+ """
+
+ def __init__(self, options):
+ self.options = options
+ self.request = None
+
+ @utils.wrap_error
+ def authenticate(self, req):
+ self.request = req
+
+ creds = auth.PasswordCredentials(utils.get_auth_user(req),
+ utils.get_auth_key(req),
+ None)
+
+ """HTTP/1.1 204 No Content
+ Date: Mon, 12 Nov 2007 15:32:21 GMT
+ X-Storage-Url: https://storage.clouddrive.com/v1/CF_xer7_34
+ X-CDN-Management-Url: https://cdn.clouddrive.com/v1/CF_xer7_34
+ X-Auth-Token: eaaafd18-0fed-4b3a-81b4-663c99ec1cbb
+ Content-Length: 0
+ Content-Type: text/plain; charset=UTF-8"""
+
+ result = service.authenticate(creds)
+ headers = {"X-Auth-Token": result.token.token_id}
+ return utils.send_legacy_result(204, headers)
+
+
class TenantController(wsgi.Controller):
"""
Tenant Controller -
@@ -484,13 +515,94 @@ class GroupsController(wsgi.Controller):
class KeystoneAPI(wsgi.Router):
- """WSGI entry point for all Keystone Auth API requests."""
+ """WSGI entry point for public Keystone API requests."""
def __init__(self, options):
self.options = options
mapper = routes.Mapper()
-
+
db_api.configure_db(options)
+
+ # Legacy Token Operations
+ auth_controller = AuthController(options)
+ legacy_auth_controller = LegacyAuthController(options)
+ mapper.connect("/v1.0", controller=legacy_auth_controller,
+ action="authenticate")
+ mapper.connect("/v1.0/", controller=legacy_auth_controller,
+ action="authenticate")
+
+ mapper.connect("/v1.1/tokens", controller=auth_controller,
+ action="authenticate",
+ conditions=dict(method=["POST"]))
+ mapper.connect("/v1.1/tokens/", controller=auth_controller,
+ action="authenticate",
+ conditions=dict(method=["POST"]))
+
+ # Token Operations
+ mapper.connect("/v2.0/token", controller=auth_controller,
+ action="authenticate")
+ mapper.connect("/v2.0/token/{token_id}", controller=auth_controller,
+ action="delete_token",
+ conditions=dict(method=["DELETE"]))
+
+ # Tenant Operations
+ tenant_controller = TenantController(options)
+ mapper.connect("/v2.0/tenants", controller=tenant_controller,
+ action="get_tenants", conditions=dict(method=["GET"]))
+
+ # Miscellaneous Operations
+ version_controller = VersionController(options)
+ mapper.connect("/v2.0/", controller=version_controller,
+ action="get_version_info",
+ conditions=dict(method=["GET"]))
+ mapper.connect("/v2.0", controller=version_controller,
+ action="get_version_info",
+ conditions=dict(method=["GET"]))
+
+ # Static Files Controller
+ static_files_controller = StaticFilesController(options)
+ mapper.connect("/v2.0/identitydevguide.pdf",
+ controller=static_files_controller,
+ action="get_pdf_contract",
+ conditions=dict(method=["GET"]))
+ mapper.connect("/v2.0/identity.wadl",
+ controller=static_files_controller,
+ action="get_identity_wadl",
+ conditions=dict(method=["GET"]))
+ mapper.connect("/v2.0/xsd/{xsd}",
+ controller=static_files_controller,
+ action="get_pdf_contract",
+ conditions=dict(method=["GET"]))
+ mapper.connect("/v2.0/xsd/atom/{xsd}",
+ controller=static_files_controller,
+ action="get_pdf_contract",
+ conditions=dict(method=["GET"]))
+
+ super(KeystoneAPI, self).__init__(mapper)
+
+
+class KeystoneAdminAPI(wsgi.Router):
+ """WSGI entry point for admin Keystone API requests."""
+
+ def __init__(self, options):
+ self.options = options
+ mapper = routes.Mapper()
+
+ db_api.configure_db(options)
+
+ # Legacy Token Operations
+ legacy_auth_controller = LegacyAuthController(options)
+ mapper.connect("/v1.0", controller=legacy_auth_controller,
+ action="authenticate")
+ mapper.connect("/v1.0/", controller=legacy_auth_controller,
+ action="authenticate")
+ mapper.connect("/v1.1/tokens", controller=legacy_auth_controller,
+ action="authenticate",
+ conditions=dict(method=["POST"]))
+ mapper.connect("/v1.1/tokens/", controller=legacy_auth_controller,
+ action="authenticate",
+ conditions=dict(method=["POST"]))
+
# Token Operations
auth_controller = AuthController(options)
mapper.connect("/v2.0/token", controller=auth_controller,
@@ -649,7 +761,7 @@ class KeystoneAPI(wsgi.Router):
action="get_pdf_contract",
conditions=dict(method=["GET"]))
- super(KeystoneAPI, self).__init__(mapper)
+ super(KeystoneAdminAPI, self).__init__(mapper)
def app_factory(global_conf, **local_conf):
@@ -660,3 +772,13 @@ def app_factory(global_conf, **local_conf):
except Exception as err:
print err
return KeystoneAPI(conf)
+
+
+def admin_app_factory(global_conf, **local_conf):
+ """paste.deploy app factory for creating OpenStack API server apps"""
+ try:
+ conf = global_conf.copy()
+ conf.update(local_conf)
+ except Exception as err:
+ print err
+ return KeystoneAdminAPI(conf)
diff --git a/keystone/utils.py b/keystone/utils.py
index df480e31..a72d40b6 100644
--- a/keystone/utils.py
+++ b/keystone/utils.py
@@ -56,6 +56,20 @@ def get_auth_token(req):
return auth_token
+def get_auth_user(req):
+ auth_user = None
+ if "X-Auth-User" in req.headers:
+ auth_user = req.headers["X-Auth-User"]
+ return auth_user
+
+
+def get_auth_key(req):
+ auth_key = None
+ if "X-Auth-Key" in req.headers:
+ auth_key = req.headers["X-Auth-Key"]
+ return auth_key
+
+
def wrap_error(func):
@functools.wraps(func)
@@ -68,7 +82,8 @@ def wrap_error(func):
else:
logging.exception(err)
return send_error(500, kwargs['req'],
- fault.IdentityFault("Unhandled error", str(err)))
+ fault.IdentityFault("Unhandled error",
+ str(err)))
return check_error
@@ -80,7 +95,8 @@ def get_normalized_request_content(model, req):
elif req.content_type == "application/json":
ret = model.from_json(req.body)
else:
- raise fault.IDMFault("I don't understand the content type ", code=415)
+ raise fault.IdentityFault("I don't understand the content type ",
+ code=415)
return ret
@@ -129,3 +145,18 @@ def send_result(code, req, result):
resp.unicode_body = content.decode('UTF-8')
return resp
+
+
+def send_legacy_result(code, headers):
+ resp = Response()
+ if 'content-type' not in headers:
+ headers['content-type'] = "text/plain"
+
+ resp.headers = headers
+ resp.status = code
+ if code > 399:
+ return resp
+
+ resp.content_type_params = {'charset': 'UTF-8'}
+
+ return resp
diff --git a/keystone/version.py b/keystone/version.py
deleted file mode 100755
index 770259b4..00000000
--- a/keystone/version.py
+++ /dev/null
@@ -1,33 +0,0 @@
-# vim: tabstop=4 shiftwidth=4 softtabstop=4
-
-# Copyright 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.
-
-""" Version file for the python daemon script """
-
-KEYSTONE_VERSION = ['1', '0']
-MAJOR, MINOR = KEYSTONE_VERSION
-
-FINAL = True
-
-
-def canonical_version_string():
- return '.'.join([MAJOR, MINOR])
-
-
-def version_string():
- if FINAL:
- return canonical_version_string()
- else:
- return '%s-dev' % (canonical_version_string(),)
diff --git a/management/delgroup.py b/management/delgroup.py
index 9ef2d733..1b48cd77 100644..100755
--- a/management/delgroup.py
+++ b/management/delgroup.py
@@ -1,42 +1,54 @@
-# Copyright (c) 2010-2011 OpenStack, LLC.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
-# implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import optparse
-import keystone.db.sqlalchemy.api as db_api
-
-
-def main():
- usage = "usage: %prog group_id"
- parser = optparse.OptionParser(usage)
- options, args = parser.parse_args()
- if len(args) != 1:
- parser.error("Incorrect number of arguments")
- else:
- index = args[0]
-
- try:
- o = db_api.group_get(index)
- if o == None:
- raise IndexError("Group %s not found", index)
- else:
- db_api.group_delete(index)
- print 'Group', index, 'deleted.'
-
- except Exception, e:
- print 'Error deleting group', index, str(e)
-
-
-if __name__ == '__main__':
- main()
+#!/usr/bin/env python
+# Copyright (c) 2010-2011 OpenStack, LLC.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+# implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import os
+import sys
+
+# If ../keystone/__init__.py exists, add ../ to Python search path, so that
+# it will override what happens to be installed in /usr/(local/)lib/python...
+possible_topdir = os.path.normpath(os.path.join(os.path.abspath(sys.argv[0]),
+ os.pardir,
+ os.pardir))
+if os.path.exists(os.path.join(possible_topdir, 'keystone', '__init__.py')):
+ sys.path.insert(0, possible_topdir)
+
+import optparse
+import keystone.db.sqlalchemy.api as db_api
+
+
+def main():
+ usage = "usage: %prog group_id"
+ parser = optparse.OptionParser(usage)
+ options, args = parser.parse_args()
+ if len(args) != 1:
+ parser.error("Incorrect number of arguments")
+ else:
+ index = args[0]
+
+ try:
+ o = db_api.group_get(index)
+ if o == None:
+ raise IndexError("Group %s not found", index)
+ else:
+ db_api.group_delete(index)
+ print 'Group', index, 'deleted.'
+
+ except Exception, e:
+ print 'Error deleting group', index, str(e)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/management/getgroup.py b/management/getgroup.py
index 38a6b8f2..9bb0e41f 100644..100755
--- a/management/getgroup.py
+++ b/management/getgroup.py
@@ -1,40 +1,52 @@
-# Copyright (c) 2010-2011 OpenStack, LLC.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
-# implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import optparse
-import keystone.db.sqlalchemy.api as db_api
-
-
-def main():
- usage = "usage: %prog group_id"
- parser = optparse.OptionParser(usage)
- options, args = parser.parse_args()
- if len(args) != 1:
- parser.error("Incorrect number of arguments")
- else:
- index = args[0]
-
- try:
- o = db_api.group_get(index)
- if o == None:
- raise IndexError("Group %s not found", index)
-
- print o.id, o.desc
- except Exception, e:
- print 'Error getting group', index, str(e)
-
-
-if __name__ == '__main__':
- main()
+#!/usr/bin/env python
+# Copyright (c) 2010-2011 OpenStack, LLC.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+# implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import os
+import sys
+
+# If ../keystone/__init__.py exists, add ../ to Python search path, so that
+# it will override what happens to be installed in /usr/(local/)lib/python...
+possible_topdir = os.path.normpath(os.path.join(os.path.abspath(sys.argv[0]),
+ os.pardir,
+ os.pardir))
+if os.path.exists(os.path.join(possible_topdir, 'keystone', '__init__.py')):
+ sys.path.insert(0, possible_topdir)
+
+import optparse
+import keystone.db.sqlalchemy.api as db_api
+
+
+def main():
+ usage = "usage: %prog group_id"
+ parser = optparse.OptionParser(usage)
+ options, args = parser.parse_args()
+ if len(args) != 1:
+ parser.error("Incorrect number of arguments")
+ else:
+ index = args[0]
+
+ try:
+ o = db_api.group_get(index)
+ if o == None:
+ raise IndexError("Group %s not found", index)
+
+ print o.id, o.desc
+ except Exception, e:
+ print 'Error getting group', index, str(e)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/management/getgroups.py b/management/getgroups.py
index 501d948f..2fc94cb9 100644..100755
--- a/management/getgroups.py
+++ b/management/getgroups.py
@@ -1,37 +1,49 @@
-# Copyright (c) 2010-2011 OpenStack, LLC.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
-# implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import optparse
-import keystone.db.sqlalchemy.api as db_api
-
-
-def main():
- usage = "usage: %prog "
- parser = optparse.OptionParser(usage)
- options, args = parser.parse_args()
- if len(args) != 0:
- parser.error("Incorrect number of arguments")
- else:
- try:
- u = db_api.group_get_all()
- if u == None:
- raise IndexError("Groups not found")
- for row in u:
- print row.id
- except Exception, e:
- print 'Error getting groups:', str(e)
-
-if __name__ == '__main__':
- main()
+#!/usr/bin/env python
+# Copyright (c) 2010-2011 OpenStack, LLC.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+# implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import os
+import sys
+
+# If ../keystone/__init__.py exists, add ../ to Python search path, so that
+# it will override what happens to be installed in /usr/(local/)lib/python...
+possible_topdir = os.path.normpath(os.path.join(os.path.abspath(sys.argv[0]),
+ os.pardir,
+ os.pardir))
+if os.path.exists(os.path.join(possible_topdir, 'keystone', '__init__.py')):
+ sys.path.insert(0, possible_topdir)
+
+import optparse
+import keystone.db.sqlalchemy.api as db_api
+
+
+def main():
+ usage = "usage: %prog "
+ parser = optparse.OptionParser(usage)
+ options, args = parser.parse_args()
+ if len(args) != 0:
+ parser.error("Incorrect number of arguments")
+ else:
+ try:
+ u = db_api.group_get_all()
+ if u == None:
+ raise IndexError("Groups not found")
+ for row in u:
+ print row.id
+ except Exception, e:
+ print 'Error getting groups:', str(e)
+
+if __name__ == '__main__':
+ main()
diff --git a/management/getgroupusers.py b/management/getgroupusers.py
index 0fc7d231..1d663eb4 100644..100755
--- a/management/getgroupusers.py
+++ b/management/getgroupusers.py
@@ -1,39 +1,51 @@
-# Copyright (c) 2010-2011 OpenStack, LLC.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
-# implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import optparse
-import keystone.db.sqlalchemy.api as db_api
-
-
-def main():
- usage = "usage: %prog group_id"
- parser = optparse.OptionParser(usage)
- options, args = parser.parse_args()
- if len(args) != 1:
- parser.error("Incorrect number of arguments")
- else:
- group_id = args[0]
-
- try:
- g = db_api.group_users(group_id)
- if g == None:
- raise IndexError("Group users not found")
- for row in g:
- print row
- except Exception, e:
- print 'Error getting group users for group', group_id, ':', str(e)
-
-if __name__ == '__main__':
- main()
+#!/usr/bin/env python
+# Copyright (c) 2010-2011 OpenStack, LLC.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+# implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import os
+import sys
+
+# If ../keystone/__init__.py exists, add ../ to Python search path, so that
+# it will override what happens to be installed in /usr/(local/)lib/python...
+possible_topdir = os.path.normpath(os.path.join(os.path.abspath(sys.argv[0]),
+ os.pardir,
+ os.pardir))
+if os.path.exists(os.path.join(possible_topdir, 'keystone', '__init__.py')):
+ sys.path.insert(0, possible_topdir)
+
+import optparse
+import keystone.db.sqlalchemy.api as db_api
+
+
+def main():
+ usage = "usage: %prog group_id"
+ parser = optparse.OptionParser(usage)
+ options, args = parser.parse_args()
+ if len(args) != 1:
+ parser.error("Incorrect number of arguments")
+ else:
+ group_id = args[0]
+
+ try:
+ g = db_api.group_users(group_id)
+ if g == None:
+ raise IndexError("Group users not found")
+ for row in g:
+ print row
+ except Exception, e:
+ print 'Error getting group users for group', group_id, ':', str(e)
+
+if __name__ == '__main__':
+ main()
diff --git a/management/getuser.py b/management/getuser.py
index 036f10f4..af0ba89f 100644..100755
--- a/management/getuser.py
+++ b/management/getuser.py
@@ -1,37 +1,49 @@
-# Copyright (c) 2010-2011 OpenStack, LLC.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
-# implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import optparse
-import keystone.db.sqlalchemy.api as db_api
-
-
-def main():
- usage = "usage: %prog username"
- parser = optparse.OptionParser(usage)
- options, args = parser.parse_args()
- if len(args) != 1:
- parser.error("Incorrect number of arguments")
- else:
- username = args[0]
- try:
- u = db_api.user_get(username)
- if u == None:
- raise IndexError("User not found")
- print u.id, u.email, u.enabled
- except Exception, e:
- print 'Error finding user', username, ':', str(e)
-
-if __name__ == '__main__':
- main()
+#!/usr/bin/env python
+# Copyright (c) 2010-2011 OpenStack, LLC.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+# implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import os
+import sys
+
+# If ../keystone/__init__.py exists, add ../ to Python search path, so that
+# it will override what happens to be installed in /usr/(local/)lib/python...
+possible_topdir = os.path.normpath(os.path.join(os.path.abspath(sys.argv[0]),
+ os.pardir,
+ os.pardir))
+if os.path.exists(os.path.join(possible_topdir, 'keystone', '__init__.py')):
+ sys.path.insert(0, possible_topdir)
+
+import optparse
+import keystone.db.sqlalchemy.api as db_api
+
+
+def main():
+ usage = "usage: %prog username"
+ parser = optparse.OptionParser(usage)
+ options, args = parser.parse_args()
+ if len(args) != 1:
+ parser.error("Incorrect number of arguments")
+ else:
+ username = args[0]
+ try:
+ u = db_api.user_get(username)
+ if u == None:
+ raise IndexError("User not found")
+ print u.id, u.email, u.enabled
+ except Exception, e:
+ print 'Error finding user', username, ':', str(e)
+
+if __name__ == '__main__':
+ main()
diff --git a/management/getusergroups.py b/management/getusergroups.py
index 3fd7d3f9..33801a93 100644..100755
--- a/management/getusergroups.py
+++ b/management/getusergroups.py
@@ -1,38 +1,50 @@
-# Copyright (c) 2010-2011 OpenStack, LLC.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
-# implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import optparse
-import keystone.db.sqlalchemy.api as db_api
-
-
-def main():
- usage = "usage: %prog user_id"
- parser = optparse.OptionParser(usage)
- options, args = parser.parse_args()
- if len(args) != 1:
- parser.error("Incorrect number of arguments")
- else:
- username = args[0]
- try:
- g = db_api.user_groups(username)
- if g == None:
- raise IndexError("User groups not found")
- for row in g:
- print row
- except Exception, e:
- print 'Error getting user groups for user', user_id, ':', str(e)
-
-if __name__ == '__main__':
- main()
+#!/usr/bin/env python
+# Copyright (c) 2010-2011 OpenStack, LLC.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+# implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import os
+import sys
+
+# If ../keystone/__init__.py exists, add ../ to Python search path, so that
+# it will override what happens to be installed in /usr/(local/)lib/python...
+possible_topdir = os.path.normpath(os.path.join(os.path.abspath(sys.argv[0]),
+ os.pardir,
+ os.pardir))
+if os.path.exists(os.path.join(possible_topdir, 'keystone', '__init__.py')):
+ sys.path.insert(0, possible_topdir)
+
+import optparse
+import keystone.db.sqlalchemy.api as db_api
+
+
+def main():
+ usage = "usage: %prog user_id"
+ parser = optparse.OptionParser(usage)
+ options, args = parser.parse_args()
+ if len(args) != 1:
+ parser.error("Incorrect number of arguments")
+ else:
+ username = args[0]
+ try:
+ g = db_api.user_groups(username)
+ if g == None:
+ raise IndexError("User groups not found")
+ for row in g:
+ print row
+ except Exception, e:
+ print 'Error getting user groups for user', user_id, ':', str(e)
+
+if __name__ == '__main__':
+ main()
diff --git a/management/getusers.py b/management/getusers.py
deleted file mode 100644
index 18a5de56..00000000
--- a/management/getusers.py
+++ /dev/null
@@ -1,38 +0,0 @@
-# Copyright (c) 2010-2011 OpenStack, LLC.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
-# implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import optparse
-import keystone.db.sqlalchemy.api as db_api
-
-
-def main():
- usage = "usage: %prog tenant_id"
- parser = optparse.OptionParser(usage)
- options, args = parser.parse_args()
- if len(args) != 1:
- parser.error("Incorrect number of arguments")
- else:
- tenant_id = args[0]
- try:
- u = db_api.user_get_by_tenant(tenant_id)
- if u == None:
- raise IndexError("Users not found")
- for row in u:
- print row
- except Exception, e:
- print 'Error getting users for tenant', tenant_id, ':', str(e)
-
-if __name__ == '__main__':
- main()
diff --git a/management/groupadd.py b/management/groupadd.py
index adec58dc..11329e44 100644..100755
--- a/management/groupadd.py
+++ b/management/groupadd.py
@@ -1,40 +1,52 @@
-# Copyright (c) 2010-2011 OpenStack, LLC.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
-# implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import optparse
-import keystone.db.sqlalchemy.api as db_api
-from keystone.db.sqlalchemy import models
-
-
-def main():
- usage = "usage: %prog group_id group_desc"
- parser = optparse.OptionParser(usage)
- options, args = parser.parse_args()
- if len(args) != 2:
- parser.error("Incorrect number of arguments")
- else:
- group_id = args[0]
- group_desc = args[1]
- try:
- g = models.Group()
- g.id = group_id
- g.desc = group_desc
- db_api.group_create(g)
- print 'Group', g.id, 'created.'
- except Exception, e:
- print 'Error creating group', group_id, ':', str(e)
-
-if __name__ == '__main__':
- main()
+#!/usr/bin/env python
+# Copyright (c) 2010-2011 OpenStack, LLC.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+# implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import os
+import sys
+
+# If ../keystone/__init__.py exists, add ../ to Python search path, so that
+# it will override what happens to be installed in /usr/(local/)lib/python...
+possible_topdir = os.path.normpath(os.path.join(os.path.abspath(sys.argv[0]),
+ os.pardir,
+ os.pardir))
+if os.path.exists(os.path.join(possible_topdir, 'keystone', '__init__.py')):
+ sys.path.insert(0, possible_topdir)
+
+import optparse
+import keystone.db.sqlalchemy.api as db_api
+from keystone.db.sqlalchemy import models
+
+
+def main():
+ usage = "usage: %prog group_id group_desc"
+ parser = optparse.OptionParser(usage)
+ options, args = parser.parse_args()
+ if len(args) != 2:
+ parser.error("Incorrect number of arguments")
+ else:
+ group_id = args[0]
+ group_desc = args[1]
+ try:
+ g = models.Group()
+ g.id = group_id
+ g.desc = group_desc
+ db_api.group_create(g)
+ print 'Group', g.id, 'created.'
+ except Exception, e:
+ print 'Error creating group', group_id, ':', str(e)
+
+if __name__ == '__main__':
+ main()
diff --git a/management/setuserlock.py b/management/setuserlock.py
index 622d876b..7cfa8c15 100644..100755
--- a/management/setuserlock.py
+++ b/management/setuserlock.py
@@ -1,49 +1,61 @@
-# Copyright (c) 2010-2011 OpenStack, LLC.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
-# implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import optparse
-import keystone.db.sqlalchemy.api as db_api
-
-
-def main():
- usage = "usage: %prog username enabled"
- parser = optparse.OptionParser(usage)
- options, args = parser.parse_args()
- if len(args) != 2:
- parser.error("Incorrect number of arguments")
- else:
- username = args[0]
- enabled = args[1].capitalize().strip()
-
- if enabled == 'True' or enabled == '1':
- enabled = 1
- elif enabled == 'False' or enabled == '0':
- enabled = 0
- else:
- parser.error("Incorrect arguments value")
-
- try:
- u = db_api.user_get(username)
- if u == None:
- raise IndexError("User not found")
- else:
- values = {'enabled': enabled}
- db_api.user_update(username, values)
- print 'User', u.id, 'updated. Enabled =', enabled
- except Exception, e:
- print 'Error updating user', username, ':', str(e)
-
-if __name__ == '__main__':
- main()
+#!/usr/bin/env python
+# Copyright (c) 2010-2011 OpenStack, LLC.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+# implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import os
+import sys
+
+# If ../keystone/__init__.py exists, add ../ to Python search path, so that
+# it will override what happens to be installed in /usr/(local/)lib/python...
+possible_topdir = os.path.normpath(os.path.join(os.path.abspath(sys.argv[0]),
+ os.pardir,
+ os.pardir))
+if os.path.exists(os.path.join(possible_topdir, 'keystone', '__init__.py')):
+ sys.path.insert(0, possible_topdir)
+
+import optparse
+import keystone.db.sqlalchemy.api as db_api
+
+
+def main():
+ usage = "usage: %prog username enabled"
+ parser = optparse.OptionParser(usage)
+ options, args = parser.parse_args()
+ if len(args) != 2:
+ parser.error("Incorrect number of arguments")
+ else:
+ username = args[0]
+ enabled = args[1].capitalize().strip()
+
+ if enabled == 'True' or enabled == '1':
+ enabled = 1
+ elif enabled == 'False' or enabled == '0':
+ enabled = 0
+ else:
+ parser.error("Incorrect arguments value")
+
+ try:
+ u = db_api.user_get(username)
+ if u == None:
+ raise IndexError("User not found")
+ else:
+ values = {'enabled': enabled}
+ db_api.user_update(username, values)
+ print 'User', u.id, 'updated. Enabled =', enabled
+ except Exception, e:
+ print 'Error updating user', username, ':', str(e)
+
+if __name__ == '__main__':
+ main()
diff --git a/management/setuserpswd.py b/management/setuserpswd.py
index 6283bf73..abfddbda 100644..100755
--- a/management/setuserpswd.py
+++ b/management/setuserpswd.py
@@ -1,41 +1,53 @@
-# Copyright (c) 2010-2011 OpenStack, LLC.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
-# implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import optparse
-import keystone.db.sqlalchemy.api as db_api
-
-
-def main():
- usage = "usage: %prog username password"
- parser = optparse.OptionParser(usage)
- options, args = parser.parse_args()
- if len(args) != 2:
- parser.error("Incorrect number of arguments")
- else:
- username = args[0]
- password = args[1]
- try:
- u = db_api.user_get(username)
- if u == None:
- raise IndexError("User not found")
- else:
- values = {'password': password}
- db_api.user_update(username, values)
- print 'User', u.id, 'updated.'
- except Exception, e:
- print 'Error updating user', username, ':', str(e)
-
-if __name__ == '__main__':
- main()
+#!/usr/bin/env python
+# Copyright (c) 2010-2011 OpenStack, LLC.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+# implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import os
+import sys
+
+# If ../keystone/__init__.py exists, add ../ to Python search path, so that
+# it will override what happens to be installed in /usr/(local/)lib/python...
+possible_topdir = os.path.normpath(os.path.join(os.path.abspath(sys.argv[0]),
+ os.pardir,
+ os.pardir))
+if os.path.exists(os.path.join(possible_topdir, 'keystone', '__init__.py')):
+ sys.path.insert(0, possible_topdir)
+
+import optparse
+import keystone.db.sqlalchemy.api as db_api
+
+
+def main():
+ usage = "usage: %prog username password"
+ parser = optparse.OptionParser(usage)
+ options, args = parser.parse_args()
+ if len(args) != 2:
+ parser.error("Incorrect number of arguments")
+ else:
+ username = args[0]
+ password = args[1]
+ try:
+ u = db_api.user_get(username)
+ if u == None:
+ raise IndexError("User not found")
+ else:
+ values = {'password': password}
+ db_api.user_update(username, values)
+ print 'User', u.id, 'updated.'
+ except Exception, e:
+ print 'Error updating user', username, ':', str(e)
+
+if __name__ == '__main__':
+ main()
diff --git a/management/updategroup.py b/management/updategroup.py
index 598d38ef..35b4ef66 100644..100755
--- a/management/updategroup.py
+++ b/management/updategroup.py
@@ -1,42 +1,54 @@
-# Copyright (c) 2010-2011 OpenStack, LLC.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
-# implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import optparse
-import keystone.db.sqlalchemy.api as db_api
-from keystone.db.sqlalchemy import models
-
-
-def main():
- usage = "usage: %prog group_id group_desc"
- parser = optparse.OptionParser(usage)
- options, args = parser.parse_args()
- if len(args) != 2:
- parser.error("Incorrect number of arguments")
- else:
- group = args[0]
- desc = args[1]
- try:
- g = db_api.group_get(group)
- if g == None:
- raise IndexError("Group not found")
- else:
- values = {'desc': desc}
- db_api.group_update(group, values)
- print 'Group', g.id, 'updated.'
- except Exception, e:
- print 'Error updating user', group, ':', str(e)
-
-if __name__ == '__main__':
- main()
+#!/usr/bin/env python
+# Copyright (c) 2010-2011 OpenStack, LLC.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+# implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import os
+import sys
+
+# If ../keystone/__init__.py exists, add ../ to Python search path, so that
+# it will override what happens to be installed in /usr/(local/)lib/python...
+possible_topdir = os.path.normpath(os.path.join(os.path.abspath(sys.argv[0]),
+ os.pardir,
+ os.pardir))
+if os.path.exists(os.path.join(possible_topdir, 'keystone', '__init__.py')):
+ sys.path.insert(0, possible_topdir)
+
+import optparse
+import keystone.db.sqlalchemy.api as db_api
+from keystone.db.sqlalchemy import models
+
+
+def main():
+ usage = "usage: %prog group_id group_desc"
+ parser = optparse.OptionParser(usage)
+ options, args = parser.parse_args()
+ if len(args) != 2:
+ parser.error("Incorrect number of arguments")
+ else:
+ group = args[0]
+ desc = args[1]
+ try:
+ g = db_api.group_get(group)
+ if g == None:
+ raise IndexError("Group not found")
+ else:
+ values = {'desc': desc}
+ db_api.group_update(group, values)
+ print 'Group', g.id, 'updated.'
+ except Exception, e:
+ print 'Error updating user', group, ':', str(e)
+
+if __name__ == '__main__':
+ main()
diff --git a/management/useradd.py b/management/useradd.py
deleted file mode 100644
index fc3fc8b6..00000000
--- a/management/useradd.py
+++ /dev/null
@@ -1,42 +0,0 @@
-# Copyright (c) 2010-2011 OpenStack, LLC.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
-# implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import optparse
-import keystone.db.sqlalchemy.api as db_api
-from keystone.db.sqlalchemy import models
-
-
-def main():
- usage = "usage: %prog username password"
- parser = optparse.OptionParser(usage)
- options, args = parser.parse_args()
- if len(args) != 2:
- parser.error("Incorrect number of arguments")
- else:
- username = args[0]
- password = args[1]
- try:
- u = models.User()
- u.id = username
- u.email = username
- u.password = password
- u.enabled = True
- db_api.user_create(u)
- print 'User', u.id, 'created.'
- except Exception, e:
- print 'Error creating user', username, ':', str(e)
-
-if __name__ == '__main__':
- main()
diff --git a/management/userdel.py b/management/userdel.py
index 928dfc55..f76bc632 100644..100755
--- a/management/userdel.py
+++ b/management/userdel.py
@@ -1,39 +1,51 @@
-# Copyright (c) 2010-2011 OpenStack, LLC.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
-# implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import optparse
-import keystone.db.sqlalchemy.api as db_api
-
-
-def main():
- usage = "usage: %prog username"
- parser = optparse.OptionParser(usage)
- options, args = parser.parse_args()
- if len(args) != 1:
- parser.error("Incorrect number of arguments")
- else:
- username = args[0]
- try:
- u = db_api.user_get(username)
- if u == None:
- raise IndexError("User not found")
- else:
- db_api.user_delete(username)
- print 'User', username, 'deleted.'
- except Exception, e:
- print 'Error deleting user', username, ':', str(e)
-
-if __name__ == '__main__':
- main()
+#!/usr/bin/env python
+# Copyright (c) 2010-2011 OpenStack, LLC.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+# implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import os
+import sys
+
+# If ../keystone/__init__.py exists, add ../ to Python search path, so that
+# it will override what happens to be installed in /usr/(local/)lib/python...
+possible_topdir = os.path.normpath(os.path.join(os.path.abspath(sys.argv[0]),
+ os.pardir,
+ os.pardir))
+if os.path.exists(os.path.join(possible_topdir, 'keystone', '__init__.py')):
+ sys.path.insert(0, possible_topdir)
+
+import optparse
+import keystone.db.sqlalchemy.api as db_api
+
+
+def main():
+ usage = "usage: %prog username"
+ parser = optparse.OptionParser(usage)
+ options, args = parser.parse_args()
+ if len(args) != 1:
+ parser.error("Incorrect number of arguments")
+ else:
+ username = args[0]
+ try:
+ u = db_api.user_get(username)
+ if u == None:
+ raise IndexError("User not found")
+ else:
+ db_api.user_delete(username)
+ print 'User', username, 'deleted.'
+ except Exception, e:
+ print 'Error deleting user', username, ':', str(e)
+
+if __name__ == '__main__':
+ main()
diff --git a/management/userupdate.py b/management/userupdate.py
index 89174d04..5196d681 100644..100755
--- a/management/userupdate.py
+++ b/management/userupdate.py
@@ -1,41 +1,53 @@
-# Copyright (c) 2010-2011 OpenStack, LLC.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
-# implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import optparse
-import keystone.db.sqlalchemy.api as db_api
-
-
-def main():
- usage = "usage: %prog username email"
- parser = optparse.OptionParser(usage)
- options, args = parser.parse_args()
- if len(args) != 2:
- parser.error("Incorrect number of arguments")
- else:
- username = args[0]
- email = args[1]
- try:
- u = db_api.user_get(username)
- if u == None:
- raise IndexError("User not found")
- else:
- values = {'email': email}
- db_api.user_update(username, values)
- print 'User', u.id, 'updated.'
- except Exception, e:
- print 'Error updating user', username, ':', str(e)
-
-if __name__ == '__main__':
- main()
+#!/usr/bin/env python
+# Copyright (c) 2010-2011 OpenStack, LLC.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+# implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import os
+import sys
+
+# If ../keystone/__init__.py exists, add ../ to Python search path, so that
+# it will override what happens to be installed in /usr/(local/)lib/python...
+possible_topdir = os.path.normpath(os.path.join(os.path.abspath(sys.argv[0]),
+ os.pardir,
+ os.pardir))
+if os.path.exists(os.path.join(possible_topdir, 'keystone', '__init__.py')):
+ sys.path.insert(0, possible_topdir)
+
+import optparse
+import keystone.db.sqlalchemy.api as db_api
+
+
+def main():
+ usage = "usage: %prog username email"
+ parser = optparse.OptionParser(usage)
+ options, args = parser.parse_args()
+ if len(args) != 2:
+ parser.error("Incorrect number of arguments")
+ else:
+ username = args[0]
+ email = args[1]
+ try:
+ u = db_api.user_get(username)
+ if u == None:
+ raise IndexError("User not found")
+ else:
+ values = {'email': email}
+ db_api.user_update(username, values)
+ print 'User', u.id, 'updated.'
+ except Exception, e:
+ print 'Error updating user', username, ':', str(e)
+
+if __name__ == '__main__':
+ main()
diff --git a/setup.py b/setup.py
index 257f5f65..a3e4a414 100644
--- a/setup.py
+++ b/setup.py
@@ -30,13 +30,14 @@ setup(
url='http://www.openstack.org',
include_package_data=True,
packages=find_packages(exclude=['test', 'bin']),
- scripts=['bin/keystone']
+ scripts=['bin/keystone, bin/leystone-auth, bin/keystone-admin'
+ ', bin/keystone-manage'],
zip_safe=False,
install_requires=['setuptools'],
entry_points={
'paste.app_factory': ['main=identity:app_factory'],
'paste.filter_factory': [
- 'papiauth=keystone:papiauth_factory',
+ 'remoteauth=keystone:remoteauth_factory',
'tokenauth=keystone:tokenauth_factory',
],
},
diff --git a/test/unit/test_authentication.py b/test/unit/test_authentication.py
index a6531473..2cfd8e31 100644
--- a/test/unit/test_authentication.py
+++ b/test/unit/test_authentication.py
@@ -55,6 +55,11 @@ class AuthenticationTest(unittest.TestCase):
self.assertEqual(200, int(resp['status']))
self.assertEqual('application/xml', utils.content_type(resp))
+ def test_a_authorize_legacy(self):
+ resp, content = utils.get_token_legacy('joeuser', 'secrete')
+ self.assertEqual(204, int(resp['status']))
+ self.assertTrue(resp['x-auth-token'])
+
def test_a_authorize_user_disabled(self):
header = httplib2.Http(".cache")
url = '%stoken' % utils.URL
@@ -153,7 +158,7 @@ class MultiToken(unittest.TestCase):
utils.delete_user('test_tenant2', 'test_user1', self.auth_token)
utils.delete_tenant('test_tenant1', self.auth_token)
utils.delete_tenant('test_tenant2', self.auth_token)
-
+
""" INVALID TEST - we're changing how we delegate access to second tenant
def test_multi_token(self):
#get token for user1 with tenant1
@@ -170,7 +175,7 @@ class MultiToken(unittest.TestCase):
resp = utils.delete_token(token1, self.auth_token)
resp = utils.delete_token(token2, self.auth_token)
"""
-
+
def test_unassigned_user(self):
resp, content = utils.get_token('test_user2', 'secrete', \
'test_tenant2')
diff --git a/test/unit/test_common.py b/test/unit/test_common.py
index 07c46f50..800f1a7c 100644
--- a/test/unit/test_common.py
+++ b/test/unit/test_common.py
@@ -24,15 +24,13 @@ sys.path.append(os.path.abspath(os.path.join(os.path.abspath(__file__),
'..', '..', '..', '..', 'keystone')))
import unittest
-
-
-URL = 'http://localhost:8080/v2.0/'
-
+URL = 'http://localhost:8081/v2.0/'
+URLv1 = 'http://localhost:8081/v1.0/'
def get_token(user, pswd, tenant_id, kind=''):
header = httplib2.Http(".cache")
url = '%stoken' % URL
- # to test multi token, removing below code
+
if not tenant_id:
body = {"passwordCredentials": {"username": user,
"password": pswd}}
@@ -40,7 +38,7 @@ def get_token(user, pswd, tenant_id, kind=''):
body = {"passwordCredentials": {"username": user,
"password": pswd,
"tenantId": tenant_id}}
-
+
resp, content = header.request(url, "POST", body=json.dumps(body),
headers={"Content-Type": "application/json"})
@@ -55,6 +53,23 @@ def get_token(user, pswd, tenant_id, kind=''):
return (resp, content)
+def get_token_legacy(user, pswd, kind=''):
+ header = httplib2.Http(".cache")
+ url = URLv1
+ resp, content = header.request(url, "GET", '',
+ headers={"Content-Type": "application/json",
+ "X-Auth-User": user,
+ "X-Auth-Key": pswd})
+
+ if int(resp['status']) == 204:
+ token = resp['x-auth-token']
+ else:
+ token = None
+ if kind == 'token':
+ return token
+ else:
+ return (resp, content)
+
def delete_token(token, auth_token):
header = httplib2.Http(".cache")
@@ -180,7 +195,8 @@ def get_token_xml(user, pswd, tenant_id, type=''):
"ACCEPT": "application/xml"})
if int(resp['status']) == 200:
dom = etree.fromstring(content)
- root = dom.find("{http://docs.openstack.org/identity/api/v2.0}token")
+ root = dom.find("{http://docs.openstack.org/" \
+ "identity/api/v2.0}token")
token_root = root.attrib
token = str(token_root['id'])
else:
@@ -318,6 +334,7 @@ def delete_user_xml(tenantid, userid, auth_token):
"ACCEPT": "application/xml"})
return resp
+
def add_user_json(tenantid, userid, auth_token):
header = httplib2.Http(".cache")
url = '%stenants/%s/users/%s/add' % (URL, tenantid, userid)
@@ -676,7 +693,8 @@ def handle_user_resp(self, content, respvalue, resptype):
if respvalue == 200:
if resptype == 'application/json':
content = json.loads(content)
- self.tenant = content['user']['tenantId']
+ if 'tenantId' in content['user']:
+ self.tenant = content['user']['tenantId']
self.userid = content['user']['id']
if resptype == 'application/xml':
content = etree.fromstring(content)
@@ -686,7 +704,3 @@ def handle_user_resp(self, content, respvalue, resptype):
self.fail('Identity Fault')
elif respvalue == 503:
self.fail('Service Not Available')
-
-
-def content_type(resp):
- return resp['content-type'].split(';')[0]
diff --git a/test/unit/test_exthandler.py b/test/unit/test_exthandler.py
index 5d41aeac..4379e03f 100644
--- a/test/unit/test_exthandler.py
+++ b/test/unit/test_exthandler.py
@@ -1,3 +1,19 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+# Copyright (c) 2010-2011 OpenStack, LLC.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+# implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
import os
import sys
# Need to access identity module
diff --git a/tools/__init__.py b/tools/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/tools/__init__.py
diff --git a/tools/tracer.py b/tools/tracer.py
new file mode 100644
index 00000000..27d52eb2
--- /dev/null
+++ b/tools/tracer.py
@@ -0,0 +1,108 @@
+#!/usr/bin/env python
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2010 United States Government as represented by the
+# Administrator of the National Aeronautics and Space Administration.
+# Copyright 2011 OpenStack LLC.
+# All Rights Reserved.
+#
+# 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.
+#
+# Author: Ziad Sawalha (http://launchpad.net/~ziad-sawalha)
+
+"""
+OpenStack Call Tracing Tool
+
+To use this:
+1. include the tools dirextory in your project (__init__.py and tracer.py)
+2. import tools.tracer as early as possible into your module
+3. add --trace-calls to any argument parsers you use so the argument doesn't get
+flagged as invalid.
+
+Usage:
+# Add this as early as possible in the first module called in your service
+import tools.tracer #load this first
+
+If a '--trace-calls' parameter is found, it will trace calls to the console and
+space them to show the call graph.
+
+"""
+
+import os
+import sys
+
+
+if '--trace-calls' in sys.argv:
+ stack_depth = 0
+
+ def localtrace(frame, event, arg):
+ global stack_depth
+ if event == "return":
+ stack_depth = stack_depth - 1
+ elif event == "exception":
+ co = frame.f_code
+ func_name = co.co_name
+ line_no = frame.f_lineno
+ filename = co.co_filename
+ exc_type, exc_value, exc_traceback = arg
+ print '\033[91m%sERROR: %s %s on line %s of %s\033[0m' % \
+ (' ' * stack_depth, exc_type.__name__, exc_value, line_no,
+ func_name)
+ return None
+
+ def selectivetrace(frame, event, arg):
+ global stack_depth
+ if event == "exception":
+ co = frame.f_code
+ func_name = co.co_name
+ line_no = frame.f_lineno
+ filename = co.co_filename
+ exc_type, exc_value, exc_traceback = arg
+ print '\033[91m%sERROR: %s %s on line %s of %s\033[0m' % \
+ (' ' * stack_depth, exc_type.__name__, exc_value, line_no,
+ func_name)
+ if event != 'call':
+ return
+ co = frame.f_code
+ func_name = co.co_name
+ if func_name == 'write':
+ # Ignore write() calls from print statements
+ return
+ func_filename = co.co_filename
+ if func_filename == "<string>":
+ return
+ if func_filename.startswith("/System"):
+ return
+ if func_filename.startswith("/Library"):
+ return
+ if 'macosx' in func_filename:
+ return
+ func_line_no = frame.f_lineno
+ # If ../../keystone/__init__.py exists, add ../ to Python search path, so that
+ # it will override what happens to be installed in /usr/(local/)lib/python...
+ possible_topdir = os.path.normpath(os.path.join(os.path.abspath(sys.argv[0]),
+ os.pardir,
+ os.pardir))
+ func_filename = func_filename.replace(possible_topdir, '')
+ caller = frame.f_back
+ caller_line_no = caller.f_lineno
+ caller_filename = caller.f_code.co_filename.replace(possible_topdir,
+ '')
+ print '%s%s::%s:%s (from %s:%s)' % \
+ (' ' * stack_depth, func_filename, func_name, func_line_no,
+ caller_filename, caller_line_no)
+ stack_depth = stack_depth + 1
+ return localtrace
+
+ sys.settrace(selectivetrace)
+ print 'Starting OpenStack call tracer'