From 1623835e91ac71acdb20c92878aedb90c3da4ec4 Mon Sep 17 00:00:00 2001 From: Ziad Sawalha Date: Sat, 30 Apr 2011 13:24:34 -0500 Subject: Annotate TODOs --- docs/guide/src/docbkx/xsd/atom/atom.xsd | 10 +++---- keystone/auth_protocols/auth_basic.py | 2 +- keystone/auth_protocols/auth_openid.py | 2 +- keystone/auth_protocols/auth_token.py | 10 +++---- keystone/identity.py | 8 ++++-- keystone/logic/service.py | 2 +- test/unit/test_identity.py | 47 ++++++++++++++++++++++++++------- 7 files changed, 56 insertions(+), 25 deletions(-) diff --git a/docs/guide/src/docbkx/xsd/atom/atom.xsd b/docs/guide/src/docbkx/xsd/atom/atom.xsd index a619efaa..c515c497 100644 --- a/docs/guide/src/docbkx/xsd/atom/atom.xsd +++ b/docs/guide/src/docbkx/xsd/atom/atom.xsd @@ -72,7 +72,7 @@ - TODO + TODO(Jorge) @@ -80,7 +80,7 @@ - TODO + TODO(Jorge) @@ -88,7 +88,7 @@ - TODO + TODO(Jorge) @@ -96,7 +96,7 @@ - TODO + TODO(Jorge) @@ -104,7 +104,7 @@ - TODO + TODO(Jorge) diff --git a/keystone/auth_protocols/auth_basic.py b/keystone/auth_protocols/auth_basic.py index 046ca08e..17f2261a 100644 --- a/keystone/auth_protocols/auth_basic.py +++ b/keystone/auth_protocols/auth_basic.py @@ -142,7 +142,7 @@ class AuthProtocol(object): ssl=(self.service_protocol == 'https')) resp = conn.getresponse() data = resp.read() - #TODO: use a more sophisticated proxy + #TODO(ziad): use a more sophisticated proxy # we are rewriting the headers now return Response(status=resp.status, body=data)(env, start_response) diff --git a/keystone/auth_protocols/auth_openid.py b/keystone/auth_protocols/auth_openid.py index ac9121f7..d68741df 100644 --- a/keystone/auth_protocols/auth_openid.py +++ b/keystone/auth_protocols/auth_openid.py @@ -85,7 +85,7 @@ class AuthProtocol(object): ssl=(self.service_protocol == 'https')) resp = conn.getresponse() data = resp.read() - #TODO: use a more sophisticated proxy + #TODO(ziad): use a more sophisticated proxy # we are rewriting the headers now return Response(status=resp.status, body=data)(env, start_response) diff --git a/keystone/auth_protocols/auth_token.py b/keystone/auth_protocols/auth_token.py index fbc6c622..4c5c5e9c 100644 --- a/keystone/auth_protocols/auth_token.py +++ b/keystone/auth_protocols/auth_token.py @@ -113,7 +113,7 @@ class AuthProtocol(object): def __init__(self, app, conf): """ Common initialization code """ - #TODO: maybe we rafactor this into a superclass + #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 @@ -209,7 +209,7 @@ class AuthProtocol(object): # Step 1: We need to auth with the keystone service, so get an # admin token - #TODO: Need to properly implement this, where to store creds + #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"] @@ -220,7 +220,7 @@ class AuthProtocol(object): headers = {"Content-type": "application/json", "Accept": "text/json", "X-Auth-Token": self.admin_token} - ##TODO:we need to figure out how to auth to keystone + ##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} @@ -246,7 +246,7 @@ class AuthProtocol(object): headers = {"Content-type": "application/json", "Accept": "text/json", "X-Auth-Token": self.admin_token} - ##TODO:we need to figure out how to auth to keystone + ##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} @@ -294,7 +294,7 @@ class AuthProtocol(object): ssl=(self.service_protocol == 'https')) resp = conn.getresponse() data = resp.read() - #TODO: use a more sophisticated proxy + #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) diff --git a/keystone/identity.py b/keystone/identity.py index 0e2e3765..2d9a7001 100644 --- a/keystone/identity.py +++ b/keystone/identity.py @@ -299,7 +299,11 @@ def get_extension(ext_alias): # raise fault.ItemNotFoundFault("The extension is not found") +def start_server(port=8080): + app = exthandler.UrlExtensionFilter(bottle.default_app(), None) + wsgi.server(eventlet.listen(('', port)), app) if __name__ == "__main__": - app = exthandler.UrlExtensionFilter(bottle.default_app(), None) - wsgi.server(eventlet.listen(('', 8080)), app) + start_server() + + diff --git a/keystone/logic/service.py b/keystone/logic/service.py index cd0b5237..87aba159 100644 --- a/keystone/logic/service.py +++ b/keystone/logic/service.py @@ -47,7 +47,7 @@ class IDMService(object): # # Look for an existing token, or create one, - # TODO: Handle tenant/token search + # TODO(Jorge): Handle tenant/token search # dtoken = db_api.token_for_user(duser.id) if not dtoken or dtoken.expires < datetime.now(): diff --git a/test/unit/test_identity.py b/test/unit/test_identity.py index f8dca4a1..5f68e4a4 100644 --- a/test/unit/test_identity.py +++ b/test/unit/test_identity.py @@ -1,16 +1,31 @@ +# 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 httplib2 +import json +from lxml import etree import os import sys +from webtest import TestApp +import unittest + # Need to access identity module sys.path.append(os.path.abspath(os.path.join(os.path.abspath(__file__), '..', '..', '..', '..', 'keystone'))) from keystone import identity -import unittest -from webtest import TestApp -import httplib2 -import json -from lxml import etree -import unittest -from webtest import TestApp URL = 'http://localhost:8080/v1.0/' @@ -146,7 +161,7 @@ class identity_test(unittest.TestCase): #Given _a_ to make inherited test cases in an order. #here to call below method will call as last test case - def test_a_get_version(self): + def test_get_version_json(self): h = httplib2.Http(".cache") url = URL resp, content = h.request(url, "GET", body="", @@ -154,7 +169,7 @@ class identity_test(unittest.TestCase): self.assertEqual(200, int(resp['status'])) self.assertEqual('application/json', resp['content-type']) - def test_a_get_version(self): + def test_get_version_xml(self): h = httplib2.Http(".cache") url = URL resp, content = h.request(url, "GET", body="", @@ -971,5 +986,17 @@ class delete_tenant_test(tenant_test): self.assertEqual(204, int(resp['status'])) +def setup(): + pass + + +def teardown(): + pass + + if __name__ == '__main__': - unittest.main() + setup() + try: + unittest.main() + finally: + teardown() -- cgit From 4368fcc01578e2fb3f02b670f9e5e5e00b221e20 Mon Sep 17 00:00:00 2001 From: Ziad Sawalha Date: Thu, 5 May 2011 06:57:39 -0500 Subject: Adding router to requires. Updating standards in HACKING. Removing schema (generated from ORM) --- HACKING | 29 ++++++++++++++++++++--------- README.md | 10 ---------- pip-requires | 1 + 3 files changed, 21 insertions(+), 19 deletions(-) diff --git a/HACKING b/HACKING index e58d60e5..4841e6d9 100644 --- a/HACKING +++ b/HACKING @@ -1,5 +1,5 @@ -Nova Style Commandments -======================= +Keystone Style Commandments (pilfered from Nova and added to) +============================================================= Step 1: Read http://www.python.org/dev/peps/pep-0008/ Step 2: Read http://www.python.org/dev/peps/pep-0008/ again @@ -16,7 +16,7 @@ Imports # vim: tabstop=4 shiftwidth=4 softtabstop=4 {{stdlib imports in human alphabetical order}} \n - {{nova imports in human alphabetical order}} + {{OpenStack/Keystone imports in human alphabetical order}} \n \n {{begin your code}} @@ -27,8 +27,9 @@ General - thou shalt put two newlines twixt toplevel code (funcs, classes, etc) - thou shalt put one newline twixt methods in classes and anywhere else - thou shalt not write "except:", use "except Exception:" at the very least -- thou shalt include your name with TODOs as in "TODO(termie)" +- thou shalt include your name with TODOs as in "TODO(waldo)" - thou shalt not name anything the same name as a builtin or reserved word +- thou shouldeth comment profusely - thou shalt not violate causality in our time cone, or else @@ -42,14 +43,12 @@ Human Alphabetical Order Examples import time import unittest - from nova import flags - from nova import test - from nova.auth import users - from nova.endpoint import api - from nova.endpoint import cloud + import keystone.logic.types.fault as fault + import keystone.db.sqlalchemy.api as db_api Docstrings ---------- +Add them to modules, classes, and functions: """Summary of the function, class or method, less than 80 characters. New paragraph after newline that explains in more detail any general @@ -66,3 +65,15 @@ Docstrings :returns: description of the return value """ + +Done/Done Criteria +------------------ +How we define our code is done and ready for release: +1. PEP-8 compliance +2. pylint (same rules as Nova) +3. McCabe 10 or less +4. 65.258% test coverage +5. All functional and unit tests pass +6. Q/A Approval (if applicable - it is for Rackspace Integration dev teams) +7. No sev A bugs (this shoud have been #1) + diff --git a/README.md b/README.md index fa925ac4..54af5cc2 100644 --- a/README.md +++ b/README.md @@ -151,13 +151,3 @@ For more on unit testing please refer python test_identity --help - -DATABASE SCHEMA ---------------- - - CREATE TABLE groups(group_id varchar(255),group_desc varchar(255),tenant_id varchar(255),FOREIGN KEY(tenant_id) REFERENCES tenant(tenant_id)); - CREATE TABLE tenants(tenant_id varchar(255), tenant_desc varchar(255), tenant_enabled INTEGER, PRIMARY KEY(tenant_id ASC)); - CREATE TABLE token(token_id varchar(255),user_id varchar(255),expires datetime,tenant_id varchar(255)); - CREATE TABLE user_group(user_id varchar(255),group_id varchar(255), FOREIGN KEY(user_id) REFERENCES user(id), FOREIGN KEY(group_id) REFERENCES groups(group_id)); - CREATE TABLE user_tenant(tenant_id varchar(255),user_id varchar(255),FOREIGN KEY(tenant_id) REFERENCES tenant(tenant_id),FOREIGN KEY(user_id) REFERENCES user(id)); - CREATE TABLE users(id varchar(255),password varchar(255),email varchar(255),enabled integer); diff --git a/pip-requires b/pip-requires index 1ef8484b..6eb85607 100644 --- a/pip-requires +++ b/pip-requires @@ -5,5 +5,6 @@ paste pastedeploy pastescript pysqlite +routes sqlalchemy webob -- cgit From 6f9c644f5a55fee3ae34277d4571e5df1e3e44a9 Mon Sep 17 00:00:00 2001 From: Ziad Sawalha Date: Thu, 5 May 2011 07:42:43 -0500 Subject: Renamed identity.py to server.py and added bin directory --- bin/keystoned | 26 +++++ keystone/server.py | 309 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 335 insertions(+) create mode 100755 bin/keystoned create mode 100644 keystone/server.py diff --git a/bin/keystoned b/bin/keystoned new file mode 100755 index 00000000..f336ca1d --- /dev/null +++ b/bin/keystoned @@ -0,0 +1,26 @@ +#!/bin/sh +# 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. + +# If ../keystone/__init__.py exists, add ../ to the Python search path so +# that it will override whatever may be installed in the default Python +# search path. +script_dir=`dirname $0` +if [ -f "$script_dir/../keystone/__init__.py" ] +then + PYTHONPATH="$script_dir/..:$PYTHONPATH" + export PYTHONPATH +fi + +/usr/bin/env python -m keystone.server $* diff --git a/keystone/server.py b/keystone/server.py new file mode 100644 index 00000000..2d9a7001 --- /dev/null +++ b/keystone/server.py @@ -0,0 +1,309 @@ +# 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. + + +""" +Service that stores identities and issues and manages tokens + +HEADERS +------- +HTTP_ is a standard http header +HTTP_X is an extended http header + +> Coming in from initial call +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 + +""" + +import functools +import logging +import os +import sys +import eventlet +from eventlet import wsgi + +import bottle +from bottle import request +from bottle import response +from queryext import exthandler + +# 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.logic.service as serv +import keystone.logic.types.auth as auth +import keystone.logic.types.tenant as tenants +import keystone.logic.types.fault as fault + +VERSION_STATUS = "ALPHA" +VERSION_DATE = "2011-04-23T00:00:00Z" + +bottle.debug(True) + +service = serv.IDMService() + +## +## Override error pages +## + + +@bottle.error(400) +@bottle.error(401) +@bottle.error(403) +@bottle.error(404) +@bottle.error(409) +@bottle.error(415) +@bottle.error(500) +@bottle.error(503) +def error_handler(err): + return err.output + + +def is_xml_response(): + if not "Accept" in request.header: + return False + return request.header["Accept"] == "application/xml" + + +def get_app_root(): + return os.path.abspath(os.path.dirname(__file__)) + + +def send_result(code, result): + content = None + response.content_type = None + if result: + if is_xml_response(): + content = result.to_xml() + response.content_type = "application/xml" + else: + content = result.to_json() + response.content_type = "application/json" + response.status = code + if code > 399: + return bottle.abort(code, content) + return content + + +def get_normalized_request_content(model): + """initialize a model from json/xml contents of request body""" + + ctype = request.environ.get("CONTENT_TYPE") + if ctype == "application/xml": + ret = model.from_xml(request.body.read()) + elif ctype == "application/json": + ret = model.from_json(request.body.read()) + else: + raise fault.IDMFault("I don't understand the content type ", code=415) + return ret + + +def get_auth_token(): + auth_token = None + if "X-Auth-Token" in request.header: + auth_token = request.header["X-Auth-Token"] + return auth_token + + +def wrap_error(func): + @functools.wraps(func) + def check_error(*args, **kwargs): + try: + return func(*args, **kwargs) + except Exception as err: + if isinstance(err, fault.IDMFault): + send_result(err.code, err) + else: + logging.exception(err) + send_result(500, fault.IDMFault("Unhandled error", str(err))) + return check_error + + +@bottle.route('/v1.0', method='GET') +@bottle.route('/v1.0/', method='GET') +@wrap_error +def get_version_info(): + if is_xml_response(): + resp_file = "content/version.xml" + response.content_type = "application/xml" + else: + resp_file = "content/version.json" + response.content_type = "application/json" + hostname = request.environ.get("SERVER_NAME") + port = request.environ.get("SERVER_PORT") + return bottle.template(resp_file, HOST=hostname, PORT=port, + VERSION_STATUS=VERSION_STATUS, + VERSION_DATE=VERSION_DATE) + +## +## Version links: +## + + +@bottle.route('/v1.0/idmdevguide.pdf', method='GET') +@wrap_error +def get_pdf_contract(): + return bottle.static_file("content/idmdevguide.pdf", + root=get_app_root(), + mimetype="application/pdf") + + +@bottle.route('/v1.0/identity.wadl', method='GET') +@wrap_error +def get_wadl_contract(): + return bottle.static_file("identity.wadl", + root=get_app_root(), + mimetype="application/vnd.sun.wadl+xml") + + +@bottle.route('/v1.0/xsd/:xsd', method='GET') +@wrap_error +def get_xsd_contract(xsd): + return bottle.static_file("/xsd/" + xsd, + root=get_app_root(), + mimetype="application/xml") + + +@bottle.route('/v1.0/xsd/atom/:xsd', method='GET') +@wrap_error +def get_xsd_atom_contract(xsd): + return bottle.static_file("/xsd/atom/" + xsd, + root=get_app_root(), + mimetype="application/xml") + +## +## Token Operations +## + + +@bottle.route('/v1.0/token', method='POST') +@wrap_error +def authenticate(): + creds = get_normalized_request_content(auth.PasswordCredentials) + return send_result(200, service.authenticate(creds)) + + +@bottle.route('/v1.0/token/:token_id', method='GET') +@wrap_error +def validate_token(token_id): + belongs_to = None + if "belongsTo" in request.GET: + belongs_to = request.GET["belongsTo"] + rval = service.validate_token(get_auth_token(), token_id, belongs_to) + return send_result(200, rval) + + +@bottle.route('/v1.0/token/:token_id', method='DELETE') +@wrap_error +def delete_token(token_id): + return send_result(204, + service.revoke_token(get_auth_token(), token_id)) + +## +## Tenant Operations +## + + +@bottle.route('/v1.0/tenants', method='POST') +@wrap_error +def create_tenant(): + tenant = get_normalized_request_content(tenants.Tenant) + return send_result(201, + service.create_tenant(get_auth_token(), tenant)) + + +@bottle.route('/v1.0/tenants', method='GET') +@wrap_error +def get_tenants(): + marker = None + if "marker" in request.GET: + marker = request.GET["marker"] + limit = None + if "limit" in request.GET: + limit = request.GET["limit"] + tenants = service.get_tenants(get_auth_token(), marker, limit) + return send_result(200, tenants) + + +@bottle.route('/v1.0/tenants/:tenant_id', method='GET') +@wrap_error +def get_tenant(tenant_id): + tenant = service.get_tenant(get_auth_token(), tenant_id) + return send_result(200, tenant) + + +@bottle.route('/v1.0/tenants/:tenant_id', method='PUT') +@wrap_error +def update_tenant(tenant_id): + tenant = get_normalized_request_content(tenants.Tenant) + rval = service.update_tenant(get_auth_token(), tenant_id, tenant) + return send_result(200, rval) + + +@bottle.route('/v1.0/tenants/:tenant_id', method='DELETE') +@wrap_error +def delete_tenant(tenant_id): + rval = service.delete_tenant(get_auth_token(), tenant_id) + return send_result(204, rval) + + +## +## Extensions +## + +@bottle.route('/v1.0/extensions', method='GET') +@wrap_error +def get_extensions(): + if is_xml_response(): + resp_file = "content/extensions.xml" + mimetype = "application/xml" + else: + resp_file = "content/extensions.json" + mimetype = "application/json" + return bottle.static_file(resp_file, + root=get_app_root(), + mimetype=mimetype) + + +@bottle.route('/v1.0/extensions/:ext_alias', method='GET') +@wrap_error +def get_extension(ext_alias): + # + # Todo: Define some extensions :-) + # + raise fault.ItemNotFoundFault("The extension is not found") + +def start_server(port=8080): + app = exthandler.UrlExtensionFilter(bottle.default_app(), None) + wsgi.server(eventlet.listen(('', port)), app) + +if __name__ == "__main__": + start_server() + + -- cgit From a0452fe1a376550bddd18987bd6d0d902eb649b4 Mon Sep 17 00:00:00 2001 From: Ziad Sawalha Date: Thu, 5 May 2011 07:50:29 -0500 Subject: Git problems - lingering commit --- README.md | 20 +-- keystone/identity.py | 309 --------------------------------------------- keystone/server.py | 5 +- test/unit/test_identity.py | 8 +- 4 files changed, 11 insertions(+), 331 deletions(-) delete mode 100644 keystone/identity.py diff --git a/README.md b/README.md index 54af5cc2..20b28557 100644 --- a/README.md +++ b/README.md @@ -41,27 +41,15 @@ SETUP: ------ Install http://pypi.python.org/pypi/setuptools - - sudo easy_install bottle - sudo easy_install eventlet - sudo easy_install lxml - sudo easy_install paste - sudo easy_install pastedeploy - sudo easy_install pastescript - sudo easy_install pysqlite - sudo easy_install sqlalchemy - sudo easy_install webob - -Or using pip: - + sudo easy_install pip sudo pip install -r pip-requires RUNNING KEYSTONE: ----------------- - $ cd keystone - $ python identity.py + $ cd bin + $ ./keystoned RUNNING TEST SERVICE: @@ -140,7 +128,7 @@ Unit Test on Identity Services ------------------------------ In order to run the unit test on identity services, run from the keystone directory - python identity.py + python server.py Once the Identity service is running, go to unit test/unit directory diff --git a/keystone/identity.py b/keystone/identity.py deleted file mode 100644 index 2d9a7001..00000000 --- a/keystone/identity.py +++ /dev/null @@ -1,309 +0,0 @@ -# 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. - - -""" -Service that stores identities and issues and manages tokens - -HEADERS -------- -HTTP_ is a standard http header -HTTP_X is an extended http header - -> Coming in from initial call -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 - -""" - -import functools -import logging -import os -import sys -import eventlet -from eventlet import wsgi - -import bottle -from bottle import request -from bottle import response -from queryext import exthandler - -# 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.logic.service as serv -import keystone.logic.types.auth as auth -import keystone.logic.types.tenant as tenants -import keystone.logic.types.fault as fault - -VERSION_STATUS = "ALPHA" -VERSION_DATE = "2011-04-23T00:00:00Z" - -bottle.debug(True) - -service = serv.IDMService() - -## -## Override error pages -## - - -@bottle.error(400) -@bottle.error(401) -@bottle.error(403) -@bottle.error(404) -@bottle.error(409) -@bottle.error(415) -@bottle.error(500) -@bottle.error(503) -def error_handler(err): - return err.output - - -def is_xml_response(): - if not "Accept" in request.header: - return False - return request.header["Accept"] == "application/xml" - - -def get_app_root(): - return os.path.abspath(os.path.dirname(__file__)) - - -def send_result(code, result): - content = None - response.content_type = None - if result: - if is_xml_response(): - content = result.to_xml() - response.content_type = "application/xml" - else: - content = result.to_json() - response.content_type = "application/json" - response.status = code - if code > 399: - return bottle.abort(code, content) - return content - - -def get_normalized_request_content(model): - """initialize a model from json/xml contents of request body""" - - ctype = request.environ.get("CONTENT_TYPE") - if ctype == "application/xml": - ret = model.from_xml(request.body.read()) - elif ctype == "application/json": - ret = model.from_json(request.body.read()) - else: - raise fault.IDMFault("I don't understand the content type ", code=415) - return ret - - -def get_auth_token(): - auth_token = None - if "X-Auth-Token" in request.header: - auth_token = request.header["X-Auth-Token"] - return auth_token - - -def wrap_error(func): - @functools.wraps(func) - def check_error(*args, **kwargs): - try: - return func(*args, **kwargs) - except Exception as err: - if isinstance(err, fault.IDMFault): - send_result(err.code, err) - else: - logging.exception(err) - send_result(500, fault.IDMFault("Unhandled error", str(err))) - return check_error - - -@bottle.route('/v1.0', method='GET') -@bottle.route('/v1.0/', method='GET') -@wrap_error -def get_version_info(): - if is_xml_response(): - resp_file = "content/version.xml" - response.content_type = "application/xml" - else: - resp_file = "content/version.json" - response.content_type = "application/json" - hostname = request.environ.get("SERVER_NAME") - port = request.environ.get("SERVER_PORT") - return bottle.template(resp_file, HOST=hostname, PORT=port, - VERSION_STATUS=VERSION_STATUS, - VERSION_DATE=VERSION_DATE) - -## -## Version links: -## - - -@bottle.route('/v1.0/idmdevguide.pdf', method='GET') -@wrap_error -def get_pdf_contract(): - return bottle.static_file("content/idmdevguide.pdf", - root=get_app_root(), - mimetype="application/pdf") - - -@bottle.route('/v1.0/identity.wadl', method='GET') -@wrap_error -def get_wadl_contract(): - return bottle.static_file("identity.wadl", - root=get_app_root(), - mimetype="application/vnd.sun.wadl+xml") - - -@bottle.route('/v1.0/xsd/:xsd', method='GET') -@wrap_error -def get_xsd_contract(xsd): - return bottle.static_file("/xsd/" + xsd, - root=get_app_root(), - mimetype="application/xml") - - -@bottle.route('/v1.0/xsd/atom/:xsd', method='GET') -@wrap_error -def get_xsd_atom_contract(xsd): - return bottle.static_file("/xsd/atom/" + xsd, - root=get_app_root(), - mimetype="application/xml") - -## -## Token Operations -## - - -@bottle.route('/v1.0/token', method='POST') -@wrap_error -def authenticate(): - creds = get_normalized_request_content(auth.PasswordCredentials) - return send_result(200, service.authenticate(creds)) - - -@bottle.route('/v1.0/token/:token_id', method='GET') -@wrap_error -def validate_token(token_id): - belongs_to = None - if "belongsTo" in request.GET: - belongs_to = request.GET["belongsTo"] - rval = service.validate_token(get_auth_token(), token_id, belongs_to) - return send_result(200, rval) - - -@bottle.route('/v1.0/token/:token_id', method='DELETE') -@wrap_error -def delete_token(token_id): - return send_result(204, - service.revoke_token(get_auth_token(), token_id)) - -## -## Tenant Operations -## - - -@bottle.route('/v1.0/tenants', method='POST') -@wrap_error -def create_tenant(): - tenant = get_normalized_request_content(tenants.Tenant) - return send_result(201, - service.create_tenant(get_auth_token(), tenant)) - - -@bottle.route('/v1.0/tenants', method='GET') -@wrap_error -def get_tenants(): - marker = None - if "marker" in request.GET: - marker = request.GET["marker"] - limit = None - if "limit" in request.GET: - limit = request.GET["limit"] - tenants = service.get_tenants(get_auth_token(), marker, limit) - return send_result(200, tenants) - - -@bottle.route('/v1.0/tenants/:tenant_id', method='GET') -@wrap_error -def get_tenant(tenant_id): - tenant = service.get_tenant(get_auth_token(), tenant_id) - return send_result(200, tenant) - - -@bottle.route('/v1.0/tenants/:tenant_id', method='PUT') -@wrap_error -def update_tenant(tenant_id): - tenant = get_normalized_request_content(tenants.Tenant) - rval = service.update_tenant(get_auth_token(), tenant_id, tenant) - return send_result(200, rval) - - -@bottle.route('/v1.0/tenants/:tenant_id', method='DELETE') -@wrap_error -def delete_tenant(tenant_id): - rval = service.delete_tenant(get_auth_token(), tenant_id) - return send_result(204, rval) - - -## -## Extensions -## - -@bottle.route('/v1.0/extensions', method='GET') -@wrap_error -def get_extensions(): - if is_xml_response(): - resp_file = "content/extensions.xml" - mimetype = "application/xml" - else: - resp_file = "content/extensions.json" - mimetype = "application/json" - return bottle.static_file(resp_file, - root=get_app_root(), - mimetype=mimetype) - - -@bottle.route('/v1.0/extensions/:ext_alias', method='GET') -@wrap_error -def get_extension(ext_alias): - # - # Todo: Define some extensions :-) - # - raise fault.ItemNotFoundFault("The extension is not found") - -def start_server(port=8080): - app = exthandler.UrlExtensionFilter(bottle.default_app(), None) - wsgi.server(eventlet.listen(('', port)), app) - -if __name__ == "__main__": - start_server() - - diff --git a/keystone/server.py b/keystone/server.py index 2d9a7001..19f24746 100644 --- a/keystone/server.py +++ b/keystone/server.py @@ -55,6 +55,7 @@ POSSIBLE_TOPDIR = os.path.normpath(os.path.join(os.path.abspath(sys.argv[0]), os.pardir)) if os.path.exists(os.path.join(POSSIBLE_TOPDIR, 'keystone', '__init__.py')): sys.path.insert(0, POSSIBLE_TOPDIR) +print POSSIBLE_TOPDIR import keystone.logic.service as serv import keystone.logic.types.auth as auth @@ -150,10 +151,10 @@ def wrap_error(func): @wrap_error def get_version_info(): if is_xml_response(): - resp_file = "content/version.xml" + resp_file = os.path.join(POSSIBLE_TOPDIR, "keystone/content/version.xml.tpl") response.content_type = "application/xml" else: - resp_file = "content/version.json" + resp_file = os.path.join(POSSIBLE_TOPDIR, "keystone/content/version.json.tpl") response.content_type = "application/json" hostname = request.environ.get("SERVER_NAME") port = request.environ.get("SERVER_PORT") diff --git a/test/unit/test_identity.py b/test/unit/test_identity.py index 5f68e4a4..05e0dd51 100644 --- a/test/unit/test_identity.py +++ b/test/unit/test_identity.py @@ -22,10 +22,10 @@ import sys from webtest import TestApp import unittest -# Need to access identity module +# Need to access server module sys.path.append(os.path.abspath(os.path.join(os.path.abspath(__file__), '..', '..', '..', '..', 'keystone'))) -from keystone import identity +from keystone import server URL = 'http://localhost:8080/v1.0/' @@ -156,7 +156,7 @@ def get_disabled_token(): return '999888777' -class identity_test(unittest.TestCase): +class server_test(unittest.TestCase): #Given _a_ to make inherited test cases in an order. #here to call below method will call as last test case @@ -179,7 +179,7 @@ class identity_test(unittest.TestCase): self.assertEqual('application/xml', resp['content-type']) -class authorize_test(identity_test): +class authorize_test(server_test): def setUp(self): self.token = get_token('joeuser', 'secrete', 'token') -- cgit