Add ability to create users and projects from keystone-manage
This adds the ability to create users and projects directly from keystone-manage. We also add the ability to specify specific UUIDs for both users and projects via the creation functions. Change-Id: Icd193eff25556d21ec26bb29908b8ad6548fdc91
This commit is contained in:
parent
2ac039b717
commit
a8366c4827
|
@ -26,6 +26,7 @@ import pbr.version
|
|||
|
||||
from keystone.cmd import bootstrap
|
||||
from keystone.cmd import doctor
|
||||
from keystone.cmd import idutils
|
||||
from keystone.common import driver_hints
|
||||
from keystone.common import fernet_utils
|
||||
from keystone.common import jwt_utils
|
||||
|
@ -201,6 +202,73 @@ class BootStrap(BaseApp):
|
|||
klass.do_bootstrap()
|
||||
|
||||
|
||||
class ProjectSetup(BaseApp):
|
||||
"""Create project with specified UUID."""
|
||||
|
||||
name = 'project_setup'
|
||||
|
||||
def __init__(self):
|
||||
self.identity = idutils.Identity()
|
||||
|
||||
@classmethod
|
||||
def add_argument_parser(cls, subparsers):
|
||||
parser = super(ProjectSetup, cls).add_argument_parser(subparsers)
|
||||
parser.add_argument('--project-name', default=None, required=True,
|
||||
help='The name of the keystone project being'
|
||||
' created.')
|
||||
parser.add_argument('--project-id', default=None,
|
||||
help='The UUID of the keystone project being'
|
||||
' created.')
|
||||
return parser
|
||||
|
||||
def do_project_setup(self):
|
||||
"""Create project with specified UUID."""
|
||||
self.identity.project_name = CONF.command.project_name
|
||||
self.identity.project_id = CONF.command.project_id
|
||||
self.identity.project_setup()
|
||||
|
||||
@classmethod
|
||||
def main(cls):
|
||||
klass = cls()
|
||||
klass.do_project_setup()
|
||||
|
||||
|
||||
class UserSetup(BaseApp):
|
||||
"""Create user with specified UUID."""
|
||||
|
||||
name = 'user_setup'
|
||||
|
||||
def __init__(self):
|
||||
self.identity = idutils.Identity()
|
||||
|
||||
@classmethod
|
||||
def add_argument_parser(cls, subparsers):
|
||||
parser = super(UserSetup, cls).add_argument_parser(subparsers)
|
||||
parser.add_argument('--username', default=None, required=True,
|
||||
help='The username of the keystone user that'
|
||||
' is being created.')
|
||||
parser.add_argument('--user-password-plain', default=None,
|
||||
required=True,
|
||||
help='The plaintext password for the keystone'
|
||||
' user that is being created.')
|
||||
parser.add_argument('--user-id', default=None,
|
||||
help='The UUID of the keystone user being '
|
||||
'created.')
|
||||
return parser
|
||||
|
||||
def do_user_setup(self):
|
||||
"""Create user with specified UUID."""
|
||||
self.identity.user_name = CONF.command.username
|
||||
self.identity.user_password = CONF.command.user_password_plain
|
||||
self.identity.user_id = CONF.command.user_id
|
||||
self.identity.user_setup()
|
||||
|
||||
@classmethod
|
||||
def main(cls):
|
||||
klass = cls()
|
||||
klass.do_user_setup()
|
||||
|
||||
|
||||
class Doctor(BaseApp):
|
||||
"""Diagnose common problems with keystone deployments."""
|
||||
|
||||
|
@ -1330,12 +1398,14 @@ CMDS = [
|
|||
MappingPopulate,
|
||||
MappingPurge,
|
||||
MappingEngineTester,
|
||||
ProjectSetup,
|
||||
ReceiptRotate,
|
||||
ReceiptSetup,
|
||||
SamlIdentityProviderMetadata,
|
||||
TokenRotate,
|
||||
TokenSetup,
|
||||
TrustFlush
|
||||
TrustFlush,
|
||||
UserSetup
|
||||
]
|
||||
|
||||
|
||||
|
|
|
@ -0,0 +1,151 @@
|
|||
# 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 uuid
|
||||
|
||||
from oslo_log import log
|
||||
|
||||
from keystone.common import provider_api
|
||||
from keystone.common.validation import validators
|
||||
import keystone.conf
|
||||
from keystone import exception
|
||||
from keystone.identity.mapping_backends import mapping
|
||||
from keystone import notifications
|
||||
from keystone.server import backends
|
||||
|
||||
CONF = keystone.conf.CONF
|
||||
LOG = log.getLogger(__name__)
|
||||
PROVIDERS = provider_api.ProviderAPIs
|
||||
|
||||
|
||||
class Identity(object):
|
||||
|
||||
def __init__(self):
|
||||
backends.load_backends()
|
||||
|
||||
self.user_id = None
|
||||
self.user_name = None
|
||||
self.user_password = None
|
||||
|
||||
self.project_id = None
|
||||
self.project_name = None
|
||||
|
||||
self.default_domain_id = CONF.identity.default_domain_id
|
||||
|
||||
def project_setup(self):
|
||||
try:
|
||||
project_id = self.project_id
|
||||
if project_id is None:
|
||||
project_id = uuid.uuid4().hex
|
||||
project = {
|
||||
'enabled': True,
|
||||
'id': project_id,
|
||||
'domain_id': self.default_domain_id,
|
||||
'description': 'Bootstrap project for initializing the cloud.',
|
||||
'name': self.project_name
|
||||
}
|
||||
PROVIDERS.resource_api.create_project(project_id, project)
|
||||
LOG.info('Created project %s', self.project_name)
|
||||
except exception.Conflict:
|
||||
LOG.info('Project %s already exists, skipping creation.',
|
||||
self.project_name)
|
||||
project = PROVIDERS.resource_api.get_project_by_name(
|
||||
self.project_name, self.default_domain_id
|
||||
)
|
||||
|
||||
self.project_id = project['id']
|
||||
|
||||
def _create_user(self, user_ref, initiator=None):
|
||||
_self = PROVIDERS.identity_api.create_user.__self__
|
||||
user = user_ref.copy()
|
||||
if 'password' in user:
|
||||
validators.validate_password(user['password'])
|
||||
user['name'] = user['name'].strip()
|
||||
user.setdefault('enabled', True)
|
||||
domain_id = user['domain_id']
|
||||
PROVIDERS.resource_api.get_domain(domain_id)
|
||||
|
||||
_self._assert_default_project_id_is_not_domain(
|
||||
user_ref.get('default_project_id'))
|
||||
|
||||
# For creating a user, the domain is in the object itself
|
||||
domain_id = user_ref['domain_id']
|
||||
driver = _self._select_identity_driver(domain_id)
|
||||
user = _self._clear_domain_id_if_domain_unaware(driver, user)
|
||||
# Generate a local ID - in the future this might become a function of
|
||||
# the underlying driver so that it could conform to rules set down by
|
||||
# that particular driver type.
|
||||
user['id'] = self.user_id
|
||||
ref = _self._create_user_with_federated_objects(user, driver)
|
||||
notifications.Audit.created(_self._USER, user['id'], initiator)
|
||||
return _self._set_domain_id_and_mapping(
|
||||
ref, domain_id, driver, mapping.EntityType.USER)
|
||||
|
||||
def user_setup(self):
|
||||
# NOTE(morganfainberg): Do not create the user if it already exists.
|
||||
try:
|
||||
user = PROVIDERS.identity_api.get_user_by_name(
|
||||
self.user_name, self.default_domain_id
|
||||
)
|
||||
LOG.info('User %s already exists, skipping creation.',
|
||||
self.user_name)
|
||||
|
||||
if self.user_id is not None and user['id'] != self.user_id:
|
||||
msg = (f'user `{self.user_name}` already exists '
|
||||
f'with `{self.user_id}`')
|
||||
raise exception.Conflict(type='user_id', details=msg)
|
||||
|
||||
# If the user is not enabled, re-enable them. This also helps
|
||||
# provide some useful logging output later.
|
||||
update = {}
|
||||
enabled = user['enabled']
|
||||
if not enabled:
|
||||
update['enabled'] = True
|
||||
|
||||
try:
|
||||
PROVIDERS.identity_api.driver.authenticate(
|
||||
user['id'], self.user_password
|
||||
)
|
||||
except AssertionError:
|
||||
# This means that authentication failed and that we need to
|
||||
# update the user's password. This is going to persist a
|
||||
# revocation event that will make all previous tokens for the
|
||||
# user invalid, which is OK because it falls within the scope
|
||||
# of revocation. If a password changes, we shouldn't be able to
|
||||
# use tokens obtained with an old password.
|
||||
update['password'] = self.user_password
|
||||
|
||||
# Only make a call to update the user if the password has changed
|
||||
# or the user was previously disabled. This allows bootstrap to act
|
||||
# as a recovery tool, without having to create a new user.
|
||||
if update:
|
||||
user = PROVIDERS.identity_api.update_user(
|
||||
user['id'], update
|
||||
)
|
||||
LOG.info('Reset password for user %s.', self.user_name)
|
||||
if not enabled and user['enabled']:
|
||||
# Although we always try to enable the user, this log
|
||||
# message only makes sense if we know that the user was
|
||||
# previously disabled.
|
||||
LOG.info('Enabled user %s.', self.user_name)
|
||||
except exception.UserNotFound:
|
||||
user = self._create_user(
|
||||
user_ref={
|
||||
'name': self.user_name,
|
||||
'enabled': True,
|
||||
'domain_id': self.default_domain_id,
|
||||
'password': self.user_password
|
||||
}
|
||||
)
|
||||
LOG.info('Created user %s', self.user_name)
|
||||
|
||||
self.user_id = user['id']
|
Loading…
Reference in New Issue