diff options
| author | root <root@bsirish.(none)> | 2011-05-02 13:26:46 +0530 |
|---|---|---|
| committer | root <root@bsirish.(none)> | 2011-05-02 13:26:46 +0530 |
| commit | 20ecd99441ba8fc3d58b05d55c08eb17dd43b024 (patch) | |
| tree | 59bd111bd882369744b346efe9b31eca5ad4f243 | |
| parent | c6c0678a10f66ea7cf373b9fe54cc8878133a574 (diff) | |
| download | keystone-20ecd99441ba8fc3d58b05d55c08eb17dd43b024.tar.gz keystone-20ecd99441ba8fc3d58b05d55c08eb17dd43b024.tar.xz keystone-20ecd99441ba8fc3d58b05d55c08eb17dd43b024.zip | |
Added latest changes to sirish branch with pagination for get tenants
| -rw-r--r-- | README.md | 1 | ||||
| -rw-r--r-- | echo/echo/echo.py | 106 | ||||
| -rw-r--r-- | echo/echo/echo_basic.ini | 36 | ||||
| -rw-r--r-- | echo/echo_client.py | 24 | ||||
| -rw-r--r-- | keystone/auth_protocols/auth_basic.ini | 13 | ||||
| -rw-r--r-- | keystone/auth_protocols/auth_basic.py | 128 | ||||
| -rw-r--r-- | keystone/auth_protocols/auth_token.py | 265 | ||||
| -rw-r--r-- | keystone/db/sqlalchemy/api.py | 62 | ||||
| -rw-r--r-- | keystone/identity.py | 10 | ||||
| -rw-r--r-- | keystone/logic/service.py | 27 | ||||
| -rw-r--r-- | keystone/logic/types/atom.py | 12 | ||||
| -rw-r--r-- | keystone/logic/types/tenant.py | 3 | ||||
| -rw-r--r-- | test/test_setup.sql | 2 | ||||
| -rw-r--r-- | test/unit/test_identity.py | 4 |
14 files changed, 421 insertions, 272 deletions
@@ -17,6 +17,7 @@ SERVICES: * Echo - A sample service that responds by returning call details Also included: + * 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 diff --git a/echo/echo/echo.py b/echo/echo/echo.py index ee950b37..e7fa38d3 100644 --- a/echo/echo/echo.py +++ b/echo/echo/echo.py @@ -14,13 +14,14 @@ # See the License for the specific language governing permissions and # limitations under the License. -import os -import sys - import eventlet from eventlet import wsgi from lxml import etree +import os from paste.deploy import loadapp +import sys +from webob.exc import HTTPUnauthorized + # If ../echo/__init__.py exists, add ../ to Python search path, so that # it will override what happens to be installed in /usr/(local/)lib/python... @@ -53,14 +54,27 @@ class EchoApp(object): self.transform = etree.XSLT(etree.parse(echo_xsl)) def __iter__(self): + # We expect an X_AUTHORIZATION header to be passed in + # We assume the request is coming from a trusted source. Middleware + # is used to perform that validation. if 'HTTP_X_AUTHORIZATION' not in self.envr: - return HTTPUnauthorized(self.envr, start_response) + self.start('401 Unauthorized', [('Content-Type', + 'application/json')]) + return iter(["401 Unauthorized"]) + + if 'HTTP_X_IDENTITY_STATUS' not in self.envr: + identity_status = "Unknown" + else: + identity_status = self.envr["HTTP_X_IDENTITY_STATUS"] print ' Received:' - if 'HTTP_X_IDENTITY_STATUS' in self.envr: print ' Auth Status:', self.envr['HTTP_X_IDENTITY_STATUS'] - if 'HTTP_X_AUTHORIZATION' in self.envr: print ' Identity :', self.envr['HTTP_X_AUTHORIZATION'] - if 'HTTP_X_TENANT' in self.envr: print ' Tenant :', self.envr['HTTP_X_TENANT'] - if 'HTTP_X_GROUP' in self.envr: print ' Group :', self.envr['HTTP_X_GROUP'] + print ' Auth Status:', identity_status + if 'HTTP_X_AUTHORIZATION' in self.envr: + print ' Identity :', self.envr['HTTP_X_AUTHORIZATION'] + if 'HTTP_X_TENANT' in self.envr: + print ' Tenant :', self.envr['HTTP_X_TENANT'] + if 'HTTP_X_GROUP' in self.envr: + print ' Group :', self.envr['HTTP_X_GROUP'] accept = self.envr.get("HTTP_ACCEPT", "application/json") if accept == "application/xml": @@ -80,8 +94,7 @@ class EchoApp(object): echo = etree.Element("{http://docs.openstack.org/echo/api/v1.0}echo", method=environ["REQUEST_METHOD"], pathInfo=environ["PATH_INFO"], - queryString=environ.get('QUERY_STRING', ""), - caller_identity=self.envr['HTTP_X_AUTHORIZATION']) + queryString=environ.get('QUERY_STRING', "")) content = etree.Element( "{http://docs.openstack.org/echo/api/v1.0}content") content.set("type", environ["CONTENT_TYPE"]) @@ -97,25 +110,54 @@ def app_factory(global_conf, **local_conf): return EchoApp if __name__ == "__main__": - remote_auth = False - if len(sys.argv) > 1: - remote_auth = sys.argv[1] == '--remote' - - if remote_auth: - # running auth remotely - print "Running for use with remote auth" - - app = loadapp("config:" + \ - os.path.join(os.path.abspath(os.path.dirname(__file__)), - "echo_remote.ini"), global_conf={"log_name": "echo.log"}) - - wsgi.server(eventlet.listen(('', 8100)), app) - - else: - print "Running all components locally." - print "Use --remote option to run with remote auth proxy" - app = loadapp("config:" + \ - os.path.join(os.path.abspath(os.path.dirname(__file__)), - "echo.ini"), global_conf={"log_name": "echo.log"}) - - wsgi.server(eventlet.listen(('', 8090)), app) + def usage(): + print "Runs Echo, the canonical OpenStack service, " \ + "with auth middleware" + print "Options:" + print "-h, --help : show this usage information" + print "-b, --basic : run with basic auth (uses echo_basic.ini)" + print "-r, --remote: run with remote auth on port 8100" \ + "(uses echo_remote.ini)" + print "-i, --ini filename: run with specified ini file" + print "-p, --port: specifies port to listen on (default is 8090)" + print "by default will run with local, token auth (uses echo.ini)" + + import getopt + try: + opts, args = getopt.getopt(sys.argv[1:], + "hbrp:i:", + ["help", "basic", "remote", "port", "ini"]) + except getopt.GetoptError: + usage() + sys.exit() + + port = 0 + ini = "echo.ini" + auth_name = "local Token Auth" + + for opt, arg in opts: + if opt in ["-h", "--help"]: + usage() + sys.exit() + elif opt in ["-p", "--port"]: + port = int(arg) + elif opt in ["-i", "--ini"]: + auth_name = "with custom ini: %s" % arg + ini = arg + elif opt in ["-b", "--basic"]: + auth_name = "Basic Auth" + ini = "echo_basic.ini" + elif opt in ["-r", "--remote"]: + auth_name = "remote Token Auth" + ini = "echo_remote.ini" + if not port: + port = 8100 + + if not port: + port = 8090 + print "Running with", auth_name + app = loadapp("config:" + \ + os.path.join(os.path.abspath(os.path.dirname(__file__)), + ini), global_conf={"log_name": "echo.log"}) + + wsgi.server(eventlet.listen(('', port)), app) diff --git a/echo/echo/echo_basic.ini b/echo/echo/echo_basic.ini new file mode 100644 index 00000000..38da0c66 --- /dev/null +++ b/echo/echo/echo_basic.ini @@ -0,0 +1,36 @@ +[DEFAULT] +;delegated means we still allow unauthenticated requests through so the +;service can make the final decision on authentication +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 +;used to verify this component with the OpenStack service (or PAPIAuth) +service_pass = dTpw + + +[app:echo] +paste.app_factory = echo:app_factory + +[pipeline:main] +pipeline = + basicauth + echo + +[filter:tokenauth] +paste.filter_factory = keystone:tokenauth_factory +;where to find the token auth service +auth_host = 127.0.0.1 +auth_port = 8080 +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 + +[filter:openidauth] +paste.filter_factory = keystone:openidauth_factory diff --git a/echo/echo_client.py b/echo/echo_client.py index 677e8ab9..a06adbe4 100644 --- a/echo/echo_client.py +++ b/echo/echo_client.py @@ -19,6 +19,7 @@ Implement a client for Echo service using Identity service import httplib import json +import sys def get_auth_token(username, password, tenant): @@ -46,6 +47,23 @@ def call_service(token): ret = data return ret + +def hack_attempt(token): + # Injecting headers in the request + headers = {"X-Auth-Token": token, + "Content-type": "application/json", + "Accept": "text/json\nX_AUTHORIZATION: someone else\n" + "X_IDENTITY_STATUS: Confirmed\nINJECTED_HEADER: aha!"} + params = '{"ping": "abcdefg"}' + conn = httplib.HTTPConnection("localhost:8090") + print headers + conn.request("POST", "/", params, headers=headers) + response = conn.getresponse() + data = response.read() + ret = data + return ret + + if __name__ == '__main__': # Call the keystone service to get a token # NOTE: assumes the test_setup.sql script has loaded this user @@ -60,6 +78,12 @@ if __name__ == '__main__': print "Response received:", data print + # Use the valid token, but inject some headers + print "\033[91mInjecting some headers >:-/ \033[0m" + data = hack_attempt(token) + print "Response received:", data + print + # Use bad token to call an OpenStack service (echo) print "\033[91mTrying with bad token...\033[0m" data = call_service("xxxx_invalid_token_xxxx") diff --git a/keystone/auth_protocols/auth_basic.ini b/keystone/auth_protocols/auth_basic.ini new file mode 100644 index 00000000..ae5d75d8 --- /dev/null +++ b/keystone/auth_protocols/auth_basic.ini @@ -0,0 +1,13 @@ +[DEFAULT] + +[app:main] +paste.app_factory = auth_basic:app_factory + +delay_auth_decision = 0 + +service_protocol = http +service_host = 127.0.0.1 +service_port = 8100 +service_pass = dTpw + + diff --git a/keystone/auth_protocols/auth_basic.py b/keystone/auth_protocols/auth_basic.py index 2bc967ff..046ca08e 100644 --- a/keystone/auth_protocols/auth_basic.py +++ b/keystone/auth_protocols/auth_basic.py @@ -31,10 +31,20 @@ This is an Auth component as per: http://wiki.openstack.org/openstack-authn """ +from paste.deploy import loadapp +import eventlet +from eventlet import wsgi +import os +from webob.exc import HTTPUnauthorized, HTTPInternalServerError PROTOCOL_NAME = "Basic Authentication" +def _decorate_request_headers(header, value, proxy_headers, env): + proxy_headers[header] = value + env["HTTP_%s" % header] = value + + class AuthProtocol(object): """Auth Middleware that handles authenticating client calls""" @@ -65,33 +75,93 @@ class AuthProtocol(object): def __call__(self, env, start_response): def custom_start_response(status, headers): if self.delay_auth_decision: - headers.append(('WWW-Authenticate', "Basic realm='API Realm'")) + headers.append(('WWW-Authenticate', + "Basic realm='Use guest/guest'")) return start_response(status, headers) - #TODO(Ziad): PERFORM BASIC AUTH - - #Auth processed, headers added now decide how to pass on the call - if self.app: - # Pass to downstream WSGI component - env['HTTP_AUTHORIZATION'] = "Basic %s" % self.service_pass - return self.app(env, custom_start_response) - - proxy_headers['AUTHORIZATION'] = "Basic %s" % self.service_pass - # We are forwarding to a remote service (no downstream WSGI app) - req = Request(proxy_headers) - parsed = urlparse(req.url) - conn = http_connect(self.service_host, self.service_port, \ - req.method, parsed.path, \ - proxy_headers,\ - ssl=(self.service_protocol == 'https')) - resp = conn.getresponse() - data = resp.read() - #TODO: use a more sophisticated proxy - # we are rewriting the headers now - return Response(status=resp.status, body=data)(env, start_response) - - -def filter_factory(global_conf, **local_conf): + #Prep headers to proxy request to remote service + proxy_headers = env.copy() + user = '' + + #Look for authentication + if 'HTTP_AUTHORIZATION' not in env: + #No credentials were provided + if self.delay_auth_decision: + _decorate_request_headers("X_IDENTITY_STATUS", "Invalid", + proxy_headers, env) + else: + # If the user isn't authenticated, we reject the request and + # return 401 indicating we need Basic Auth credentials. + return HTTPUnauthorized("Authentication required", + [('WWW-Authenticate', + 'Basic realm="Use guest/guest"')] + )(env, start_response) + else: + # Claims were provided - validate them + import base64 + auth_header = env['HTTP_AUTHORIZATION'] + auth_type, encoded_creds = auth_header.split(None, 1) + user, password = base64.b64decode(encoded_creds).split(':', 1) + if not self.validateCreds(user, password): + #Claims were rejected + if not self.delay_auth_decision: + # Reject request (or ask for valid claims) + return HTTPUnauthorized("Authentication required", + [('WWW-Authenticate', + 'Basic realm="Use guest/guest"')] + )(env, start_response) + else: + # Claims are valid, forward request + _decorate_request_headers("X_IDENTITY_STATUS", "Invalid", + proxy_headers, env) + + # TODO(Ziad): add additional details we may need, + # like tenant and group info + _decorate_request_headers('X_AUTHORIZATION', "Proxy %s" % user, + proxy_headers, env) + _decorate_request_headers("X_IDENTITY_STATUS", "Confirmed", + proxy_headers, env) + _decorate_request_headers('X_TENANT', 'blank', + proxy_headers, env) + _decorate_request_headers('X_GROUP', 'Blank', + proxy_headers, env) + + #Auth processed, headers added now decide how to pass on the call + if self.app: + # Pass to downstream WSGI component + env['HTTP_AUTHORIZATION'] = "Basic %s" % self.service_pass + return self.app(env, custom_start_response) + + proxy_headers['AUTHORIZATION'] = "Basic %s" % self.service_pass + # We are forwarding to a remote service (no downstream WSGI app) + req = Request(proxy_headers) + parsed = urlparse(req.url) + conn = http_connect(self.service_host, self.service_port, \ + req.method, parsed.path, \ + proxy_headers, \ + ssl=(self.service_protocol == 'https')) + resp = conn.getresponse() + data = resp.read() + #TODO: use a more sophisticated proxy + # we are rewriting the headers now + return Response(status=resp.status, body=data)(env, start_response) + + def validateCreds(self, username, password): + #stub for password validation. + import ConfigParser + import hashlib + #usersConfig = ConfigParser.ConfigParser() + #usersConfig.readfp(open('/etc/openstack/users.ini')) + #password = hashlib.sha1(password).hexdigest() + #for un, pwd in usersConfig.items('users'): + #TODO(Ziad): add intelligent credential validation (instead of hard + # coded) + if username == 'guest' and password == 'guest': + return True + return False + + +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) @@ -101,14 +171,14 @@ def filter_factory(global_conf, **local_conf): return auth_filter -def app_factory(global_conf, **local_conf): +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_basic.ini"), - global_conf={"log_name": "auth_basic.log"}) + os.path.join(os.path.abspath(os.path.dirname(__file__)), + "auth_basic.ini"), + global_conf={"log_name": "auth_basic.log"}) wsgi.server(eventlet.listen(('', 8090)), app) diff --git a/keystone/auth_protocols/auth_token.py b/keystone/auth_protocols/auth_token.py index cee8e84f..fbc6c622 100644 --- a/keystone/auth_protocols/auth_token.py +++ b/keystone/auth_protocols/auth_token.py @@ -53,24 +53,20 @@ HTTP_X_AUTHORIZATION: the client identity being passed in import eventlet from eventlet import wsgi +import httplib import json import os from paste.deploy import loadapp +import sys from urlparse import urlparse -from webob.exc import HTTPUnauthorized +from webob.exc import HTTPUnauthorized, HTTPUseProxy from webob.exc import Request, Response -import httplib from keystone.common.bufferedhttp import http_connect_raw as http_connect PROTOCOL_NAME = "Token Authentication" -def _decorate_request_headers(header, value, proxy_headers, env): - proxy_headers[header] = value - env["HTTP_%s" % header] = value - - class AuthProtocol(object): """Auth Middleware that handles authenticating client calls""" @@ -106,8 +102,9 @@ class AuthProtocol(object): 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) + 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 @@ -120,6 +117,61 @@ class AuthProtocol(object): 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', + "Proxy %s" % 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 @@ -136,111 +188,116 @@ class AuthProtocol(object): headers=headers) response = conn.getresponse() data = response.read() - ret = data - return ret - - def __call__(self, env, start_response): - def custom_start_response(status, headers): - if self.delay_auth_decision: - headers.append(('WWW-Authenticate', "Basic realm='API Realm'")) - return start_response(status, headers) - - #Prep headers to proxy request to remote service - proxy_headers = env.copy() - user = '' - - #Look for token in request - token = env.get('HTTP_X_AUTH_TOKEN', env.get('HTTP_X_STORAGE_TOKEN')) - if not token: - #No token was provided - if self.delay_auth_decision: - _decorate_request_headers("X_IDENTITY_STATUS", "Invalid", - proxy_headers, env) - else: - # Redirect client to auth server - return HTTPUseProxy(location=self.auth_location)(env, - start_response) + 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: 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: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', + '/v1.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: - # this request is claiming it has a valid token, let's check - # with the auth service - # 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 - # 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: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', - '/v1.0/token/%s' % token, headers=headers) - resp = conn.getresponse() - data = resp.read() - conn.close() - - if not str(resp.status).startswith('20'): - # Keystone rejected claim - if self.delay_auth_decision: - # Downstream service will receive call still and decide - _decorate_request_headers("X_IDENTITY_STATUS", "Invalid", - proxy_headers, env) - else: - # Reject the response & send back the error - # (not delay_auth_decision) - return HTTPUnauthorized(headers=headers)(env, - start_response) - else: - # Valid token. Get user data and put it in to the call - # so the downstream service can use iot - dict_response = json.loads(data) - #TODO(Ziad): make this more robust - user = dict_response['auth']['user']['username'] - tenant = dict_response['auth']['user']['tenantId'] - group = '%s/%s' % (dict_response['auth']['user']['groups']['group'][0]['id'], - dict_response['auth']['user']['groups']['group'][0]['tenantId']) - - # TODO(Ziad): add additional details we may need, - # like tenant and group info - _decorate_request_headers('X_AUTHORIZATION', "Proxy %s" % user, - proxy_headers, env) - _decorate_request_headers("X_IDENTITY_STATUS", "Confirmed", - proxy_headers, env) - _decorate_request_headers('X_TENANT', tenant, - proxy_headers, env) - _decorate_request_headers('X_GROUP', group, - proxy_headers, env) - - #Token/Auth processed, headers added now decide how to pass on the call - _decorate_request_headers('AUTHORIZATION', - "Basic %s" % self.service_pass, - proxy_headers, - env) + #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: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', + '/v1.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(env, custom_start_response) + 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(proxy_headers) + req = Request(self.proxy_headers) parsed = urlparse(req.url) - conn = http_connect(self.service_host, self.service_port, \ - req.method, parsed.path, \ - proxy_headers,\ - ssl=(self.service_protocol == 'https')) + 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: use a more sophisticated proxy # we are rewriting the headers now - return Response(status=resp.status, body=data)(proxy_headers, - start_response) + return Response(status=resp.status, body=data)(self.proxy_headers, + self.start_response) def filter_factory(global_conf, **local_conf): diff --git a/keystone/db/sqlalchemy/api.py b/keystone/db/sqlalchemy/api.py index 4f9997c7..a0910778 100644 --- a/keystone/db/sqlalchemy/api.py +++ b/keystone/db/sqlalchemy/api.py @@ -19,7 +19,7 @@ from session import get_session from sqlalchemy.orm import joinedload import models -import pprint + def tenant_create(values): tenant_ref = models.Tenant() @@ -34,70 +34,12 @@ def tenant_get(id, session=None): result = session.query(models.Tenant).filter_by(id=id).first() return result + def tenant_get_all(session=None): if not session: session = get_session() return session.query(models.Tenant).all() -def tenant_get_page(marker,limit,session=None): - if not session: - session = get_session() - - if marker: - return session.query(models.Tenant).filter("id>=:marker").params(\ - marker = '%s' % marker).order_by\ - (models.Tenant.id).limit(limit).all() - else: - return session.query(models.Tenant).order_by(\ - models.Tenant.id).limit(limit).all() - #return session.query(models.Tenant).all() -def tenant_get_page_markers(marker,limit,session=None): - if not session: - session = get_session() - - first = session.query(models.Tenant).order_by(\ - models.Tenant.id).first() - last = session.query(models.Tenant).order_by(\ - models.Tenant.id.desc()).first() - - - if marker is None: - marker=first.id - print marker - next=session.query(models.Tenant).filter("id > :marker").params(\ - marker = '%s' % marker).order_by(\ - models.Tenant.id).limit(limit).all() - - prev=session.query(models.Tenant).filter("id < :marker").params(\ - marker = '%s' % marker).order_by(\ - models.Tenant.id.desc()).limit(int(limit)).all() - - if len(next) == 0: - next=last - else: - for t in next: - next=t - - if len(prev) == 0: - prev=first - else: - for t in prev: - prev=t - - - if prev.id == marker: - prev = None - else: - prev=prev.id - - if next.id == last.id: - next = None - else: - next = next.id - - - return (prev,next) - def tenant_is_empty(id, session=None): if not session: diff --git a/keystone/identity.py b/keystone/identity.py index 4adc30e1..0e2e3765 100644 --- a/keystone/identity.py +++ b/keystone/identity.py @@ -229,6 +229,7 @@ def delete_token(token_id): ## Tenant Operations ## + @bottle.route('/v1.0/tenants', method='POST') @wrap_error def create_tenant(): @@ -243,15 +244,10 @@ 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"] - else: - limit=10 - print limit, marker - url = '%s://%s%s' % (request.environ['wsgi.url_scheme'],request.environ['HTTP_HOST'],request.environ['PATH_INFO']) - - tenants = service.get_tenants(get_auth_token(), marker, limit,url) + tenants = service.get_tenants(get_auth_token(), marker, limit) return send_result(200, tenants) diff --git a/keystone/logic/service.py b/keystone/logic/service.py index 5fb180d3..cd0b5237 100644 --- a/keystone/logic/service.py +++ b/keystone/logic/service.py @@ -114,35 +114,16 @@ class IDMService(object): return tenant - """ def get_tenants(self, admin_token, marker, limit): - self.__validate_token(admin_token) - - ts = [] - dtenants = db_api.tenant_get_all() - for dtenant in dtenants: - ts.append(tenants.Tenant(dtenant.id, - dtenant.desc, dtenant.enabled)) - - return tenants.Tenants(ts, []) - """ - - def get_tenants(self, admin_token, marker, limit, url): + def get_tenants(self, admin_token, marker, limit): self.__validate_token(admin_token) ts = [] - dtenants = db_api.tenant_get_page(marker,limit) + dtenants = db_api.tenant_get_all() for dtenant in dtenants: ts.append(tenants.Tenant(dtenant.id, dtenant.desc, dtenant.enabled)) - prev,next=db_api.tenant_get_page_markers(marker,limit) - links=[] - if prev: - links.append(atom.Link('prev',"%s?'marker=%s&limit=%s'" % (url,prev,limit))) - if next: - links.append(atom.Link('next',"%s?'marker=%s&limit=%s'" % (url,next,limit))) - - - return tenants.Tenants(ts, links) + + return tenants.Tenants(ts, []) def get_tenant(self, admin_token, tenant_id): self.__validate_token(admin_token) diff --git a/keystone/logic/types/atom.py b/keystone/logic/types/atom.py index a98ba4c8..04431ce2 100644 --- a/keystone/logic/types/atom.py +++ b/keystone/logic/types/atom.py @@ -23,15 +23,3 @@ class Link(object): self.link_type = link_type self.hreflang = hreflang self.title = title - def to_dict(self): - links = {} - if self.link_type: - links["link_type"] = self.link_type - if self.hreflang: - links["hreflang"] = self.hreflang - if self.title: - links["title"] = self.title - - links["rel"] = self.rel - links["href"] = self.href - return {'links': links}
\ No newline at end of file diff --git a/keystone/logic/types/tenant.py b/keystone/logic/types/tenant.py index d972f4f9..b161476b 100644 --- a/keystone/logic/types/tenant.py +++ b/keystone/logic/types/tenant.py @@ -116,5 +116,4 @@ class Tenants(object): def to_json(self): values = [t.to_dict()["tenant"] for t in self.values] - links = [t.to_dict()["links"] for t in self.links] - return json.dumps({"tenants": {"values": values,"links":links}}) + return json.dumps({"tenants": {"values": values}}) diff --git a/test/test_setup.sql b/test/test_setup.sql index a15f6d8e..a26a8947 100644 --- a/test/test_setup.sql +++ b/test/test_setup.sql @@ -24,7 +24,7 @@ insert into tenants (id, "desc", enabled) values -- Groups insert into groups (id, "desc", tenant_id) values - ("Admin", "Andmin users", "1234"); + ("Admin", "Admin users", "1234"); insert into groups (id, "desc", tenant_id) values ("Default", "Standard users", "1234"); diff --git a/test/unit/test_identity.py b/test/unit/test_identity.py index 24a31104..f8dca4a1 100644 --- a/test/unit/test_identity.py +++ b/test/unit/test_identity.py @@ -1,7 +1,8 @@ import os
import sys
# Need to access identity module
-sys.path.append(os.path.abspath(os.path.join(os.path.abspath( __file__ ), '..', '..', '..', '..', 'keystone')))
+sys.path.append(os.path.abspath(os.path.join(os.path.abspath(__file__),
+ '..', '..', '..', '..', 'keystone')))
from keystone import identity
import unittest
from webtest import TestApp
@@ -970,6 +971,5 @@ class delete_tenant_test(tenant_test): self.assertEqual(204, int(resp['status']))
-
if __name__ == '__main__':
unittest.main()
|
