Skip to content

Commit

Permalink
bug:884518 Changes to support passwordcredentials calls as per API co…
Browse files Browse the repository at this point in the history
…ntract.

Minor LDAP code change to support tests.

Change-Id: I3136e186c146fcf51d0a7b250d4c04342b11ba20
  • Loading branch information
yoga80 committed Nov 2, 2011
1 parent 576f2da commit 5cf7358
Show file tree
Hide file tree
Showing 7 changed files with 550 additions and 2 deletions.
5 changes: 4 additions & 1 deletion keystone/backends/ldap/api/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,10 @@ def _ldap_res_to_model(self, res):
except KeyError:
pass
else:
obj[k] = v[0]
try:
obj[k] = v[0]
except IndexError:
obj[k] = None
return obj

def create(self, values):
Expand Down
26 changes: 26 additions & 0 deletions keystone/contrib/extensions/admin/osksadm/extension_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,15 @@
from keystone.controllers.roles import RolesController
from keystone.controllers.user import UserController
from keystone.controllers.tenant import TenantController
from keystone.controllers.credentials import CredentialsController


class ExtensionHandler(BaseExtensionHandler):
def map_extension_methods(self, mapper, options):
tenant_controller = TenantController(options)
roles_controller = RolesController(options)
user_controller = UserController(options)
credentials_controller = CredentialsController(options)

# Tenant Operations
mapper.connect("/tenants", controller=tenant_controller,
Expand Down Expand Up @@ -92,6 +94,7 @@ def map_extension_methods(self, mapper, options):
mapper.connect("/users/{user_id}/roles/OS-KSADM/{role_id}",
controller=roles_controller, action="delete_role_from_user",
conditions=dict(method=["DELETE"]))

# Services Operations
services_controller = ServicesController(options)
mapper.connect("/OS-KSADM/services",
Expand Down Expand Up @@ -121,3 +124,26 @@ def map_extension_methods(self, mapper, options):
mapper.connect("/OS-KSADM/roles/{role_id}",
controller=roles_controller, action="delete_role",
conditions=dict(method=["DELETE"]))

#Credentials Operations
mapper.connect("/users/{user_id}/OS-KSADM/credentials",
controller=credentials_controller, action="get_credentials",
conditions=dict(method=["GET"]))
mapper.connect("/users/{user_id}/OS-KSADM/credentials",
controller=credentials_controller, action="add_credential",
conditions=dict(method=["POST"]))
mapper.connect("/users/{user_id}/OS-KSADM/"\
"credentials/passwordCredentials",
controller=credentials_controller,
action="get_password_credential",
conditions=dict(method=["GET"]))
mapper.connect("/users/{user_id}/OS-KSADM/credentials"\
"/passwordCredentials",
controller=credentials_controller,
action="update_password_credential",
conditions=dict(method=["POST"]))
mapper.connect("/users/{user_id}/"\
"OS-KSADM/credentials/passwordCredentials",
controller=credentials_controller,
action="delete_password_credential",
conditions=dict(method=["DELETE"]))
46 changes: 46 additions & 0 deletions keystone/controllers/credentials.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
from keystone import utils
from keystone.common import wsgi
import keystone.config as config
from keystone.logic.types.credential import PasswordCredentials
from . import get_marker_limit_and_url


class CredentialsController(wsgi.Controller):
"""Controller for Credentials related operations"""
def __init__(self, options):
self.options = options

@utils.wrap_error
def get_credentials(self, req, user_id):
marker, limit, url = get_marker_limit_and_url(req)
credentials = config.SERVICE.get_credentials(
utils.get_auth_token(req), user_id, marker, limit, url)
return utils.send_result(200, req, credentials)

@utils.wrap_error
def get_password_credential(self, req, user_id):
credentials = config.SERVICE.get_password_credentials(
utils.get_auth_token(req), user_id)
return utils.send_result(200, req, credentials)

@utils.wrap_error
def delete_password_credential(self, req, user_id):
config.SERVICE.delete_password_credentials(utils.get_auth_token(req),
user_id)
return utils.send_result(204, None)

@utils.wrap_error
def add_credential(self, req, user_id):
credential = utils.get_normalized_request_content(
PasswordCredentials, req)
credential = config.SERVICE.create_password_credentials(
utils.get_auth_token(req), user_id, credential)
return utils.send_result(201, req, credential)

@utils.wrap_error
def update_password_credential(self, req, user_id):
credential = utils.get_normalized_request_content(
PasswordCredentials, req)
credential = config.SERVICE.update_password_credentials(
utils.get_auth_token(req), user_id, credential)
return utils.send_result(200, req, credential)
76 changes: 75 additions & 1 deletion keystone/logic/service.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
from keystone.logic.types.user import User, User_Update, Users
from keystone.logic.types.endpoint import Endpoint, Endpoints, \
EndpointTemplate, EndpointTemplates

from keystone.logic.types.credential import Credentials, PasswordCredentials

LOG = logging.getLogger('keystone.logic.service')

Expand Down Expand Up @@ -1148,3 +1148,77 @@ def delete_service(self, admin_token, service_id):
api.ROLE.ref_delete(role_ref.id)
api.ROLE.delete(role.id)
api.SERVICE.delete(service_id)

def get_credentials(self, admin_token, user_id, marker, limit, url):
self.__validate_admin_token(admin_token)
ts = []
duser = api.USER.get(user_id)
if not duser:
raise fault.ItemNotFoundFault("The user could not be found")
ts.append(PasswordCredentials(duser.name, None))
links = []
return Credentials(ts, links)

def get_password_credentials(self, admin_token, user_id):
self.__validate_admin_token(admin_token)
duser = api.USER.get(user_id)
if not duser:
raise fault.ItemNotFoundFault("The user could not be found")
if not duser.password:
raise fault.ItemNotFoundFault(
"Password credentials could not be found")
return PasswordCredentials(duser.name, None)

def delete_password_credentials(self, admin_token, user_id):
self.__validate_admin_token(admin_token)
duser = api.USER.get(user_id)
if not duser:
raise fault.ItemNotFoundFault("The user could not be found")
values = {'password': None}
api.USER.update(user_id, values)
return

def update_password_credentials(self, admin_token,
user_id, password_credentials):
self.__validate_admin_token(admin_token)
duser = api.USER.get(user_id)
if not duser:
raise fault.ItemNotFoundFault("The user could not be found")

if password_credentials.user_name is None\
or not password_credentials.user_name.strip():
raise fault.BadRequestFault("Expecting a username.")
duser_name = api.USER.get_by_name(password_credentials.user_name)
if duser_name.id != duser.id:
raise fault.UserConflictFault(
"A user with that name already exists")
values = {'password': password_credentials.password,\
'name': password_credentials.user_name}
api.USER.update(user_id, values)
duser = api.USER.get(user_id)
return PasswordCredentials(duser.name, duser.password)

def create_password_credentials(self, admin_token, user_id,\
password_credentials):
self.__validate_admin_token(admin_token)
duser = api.USER.get(user_id)
if not duser:
raise fault.ItemNotFoundFault("The user could not be found")

if password_credentials.user_name is None or\
not password_credentials.user_name.strip():
raise fault.BadRequestFault("Expecting a username.")

if password_credentials.user_name != duser.name:
duser_name = api.USER.get_by_name(password_credentials.user_name)
if duser_name:
raise fault.UserConflictFault(
"A user with that name already exists")
if duser.password:
raise fault.BadRequestFault(
"Password credentials already available.")
values = {'password': password_credentials.password,\
'name': password_credentials.user_name}
api.USER.update(user_id, values)
duser = api.USER.get(user_id)
return PasswordCredentials(duser.name, duser.password)
119 changes: 119 additions & 0 deletions keystone/logic/types/credential.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
# Copyright (c) 2010-2011 OpenStack, LLC.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import json
from lxml import etree

from keystone.logic.types import fault


class PasswordCredentials(object):
def __init__(self, user_name, password):
self.user_name = user_name
self.password = password

@staticmethod
def from_xml(xml_str):
try:
dom = etree.Element("root")
dom.append(etree.fromstring(xml_str))
root = dom.find("{http://docs.openstack.org/identity/api/v2.0}" \
"passwordCredentials")
if root == None:
raise fault.BadRequestFault("Expecting passwordCredentials")
user_name = root.get("username")
password = root.get("password")
if password is None:
raise fault.BadRequestFault("Expecting password")
return PasswordCredentials(user_name, password)
except etree.LxmlError as e:
raise fault.BadRequestFault(
"Cannot parse passwordCredentials", str(e))

@staticmethod
def from_json(json_str):
try:
obj = json.loads(json_str)
if not "passwordCredentials" in obj:
raise fault.BadRequestFault("Expecting passwordCredentials")
password_credentials = obj["passwordCredentials"]

user_name = password_credentials.get('username')
password = password_credentials.get('password')
if password is None:
raise fault.BadRequestFault("Expecting password.")
return PasswordCredentials(user_name, password)
except (ValueError, TypeError) as e:
raise fault.BadRequestFault(
"Cannot parse passwordCredentials", str(e))

def to_dom(self):
dom = etree.Element("passwordCredentials",
xmlns="http://docs.openstack.org/identity/api/v2.0")
if self.user_name:
dom.set("username", unicode(self.user_name))
if self.password:
dom.set("password", unicode(self.password))
return dom

def to_xml(self):
return etree.tostring(self.to_dom())

def to_dict(self):
password_credentials = {}
if self.user_name:
password_credentials["username"] = unicode(self.user_name)
if self.password:
password_credentials['password'] = unicode(self.password)
return {'passwordCredentials': password_credentials}

def to_json(self):
return json.dumps(self.to_dict())


class Credentials(object):
"A collection of credentials."

def __init__(self, values, links):
self.values = values
self.links = links

def to_xml(self):
dom = etree.Element("credentials")
dom.set(u"xmlns", "http://docs.openstack.org/identity/api/v2.0")

for t in self.values:
dom.append(t.to_dom())

for t in self.links:
dom.append(t.to_dom())

return etree.tostring(dom)

def to_dom(self):
dom = etree.Element("credentials")
dom.set(u"xmlns", "http://docs.openstack.org/identity/api/v2.0")

for t in self.values:
dom.append(t.to_dom())

for t in self.links:
dom.append(t.to_dom())

return dom

def to_json(self):
values = [t.to_dict() for t in self.values]
links = [t.to_dict()["links"] for t in self.links]
return json.dumps({"credentials": values, "credentials_links": links})
63 changes: 63 additions & 0 deletions keystone/test/functional/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -477,6 +477,35 @@ def get_sample(self, filename, **kwargs):
return self.service_request(method='GET',
path='/samples/%s' % (filename,), **kwargs)

def get_user_credentials(self, user_id, **kwargs):
"""GET /users/{user_id}/OS-KSADM/credentials"""
return self.admin_request(method='GET',
path='/users/%s/OS-KSADM/credentials' % (user_id,), **kwargs)

def get_user_credentials_by_type(self,
user_id, credentials_type, **kwargs):
"""GET /users/{user_id}/OS-KSADM/credentials/{credentials_type}"""
return self.admin_request(method='GET',
path='/users/%s/OS-KSADM/credentials/%s'\
% (user_id, credentials_type,), **kwargs)

def post_credentials(self, user_id, **kwargs):
"""POST /users/{user_id}/OS-KSADM/credentials"""
return self.admin_request(method='POST',
path='/users/%s/OS-KSADM/credentials' % (user_id,), **kwargs)

def post_credentials_by_type(self, user_id, credentials_type, **kwargs):
"""POST /users/{user_id}/OS-KSADM/credentials/{credentials_type}"""
return self.admin_request(method='POST',
path='/users/%s/OS-KSADM/credentials/%s' %\
(user_id, credentials_type), **kwargs)

def delete_user_credentials_by_type(self, user_id,\
credentials_type, **kwargs):
"""DELETE /users/{user_id}/OS-KSADM/credentials/{credentials_type}"""
return self.admin_request(method='DELETE',
path='/users/%s/OS-KSADM/credentials/%s' %\
(user_id, credentials_type,), **kwargs)

# Generates and return a unique string
unique_str = lambda: str(uuid.uuid4())
Expand Down Expand Up @@ -883,3 +912,37 @@ def update_endpoint_template(self, endpoint_template_id=None, region=None,

return self.put_endpoint_template(endpoint_template_id, as_json=data,
**kwargs)

def fetch_user_credentials(self, user_id=None, **kwargs):
user_id = optional_str(user_id)
return self.get_user_credentials(user_id, **kwargs)

def fetch_password_credentials(self, user_id=None, **kwargs):
user_id = optional_str(user_id)
return self.get_user_credentials_by_type(
user_id, 'passwordCredentials', **kwargs)

def create_password_credentials(self, user_id, user_name, **kwargs):
user_id = optional_str(user_id)
password = unique_str()
data = {
"passwordCredentials": {
"username": user_name,
"password": password}}
return self.post_credentials(user_id, as_json=data, **kwargs)

def update_password_credentials(self, user_id, user_name,
password=None, **kwargs):
user_id = optional_str(user_id)
password = optional_str(password)
data = {
"passwordCredentials": {
"username": user_name,
"password": password}}
return self.post_credentials_by_type(
user_id, 'passwordCredentials', as_json=data, **kwargs)

def delete_password_credentials(self, user_id, **kwargs):
user_id = optional_str(user_id)
return self.delete_user_credentials_by_type(
user_id, 'passwordCredentials', **kwargs)

0 comments on commit 5cf7358

Please sign in to comment.