Adds ADSI based Windows user management
Replaces the Windows user creation and password management implementation from "NET USER" execution to ADSI COM usage. Change-Id: I4f95b094c95abb11762943afdb4be26541493159 Closes-Bug: #1404533
This commit is contained in:
parent
2195a97213
commit
414fd1116c
|
@ -18,6 +18,7 @@ import os
|
||||||
import re
|
import re
|
||||||
import time
|
import time
|
||||||
|
|
||||||
|
import pywintypes
|
||||||
import six
|
import six
|
||||||
from six.moves import winreg
|
from six.moves import winreg
|
||||||
from win32com import client
|
from win32com import client
|
||||||
|
@ -325,24 +326,37 @@ class WindowsUtils(base.BaseOSUtils):
|
||||||
def user_exists(self, username):
|
def user_exists(self, username):
|
||||||
return self._get_user_wmi_object(username) is not None
|
return self._get_user_wmi_object(username) is not None
|
||||||
|
|
||||||
|
def _get_adsi_object(self, hostname='.', object_name=None,
|
||||||
|
object_type='computer'):
|
||||||
|
adsi = client.Dispatch("ADsNameSpaces")
|
||||||
|
winnt = adsi.GetObject("", "WinNT:")
|
||||||
|
query = "WinNT://%s" % hostname
|
||||||
|
if object_name:
|
||||||
|
object_name_san = self.sanitize_shell_input(object_name)
|
||||||
|
query += "/%s" % object_name_san
|
||||||
|
query += ",%s" % object_type
|
||||||
|
return winnt.OpenDSObject(query, "", "", 0)
|
||||||
|
|
||||||
def _create_or_change_user(self, username, password, create,
|
def _create_or_change_user(self, username, password, create,
|
||||||
password_expires):
|
password_expires):
|
||||||
username_san = self.sanitize_shell_input(username)
|
try:
|
||||||
password_san = self.sanitize_shell_input(password)
|
if create:
|
||||||
|
host = self._get_adsi_object()
|
||||||
|
user = host.Create('user', username)
|
||||||
|
else:
|
||||||
|
user = self._get_adsi_object(object_name=username,
|
||||||
|
object_type='user')
|
||||||
|
|
||||||
args = ['NET', 'USER', username_san, password_san]
|
user.setpassword(password)
|
||||||
if create:
|
user.SetInfo()
|
||||||
args.append('/ADD')
|
|
||||||
|
|
||||||
(out, err, ret_val) = self.execute_process(args)
|
|
||||||
if not ret_val:
|
|
||||||
self._set_user_password_expiration(username, password_expires)
|
self._set_user_password_expiration(username, password_expires)
|
||||||
else:
|
except pywintypes.com_error as ex:
|
||||||
if create:
|
if create:
|
||||||
msg = "Create user failed: %s"
|
msg = "Create user failed: %s"
|
||||||
else:
|
else:
|
||||||
msg = "Set user password failed: %s"
|
msg = "Set user password failed: %s"
|
||||||
raise exception.CloudbaseInitException(msg % err)
|
raise exception.CloudbaseInitException(msg % ex.excepinfo[2])
|
||||||
|
|
||||||
def _sanitize_wmi_input(self, value):
|
def _sanitize_wmi_input(self, value):
|
||||||
return value.replace('\'', '\'\'')
|
return value.replace('\'', '\'\'')
|
||||||
|
|
|
@ -0,0 +1,19 @@
|
||||||
|
# Copyright 2013 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.
|
||||||
|
|
||||||
|
|
||||||
|
class FakeComError(Exception):
|
||||||
|
def __init__(self):
|
||||||
|
super(FakeComError, self).__init__()
|
||||||
|
self.excepinfo = [None, None, None, None, None, -2144108544]
|
|
@ -22,7 +22,7 @@ from oslo.config import cfg
|
||||||
import six
|
import six
|
||||||
|
|
||||||
from cloudbaseinit import exception
|
from cloudbaseinit import exception
|
||||||
|
from cloudbaseinit.tests import fake
|
||||||
|
|
||||||
CONF = cfg.CONF
|
CONF = cfg.CONF
|
||||||
|
|
||||||
|
@ -40,6 +40,7 @@ class WindowsUtilsTest(unittest.TestCase):
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self._pywintypes_mock = mock.MagicMock()
|
self._pywintypes_mock = mock.MagicMock()
|
||||||
|
self._pywintypes_mock.com_error = fake.FakeComError
|
||||||
self._win32com_mock = mock.MagicMock()
|
self._win32com_mock = mock.MagicMock()
|
||||||
self._win32process_mock = mock.MagicMock()
|
self._win32process_mock = mock.MagicMock()
|
||||||
self._win32security_mock = mock.MagicMock()
|
self._win32security_mock = mock.MagicMock()
|
||||||
|
@ -177,15 +178,10 @@ class WindowsUtilsTest(unittest.TestCase):
|
||||||
@mock.patch('cloudbaseinit.osutils.windows.WindowsUtils'
|
@mock.patch('cloudbaseinit.osutils.windows.WindowsUtils'
|
||||||
'._set_user_password_expiration')
|
'._set_user_password_expiration')
|
||||||
@mock.patch('cloudbaseinit.osutils.windows.WindowsUtils'
|
@mock.patch('cloudbaseinit.osutils.windows.WindowsUtils'
|
||||||
'.execute_process')
|
'._get_adsi_object')
|
||||||
def _test_create_or_change_user(self, mock_execute_process,
|
def _test_create_or_change_user(self, mock_get_adsi_object,
|
||||||
mock_set_user_password_expiration,
|
mock_set_user_password_expiration,
|
||||||
create, password_expires, ret_value=0):
|
create, password_expires, ret_value=0):
|
||||||
args = ['NET', 'USER', self._USERNAME, self._PASSWORD]
|
|
||||||
if create:
|
|
||||||
args.append('/ADD')
|
|
||||||
|
|
||||||
mock_execute_process.return_value = (None, None, ret_value)
|
|
||||||
|
|
||||||
if not ret_value:
|
if not ret_value:
|
||||||
self._winutils._create_or_change_user(self._USERNAME,
|
self._winutils._create_or_change_user(self._USERNAME,
|
||||||
|
@ -194,12 +190,18 @@ class WindowsUtilsTest(unittest.TestCase):
|
||||||
mock_set_user_password_expiration.assert_called_with(
|
mock_set_user_password_expiration.assert_called_with(
|
||||||
self._USERNAME, password_expires)
|
self._USERNAME, password_expires)
|
||||||
else:
|
else:
|
||||||
|
mock_get_adsi_object.side_effect = [
|
||||||
|
self._pywintypes_mock.com_error]
|
||||||
self.assertRaises(
|
self.assertRaises(
|
||||||
exception.CloudbaseInitException,
|
exception.CloudbaseInitException,
|
||||||
self._winutils._create_or_change_user,
|
self._winutils._create_or_change_user,
|
||||||
self._USERNAME, self._PASSWORD, create, password_expires)
|
self._USERNAME, self._PASSWORD, create, password_expires)
|
||||||
|
|
||||||
mock_execute_process.assert_called_with(args)
|
if create:
|
||||||
|
mock_get_adsi_object.assert_called_with()
|
||||||
|
else:
|
||||||
|
mock_get_adsi_object.assert_called_with(
|
||||||
|
object_name=self._USERNAME, object_type='user')
|
||||||
|
|
||||||
def test_create_user_and_add_password_expire_true(self):
|
def test_create_user_and_add_password_expire_true(self):
|
||||||
self._test_create_or_change_user(create=True, password_expires=True)
|
self._test_create_or_change_user(create=True, password_expires=True)
|
||||||
|
|
|
@ -19,18 +19,13 @@ import mock
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
from cloudbaseinit import exception
|
from cloudbaseinit import exception
|
||||||
|
from cloudbaseinit.tests import fake
|
||||||
|
|
||||||
class FakeComError(Exception):
|
|
||||||
def __init__(self):
|
|
||||||
super(FakeComError, self).__init__()
|
|
||||||
self.excepinfo = [None, None, None, None, None, -2144108544]
|
|
||||||
|
|
||||||
|
|
||||||
class WinRMConfigTests(unittest.TestCase):
|
class WinRMConfigTests(unittest.TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self._pywintypes_mock = mock.MagicMock()
|
self._pywintypes_mock = mock.MagicMock()
|
||||||
self._pywintypes_mock.com_error = FakeComError
|
self._pywintypes_mock.com_error = fake.FakeComError
|
||||||
self._win32com_mock = mock.MagicMock()
|
self._win32com_mock = mock.MagicMock()
|
||||||
self._module_patcher = mock.patch.dict(
|
self._module_patcher = mock.patch.dict(
|
||||||
'sys.modules',
|
'sys.modules',
|
||||||
|
|
Loading…
Reference in New Issue