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:
commit
44f68020c5
@ -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)
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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
8
gpoa/test/files/share.desktop
Executable 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/
|
||||
|
3
gpoa/test/gpt/data/Shortcuts_link.xml
Normal file
3
gpoa/test/gpt/data/Shortcuts_link.xml
Normal 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>
|
@ -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/')
|
||||
|
||||
|
@ -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()
|
||||
|
@ -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
|
||||
|
@ -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):
|
||||
'''
|
||||
|
Loading…
x
Reference in New Issue
Block a user