def test_setOwner_ok(self): """ Take ownership of file/folder with valid owner ID. """ file_name = mk.makeFilename() file_segments = self.filesystem.home_segments file_segments.append(file_name) file_object = self.filesystem.openFileForWriting(file_segments) file_object.close() root_avatar = SuperAvatar() root_avatar._home_folder_path = self.avatar.home_folder_path root_filesystem = LocalFilesystem(root_avatar) root_filesystem.setOwner(file_segments, TEST_ACCOUNT_USERNAME_OTHER) current_owner = self.filesystem.getOwner(file_segments) self.assertEqual(TEST_ACCOUNT_USERNAME_OTHER, current_owner) folder_name = mk.makeFilename() folder_segments = self.filesystem.home_segments folder_segments.append(folder_name) self.filesystem.createFolder(folder_segments) root_filesystem.setOwner(folder_segments, TEST_ACCOUNT_USERNAME_OTHER) current_owner = self.filesystem.getOwner(folder_segments) self.assertEqual(TEST_ACCOUNT_USERNAME_OTHER, current_owner)
def test_addGroup_ok_group_folder(self): """ Check successful adding a group for a folder. """ folder_name = mk.makeFilename() folder_segments = self.filesystem.home_segments folder_segments.append(folder_name) self.filesystem.createFolder(folder_segments) if os.name == 'posix': root_avatar = SuperAvatar() root_avatar._home_folder_path = self.avatar.home_folder_path root_avatar._root_folder_path = self.avatar.root_folder_path root_filesystem = LocalFilesystem(avatar=root_avatar) else: root_filesystem = self.filesystem self.assertFalse( self.filesystem.hasGroup( folder_segments, TEST_ACCOUNT_GROUP_OTHER)) root_filesystem.addGroup( folder_segments, TEST_ACCOUNT_GROUP_OTHER) self.assertTrue( self.filesystem.hasGroup( folder_segments, TEST_ACCOUNT_GROUP_OTHER))
def _cleanFolder(cls, folder_segments): """ Clean all test files from folder_segments. Return a list of members which were removed. """ if not factory.fs.exists(folder_segments): return [] # In case we are running the test suite as super user, # we use super filesystem for cleaning. if cls._environ_user == cls._drop_user: temp_avatar = SuperAvatar() else: temp_avatar = DefaultAvatar() temp_filesystem = LocalFilesystem(avatar=temp_avatar) temp_members = [] for member in (temp_filesystem.getFolderContent(folder_segments)): if member.find(TEST_NAME_MARKER) != -1: temp_members.append(member) segments = folder_segments[:] segments.append(member) if temp_filesystem.isFolder(segments): temp_filesystem.deleteFolder(segments, recursive=True) else: temp_filesystem.deleteFile(segments) return temp_members
def test_generate_ssh_key_pair(self): """ Private key is generated in the specified path, and the public key is generated on a path based on private key path. """ private_path, self.private_segments = mk.fs.makePathInTemp() public_path = u"%s.pub" % (private_path) self.public_segments = self.private_segments[:] self.public_segments[-1] = u"%s.pub" % (self.public_segments[-1]) comment = u"%s %s" % (mk.string(), mk.string()) # The current code doesn't allow creating smaller keys, so at 1024 # bit, this test is very slow. options = self.Bunch(key_size=1024, key_type="rsa", key_file=private_path, key_comment=comment) private_path = LocalFilesystem.getEncodedPath(private_path) public_path = LocalFilesystem.getEncodedPath(public_path) self.assertFalse(mk.fs.exists(self.private_segments)) self.assertFalse(mk.fs.exists(self.public_segments)) generate_ssh_key(options) self.assertTrue(mk.fs.exists(self.private_segments)) self.assertTrue(mk.fs.exists(self.public_segments)) # Check content of private key. private_key = Key.fromFile(filename=private_path) self.assertEqual(1024, private_key.size) self.assertIsFalse(private_key.isPublic()) self.assertEqual("RSA", private_key.type()) # Check content of public key. public_key = Key.fromFile(filename=public_path) self.assertEqual(1024, public_key.size) self.assertIsTrue(public_key.isPublic()) self.assertEqual("RSA", public_key.type()) # Check that public key is the pair of private key. private_data = private_key.data() public_data = public_key.data() self.assertEqual(private_data["e"], public_data["e"]) self.assertEqual(private_data["n"], public_data["n"])
def generate_ssh_key(options, key=None, open_method=None): """ Generate a SSH RSA or DSA key. Return a pair of (exit_code, operation_message). For success, exit_code is 0. `key` and `open_method` are helpers for dependency injection during tests. """ if key is None: from chevah.utils.crypto import Key key = Key() if open_method is None: open_method = open exit_code = 0 message = '' try: key_size = options.key_size if options.key_type.lower() == u'rsa': key_type = crypto.TYPE_RSA elif options.key_type.lower() == u'dsa': key_type = crypto.TYPE_DSA else: key_type = options.key_type if not hasattr(options, 'key_file') or options.key_file is None: options.key_file = 'id_%s' % (options.key_type.lower()) private_file = options.key_file public_file = u'%s%s' % ( options.key_file, DEFAULT_PUBLIC_KEY_EXTENSION) key.generate(key_type=key_type, key_size=key_size) private_file_path = LocalFilesystem.getEncodedPath(private_file) public_file_path = LocalFilesystem.getEncodedPath(public_file) with open_method(private_file_path, 'wb') as file_handler: key.store(private_file=file_handler) key_comment = None if hasattr(options, 'key_comment') and options.key_comment: key_comment = options.key_comment message_comment = u'having comment "%s"' % key_comment else: message_comment = u'without a comment' with open_method(public_file_path, 'wb') as file_handler: key.store(public_file=file_handler, comment=key_comment) message = ( u'SSH key of type "%s" and length "%d" generated as ' u'public key file "%s" and private key file "%s" %s.') % ( options.key_type, key_size, public_file, private_file, message_comment, ) exit_code = 0 except UtilsError, error: exit_code = 1 message = unicode(error)
def __init__(self): self.name = process_capabilities.os_name self.fs = LocalFilesystem(SuperAvatar())
class OSAdministrationUnix(object): shadow_segments = ['etc', 'shadow'] passwd_segments = ['etc', 'passwd'] group_segments = ['etc', 'group'] gshadow_segments = ['etc', 'gshadow'] def __init__(self): self.name = process_capabilities.os_name self.fs = LocalFilesystem(SuperAvatar()) def addGroup(self, group): """ Add the group to the local computer or domain. """ add_group_method = getattr(self, '_addGroup_' + self.name) add_group_method(group=group) def _addGroup_unix(self, group): group_line = u'%s:x:%d:' % (group.name, group.gid) gshadow_line = u'%s:!::' % (group.name) self._appendUnixEntry(self.group_segments, group_line) if self.fs.exists(self.gshadow_segments): self._appendUnixEntry(self.gshadow_segments, gshadow_line) # Wait for group to be available. self._getUnixGroup(group.name) def _getUnixGroup(self, name): """ Get unix group entry, retrying if group is not available yet. """ import grp name_encoded = codecs.encode(name, 'utf-8') # Try to get the group in list of all groups. group_found = False for iterator in range(1000): if group_found: break for group in grp.getgrall(): if group[0] == name_encoded: group_found = True break time.sleep(0.1) if not group_found: raise AssertionError('Failed to get group from all: %s' % ( name_encoded)) # Now we find the group in list of all groups, but # we need to make sure it is also available to be # retrieved by name. for iterator in range(1000): try: return grp.getgrnam(name_encoded) except KeyError: # Group not ready yet. pass time.sleep(0.1) raise AssertionError( 'Group found in all, but not available by name %s' % ( name_encoded)) def _addGroup_aix(self, group): group_name = group.name.encode('utf-8') execute(['sudo', 'mkgroup', 'id=' + str(group.gid), group_name]) def _addGroup_linux(self, group): self._addGroup_unix(group) def _addGroup_osx(self, group): groupdb_name = u'/Groups/' + group.name execute([ 'sudo', 'dscl', '.', '-create', groupdb_name, 'gid', str(group.gid), 'passwd', '"*"', ]) def _addGroup_solaris(self, group): self._addGroup_unix(group) def _addGroup_hpux(self, group): self._addGroup_unix(group) def _addGroup_freebsd(self, group): group_name = group.name.encode('utf-8') execute([ 'sudo', 'pw', 'groupadd', '-g', str(group.gid), '-n', group_name, ]) def _addGroup_openbsd(self, group): group_name = group.name.encode('utf-8') execute([ 'sudo', 'groupadd', '-g', str(group.gid), group_name, ]) def addUsersToGroup(self, group, users=None): """ Add the users to the specified group. """ if users is None: users = [] add_user_method = getattr(self, '_addUsersToGroup_' + self.name) add_user_method(group=group, users=users) def _addUsersToGroup_unix(self, group, users): segments = ['etc', 'group'] members = u','.join(users) self._changeUnixEntry( segments=segments, name=group.name, field=4, value_when_empty=members, value_to_append=u',' + members, ) def _addUsersToGroup_aix(self, group, users): if not len(users): return group_name = group.name.encode('utf-8') members_list = ','.join(users) members_list = 'users=' + members_list members_list = members_list.encode('utf-8') execute(['sudo', 'chgroup', members_list, group_name]) def _addUsersToGroup_linux(self, group, users): self._addUsersToGroup_unix(group, users) def _addUsersToGroup_osx(self, group, users): groupdb_name = u'/Groups/' + group.name for member in users: execute([ 'sudo', 'dscl', '.', '-append', groupdb_name, 'GroupMembership', member, ]) def _addUsersToGroup_solaris(self, group, users): self._addUsersToGroup_unix(group, users) def _addUsersToGroup_hpux(self, group, users): self._addUsersToGroup_unix(group, users) def _addUsersToGroup_freebsd(self, group, users): if not len(users): return group_name = group.name.encode('utf-8') members_list = ','.join(users) execute([ 'sudo', 'pw', 'groupmod', group_name, '-M', members_list.encode('utf-8')]) def _addUsersToGroup_openbsd(self, group, users): group_name = group.name.encode('utf-8') for user in users: execute([ 'sudo', 'usermod', '-G', group_name, user.encode('utf-8')]) def addUser(self, user): """ Add the user and set the corresponding passwords to local computer or domain. """ add_user_method = getattr(self, '_addUser_' + self.name) add_user_method(user=user) if user.password: self.setUserPassword(user=user) def _addUser_unix(self, user): # Prevent circular import. from chevah.compat.testing import TestGroup group = TestGroup(name=user.name, posix_gid=user.uid) self._addGroup_unix(group) values = ( user.name, user.uid, user.gid, user.posix_home_path, user.shell) passwd_line = u'%s:x:%d:%d::%s:%s' % values shadow_line = u'%s:!:15218:0:99999:7:::' % (user.name) self._appendUnixEntry(self.passwd_segments, passwd_line) if self.fs.exists(self.shadow_segments): # Only write shadow if exists. self._appendUnixEntry(self.shadow_segments, shadow_line) # Wait for user to be available before creating home folder. self._getUnixUser(user.name) if user.posix_home_path == u'/tmp': return encoded_home_path = user.posix_home_path.encode('utf-8') execute(['sudo', 'mkdir', encoded_home_path]) execute([ 'sudo', 'chown', str(user.uid), encoded_home_path, ]) if user.home_group: # On some Unix system we can change group as unicode, # so we get the ID and change using the group ID. group = self._getUnixGroup(user.home_group) execute([ 'sudo', 'chgrp', str(group[2]), encoded_home_path, ]) else: execute([ 'sudo', 'chgrp', str(user.uid), encoded_home_path, ]) def _getUnixUser(self, name): """ Get Unix user entry, retrying if user is not available yet. """ import pwd name_encoded = name.encode('utf-8') error = None for iterator in range(1000): try: user = pwd.getpwnam(name_encoded) return user except (KeyError, OSError) as e: error = e pass time.sleep(0.2) raise AssertionError( 'Could not get user %s: %s' % (name_encoded, error)) def _addUser_aix(self, user): # AIX will only allow creating users with shells from # /etc/security/login.cfg. user_shell = user.shell if user.shell == '/bin/false': user_shell = '/bin/sh' user_name = user.name.encode('utf-8') command = [ 'sudo', 'mkuser', 'id=' + str(user.uid), 'home=' + user.posix_home_path.encode('utf-8'), 'shell=' + user_shell, ] if user.primary_group_name: command.append('pgrp=' + user.primary_group_name) command.append(user_name) execute(command) if user.home_group: execute([ 'sudo', 'chgrp', user.home_group.encode('utf-8'), user.posix_home_path.encode('utf-8') ]) def _addUser_linux(self, user): self._addUser_unix(user) def _addUser_osx(self, user): userdb_name = u'/Users/' + user.name home_folder = u'/Users/' + user.name execute([ 'sudo', 'dscl', '.', '-create', userdb_name, 'UserShell', '/bin/bash', ]) execute([ 'sudo', 'dscl', '.', '-create', userdb_name, 'UniqueID', str(user.uid), ]) execute([ 'sudo', 'dscl', '.', '-create', userdb_name, 'PrimaryGroupID', str(user.gid), ]) execute([ 'sudo', 'dscl', '.', '-create', userdb_name, 'NFSHomeDirectory', home_folder, ]) # Create home folder. execute(['sudo', 'mkdir', home_folder]) execute(['sudo', 'chown', user.name, home_folder]) execute(['sudo', 'chgrp', str(user.gid), home_folder]) if user.home_group: execute(['sudo', 'chgrp', user.home_group, user.posix_home_path]) else: execute(['sudo', 'chgrp', user.name, home_folder]) def _addUser_solaris(self, user): self._addUser_unix(user) def _addUser_hpux(self, user): self._addUser_unix(user) def _addUser_freebsd(self, user): user_name = user.name.encode('utf-8') home_path = user.posix_home_path.encode('utf-8') command = [ 'sudo', 'pw', 'user', 'add', user_name, '-u', str(user.uid), '-d', home_path, '-s', user.shell.encode('utf-8'), '-m', ] # Only add gid if required. if user.uid != user.gid: command.extend(['-g', str(user.gid)]) execute(command) if user.home_group: execute([ 'sudo', 'chgrp', user.home_group.encode('utf-8'), home_path]) def _addUser_openbsd(self, user): home_path = user.posix_home_path.encode('utf-8') command = [ 'sudo', 'useradd', '-u', str(user.uid).encode('utf-8'), '-d', home_path, '-s', user.shell.encode('utf-8'), ] if user.posix_home_path != '/tmp': command.append('-m'), # Only add gid if required. if user.uid != user.gid: command.extend(['-g', str(user.gid)]) command.append(user.name.encode('utf-8')) execute(command) # Wait a bit for the user to be created. time.sleep(0.2) if user.home_group: execute([ 'sudo', 'chgrp', user.home_group.encode('utf-8'), home_path]) def setUserPassword(self, user): """ Set a password for the user. The password is an attribute of the 'user'. """ set_password_method = getattr(self, '_setUserPassword_' + self.name) set_password_method(user) def _setUserPassword_unix(self, user): """ Set a password for the `user` on Unix. The password is an attribute of the 'user'. This function is common for Unix compliant OSes. It is implemented by writing directly to shadow or passwd file. """ if self.fs.exists(self.shadow_segments): return self._setUserPassword_shadow(user, self.shadow_segments) else: return self._setUserPassword_passwd(user, self.passwd_segments) def _setUserPassword_shadow(self, user, segments): """ Set a password in shadow file. """ import crypt ALPHABET = ( '0123456789' 'abcdefghijklmnopqrstuvwxyz' 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' ) salt = ''.join(random.choice(ALPHABET) for i in range(8)) shadow_password = crypt.crypt( user.password.encode('utf-8'), '$1$' + salt + '$', ) self._changeUnixEntry( segments=segments, name=user.name, field=2, value_to_replace=shadow_password, ) def _setUserPassword_passwd(self, user, segments): """ Set a password in passwd file. """ import crypt ALPHABET = ( '0123456789' 'abcdefghijklmnopqrstuvwxyz' 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' ) salt = ''.join(random.choice(ALPHABET) for i in range(2)) passwd_password = crypt.crypt( user.password.encode('utf-8'), salt) self._changeUnixEntry( segments=segments, name=user.name, field=2, value_to_replace=passwd_password, ) def _setUserPassword_aix(self, user): """ Set a password for the user on AIX. The password is an attribute of the 'user'. """ input_text = u'%s:%s' % (user.name, user.password) execute( command=['sudo', 'chpasswd', '-c'], input_text=input_text.encode('utf-8'), ) def _setUserPassword_linux(self, user): """ Set a password for the user on Linux. The password is an attribute of the 'user'. """ self._setUserPassword_unix(user) def _setUserPassword_osx(self, user): """ Set a password for the user on Mac OS X. The password is an attribute of the 'user'. """ userdb_name = u'/Users/' + user.name execute([ 'sudo', 'dscl', '.', '-passwd', userdb_name, user.password, ]) def _setUserPassword_solaris(self, user): """ Set a password for the user on Solaris. The password is an attribute of the 'user'. """ self._setUserPassword_unix(user) def _setUserPassword_hpux(self, user): self._setUserPassword_unix(user) def _setUserPassword_freebsd(self, user): execute( command=[ 'sudo', 'pw', 'mod', 'user', user.name.encode('utf-8'), '-h', '0', ], input_text=user.password.encode('utf-8'), ) def _setUserPassword_openbsd(self, user): code, out = execute( command=['encrypt'], input_text=user.password.encode('utf-8'), ) execute( command=[ 'sudo', 'usermod', '-p', out.strip(), user.name.encode('utf-8'), ], ) def deleteUser(self, user): """ Delete user from the local operating system. """ delete_user_method = getattr(self, '_deleteUser_' + self.name) delete_user_method(user) def deleteHomeFolder(self, user): """ Removes user's home folder if outside temporary folder. """ if user.posix_home_path and user.posix_home_path.startswith(u'/tmp'): return delete_folder_method = getattr(self, '_deleteHomeFolder_' + self.name) delete_folder_method(user) def _deleteHomeFolder_linux(self, user): self._deleteHomeFolder_unix(user) def _deleteHomeFolder_solaris(self, user): self._deleteHomeFolder_unix(user) def _deleteHomeFolder_hpux(self, user): self._deleteHomeFolder_unix(user) def _deleteHomeFolder_aix(self, user): self._deleteHomeFolder_unix(user) def _deleteHomeFolder_freebsd(self, user): self._deleteHomeFolder_unix(user) def _deleteHomeFolder_openbsd(self, user): self._deleteHomeFolder_unix(user) def _deleteHomeFolder_unix(self, user): encoded_home_path = user.posix_home_path.encode('utf-8') execute(['sudo', 'rm', '-rf', encoded_home_path]) def _deleteHomeFolder_osx(self, user): home_folder = u'/Users/%s' % user.name execute(['sudo', 'rm', '-rf', home_folder]) def _deleteUser_unix(self, user): self._deleteUnixEntry( kind='user', name=user.name, files=[['etc', 'passwd'], ['etc', 'shadow']]) # Prevent circular import. from chevah.compat.testing import TestGroup group = TestGroup(name=user.name, posix_gid=user.uid) self._deleteGroup_unix(group) self.deleteHomeFolder(user) def _deleteUser_aix(self, user): execute(['sudo', 'rmuser', '-p', user.name.encode('utf-8')]) self.deleteHomeFolder(user) def _deleteUser_linux(self, user): self._deleteUser_unix(user) def _deleteUser_osx(self, user): userdb_name = u'/Users/' + user.name execute(['sudo', 'dscl', '.', '-delete', userdb_name]) self.deleteHomeFolder(user) def _deleteUser_solaris(self, user): self._deleteUser_unix(user) def _deleteUser_hpux(self, user): self._deleteUser_unix(user) def _deleteUser_freebsd(self, user): execute(['sudo', 'pw', 'userdel', user.name.encode('utf-8')]) self.deleteHomeFolder(user) def _deleteUser_openbsd(self, user): execute(['sudo', 'userdel', user.name.encode('utf-8')]) self.deleteHomeFolder(user) def deleteGroup(self, group): """ Delete group from the local operating system. """ delete_group_method = getattr(self, '_deleteGroup_' + self.name) delete_group_method(group=group) def _deleteGroup_unix(self, group): self._deleteUnixEntry( kind='group', name=group.name, files=[['etc', 'group'], ['etc', 'gshadow']]) def _deleteGroup_aix(self, group): execute(['sudo', 'rmgroup', group.name.encode('utf-8')]) def _deleteGroup_linux(self, group): self._deleteGroup_unix(group) def _deleteGroup_osx(self, group): groupdb_name = u'/groups/' + group.name execute(['sudo', 'dscl', '.', '-delete', groupdb_name]) def _deleteGroup_solaris(self, group): self._deleteGroup_unix(group) def _deleteGroup_hpux(self, group): self._deleteGroup_unix(group) def _deleteGroup_freebsd(self, group): execute(['sudo', 'pw', 'group', 'del', group.name.encode('utf-8')]) def _deleteGroup_openbsd(self, group): execute(['sudo', 'groupdel', group.name.encode('utf-8')]) def _appendUnixEntry(self, segments, new_line): """ Add the new_line to the end of `segments`. """ temp_segments = segments[:] temp_segments[-1] = temp_segments[-1] + '-' content = self._getFileContent(segments) opened_file = self.fs.openFileForWriting(temp_segments, utf8=True) try: for line in content: opened_file.write(line + '\n') opened_file.write(new_line + '\n') finally: opened_file.close() self._replaceFile(temp_segments, segments) def _deleteUnixEntry(self, files, name, kind): """ Delete a generic unix entry with 'name' from all `files`. """ exists = False for segments in files: if not self.fs.exists(segments): continue exists = False temp_segments = segments[:] temp_segments[-1] = temp_segments[-1] + '-' content = self._getFileContent(segments) opened_file = self.fs.openFileForWriting( temp_segments, utf8=True) try: for line in content: entry_name = line.split(':')[0] if entry_name == name: exists = True continue opened_file.write(line + '\n') finally: if opened_file: opened_file.close() if exists: self._replaceFile(temp_segments, segments) if not exists: raise AssertionError(( 'No such %s: %s' % (kind, name)).encode('utf-8')) def _changeUnixEntry( self, segments, name, field, value_when_empty=None, value_to_append=None, value_to_replace=None, ): """ Update entry 'name' with a new value or an appended value. Field is the number of entry filed to update, counting with 1. """ exists = False temp_segments = segments[:] temp_segments[-1] = temp_segments[-1] + '-' content = self._getFileContent(segments) opened_file = self.fs.openFileForWriting( temp_segments, utf8=True) try: for line in content: fields = line.split(':') field_name = fields[0] if name == field_name: exists = True if fields[field - 1] == '': if value_when_empty: fields[field - 1] = value_when_empty elif value_to_replace: fields[field - 1] = value_to_replace else: pass elif value_to_append: fields[field - 1] = ( fields[field - 1] + value_to_append) elif value_to_replace: fields[field - 1] = value_to_replace else: pass new_line = u':'.join(fields) else: new_line = line opened_file.write(new_line + '\n') finally: opened_file.close() if exists: self._replaceFile(temp_segments, segments) else: raise AssertionError(u'No such entry: %s' % (name)) def _replaceFile(self, from_segments, to_segments): attributes = self.fs.getAttributes(to_segments) self.fs.setAttributes( from_segments, { 'mode': attributes.mode, 'uid': attributes.uid, 'gid': attributes.gid, }, ) self.fs.rename(from_segments, to_segments) def _getFileContent(self, segments): """ Return a list of all lines from file. """ opened_file = self.fs.openFileForReading(segments, utf8=True) content = [] try: for line in opened_file: content.append(line.rstrip()) finally: opened_file.close() return content