1
0
mirror of https://github.com/altlinux/gpupdate.git synced 2025-03-21 10:50:35 +03:00

Merge pull request #24 from altlinux/shortcuts_testing

Shortcuts fixes and improvements
This commit is contained in:
Evgeny Sinelnikov 2020-02-19 16:32:44 +04:00 committed by GitHub
commit 44f68020c5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 183 additions and 50 deletions

View File

@ -23,6 +23,10 @@ from .applier_frontend import applier_frontend
from gpt.shortcuts import json2sc
from util.windows import expand_windows_var
from util.logging import slogm
from util.util import (
get_homedir,
homedir_exists
)
def storage_get_shortcuts(storage, sid):
'''
@ -44,6 +48,17 @@ def write_shortcut(shortcut, username=None):
:username: None means working with machine variables and paths
'''
dest_abspath = expand_windows_var(shortcut.dest, username).replace('\\', '/') + '.desktop'
# Check that we're working for user, not on global system level
if username:
# Check that link destination path starts with specification of
# user's home directory
if dest_abspath.startswith(get_homedir(username)):
# Don't try to operate on non-existent directory
if not homedir_exists(username):
logging.warning(slogm('No home directory exists for user {}: will not create link {}'.format(username, dest_abspath)))
return None
logging.debug(slogm('Writing shortcut file to {}'.format(dest_abspath)))
shortcut.write_desktop(dest_abspath)

View File

@ -58,9 +58,10 @@ class gpt:
if 'default' == self.guid:
self.guid = 'Local Policy'
self._machine_path = None
self._user_path = None
self._get_machine_user_dirs()
self._machine_path = find_dir(self.path, 'Machine')
self._user_path = find_dir(self.path, 'User')
self._machine_prefs = find_dir(self._machine_path, 'Preferences')
self._user_prefs = find_dir(self._user_path, 'Preferences')
logging.debug(slogm('Looking for machine part of GPT {}'.format(self.guid)))
self._find_machine()
@ -88,19 +89,6 @@ class gpt:
return upm
def _get_machine_user_dirs(self):
'''
Find full path to Machine and User parts of GPT.
'''
entries = os.listdir(self.path)
for entry in entries:
full_entry_path = os.path.join(self.path, entry)
if os.path.isdir(full_entry_path):
if 'machine' == entry.lower():
self._machine_path = full_entry_path
if 'user' == entry.lower():
self._user_path = full_entry_path
def _find_user(self):
self._user_regpol = self._find_regpol('user')
self._user_shortcuts = self._find_shortcuts('user')
@ -125,16 +113,14 @@ class gpt:
'''
Find Shortcuts.xml files.
'''
search_path = os.path.join(self._machine_path, 'Preferences', 'Shortcuts')
if 'user' == part:
try:
search_path = os.path.join(self._user_path, 'Preferences', 'Shortcuts')
except Exception as exc:
return None
if not search_path:
return None
shortcuts_dir = find_dir(self._machine_prefs, 'Shortcuts')
shortcuts_file = find_file(shortcuts_dir, 'shortcuts.xml')
return find_file(search_path, 'shortcuts.xml')
if 'user' == part:
shortcuts_dir = find_dir(self._user_prefs, 'Shortcuts')
shortcuts_file = find_file(shortcuts_dir, 'shortcuts.xml')
return shortcuts_file
def _find_envvars(self, part):
'''
@ -240,15 +226,42 @@ User Shortcuts.xml: {}
)
return result
def find_dir(search_path, name):
'''
Attempt for case-insensitive search of directory
:param search_path: Path to get file list from
:param name: Name of the directory to search for
'''
if not search_path:
return None
try:
file_list = os.listdir(search_path)
for entry in file_list:
dir_path = os.path.join(search_path, entry)
if os.path.isdir(dir_path) and name.lower() == str(entry).lower():
return dir_path
except Exception as exc:
pass
return None
def find_file(search_path, name):
'''
Attempt for case-insensitive file search in directory.
'''
if not search_path:
return None
if not name:
return None
try:
file_list = os.listdir(search_path)
for entry in file_list:
file_path = os.path.join(search_path, entry)
if os.path.isfile(file_path) and name.lower() == entry.lower():
if os.path.isfile(file_path) and name.lower() == str(entry).lower():
return file_path
except Exception as exc:
#logging.error(exc)

View File

@ -16,7 +16,10 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from pathlib import Path
import stat
import logging
from enum import Enum
from xml.etree import ElementTree
from xdg.DesktopEntry import DesktopEntry
@ -25,18 +28,58 @@ import json
from util.windows import transform_windows_path
from util.xml import get_xml_root
class TargetType(Enum):
FILESYSTEM = 'FILESYSTEM'
URL = 'URL'
def get_ttype(targetstr):
'''
Validation function for targetType property
:targetstr: String representing link type.
:returns: Object of type TargetType.
'''
ttype = TargetType.FILESYSTEM
if targetstr == 'URL':
ttype = TargetType.URL
return ttype
def ttype2str(ttype):
'''
Transform TargetType to string for JSON serialization
:param ttype: TargetType object
'''
result = 'FILESYSTEM'
if ttype == TargetType.URL:
result = 'URL'
return result
def read_shortcuts(shortcuts_file):
'''
Read shortcut objects from GPTs XML file
:shortcuts_file: Location of Shortcuts.xml
'''
shortcuts = list()
for link in get_xml_root(shortcuts_file):
props = link.find('Properties')
# Location of the link itself
dest = props.get('shortcutPath')
# Location where link should follow
path = transform_windows_path(props.get('targetPath'))
# Arguments to executable file
arguments = props.get('arguments')
sc = shortcut(dest, path, arguments, link.get('name'))
# URL or FILESYSTEM
target_type = get_ttype(props.get('targetType'))
sc = shortcut(dest, path, arguments, link.get('name'), target_type)
sc.set_changed(link.get('changed'))
sc.set_clsid(link.get('clsid'))
sc.set_guid(link.get('uid'))
@ -50,8 +93,9 @@ def json2sc(json_str):
Build shortcut out of string-serialized JSON
'''
json_obj = json.loads(json_str)
link_type = get_ttype(json_obj['type'])
sc = shortcut(json_obj['dest'], json_obj['path'], json_obj['arguments'], json_obj['name'])
sc = shortcut(json_obj['dest'], json_obj['path'], json_obj['arguments'], json_obj['name'], link_type)
sc.set_changed(json_obj['changed'])
sc.set_clsid(json_obj['clsid'])
sc.set_guid(json_obj['guid'])
@ -60,13 +104,21 @@ def json2sc(json_str):
return sc
class shortcut:
def __init__(self, dest, path, arguments, name=None):
def __init__(self, dest, path, arguments, name=None, ttype=TargetType.FILESYSTEM):
'''
:param dest: Path to resulting file on file system
:param path: Path where the link should point to
:param arguments: Arguemnts to eecutable file
:param name: Name of the application
:param type: Link type - FILESYSTEM or URL
'''
self.dest = dest
self.path = path
self.arguments = arguments
self.name = name
self.changed = ''
self.is_in_user_context = self.set_usercontext()
self.type = ttype
def __str__(self):
result = self.to_json()
@ -84,6 +136,14 @@ class shortcut:
def set_guid(self, uid):
self.guid = uid
def set_type(self, ttype):
'''
Set type of the hyperlink - FILESYSTEM or URL
:ttype: - object of class TargetType
'''
self.type = ttype
def set_usercontext(self, usercontext=False):
'''
Perform action in user context or not
@ -111,6 +171,7 @@ class shortcut:
content['guid'] = self.guid
content['changed'] = self.changed
content['is_in_user_context'] = self.is_in_user_context
content['type'] = ttype2str(self.type)
result = self.desktop()
result.content.update(content)
@ -123,17 +184,30 @@ class shortcut:
'''
self.desktop_file = DesktopEntry()
self.desktop_file.addGroup('Desktop Entry')
self.desktop_file.set('Type', 'Application')
if self.type == TargetType.URL:
self.desktop_file.set('Type', 'Link')
else:
self.desktop_file.set('Type', 'Application')
self.desktop_file.set('Version', '1.0')
self.desktop_file.set('Terminal', 'false')
self.desktop_file.set('Exec', '{} {}'.format(self.path, self.arguments))
self.desktop_file.set('Name', self.name)
if self.type == TargetType.URL:
self.desktop_file.set('URL', self.path)
else:
self.desktop_file.set('Terminal', 'false')
self.desktop_file.set('Exec', '{} {}'.format(self.path, self.arguments))
return self.desktop_file
def write_desktop(self, dest):
'''
Write .desktop file to disk using path 'dest'
Write .desktop file to disk using path 'dest'. Please note that
.desktop files must have executable bit set in order to work in
GUI.
'''
self.desktop().write(dest)
sc = Path(dest)
sc.chmod(sc.stat().st_mode | stat.S_IEXEC)

8
gpoa/test/files/share.desktop Executable file
View File

@ -0,0 +1,8 @@
[Desktop Entry]
Type=Link
Version=1.0
Name=Docs
GenericName=Link to CIFS disk
Comment=Link to Samba share
URL=smb://10.0.0.0/

View File

@ -0,0 +1,3 @@
<?xml version="1.0" encoding="utf-8"?>
<Shortcuts clsid="{872ECB34-B2EC-401b-A585-D32574AA90EE}"><Shortcut clsid="{4F2F7C55-2790-433e-8127-0739D1CFA327}" name="Documents" status="Far" image="1" removePolicy="1" userContext="0" bypassErrors="0" changed="2019-12-11 16:53:37" uid="{3C6978C0-F9F6-4B8B-822C-2846327A1C45}"><Properties pidl="" targetType="URL" action="R" comment="Far 32-bit" shortcutKey="0" startIn="" arguments="%HOME%" iconIndex="13" targetPath="smb://10.0.0.0/" iconPath="%SystemRoot%\system32\SHELL32.dll" window="MIN" shortcutPath="%DesktopDir%\Docs"/></Shortcut>
</Shortcuts>

View File

@ -22,6 +22,7 @@ import unittest.mock
import os
import util.paths
import json
class GptShortcutsTestCase(unittest.TestCase):
@ -35,5 +36,25 @@ class GptShortcutsTestCase(unittest.TestCase):
import gpt.shortcuts
testdata_path = '{}/test/gpt/data/Shortcuts.xml'.format(os.getcwd())
sc = gpt.shortcuts.read_shortcuts(testdata_path)
print(sc[0].to_json())
json_obj = json.loads(sc[0].to_json())
self.assertIsNotNone(json_obj['Desktop Entry'])
self.assertEqual(json_obj['Desktop Entry']['Type'], 'Application')
@unittest.mock.patch('util.paths.cache_dir')
def test_shortcut_link(self, cdir_mock):
'''
Test generation of .desktop file with Type=Link pointing to
Samba share.
'''
cdir_mock.return_value = '/var/cache/gpupdate'
import gpt.shortcuts
testdata_path = '{}/test/gpt/data/Shortcuts_link.xml'.format(os.getcwd())
sc = gpt.shortcuts.read_shortcuts(testdata_path)
json_obj = json.loads(sc[0].to_json())
self.assertIsNotNone(json_obj['Desktop Entry'])
self.assertEqual(json_obj['Desktop Entry']['Type'], 'Link')
self.assertEqual(json_obj['Desktop Entry']['URL'], 'smb://10.0.0.0/')

View File

@ -1,16 +0,0 @@
#! /usr/bin/env python3
from gpoa.util.users import with_privileges
from gpoa.util.arguments import set_loglevel
def test_fn():
with open('testfile', 'w') as f:
f.write('test')
def main():
set_loglevel(1)
with_privileges('test', test_fn)
if '__main__' == __name__:
main()

View File

@ -21,6 +21,7 @@ import unittest
from util.roles import fill_roles
class RolesTestCase(unittest.TestCase):
@unittest.skip('Role module test disabled because of instability')
def test_roles(self):
'''
Test utility functions to work with roles

View File

@ -20,6 +20,7 @@
import socket
import os
import pwd
from pathlib import Path
def get_machine_name():
@ -53,6 +54,19 @@ def get_homedir(username):
'''
return pwd.getpwnam(username).pw_dir
def homedir_exists(username):
'''
Check that home directory exists for specified user.
:param username: string representing user name to work with
:return: True in case home directory exists and False otherwise
'''
hd = Path(get_homedir(username))
if hd.exists() and hd.is_dir():
return True
return False
def mk_homedir_path(username, homedir_path):
'''