summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorroot <root@bsirish.(none)>2011-05-02 13:26:46 +0530
committerroot <root@bsirish.(none)>2011-05-02 13:26:46 +0530
commit20ecd99441ba8fc3d58b05d55c08eb17dd43b024 (patch)
tree59bd111bd882369744b346efe9b31eca5ad4f243
parentc6c0678a10f66ea7cf373b9fe54cc8878133a574 (diff)
downloadkeystone-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.md1
-rw-r--r--echo/echo/echo.py106
-rw-r--r--echo/echo/echo_basic.ini36
-rw-r--r--echo/echo_client.py24
-rw-r--r--keystone/auth_protocols/auth_basic.ini13
-rw-r--r--keystone/auth_protocols/auth_basic.py128
-rw-r--r--keystone/auth_protocols/auth_token.py265
-rw-r--r--keystone/db/sqlalchemy/api.py62
-rw-r--r--keystone/identity.py10
-rw-r--r--keystone/logic/service.py27
-rw-r--r--keystone/logic/types/atom.py12
-rw-r--r--keystone/logic/types/tenant.py3
-rw-r--r--test/test_setup.sql2
-rw-r--r--test/unit/test_identity.py4
14 files changed, 421 insertions, 272 deletions
diff --git a/README.md b/README.md
index 3753b402..fa925ac4 100644
--- a/README.md
+++ b/README.md
@@ -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()