summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorZiad Sawalha <ziad.sawalha@rackspace.com>2011-07-19 16:08:50 -0700
committerZiad Sawalha <ziad.sawalha@rackspace.com>2011-07-19 16:08:50 -0700
commit0a20d474842774ba1c2949e70ecbb70de10db5dc (patch)
tree6174cb89fe12ad0ff76cadfcb88b9585c9902df3
parent9f6953e22345ce012815b53b61c721e6cbd7d3de (diff)
parent6a012ffd3b46518019a8ecbf7c5285e890ab15fa (diff)
Merge pull request #104 from dolph/master
Fix for issue #85
-rwxr-xr-xetc/keystone.conf20
-rwxr-xr-xkeystone/backends/alterdb/models.py7
-rwxr-xr-xkeystone/backends/sqlalchemy/models.py23
-rwxr-xr-xkeystone/common/config.py33
-rwxr-xr-xkeystone/common/wsgi.py23
-rwxr-xr-xkeystone/logic/service.py187
-rw-r--r--keystone/test/system/common.py26
-rw-r--r--keystone/test/system/test_auth.py54
-rw-r--r--keystone/test/system/test_issue_85.py21
-rw-r--r--keystone/test/system/test_request_specs.py10
10 files changed, 230 insertions, 174 deletions
diff --git a/etc/keystone.conf b/etc/keystone.conf
index 243f9158..52d42c04 100755
--- a/etc/keystone.conf
+++ b/etc/keystone.conf
@@ -15,10 +15,11 @@ default_store = sqlite
#log_file = /var/log/keystone.log
log_file = keystone.log
-#List of backends to be configured
+# List of backends to be configured
backends = keystone.backends.sqlalchemy,keystone.backends.alterdb
-#Dictionary Maps every service to a header.Missing services would get header X_(SERVICE_NAME) Key => Service Name, Value => Header Name
+# Dictionary Maps every service to a header.Missing services would get header
+# X_(SERVICE_NAME) Key => Service Name, Value => Header Name
service-header-mappings = {
'nova' : 'X-Server-Management-Url',
'swift' : 'X-Storage-Url',
@@ -41,20 +42,21 @@ admin_port = 5001
keystone-admin-role = Admin
[keystone.backends.sqlalchemy]
-# SQLAlchemy connection string for the reference implementation
-# registry server. Any valid SQLAlchemy connection string is fine.
-# See: http://www.sqlalchemy.org/docs/05/reference/sqlalchemy/connections.html#sqlalchemy.create_engine
+# SQLAlchemy connection string for the reference implementation registry
+# server. Any valid SQLAlchemy connection string is fine.
+# See: http://bit.ly/ideIpI
sql_connection = sqlite:///keystone.db
-backend_entities = ['UserGroupAssociation', 'UserRoleAssociation', 'Endpoints', 'Role', 'Tenant', 'User', 'Credentials', 'Group', 'EndpointTemplates']
+backend_entities = ['UserGroupAssociation', 'UserRoleAssociation', 'Endpoints',
+ 'Role', 'Tenant', 'User', 'Credentials', 'Group', 'EndpointTemplates']
# Period in seconds after which SQLAlchemy should reestablish its connection
# to the database.
sql_idle_timeout = 30
[keystone.backends.alterdb]
-# SQLAlchemy connection string for the reference implementation
-# registry server. Any valid SQLAlchemy connection string is fine.
-# See: http://www.sqlalchemy.org/docs/05/reference/sqlalchemy/connections.html#sqlalchemy.create_engine
+# SQLAlchemy connection string for the reference implementation registry
+# server. Any valid SQLAlchemy connection string is fine.
+# See: http://bit.ly/ideIpI
sql_connection = sqlite:///keystone.token.db
backend_entities = ['Token']
diff --git a/keystone/backends/alterdb/models.py b/keystone/backends/alterdb/models.py
index 7aa30c18..25d9642c 100755
--- a/keystone/backends/alterdb/models.py
+++ b/keystone/backends/alterdb/models.py
@@ -14,11 +14,10 @@
# See the License for the specific language governing permissions and
# limitations under the License.
# Not Yet PEP8 standardized
-from sqlalchemy import Column, String, Integer, ForeignKey, \
- UniqueConstraint, Boolean, DateTime
+from sqlalchemy import Column, String, DateTime
from sqlalchemy.exc import IntegrityError
from sqlalchemy.ext.declarative import declarative_base
-from sqlalchemy.orm import relationship, object_mapper
+from sqlalchemy.orm import object_mapper
Base = declarative_base()
@@ -77,7 +76,7 @@ class KeystoneBase(object):
class Token(Base, KeystoneBase):
__tablename__ = 'token'
- __api__ ='token'
+ __api__ = 'token'
id = Column(String(255), primary_key=True, unique=True)
user_id = Column(String(255))
tenant_id = Column(String(255))
diff --git a/keystone/backends/sqlalchemy/models.py b/keystone/backends/sqlalchemy/models.py
index f2ca7dc0..4473dbed 100755
--- a/keystone/backends/sqlalchemy/models.py
+++ b/keystone/backends/sqlalchemy/models.py
@@ -16,7 +16,7 @@
# Not Yet PEP8 standardized
from sqlalchemy import Column, String, Integer, ForeignKey, \
- UniqueConstraint, Boolean, DateTime
+ UniqueConstraint, Boolean, DateTime
from sqlalchemy.exc import IntegrityError
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import relationship, object_mapper
@@ -79,7 +79,7 @@ class KeystoneBase(object):
# Define associations first
class UserGroupAssociation(Base, KeystoneBase):
__tablename__ = 'user_group_association'
- __api__ ='tenant_group'
+ __api__ = 'tenant_group'
user_id = Column(String(255), ForeignKey('users.id'), primary_key=True)
group_id = Column(String(255), ForeignKey('groups.id'), primary_key=True)
@@ -99,32 +99,31 @@ class Endpoints(Base, KeystoneBase):
id = Column(Integer, primary_key=True)
tenant_id = Column(String(255), ForeignKey('tenants.id'))
endpoint_template_id = Column(Integer, ForeignKey('endpoint_templates.id'))
- __table_args__ = (UniqueConstraint("endpoint_template_id",\
- "tenant_id"), {})
+ __table_args__ = (
+ UniqueConstraint("endpoint_template_id", "tenant_id"), {})
# Define objects
class Role(Base, KeystoneBase):
__tablename__ = 'roles'
- __api__ ='role'
+ __api__ = 'role'
id = Column(String(255), primary_key=True, unique=True)
desc = Column(String(255))
class Tenant(Base, KeystoneBase):
__tablename__ = 'tenants'
- __api__ ='tenant'
+ __api__ = 'tenant'
id = Column(String(255), primary_key=True, unique=True)
desc = Column(String(255))
enabled = Column(Integer)
groups = relationship('Group', backref='tenants')
- endpoints = relationship('Endpoints', backref='tenant',
- cascade="all")
+ endpoints = relationship('Endpoints', backref='tenant', cascade="all")
class User(Base, KeystoneBase):
__tablename__ = 'users'
- __api__ ='user'
+ __api__ = 'user'
id = Column(String(255), primary_key=True, unique=True)
password = Column(String(255))
email = Column(String(255))
@@ -146,14 +145,14 @@ class Credentials(Base, KeystoneBase):
class Group(Base, KeystoneBase):
__tablename__ = 'groups'
- __api__ ='group'
+ __api__ = 'group'
id = Column(String(255), primary_key=True, unique=True)
desc = Column(String(255))
tenant_id = Column(String(255), ForeignKey('tenants.id'))
class Token(Base, KeystoneBase):
__tablename__ = 'token'
- __api__ ='token'
+ __api__ = 'token'
id = Column(String(255), primary_key=True, unique=True)
user_id = Column(String(255))
tenant_id = Column(String(255))
@@ -161,7 +160,7 @@ class Token(Base, KeystoneBase):
class EndpointTemplates(Base, KeystoneBase):
__tablename__ = 'endpoint_templates'
- __api__ ='endpoint_template'
+ __api__ = 'endpoint_template'
id = Column(Integer, primary_key=True)
region = Column(String(255))
service = Column(String(255))
diff --git a/keystone/common/config.py b/keystone/common/config.py
index afe3668e..9de65389 100755
--- a/keystone/common/config.py
+++ b/keystone/common/config.py
@@ -26,6 +26,7 @@ import os
from paste import deploy
import sys
import ConfigParser
+from keystone.common.wsgi import add_console_handler
DEFAULT_LOG_FORMAT = "%(asctime)s %(levelname)8s [%(name)s] %(message)s"
DEFAULT_LOG_DATE_FORMAT = "%Y-%m-%d %H:%M:%S"
@@ -125,34 +126,6 @@ def add_log_options(parser):
parser.add_option_group(group)
return group
-
-def add_console_handler(logger, level=logging.INFO):
- """
- Add a Handler which writes log messages to sys.stderr
- (which is often the console)
- There is a copy of this function in wsgi.py (TODO(Ziad): Make it one copy)
- """
- console = None
- for console in logger.handlers:
- if isinstance(console, logging.StreamHandler):
- break
-
- if not console:
- console = logging.StreamHandler()
- console.setLevel(level)
- # set a format which is simpler for console use
- formatter = logging.Formatter("%(name)-12s: "\
- "%(levelname)-8s %(message)s")
- # tell the handler to use this format
- console.setFormatter(formatter)
- # add the handler to the root logger
- logger.addHandler(console)
- else:
- if console.level != level:
- console.setLevel(level)
- return console
-
-
def setup_logging(options, conf):
"""
Sets up the logging options for a log with supplied name
@@ -205,9 +178,7 @@ def setup_logging(options, conf):
logfile.setFormatter(formatter)
root_logger.addHandler(logfile)
# Mirror to console if verbose or debug
- if debug:
- add_console_handler(root_logger, logging.INFO) #debug too noisy
- elif verbose:
+ if debug or verbose:
add_console_handler(root_logger, logging.INFO)
else:
handler = logging.StreamHandler(sys.stdout)
diff --git a/keystone/common/wsgi.py b/keystone/common/wsgi.py
index cf5fbf72..3eafe296 100755
--- a/keystone/common/wsgi.py
+++ b/keystone/common/wsgi.py
@@ -32,22 +32,24 @@ import routes.middleware
import webob.dec
import webob.exc
-def add_console_handler(logger, level):
+def find_stream_handler(logger):
+ """Returns a stream handler, if any"""
+ for handler in logger.handlers:
+ if isinstance(handler, logging.StreamHandler):
+ return handler
+
+def add_console_handler(logger, level=logging.INFO):
"""
- Add a Handler which writes messages to sys.stderr which is often the
- console.
- There is a copy of this function in config.py (TODO(Ziad): Make it one copy)
+ Add a Handler which writes log messages to sys.stderr (usually the console)
"""
- console = None
- for console in logger.handlers:
- if isinstance(console, logging.StreamHandler):
- break
-
+ console = find_stream_handler(logger)
+
if not console:
console = logging.StreamHandler()
console.setLevel(level)
# set a format which is simpler for console use
- formatter = logging.Formatter('%(name)-12s: %(levelname)-8s %(message)s')
+ formatter = logging.Formatter(
+ "%(name)-12s: %(levelname)-8s %(message)s")
# tell the handler to use this format
console.setFormatter(formatter)
# add the handler to the root logger
@@ -56,7 +58,6 @@ def add_console_handler(logger, level):
console.setLevel(level)
return console
-
class WritableLogger(object):
"""A thin wrapper that responds to `write` and logs."""
diff --git a/keystone/logic/service.py b/keystone/logic/service.py
index cc309ef2..eefcdfc2 100755
--- a/keystone/logic/service.py
+++ b/keystone/logic/service.py
@@ -47,7 +47,7 @@ class IdentityService(object):
raise fault.UnauthorizedFault("Unauthorized")
else:
duser = api.user.get_by_tenant(credentials.username,
- credentials.tenant_id)
+ credentials.tenant_id)
if duser == None:
raise fault.UnauthorizedFault("Unauthorized on this tenant")
@@ -55,7 +55,7 @@ class IdentityService(object):
raise fault.UserDisabledFault("Your account has been disabled")
if duser.password != utils.get_hashed_password(credentials.password):
raise fault.UnauthorizedFault("Unauthorized")
-
+
#
# Look for an existing token, or create one,
# TODO: Handle tenant/token search
@@ -82,22 +82,17 @@ class IdentityService(object):
return self.__get_auth_data(dtoken, tenant_id)
def validate_token(self, admin_token, token_id, belongs_to=None):
- self.__validate_token(admin_token)
- if not token_id:
- raise fault.UnauthorizedFault("Missing token")
- (token, user) = self.__get_dauth_data(token_id)
-
- if not token:
+ self.__validate_admin_token(admin_token)
+
+ if not api.token.get(token_id):
raise fault.UnauthorizedFault("Bad token, please reauthenticate")
- if token.expires < datetime.now():
- raise fault.ForbiddenFault("Token expired, please renew")
- if not user.enabled:
- raise fault.UserDisabledFault("The user %s has been disabled!"
- % user.id)
+
+ (token, user) = self.__validate_token(token_id, belongs_to)
+
return self.__get_validate_data(token, user)
def revoke_token(self, admin_token, token_id):
- self.__validate_token(admin_token)
+ self.__validate_admin_token(admin_token)
dtoken = api.token.get(token_id)
if not dtoken:
@@ -110,7 +105,7 @@ class IdentityService(object):
#
def create_tenant(self, admin_token, tenant):
- self.__validate_token(admin_token)
+ self.__validate_admin_token(admin_token)
if not isinstance(tenant, Tenant):
raise fault.BadRequestFault("Expecting a Tenant")
@@ -135,7 +130,7 @@ class IdentityService(object):
##
def get_tenants(self, admin_token, marker, limit, url):
try:
- (_token, user) = self.__validate_token(admin_token)
+ (_token, user) = self.__validate_admin_token(admin_token)
# If Global admin return all
ts = []
dtenants = api.tenant.get_page(marker, limit)
@@ -145,34 +140,34 @@ class IdentityService(object):
prev, next = api.tenant.get_page_markers(marker, limit)
links = []
if prev:
- links.append(atom.Link('prev', "%s?'marker=%s&limit=%s'" \
- % (url, prev, limit)))
+ 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)))
+ links.append(atom.Link('next',
+ "%s?'marker=%s&limit=%s'" % (url, next, limit)))
return Tenants(ts, links)
except fault.UnauthorizedFault:
#If not global admin ,return tenants specific to user.
(_token, user) = self.__validate_token(admin_token, False)
ts = []
- dtenants = api.tenant.tenants_for_user_get_page(\
+ dtenants = api.tenant.tenants_for_user_get_page(
user, marker, limit)
for dtenant in dtenants:
ts.append(Tenant(dtenant.id,
dtenant.desc, dtenant.enabled))
- prev, next = api.tenant.tenants_for_user_get_page_markers(\
+ prev, next = api.tenant.tenants_for_user_get_page_markers(
user, marker, limit)
links = []
if prev:
- links.append(atom.Link('prev', "%s?'marker=%s&limit=%s'" \
- % (url, prev, limit)))
+ 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)))
+ links.append(atom.Link('next',
+ "%s?'marker=%s&limit=%s'" % (url, next, limit)))
return Tenants(ts, links)
def get_tenant(self, admin_token, tenant_id):
- self.__validate_token(admin_token)
+ self.__validate_admin_token(admin_token)
dtenant = api.tenant.get(tenant_id)
if not dtenant:
@@ -180,11 +175,11 @@ class IdentityService(object):
return Tenant(dtenant.id, dtenant.desc, dtenant.enabled)
def update_tenant(self, admin_token, tenant_id, tenant):
- self.__validate_token(admin_token)
+ self.__validate_admin_token(admin_token)
if not isinstance(tenant, Tenant):
raise fault.BadRequestFault("Expecting a Tenant")
-
+
dtenant = api.tenant.get(tenant_id)
if dtenant == None:
raise fault.ItemNotFoundFault("The tenant could not be found")
@@ -193,16 +188,16 @@ class IdentityService(object):
return Tenant(dtenant.id, tenant.description, tenant.enabled)
def delete_tenant(self, admin_token, tenant_id):
- self.__validate_token(admin_token)
-
+ self.__validate_admin_token(admin_token)
+
dtenant = api.tenant.get(tenant_id)
if dtenant == None:
raise fault.ItemNotFoundFault("The tenant could not be found")
-
+
if not api.tenant.is_empty(tenant_id):
raise fault.ForbiddenFault("You may not delete a tenant that "
"contains get_users or groups")
-
+
api.tenant.delete(dtenant.id)
return None
@@ -211,7 +206,7 @@ class IdentityService(object):
#
def create_tenant_group(self, admin_token, tenant, group):
- self.__validate_token(admin_token)
+ self.__validate_admin_token(admin_token)
if not isinstance(group, Group):
raise fault.BadRequestFault("Expecting a Group")
@@ -238,7 +233,7 @@ class IdentityService(object):
return Group(dtenant.id, dtenant.desc, dtenant.tenant_id)
def get_tenant_groups(self, admin_token, tenant_id, marker, limit, url):
- self.__validate_token(admin_token)
+ self.__validate_admin_token(admin_token)
if tenant_id == None:
raise fault.BadRequestFault("Expecting a Tenant Id")
@@ -266,7 +261,7 @@ class IdentityService(object):
return Groups(ts, links)
def get_tenant_group(self, admin_token, tenant_id, group_id):
- self.__validate_token(admin_token)
+ self.__validate_admin_token(admin_token)
dtenant = api.tenant.get(tenant_id)
if dtenant == None:
@@ -279,7 +274,7 @@ class IdentityService(object):
return Group(dtenant.id, dtenant.desc, dtenant.tenant_id)
def update_tenant_group(self, admin_token, tenant_id, group_id, group):
- self.__validate_token(admin_token)
+ self.__validate_admin_token(admin_token)
if not isinstance(group, Group):
raise fault.BadRequestFault("Expecting a Group")
@@ -308,7 +303,7 @@ class IdentityService(object):
return Group(group_id, group.description, tenant_id)
def delete_tenant_group(self, admin_token, tenant_id, group_id):
- self.__validate_token(admin_token)
+ self.__validate_admin_token(admin_token)
dtenant = api.tenant.get(tenant_id)
@@ -328,7 +323,7 @@ class IdentityService(object):
def get_users_tenant_group(self, admin_token, tenantId, groupId, marker,
limit, url):
- self.__validate_token(admin_token)
+ self.__validate_admin_token(admin_token)
if tenantId == None:
raise fault.BadRequestFault("Expecting a Tenant Id")
@@ -363,7 +358,7 @@ class IdentityService(object):
return Users(ts, links)
def add_user_tenant_group(self, admin_token, tenant, group, user):
- self.__validate_token(admin_token)
+ self.__validate_admin_token(admin_token)
if api.tenant.get(tenant) == None:
raise fault.ItemNotFoundFault("The Tenant not found")
@@ -397,7 +392,7 @@ class IdentityService(object):
group_id=group) #attribute no longer exists
def delete_user_tenant_group(self, admin_token, tenant, group, user):
- self.__validate_token(admin_token)
+ self.__validate_admin_token(admin_token)
if api.tenant.get(tenant) == None:
raise fault.ItemNotFoundFault("The Tenant not found")
@@ -437,7 +432,7 @@ class IdentityService(object):
# User Operations
#
def create_user(self, admin_token, user):
- self.__validate_token(admin_token)
+ self.__validate_admin_token(admin_token)
dtenant = self.validate_and_fetch_user_tenant(user.tenant_id)
@@ -452,8 +447,7 @@ class IdentityService(object):
"An user with that id already exists")
if api.user.get_by_email(user.email) != None:
- raise fault.EmailConflictFault(
- "Email already exists")
+ raise fault.EmailConflictFault("Email already exists")
duser = models.User()
duser.id = user.user_id
@@ -478,7 +472,7 @@ class IdentityService(object):
return None
def get_tenant_users(self, admin_token, tenant_id, marker, limit, url):
- self.__validate_token(admin_token)
+ self.__validate_admin_token(admin_token)
if tenant_id == None:
raise fault.BadRequestFault("Expecting a Tenant Id")
@@ -506,7 +500,7 @@ class IdentityService(object):
return Users(ts, links)
def get_users(self, admin_token, marker, limit, url):
- self.__validate_token(admin_token)
+ self.__validate_admin_token(admin_token)
ts = []
dusers = api.user.users_get_page(marker, limit)
for duser in dusers:
@@ -524,7 +518,7 @@ class IdentityService(object):
return Users(ts, links)
def get_user(self, admin_token, user_id):
- self.__validate_token(admin_token)
+ self.__validate_admin_token(admin_token)
duser = api.user.get(user_id)
if not duser:
raise fault.ItemNotFoundFault("The user could not be found")
@@ -544,7 +538,7 @@ class IdentityService(object):
duser.email, duser.enabled, ts)
def update_user(self, admin_token, user_id, user):
- self.__validate_token(admin_token)
+ self.__validate_admin_token(admin_token)
duser = api.user.get(user_id)
@@ -569,7 +563,7 @@ class IdentityService(object):
duser.email, duser.enabled)
def set_user_password(self, admin_token, user_id, user):
- self.__validate_token(admin_token)
+ self.__validate_admin_token(admin_token)
duser = api.user.get(user_id)
if not duser:
@@ -593,7 +587,7 @@ class IdentityService(object):
None, None, None, None, None)
def enable_disable_user(self, admin_token, user_id, user):
- self.__validate_token(admin_token)
+ self.__validate_admin_token(admin_token)
duser = api.user.get(user_id)
if not duser:
raise fault.ItemNotFoundFault("The user could not be found")
@@ -612,7 +606,7 @@ class IdentityService(object):
None, None, None, user.enabled, None)
def set_user_tenant(self, admin_token, user_id, user):
- self.__validate_token(admin_token)
+ self.__validate_admin_token(admin_token)
duser = api.user.get(user_id)
if not duser:
raise fault.ItemNotFoundFault("The user could not be found")
@@ -630,7 +624,7 @@ class IdentityService(object):
None, user.tenant_id, None, None, None)
def delete_user(self, admin_token, user_id):
- self.__validate_token(admin_token)
+ self.__validate_admin_token(admin_token)
duser = api.user.get(user_id)
if not duser:
raise fault.ItemNotFoundFault("The user could not be found")
@@ -644,7 +638,7 @@ class IdentityService(object):
def get_user_groups(self, admin_token, user_id, marker, limit,
url):
- self.__validate_token(admin_token)
+ self.__validate_admin_token(admin_token)
ts = []
dusergroups = api.group.get_by_user_get_page(user_id, marker,
limit)
@@ -682,7 +676,7 @@ class IdentityService(object):
return dtenant
def create_global_group(self, admin_token, group):
- self.__validate_token(admin_token)
+ self.__validate_admin_token(admin_token)
if not isinstance(group, GlobalGroup):
raise fault.BadRequestFault("Expecting a Group")
@@ -702,7 +696,7 @@ class IdentityService(object):
return GlobalGroup(dtenant.id, dtenant.desc, None)
def get_global_groups(self, admin_token, marker, limit, url):
- self.__validate_token(admin_token)
+ self.__validate_admin_token(admin_token)
gtenant = self.__check_create_global_tenant()
ts = []
dtenantgroups = api.tenant_group.get_page(gtenant.id, \
@@ -722,7 +716,7 @@ class IdentityService(object):
return GlobalGroups(ts, links)
def get_global_group(self, admin_token, group_id):
- self.__validate_token(admin_token)
+ self.__validate_admin_token(admin_token)
gtenant = self.__check_create_global_tenant()
dtenant = api.tenant.get(gtenant.id)
if dtenant == None:
@@ -735,7 +729,7 @@ class IdentityService(object):
return GlobalGroup(dtenant.id, dtenant.desc)
def update_global_group(self, admin_token, group_id, group):
- self.__validate_token(admin_token)
+ self.__validate_admin_token(admin_token)
gtenant = self.__check_create_global_tenant()
if not isinstance(group, GlobalGroup):
raise fault.BadRequestFault("Expecting a Group")
@@ -756,7 +750,7 @@ class IdentityService(object):
return GlobalGroup(group_id, group.description, gtenant.id)
def delete_global_group(self, admin_token, group_id):
- self.__validate_token(admin_token)
+ self.__validate_admin_token(admin_token)
gtenant = self.__check_create_global_tenant()
dtenant = api.tenant.get(gtenant.id)
@@ -775,7 +769,7 @@ class IdentityService(object):
return None
def get_users_global_group(self, admin_token, groupId, marker, limit, url):
- self.__validate_token(admin_token)
+ self.__validate_admin_token(admin_token)
gtenant = self.__check_create_global_tenant()
if gtenant.id == None:
@@ -810,7 +804,7 @@ class IdentityService(object):
return Users(ts, links)
def add_user_global_group(self, admin_token, group, user):
- self.__validate_token(admin_token)
+ self.__validate_admin_token(admin_token)
gtenant = self.__check_create_global_tenant()
if api.tenant.get(gtenant.id) == None:
@@ -844,7 +838,7 @@ class IdentityService(object):
group_id=group) # attribute no longer exists!
def delete_user_global_group(self, admin_token, group, user):
- self.__validate_token(admin_token)
+ self.__validate_admin_token(admin_token)
gtenant = self.__check_create_global_tenant()
if api.tenant.get(gtenant.id) == None:
@@ -890,34 +884,59 @@ class IdentityService(object):
for droleRef in droleRefs:
ts.append(RoleRef(droleRef.id, droleRef.role_id,
droleRef.tenant_id))
- user = auth.User(duser.id, duser.tenant_id, None, RoleRefs(ts,
- []))
+ user = auth.User(duser.id, duser.tenant_id, None, RoleRefs(ts, []))
return auth.ValidateData(token, user)
- def __validate_token(self, token_id, admin=True):
+ def __validate_tenant(self, tenant_id):
+ if not tenant_id:
+ raise fault.UnauthorizedFault("Missing tenant")
+
+ tenant = api.tenant.get(tenant_id)
+
+ if not tenant.enabled:
+ raise fault.TenantDisabledFault("Tenant %s has been disabled!"
+ % tenant.id)
+
+ def __validate_token(self, token_id, belongs_to=None):
if not token_id:
raise fault.UnauthorizedFault("Missing token")
+
(token, user) = self.__get_dauth_data(token_id)
if not token:
raise fault.ItemNotFoundFault("Bad token, please reauthenticate")
+
if token.expires < datetime.now():
raise fault.ForbiddenFault("Token expired, please renew")
+
if not user.enabled:
- raise fault.UserDisabledFault("The user %s has been disabled!"
+ raise fault.UserDisabledFault("User %s has been disabled!"
% user.id)
- if admin:
- roleRefs = api.role.ref_get_all_global_roles(user.id)
- for roleRef in roleRefs:
- if roleRef.role_id == backends.KeyStoneAdminRole\
- and roleRef.tenant_id is None:
- return (token, user)
- raise fault.UnauthorizedFault("You are not authorized "
- "to make this call")
+
+ if user.tenant_id:
+ self.__validate_tenant(user.tenant_id)
+
+ if token.tenant_id:
+ self.__validate_tenant(token.tenant_id)
+
+ if belongs_to and token.tenant_id != belongs_to:
+ raise fault.UnauthorizedFault("Unauthorized on this tenant")
+
return (token, user)
+
+ def __validate_admin_token(self, token_id):
+ (token, user) = self.__validate_token(token_id)
+
+ for roleRef in api.role.ref_get_all_global_roles(user.id):
+ if roleRef.role_id == backends.KeyStoneAdminRole and \
+ roleRef.tenant_id is None:
+ return (token, user)
+
+ raise fault.UnauthorizedFault(
+ "You are not authorized to make this call")
def create_role(self, admin_token, role):
- self.__validate_token(admin_token)
+ self.__validate_admin_token(admin_token)
if not isinstance(role, Role):
raise fault.BadRequestFault("Expecting a Role")
@@ -935,7 +954,7 @@ class IdentityService(object):
return role
def get_roles(self, admin_token, marker, limit, url):
- self.__validate_token(admin_token)
+ self.__validate_admin_token(admin_token)
ts = []
droles = api.role.get_page(marker, limit)
@@ -953,7 +972,7 @@ class IdentityService(object):
return Roles(ts, links)
def get_role(self, admin_token, role_id):
- self.__validate_token(admin_token)
+ self.__validate_admin_token(admin_token)
drole = api.role.get(role_id)
if not drole:
@@ -961,7 +980,7 @@ class IdentityService(object):
return Role(drole.id, drole.desc)
def create_role_ref(self, admin_token, user_id, roleRef):
- self.__validate_token(admin_token)
+ self.__validate_admin_token(admin_token)
duser = api.user.get(user_id)
if not duser:
@@ -992,12 +1011,12 @@ class IdentityService(object):
return roleRef
def delete_role_ref(self, admin_token, role_ref_id):
- self.__validate_token(admin_token)
+ self.__validate_admin_token(admin_token)
api.role.ref_delete(role_ref_id)
return None
def get_user_roles(self, admin_token, marker, limit, url, user_id):
- self.__validate_token(admin_token)
+ self.__validate_admin_token(admin_token)
duser = api.user.get(user_id)
if not duser:
@@ -1019,7 +1038,7 @@ class IdentityService(object):
return RoleRefs(ts, links)
def get_endpoint_templates(self, admin_token, marker, limit, url):
- self.__validate_token(admin_token)
+ self.__validate_admin_token(admin_token)
ts = []
dendpointTemplates = api.endpoint_template.get_page(marker, limit)
@@ -1044,7 +1063,7 @@ class IdentityService(object):
return EndpointTemplates(ts, links)
def get_endpoint_template(self, admin_token, endpoint_template_id):
- self.__validate_token(admin_token)
+ self.__validate_admin_token(admin_token)
dendpointTemplate = api.endpoint_template.get(endpoint_template_id)
if not dendpointTemplate:
@@ -1061,7 +1080,7 @@ class IdentityService(object):
dendpointTemplate.is_global)
def get_tenant_endpoints(self, admin_token, marker, limit, url, tenant_id):
- self.__validate_token(admin_token)
+ self.__validate_admin_token(admin_token)
if tenant_id == None:
raise fault.BadRequestFault("Expecting a Tenant Id")
@@ -1093,7 +1112,7 @@ class IdentityService(object):
def create_endpoint_for_tenant(self, admin_token,
tenant_id, endpoint_template, url):
- self.__validate_token(admin_token)
+ self.__validate_admin_token(admin_token)
if tenant_id == None:
raise fault.BadRequestFault("Expecting a Tenant Id")
if api.tenant.get(tenant_id) == None:
@@ -1112,6 +1131,6 @@ class IdentityService(object):
return dendpoint
def delete_endpoint(self, admin_token, endpoint_id):
- self.__validate_token(admin_token)
+ self.__validate_admin_token(admin_token)
api.endpoint_template.endpoint_delete(endpoint_id)
return None
diff --git a/keystone/test/system/common.py b/keystone/test/system/common.py
index b33537b2..1e2fd206 100644
--- a/keystone/test/system/common.py
+++ b/keystone/test/system/common.py
@@ -5,7 +5,7 @@ class HttpTestCase(unittest.TestCase):
"""Performs generic HTTP request testing"""
def request(self, host='127.0.0.1', port=80, method='GET', path='/',
- headers={}, body=None, expect_exception=False,):
+ headers={}, body=None, assert_status=None):
"""Perform request and fetch httplib.HTTPResponse from the server"""
# Initialize a connection
@@ -22,26 +22,26 @@ class HttpTestCase(unittest.TestCase):
connection.close()
# Automatically assert HTTP status code
- if not expect_exception:
- self.assertSuccessfulResponse(response)
+ if assert_status:
+ self.assertResponseStatus(response, assert_status)
else:
- self.assertExceptionalResponse(response)
+ self.assertResponseSuccessful(response)
# Contains the response headers, body, etc
return response
-
- def assertSuccessfulResponse(self, response):
+
+ def assertResponseSuccessful(self, response):
"""Asserts that a status code lies inside the 2xx range"""
self.assertTrue(response.status >= 200 and response.status <= 299,
- 'Status code %d is outside of the expected range (2xx) \n\n%s' %
- (response.status, response.body))
-
- def assertExceptionalResponse(self, response):
- """Asserts that a status code lies outside the 2xx range"""
- self.assertFalse(response.status >= 200 and response.status <= 299,
- 'Status code %d is outside of the expected range (not 2xx)\n\n%s' %
+ 'Status code %d is outside of the expected range (2xx)\n\n%s' %
(response.status, response.body))
+ def assertResponseStatus(self, response, assert_status):
+ """Asserts a specific status code on the response"""
+ self.assertEqual(response.status, assert_status,
+ 'Status code %s is not %s, as expected)\n\n%s' %
+ (response.status, assert_status, response.body))
+
class RestfulTestCase(HttpTestCase):
"""Performs restful HTTP request testing"""
diff --git a/keystone/test/system/test_auth.py b/keystone/test/system/test_auth.py
index 697c928a..b11eb3e8 100644
--- a/keystone/test/system/test_auth.py
+++ b/keystone/test/system/test_auth.py
@@ -18,6 +18,58 @@ class TestAdminAuthentication(KeystoneTestCase):
self.assertTrue(r.json['auth']['token']['id'])
self.assertTrue(r.json['auth']['token']['expires'])
+class TestAdminAuthenticationNegative(KeystoneTestCase):
+ """Negative test admin-side user authentication"""
+
+ user_id = KeystoneTestCase._uuid()
+ user_id2 = KeystoneTestCase._uuid()
+
+ def test_service_token_as_admin_token(self):
+ """Admin actions should fail for mere service tokens"""
+
+ # Admin create a user
+ self.admin_request(method='PUT', path='/users',
+ json={
+ 'user': {
+ 'id': self.user_id,
+ 'password': 'secrete',
+ 'email': self.user_id + '@openstack.org',
+ 'enabled': True,
+ }
+ })
+
+ # User authenticates to get a token
+ r = self.service_request(method='POST', path='/tokens',
+ json={
+ 'passwordCredentials': {
+ 'username': self.user_id,
+ 'password': 'secrete',
+ }
+ })
+ self.service_token = r.json['auth']['token']['id']
+
+ # Prepare to use the service token as an admin token
+ self.admin_token_backup = self.admin_token
+ self.admin_token = self.service_token
+
+ # Try creating another user
+ self.admin_request(method='PUT', path='/users', assert_status=401,
+ json={
+ 'user': {
+ 'id': self.user_id2,
+ 'password': 'secrete',
+ 'email': self.user_id2 + '@openstack.org',
+ 'enabled': True,
+ }
+ })
+
+ def tearDown(self):
+ # Restore our admin token so we can clean up
+ self.admin_token = self.admin_token_backup
+
+ # Delete user
+ self.admin_request(method='DELETE', path='/users/%s' % self.user_id)
+
class TestServiceAuthentication(KeystoneTestCase):
"""Test service-side user authentication"""
@@ -32,7 +84,7 @@ class TestServiceAuthentication(KeystoneTestCase):
'user': {
'id': self.user_id,
'password': 'secrete',
- 'email': 'user@openstack.org',
+ 'email': self.user_id + '@openstack.org',
'enabled': True,
}
})
diff --git a/keystone/test/system/test_issue_85.py b/keystone/test/system/test_issue_85.py
index 76490232..f9ec8066 100644
--- a/keystone/test/system/test_issue_85.py
+++ b/keystone/test/system/test_issue_85.py
@@ -20,7 +20,7 @@ class TestIssue85(KeystoneTestCase):
}
})
- # Create a user
+ # Create a user for a specific tenant
self.admin_request(method='PUT', path='/users',
json={
'user':{
@@ -28,7 +28,7 @@ class TestIssue85(KeystoneTestCase):
'password': 'secrete',
'email': 'user@openstack.org',
'enabled': True,
- 'tenant_id': 'tenant',
+ 'tenantId': self.tenant_id
}
})
@@ -43,18 +43,20 @@ class TestIssue85(KeystoneTestCase):
def test_disabling_tenant_disables_token(self):
"""Disabling a tenant should invalidate previously-issued tokens"""
- # Authenticate as user to get a token
+ # Authenticate as user to get a token *for a specific tenant*
r = self.service_request(method='POST', path='/tokens',
json={
'passwordCredentials': {
'username': self.user_id,
'password': 'secrete',
+ 'tenantId': self.tenant_id
}
})
self.service_token = r.json['auth']['token']['id']
- # Validate tenant token
- self.admin_request(path='/tokens/%s' % self.service_token)
+ # Validate and check that token belongs to tenant
+ self.admin_request(path='/tokens/%s?belongsTo=%s' %
+ (self.service_token, self.tenant_id))
# Disable tenant
r = self.admin_request(method='PUT',
@@ -67,10 +69,11 @@ class TestIssue85(KeystoneTestCase):
})
self.assertEqual(r.json['tenant']['enabled'], False)
- # Assert tenant token invalidated
- # Commented this out because it will fail this test
-# self.admin_request(path='/tokens/%s' % self.service_token,
-# expect_exception=True)
+ # Assert that token belonging to disabled tenant is invalid
+ r = self.admin_request(path='/tokens/%s?belongsTo=%s' %
+ (self.service_token, self.tenant_id),
+ assert_status=403)
+ self.assertTrue(r.json['tenantDisabled'], 'Tenant is disabled')
if __name__ == '__main__':
unittest.main()
diff --git a/keystone/test/system/test_request_specs.py b/keystone/test/system/test_request_specs.py
index f828d865..04e390c7 100644
--- a/keystone/test/system/test_request_specs.py
+++ b/keystone/test/system/test_request_specs.py
@@ -52,5 +52,15 @@ class TestContentTypes(KeystoneTestCase):
self.assertTrue('application/json' in r.getheader('Content-Type'))
+ def test_content_type_on_404(self):
+ """Content-Type should be honored even on 404 errors (Issue #13)"""
+ _r = self.service_request(path='/completely-invalid-path',
+ headers={'Accept': 'application/xml'},
+ assert_status=404)
+
+ # Commenting this assertion out, as it currently fails
+# self.assertTrue('application/xml' in r.getheader('Content-Type'),
+# 'application/xml not in %s' % r.getheader('Content-Type'))
+
if __name__ == '__main__':
unittest.main()