Move the logic from windows.setuserpassword back into common.setuserpassword
The password can be forced to be changed at the next login for Unix systems as well, so having a specific Windows plugin makes no sense. This means that change_password_next_logon becomes a new method in the base osutils. Change-Id: I0e470a10177e17c7817ee02abee25ed0b7247301
This commit is contained in:
parent
6c121f5016
commit
040d86e03f
@ -114,3 +114,7 @@ class BaseOSUtils(object):
|
|||||||
def set_timezone(self, timezone):
|
def set_timezone(self, timezone):
|
||||||
"""Set the timezone for this instance."""
|
"""Set the timezone for this instance."""
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
def change_password_next_logon(self, username):
|
||||||
|
"""Force the given user to change his password at the next login."""
|
||||||
|
raise NotImplementedError()
|
||||||
|
@ -1080,7 +1080,7 @@ class WindowsUtils(base.BaseOSUtils):
|
|||||||
timezone.Timezone(windows_name).set(self)
|
timezone.Timezone(windows_name).set(self)
|
||||||
|
|
||||||
def change_password_next_logon(self, username):
|
def change_password_next_logon(self, username):
|
||||||
"""Force the given user to change the password at next logon."""
|
"""Force the given user to change the password at next login."""
|
||||||
user = self._get_adsi_object(object_name=username,
|
user = self._get_adsi_object(object_name=username,
|
||||||
object_type='user')
|
object_type='user')
|
||||||
user.Put('PasswordExpired', self.PASSWORD_CHANGED_FLAG)
|
user.Put('PasswordExpired', self.PASSWORD_CHANGED_FLAG)
|
||||||
|
@ -32,7 +32,7 @@ opts = [
|
|||||||
'SetUserSSHPublicKeysPlugin',
|
'SetUserSSHPublicKeysPlugin',
|
||||||
'cloudbaseinit.plugins.windows.extendvolumes.ExtendVolumesPlugin',
|
'cloudbaseinit.plugins.windows.extendvolumes.ExtendVolumesPlugin',
|
||||||
'cloudbaseinit.plugins.common.userdata.UserDataPlugin',
|
'cloudbaseinit.plugins.common.userdata.UserDataPlugin',
|
||||||
'cloudbaseinit.plugins.windows.setuserpassword.'
|
'cloudbaseinit.plugins.common.setuserpassword.'
|
||||||
'SetUserPasswordPlugin',
|
'SetUserPasswordPlugin',
|
||||||
'cloudbaseinit.plugins.windows.winrmlistener.'
|
'cloudbaseinit.plugins.windows.winrmlistener.'
|
||||||
'ConfigWinRMListenerPlugin',
|
'ConfigWinRMListenerPlugin',
|
||||||
@ -72,8 +72,8 @@ OLD_PLUGINS = {
|
|||||||
'cloudbaseinit.plugins.windows.userdata.UserDataPlugin':
|
'cloudbaseinit.plugins.windows.userdata.UserDataPlugin':
|
||||||
'cloudbaseinit.plugins.common.userdata.UserDataPlugin',
|
'cloudbaseinit.plugins.common.userdata.UserDataPlugin',
|
||||||
|
|
||||||
'cloudbaseinit.plugins.common.setuserpassword.SetUserPasswordPlugin':
|
'cloudbaseinit.plugins.windows.setuserpassword.SetUserPasswordPlugin':
|
||||||
'cloudbaseinit.plugins.windows.setuserpassword.SetUserPasswordPlugin',
|
'cloudbaseinit.plugins.common.setuserpassword.SetUserPasswordPlugin',
|
||||||
|
|
||||||
'cloudbaseinit.plugins.windows.localscripts.LocalScriptsPlugin':
|
'cloudbaseinit.plugins.windows.localscripts.LocalScriptsPlugin':
|
||||||
'cloudbaseinit.plugins.common.localscripts.LocalScriptsPlugin',
|
'cloudbaseinit.plugins.common.localscripts.LocalScriptsPlugin',
|
||||||
|
@ -23,12 +23,31 @@ from cloudbaseinit.plugins.common import constants
|
|||||||
from cloudbaseinit.utils import crypt
|
from cloudbaseinit.utils import crypt
|
||||||
|
|
||||||
|
|
||||||
|
CLEAR_TEXT_INJECTED_ONLY = 'clear_text_injected_only'
|
||||||
|
ALWAYS_CHANGE = 'always'
|
||||||
|
NEVER_CHANGE = 'no'
|
||||||
|
LOGON_PASSWORD_CHANGE_OPTIONS = [
|
||||||
|
CLEAR_TEXT_INJECTED_ONLY,
|
||||||
|
NEVER_CHANGE,
|
||||||
|
ALWAYS_CHANGE,
|
||||||
|
]
|
||||||
opts = [
|
opts = [
|
||||||
cfg.BoolOpt('inject_user_password', default=True, help='Set the password '
|
cfg.BoolOpt('inject_user_password', default=True, help='Set the password '
|
||||||
'provided in the configuration. If False or no password is '
|
'provided in the configuration. If False or no password is '
|
||||||
'provided, a random one will be set'),
|
'provided, a random one will be set'),
|
||||||
|
cfg.StrOpt('first_logon_behaviour',
|
||||||
|
default=CLEAR_TEXT_INJECTED_ONLY,
|
||||||
|
choices=LOGON_PASSWORD_CHANGE_OPTIONS,
|
||||||
|
help='Control the behaviour of what happens at '
|
||||||
|
'next logon. If this option is set to `always`, '
|
||||||
|
'then the user will be forced to change the password '
|
||||||
|
'at next logon. If it is set to '
|
||||||
|
'`clear_text_injected_only`, '
|
||||||
|
'then the user will have to change the password only if '
|
||||||
|
'the password is a clear text password, coming from the '
|
||||||
|
'metadata. The last option is `no`, when the user is '
|
||||||
|
'never forced to change the password.'),
|
||||||
]
|
]
|
||||||
|
|
||||||
CONF = cfg.CONF
|
CONF = cfg.CONF
|
||||||
CONF.register_opts(opts)
|
CONF.register_opts(opts)
|
||||||
CONF.import_opt('username', 'cloudbaseinit.plugins.common.createuser')
|
CONF.import_opt('username', 'cloudbaseinit.plugins.common.createuser')
|
||||||
@ -103,15 +122,23 @@ class SetUserPasswordPlugin(base.BasePlugin):
|
|||||||
maximum_length)
|
maximum_length)
|
||||||
|
|
||||||
osutils.set_user_password(user_name, password)
|
osutils.set_user_password(user_name, password)
|
||||||
self.post_set_password(user_name, password,
|
self._change_logon_behaviour(user_name, password_injected=injected)
|
||||||
password_injected=injected)
|
|
||||||
return password
|
return password
|
||||||
|
|
||||||
def post_set_password(self, username, password, password_injected=False):
|
def _change_logon_behaviour(self, username, password_injected=False):
|
||||||
"""Executes post set password logic.
|
"""Post set password logic
|
||||||
|
|
||||||
This is called by :meth:`execute` after the password was set.
|
If the option is activated, force the user to change the
|
||||||
|
password at next logon.
|
||||||
"""
|
"""
|
||||||
|
if CONF.first_logon_behaviour == NEVER_CHANGE:
|
||||||
|
return
|
||||||
|
|
||||||
|
clear_text = CONF.first_logon_behaviour == CLEAR_TEXT_INJECTED_ONLY
|
||||||
|
always = CONF.first_logon_behaviour == ALWAYS_CHANGE
|
||||||
|
if always or (clear_text and password_injected):
|
||||||
|
osutils = osutils_factory.get_os_utils()
|
||||||
|
osutils.change_password_next_logon(username)
|
||||||
|
|
||||||
def execute(self, service, shared_data):
|
def execute(self, service, shared_data):
|
||||||
# TODO(alexpilotti): The username selection logic must be set in the
|
# TODO(alexpilotti): The username selection logic must be set in the
|
||||||
|
@ -1,65 +0,0 @@
|
|||||||
# Copyright 2015 Cloudbase Solutions Srl
|
|
||||||
#
|
|
||||||
# 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.
|
|
||||||
|
|
||||||
from oslo_config import cfg
|
|
||||||
|
|
||||||
from cloudbaseinit.osutils import factory
|
|
||||||
from cloudbaseinit.plugins.common import setuserpassword
|
|
||||||
|
|
||||||
CLEAR_TEXT_INJECTED_ONLY = 'clear_text_injected_only'
|
|
||||||
ALWAYS_CHANGE = 'always'
|
|
||||||
NEVER_CHANGE = 'no'
|
|
||||||
LOGON_PASSWORD_CHANGE_OPTIONS = [
|
|
||||||
CLEAR_TEXT_INJECTED_ONLY,
|
|
||||||
NEVER_CHANGE,
|
|
||||||
ALWAYS_CHANGE,
|
|
||||||
]
|
|
||||||
|
|
||||||
opts = [
|
|
||||||
cfg.StrOpt('first_logon_behaviour',
|
|
||||||
default=CLEAR_TEXT_INJECTED_ONLY,
|
|
||||||
choices=LOGON_PASSWORD_CHANGE_OPTIONS,
|
|
||||||
help='Control the behaviour of what happens at '
|
|
||||||
'next logon. If this option is set to `always`, '
|
|
||||||
'then the user will be forced to change the password '
|
|
||||||
'at next logon. If it is set to '
|
|
||||||
'`clear_text_injected_only`, '
|
|
||||||
'then the user will have to change the password only if '
|
|
||||||
'the password is a clear text password, coming from the '
|
|
||||||
'metadata. The last option is `no`, when the user is '
|
|
||||||
'never forced to change the password.'),
|
|
||||||
|
|
||||||
]
|
|
||||||
|
|
||||||
CONF = cfg.CONF
|
|
||||||
CONF.register_opts(opts)
|
|
||||||
|
|
||||||
|
|
||||||
class SetUserPasswordPlugin(setuserpassword.SetUserPasswordPlugin):
|
|
||||||
"""Plugin for changing the password, tailored to Windows."""
|
|
||||||
|
|
||||||
def post_set_password(self, username, _, password_injected=False):
|
|
||||||
"""Post set password logic
|
|
||||||
|
|
||||||
If the option is activated, force the user to change the
|
|
||||||
password at next logon.
|
|
||||||
"""
|
|
||||||
if CONF.first_logon_behaviour == NEVER_CHANGE:
|
|
||||||
return
|
|
||||||
|
|
||||||
clear_text = CONF.first_logon_behaviour == CLEAR_TEXT_INJECTED_ONLY
|
|
||||||
always = CONF.first_logon_behaviour == ALWAYS_CHANGE
|
|
||||||
if always or (clear_text and password_injected):
|
|
||||||
osutils = factory.get_os_utils()
|
|
||||||
osutils.change_password_next_logon(username)
|
|
@ -156,10 +156,11 @@ class SetUserPasswordPluginTests(unittest.TestCase):
|
|||||||
self.assertEqual(expected_logging, snatcher.output)
|
self.assertEqual(expected_logging, snatcher.output)
|
||||||
|
|
||||||
@mock.patch('cloudbaseinit.plugins.common.setuserpassword.'
|
@mock.patch('cloudbaseinit.plugins.common.setuserpassword.'
|
||||||
'SetUserPasswordPlugin.post_set_password')
|
'SetUserPasswordPlugin._change_logon_behaviour')
|
||||||
@mock.patch('cloudbaseinit.plugins.common.setuserpassword.'
|
@mock.patch('cloudbaseinit.plugins.common.setuserpassword.'
|
||||||
'SetUserPasswordPlugin._get_password')
|
'SetUserPasswordPlugin._get_password')
|
||||||
def _test_set_password(self, mock_get_password, mock_post_set_password,
|
def _test_set_password(self, mock_get_password,
|
||||||
|
mock_change_logon_behaviour,
|
||||||
password, can_update_password,
|
password, can_update_password,
|
||||||
is_password_changed, injected=False):
|
is_password_changed, injected=False):
|
||||||
expected_password = password
|
expected_password = password
|
||||||
@ -196,8 +197,8 @@ class SetUserPasswordPluginTests(unittest.TestCase):
|
|||||||
self.assertEqual(expected_password, response)
|
self.assertEqual(expected_password, response)
|
||||||
self.assertEqual(expected_logging, snatcher.output)
|
self.assertEqual(expected_logging, snatcher.output)
|
||||||
if password and can_update_password and is_password_changed:
|
if password and can_update_password and is_password_changed:
|
||||||
mock_post_set_password.assert_called_once_with(
|
mock_change_logon_behaviour.assert_called_once_with(
|
||||||
user, expected_password, password_injected=injected)
|
user, password_injected=injected)
|
||||||
|
|
||||||
def test_set_password(self):
|
def test_set_password(self):
|
||||||
self._test_set_password(password='Password',
|
self._test_set_password(password='Password',
|
||||||
@ -268,3 +269,52 @@ class SetUserPasswordPluginTests(unittest.TestCase):
|
|||||||
self._test_execute(is_password_set=False, can_post_password=True)
|
self._test_execute(is_password_set=False, can_post_password=True)
|
||||||
self._test_execute(is_password_set=True, can_post_password=True,
|
self._test_execute(is_password_set=True, can_post_password=True,
|
||||||
can_update_password=True)
|
can_update_password=True)
|
||||||
|
|
||||||
|
@mock.patch.object(setuserpassword.osutils_factory, 'get_os_utils')
|
||||||
|
@testutils.ConfPatcher('first_logon_behaviour',
|
||||||
|
setuserpassword.NEVER_CHANGE)
|
||||||
|
def test_logon_behaviour_never_change(self, mock_get_os_utils):
|
||||||
|
self._setpassword_plugin._change_logon_behaviour(
|
||||||
|
mock.sentinel.username)
|
||||||
|
|
||||||
|
self.assertFalse(mock_get_os_utils.called)
|
||||||
|
|
||||||
|
@testutils.ConfPatcher('first_logon_behaviour',
|
||||||
|
setuserpassword.ALWAYS_CHANGE)
|
||||||
|
@mock.patch.object(setuserpassword, 'osutils_factory')
|
||||||
|
def test_logon_behaviour_always(self, mock_factory):
|
||||||
|
self._setpassword_plugin._change_logon_behaviour(
|
||||||
|
mock.sentinel.username)
|
||||||
|
|
||||||
|
mock_get_os_utils = mock_factory.get_os_utils
|
||||||
|
self.assertTrue(mock_get_os_utils.called)
|
||||||
|
osutils = mock_get_os_utils.return_value
|
||||||
|
osutils.change_password_next_logon.assert_called_once_with(
|
||||||
|
mock.sentinel.username)
|
||||||
|
|
||||||
|
@testutils.ConfPatcher('first_logon_behaviour',
|
||||||
|
setuserpassword.CLEAR_TEXT_INJECTED_ONLY)
|
||||||
|
@mock.patch.object(setuserpassword, 'osutils_factory')
|
||||||
|
def test_change_logon_behaviour_clear_text_password_not_injected(
|
||||||
|
self, mock_factory):
|
||||||
|
self._setpassword_plugin._change_logon_behaviour(
|
||||||
|
mock.sentinel.username,
|
||||||
|
password_injected=False)
|
||||||
|
|
||||||
|
mock_get_os_utils = mock_factory.get_os_utils
|
||||||
|
self.assertFalse(mock_get_os_utils.called)
|
||||||
|
|
||||||
|
@testutils.ConfPatcher('first_logon_behaviour',
|
||||||
|
setuserpassword.CLEAR_TEXT_INJECTED_ONLY)
|
||||||
|
@mock.patch.object(setuserpassword, 'osutils_factory')
|
||||||
|
def test_logon_behaviour_clear_text_password_injected(
|
||||||
|
self, mock_factory):
|
||||||
|
self._setpassword_plugin._change_logon_behaviour(
|
||||||
|
mock.sentinel.username,
|
||||||
|
password_injected=True)
|
||||||
|
|
||||||
|
mock_get_os_utils = mock_factory.get_os_utils
|
||||||
|
self.assertTrue(mock_get_os_utils.called)
|
||||||
|
osutils = mock_get_os_utils.return_value
|
||||||
|
osutils.change_password_next_logon.assert_called_once_with(
|
||||||
|
mock.sentinel.username)
|
||||||
|
@ -1,69 +0,0 @@
|
|||||||
# Copyright 2015 Cloudbase Solutions Srl
|
|
||||||
#
|
|
||||||
# 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 unittest
|
|
||||||
|
|
||||||
import mock
|
|
||||||
|
|
||||||
from cloudbaseinit.plugins.windows import setuserpassword
|
|
||||||
from cloudbaseinit.tests import testutils
|
|
||||||
|
|
||||||
|
|
||||||
@mock.patch.object(setuserpassword.factory, 'get_os_utils')
|
|
||||||
class TestSetUserPassword(unittest.TestCase):
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
self._plugin = setuserpassword.SetUserPasswordPlugin()
|
|
||||||
|
|
||||||
@testutils.ConfPatcher('first_logon_behaviour',
|
|
||||||
setuserpassword.NEVER_CHANGE)
|
|
||||||
def test_post_set_password_never_change(self, mock_get_os_utils):
|
|
||||||
self._plugin.post_set_password(mock.sentinel.username,
|
|
||||||
mock.sentinel.password)
|
|
||||||
|
|
||||||
self.assertFalse(mock_get_os_utils.called)
|
|
||||||
|
|
||||||
@testutils.ConfPatcher('first_logon_behaviour',
|
|
||||||
setuserpassword.ALWAYS_CHANGE)
|
|
||||||
def test_post_set_password_always(self, mock_get_os_utils):
|
|
||||||
self._plugin.post_set_password(mock.sentinel.username,
|
|
||||||
mock.sentinel.password)
|
|
||||||
|
|
||||||
self.assertTrue(mock_get_os_utils.called)
|
|
||||||
osutils = mock_get_os_utils.return_value
|
|
||||||
osutils.change_password_next_logon.assert_called_once_with(
|
|
||||||
mock.sentinel.username)
|
|
||||||
|
|
||||||
@testutils.ConfPatcher('first_logon_behaviour',
|
|
||||||
setuserpassword.CLEAR_TEXT_INJECTED_ONLY)
|
|
||||||
def test_post_set_password_clear_text_password_not_injected(
|
|
||||||
self, mock_get_os_utils):
|
|
||||||
self._plugin.post_set_password(mock.sentinel.username,
|
|
||||||
mock.sentinel.password,
|
|
||||||
password_injected=False)
|
|
||||||
|
|
||||||
self.assertFalse(mock_get_os_utils.called)
|
|
||||||
|
|
||||||
@testutils.ConfPatcher('first_logon_behaviour',
|
|
||||||
setuserpassword.CLEAR_TEXT_INJECTED_ONLY)
|
|
||||||
def test_post_set_password_clear_text_password_injected(
|
|
||||||
self, mock_get_os_utils):
|
|
||||||
self._plugin.post_set_password(mock.sentinel.username,
|
|
||||||
mock.sentinel.password,
|
|
||||||
password_injected=True)
|
|
||||||
|
|
||||||
self.assertTrue(mock_get_os_utils.called)
|
|
||||||
osutils = mock_get_os_utils.return_value
|
|
||||||
osutils.change_password_next_logon.assert_called_once_with(
|
|
||||||
mock.sentinel.username)
|
|
Loading…
x
Reference in New Issue
Block a user