Hello community, here is the log from the commit of package yast2-adsi for openSUSE:Factory checked in at 2019-05-10 09:20:02 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/yast2-adsi (Old) and /work/SRC/openSUSE:Factory/.yast2-adsi.new.5148 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "yast2-adsi" Fri May 10 09:20:02 2019 rev:2 rq:701833 version:1.1 Changes: -------- --- /work/SRC/openSUSE:Factory/yast2-adsi/yast2-adsi.changes 2019-04-17 10:08:17.274774405 +0200 +++ /work/SRC/openSUSE:Factory/.yast2-adsi.new.5148/yast2-adsi.changes 2019-05-10 09:20:02.772499256 +0200 @@ -1,0 +2,9 @@ +Thu May 09 17:06:30 UTC 2019 - [email protected] + +- Add the Connection Settings dialog, for connecting to different + naming contexts and switching domains/servers; (bsc#1134631). +- Properties dialog opens wrong item when selected from tree view; + (bsc#1134630). +- Version 1.1 + +------------------------------------------------------------------- Old: ---- yast2-adsi-1.0.tar.bz2 New: ---- yast2-adsi-1.1.tar.bz2 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ yast2-adsi.spec ++++++ --- /var/tmp/diff_new_pack.9c5cbw/_old 2019-05-10 09:20:03.180500395 +0200 +++ /var/tmp/diff_new_pack.9c5cbw/_new 2019-05-10 09:20:03.184500406 +0200 @@ -17,7 +17,7 @@ Name: yast2-adsi -Version: 1.0 +Version: 1.1 Release: 0 Summary: ADSI Edit for YaST License: GPL-3.0-only @@ -30,7 +30,7 @@ Requires: samba-client Requires: samba-python3 Requires: yast2 -Requires: yast2-adcommon-python +Requires: yast2-adcommon-python >= 0.3 Requires: yast2-python3-bindings >= 4.0.0 BuildRequires: autoconf BuildRequires: automake ++++++ yast2-adsi-1.0.tar.bz2 -> yast2-adsi-1.1.tar.bz2 ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/yast2-adsi-1.0/package/yast2-adsi.changes new/yast2-adsi-1.1/package/yast2-adsi.changes --- old/yast2-adsi-1.0/package/yast2-adsi.changes 2019-03-28 20:03:39.000000000 +0100 +++ new/yast2-adsi-1.1/package/yast2-adsi.changes 2019-05-09 19:46:53.000000000 +0200 @@ -1,4 +1,13 @@ ------------------------------------------------------------------- +Thu May 09 17:06:30 UTC 2019 - [email protected] + +- Add the Connection Settings dialog, for connecting to different + naming contexts and switching domains/servers; (bsc#1134631). +- Properties dialog opens wrong item when selected from tree view; + (bsc#1134630). +- Version 1.1 + +------------------------------------------------------------------- Thu Mar 28 19:03:09 UTC 2019 - [email protected] - Display the right attributes for editing (mostly) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/yast2-adsi-1.0/package/yast2-adsi.spec new/yast2-adsi-1.1/package/yast2-adsi.spec --- old/yast2-adsi-1.0/package/yast2-adsi.spec 2019-03-28 20:03:39.000000000 +0100 +++ new/yast2-adsi-1.1/package/yast2-adsi.spec 2019-05-09 19:46:53.000000000 +0200 @@ -17,7 +17,7 @@ Name: yast2-adsi -Version: 1.0 +Version: 1.1 Release: 0 Summary: ADSI Edit for YaST License: GPL-3.0 @@ -31,7 +31,7 @@ Requires: yast2 Requires: yast2-python3-bindings >= 4.0.0 Requires: python3-ldap -Requires: yast2-adcommon-python +Requires: yast2-adcommon-python >= 0.3 BuildRequires: autoconf BuildRequires: automake BuildRequires: perl-XML-Writer diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/yast2-adsi-1.0/src/adsi.py new/yast2-adsi-1.1/src/adsi.py --- old/yast2-adsi-1.0/src/adsi.py 2019-03-28 20:03:39.000000000 +0100 +++ new/yast2-adsi-1.1/src/adsi.py 2019-05-09 19:46:53.000000000 +0200 @@ -34,10 +34,13 @@ # Set the loadparm context lp = LoadParm() - if os.getenv("SMB_CONF_PATH") is not None: - lp.load(os.getenv("SMB_CONF_PATH")) - else: - lp.load_default() + try: + if os.getenv("SMB_CONF_PATH") is not None: + lp.load(os.getenv("SMB_CONF_PATH")) + else: + lp.load_default() + except RuntimeError: + pass # Initialize the session creds = Credentials() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/yast2-adsi-1.0/src/complex.py new/yast2-adsi-1.1/src/complex.py --- old/yast2-adsi-1.0/src/complex.py 2019-03-28 20:03:39.000000000 +0100 +++ new/yast2-adsi-1.1/src/complex.py 2019-05-09 19:46:53.000000000 +0200 @@ -11,15 +11,36 @@ import six class Connection(Ldap): - def __init__(self, lp, creds): - super().__init__(lp, creds) + def __init__(self, lp, creds, ldap_url): + super().__init__(lp, creds, ldap_url=ldap_url) self.realm_dn = self.realm_to_dn(self.realm) + self.naming_contexts = self.__naming_contexts() + self.rootdse = False + if self.ldap_url.dn == 'Default naming context': + naming_context = 'defaultNamingContext' + elif self.ldap_url.dn == 'Configuration': + naming_context = 'configurationNamingContext' + elif self.ldap_url.dn == 'Schema': + naming_context = 'schemaNamingContext' + elif self.ldap_url.dn == 'RootDSE': + self.rootdse = True + else: + naming_context = self.ldap_url.dn + if not self.rootdse: + self.naming_context_name = self.ldap_url.dn + self.naming_context = self.naming_contexts[naming_context][-1].decode() if naming_context in self.naming_contexts else naming_context self.schema = {} self.__load_schema() def realm_to_dn(self, realm): return ','.join(['DC=%s' % part for part in realm.lower().split('.')]) + def __naming_contexts(self): + attrs = ['configurationNamingContext', 'defaultNamingContext', 'namingContexts', 'rootDomainNamingContext', 'schemaNamingContext'] + res = self.ldap_search_s('', SCOPE_BASE, '(objectclass=*)', attrs) + if res and len(res) > 0 and len(res[0]) > 1: + return res[-1][-1] + def __well_known_container(self, container): if strcmp(container, 'system'): wkguiduc = 'AB1D30F3768811D1ADED00C04FD8D5CD' @@ -104,7 +125,7 @@ def containers(self, container=None): if not container: - container = self.realm_dn + container = self.naming_context search = '(objectClass=*)' ret = self.ldap_search(container, SCOPE_ONELEVEL, search, ['name', 'objectClass']) results = [] @@ -118,7 +139,7 @@ def objs(self, container=None): if not container: - container = self.realm_dn + container = self.naming_context search = '(objectClass=*)' ret = self.ldap_search(container, SCOPE_ONELEVEL, search, ['name', 'objectClass']) return [(e[1]['name'][-1], e[1]['objectClass'][-1], e[0]) for e in ret] diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/yast2-adsi-1.0/src/dialogs.py new/yast2-adsi-1.1/src/dialogs.py --- old/yast2-adsi-1.0/src/dialogs.py 2019-03-28 20:03:39.000000000 +0100 +++ new/yast2-adsi-1.1/src/dialogs.py 2019-05-09 19:46:53.000000000 +0200 @@ -15,6 +15,9 @@ from adcommon.yldap import SCOPE_SUBTREE as SUBTREE from adcommon.creds import YCreds, MUST_USE_KERBEROS from adcommon.ui import CreateMenu, DeleteButtonBox +from samba.net import Net +from samba.dcerpc import nbt +from samba.credentials import Credentials def have_x(): from subprocess import Popen, PIPE @@ -50,7 +53,10 @@ self.conn = conn self.attribute = attr self.value = val - self.attr_type = self.conn.schema['attributeTypes'][self.attribute.encode()] + if self.attribute.encode() in self.conn.schema['attributeTypes']: + self.attr_type = self.conn.schema['attributeTypes'][self.attribute.encode()] + else: + self.attr_type = None def __dialog(self): opts = tuple() @@ -74,7 +80,7 @@ def Show(self): UI.SetApplicationTitle('String Attribute Editor') - if not self.attr_type['multi-valued'] or not self.attr_type['user-modifiable']: + if self.attr_type and (not self.attr_type['multi-valued'] or not self.attr_type['user-modifiable']): UI.OpenDialog(self.__dialog()) else: return None @@ -98,8 +104,9 @@ self.conn = conn self.obj = obj attrs = [] - for objectClass in self.obj['objectClass']: - attrs.extend(self.__extend_attrs(objectClass)) + if 'objectClass' in self.obj: # RootDSE doesn't have an objectClass + for objectClass in self.obj['objectClass']: + attrs.extend(self.__extend_attrs(objectClass)) attrs = list(set(attrs)) for attr in attrs: if not attr.decode() in self.obj.keys(): @@ -134,7 +141,13 @@ return val def __display_value(self, key, val): - attr_type = self.conn.schema['attributeTypes'][key.encode()] + if key.encode() in self.conn.schema['attributeTypes']: + attr_type = self.conn.schema['attributeTypes'][key.encode()] + else: + # RootDSE attributes don't show up in the schema, so we have to guess + if len(val) > 1: # multi-valued + return '; '.join([v.decode() for v in val]) + return val[-1] if val == None: return '<not set>' else: @@ -169,7 +182,11 @@ ), HSpacing(3))) def Show(self): - UI.SetApplicationTitle(b'CN=%s Properties' % self.obj['cn'][-1]) + if 'cn' in self.obj: + title = b'CN=%s Properties' % self.obj['cn'][-1] + else: + title = b'' + UI.SetApplicationTitle(title) UI.OpenDialog(self.__new()) while True: ret = UI.UserInput() @@ -187,7 +204,7 @@ new_val = AttrEdit(self.conn, attr, val).Show() if new_val is not None and self.obj[attr] != new_val: self.obj[attr] = new_val - UI.SetApplicationTitle(b'CN=%s Properties' % self.obj['cn'][-1]) + UI.SetApplicationTitle(title) UI.CloseDialog() return ret @@ -304,21 +321,118 @@ UI.CloseDialog() return ret +class ConnectionSettings: + def __init__(self, creds, lp): + self.creds = creds + self.lp = lp + self.conn = None + self.server = None + realm = self.lp.get('realm') + if realm: + self.server = self.__fetch_server(realm) + + def __fetch_server(self, realm): + net = Net(Credentials()) + cldap_ret = net.finddc(domain=realm, flags=(nbt.NBT_SERVER_LDAP | nbt.NBT_SERVER_DS)) + return cldap_ret.pdc_dns_name if cldap_ret else None + + def __fetch_domain(self): + net = Net(Credentials()) + cldap_ret = net.finddc(address=self.server, flags=(nbt.NBT_SERVER_LDAP | nbt.NBT_SERVER_DS)) + return cldap_ret.dns_domain if cldap_ret else None + + def __new(self): + self.contexts = ['Default naming context', 'Configuration', 'RootDSE', 'Schema'] + context = self.contexts[0] + if self.server: + path = 'ldap://%s/%s' % (self.server, context) + else: + path = '' + return MinSize(56, 22, HBox(HSpacing(3), VBox( + VSpacing(1), + HBox( + HWeight(1, Left(Label('Name:'))), + HWeight(6, InputField(Id('context'), Opt('hstretch'), '', context)), + ), + HBox( + HWeight(1, Left(Label('Path:'))), + HWeight(6, InputField(Id('path'), Opt('hstretch', 'disabled'), '', path)), + ), + Frame('Connection Point', VBox( + RadioButtonGroup(VBox( + Left(RadioButton(Id('select_dn'), Opt('hstretch', 'editable'), 'Select or type a Distinguished Name or Naming Context:', False)), + Left(ComboBox(Id('context_type'), Opt('hstretch', 'editable', 'notify', 'immediate'), '', [])), + Left(RadioButton(Id('select_nc'), Opt('hstretch'), 'Select a well known Naming Context:', True)), + Left(ComboBox(Id('context_combo'), Opt('hstretch', 'notify'), '', [Item(c) for c in self.contexts])), + )), + )), + Frame('Computer', VBox( + RadioButtonGroup(VBox( + Left(RadioButton(Id('server_select'), Opt('hstretch'), 'Select or type a domain or server: (Server | Domain [:port])', self.server is None)), + Left(ComboBox(Id('server'), Opt('hstretch', 'editable', 'notify', 'immediate'), '', [])), + Left(RadioButton(Opt('hstretch', 'disabled' if self.server is None else ''), 'Default (Domain or server that you logged in to)', self.server is not None)), + )), + Left(CheckBox(Opt('hstretch', 'disabled'), 'Use SSL-based Encryption', True)), + )), + Bottom(Right(HBox( + PushButton(Id('ok'), 'OK'), + PushButton(Id('cancel'), 'Cancel'), + ))), + VSpacing(1), + ), HSpacing(3))) + + def Show(self): + UI.SetApplicationTitle('Connection Settings') + UI.OpenDialog(self.__new()) + while True: + ret = UI.UserInput() + if str(ret) == 'abort' or str(ret) == 'cancel': + break + elif ret == 'context_combo': + UI.ChangeWidget('select_nc', 'Value', True) + context = UI.QueryWidget('context_combo', 'Value') + UI.ChangeWidget('context', 'Value', context) + path = 'ldap://%s/%s' % (self.server, context) if self.server else None + if path: + UI.ChangeWidget('path', 'Value', path) + elif ret == 'context_type': + UI.ChangeWidget('select_dn', 'Value', True) + context = UI.QueryWidget('context_type', 'Value') + UI.ChangeWidget('context', 'Value', context) + path = 'ldap://%s/%s' % (self.server, context) if self.server else None + if path: + UI.ChangeWidget('path', 'Value', path) + elif ret == 'server': + UI.ChangeWidget('server_select', 'Value', True) + self.server = UI.QueryWidget('server', 'Value') + context = UI.QueryWidget('context_combo', 'Value') + UI.ChangeWidget('path', 'Value', 'ldap://%s/%s' % (self.server, context)) + elif ret == 'ok': + realm = self.__fetch_domain().upper() + if realm: + self.lp.set('realm', realm) + path = UI.QueryWidget('path', 'Value') + ycred = YCreds(self.creds) + def cred_valid(): + try: + self.conn = Connection(self.lp, self.creds, path) + return True + except Exception as e: + ycpbuiltins.y2error(str(e)) + return False + ycred.Show(cred_valid) + if self.conn: + break + UI.CloseDialog() + return self.conn + class ADSI: def __init__(self, lp, creds): self.realm = lp.get('realm') self.lp = lp self.creds = creds self.__setup_menus() - ycred = YCreds(creds) - self.got_creds = ycred.get_creds() - while self.got_creds: - try: - self.conn = Connection(lp, creds) - break - except Exception as e: - ycpbuiltins.y2error(str(e)) - self.got_creds = ycred.get_creds() + self.conn = None def __setup_menus(self, obj=False): menus = [{'title': '&File', 'id': 'file', 'type': 'Menu'}, @@ -330,6 +444,9 @@ menus.append({'title': 'Delete', 'id': 'delete', 'type': 'MenuEntry', 'parent': 'action'}) menus.append({'title': 'Refresh', 'id': 'refresh', 'type': 'MenuEntry', 'parent': 'action'}) menus.append({'title': 'Properties', 'id': 'properties', 'type': 'MenuEntry', 'parent': 'action'}) + else: + menus.append({'title': 'Action', 'id': 'action', 'type': 'Menu'}) + menus.append({'title': 'Connect to...', 'id': 'connect', 'type': 'MenuEntry', 'parent': 'action'}) CreateMenu(menus) def __delete_selected_obj(self, current_object): @@ -337,8 +454,6 @@ self.conn.ldap_delete(current_object) def Show(self): - if not self.got_creds: - return Symbol('abort') UI.SetApplicationTitle('ADSI Edit') Wizard.SetContentsButtons('', self.__adsi_page(), '', 'Back', 'Close') DeleteButtonBox() @@ -363,6 +478,11 @@ current_object = choice self.__load_right_pane(current_container) self.__setup_menus(obj=True) + elif choice == 'rootdse': + current_container = '' + current_object = None + self.__load_right_pane(current_container) + self.__setup_menus(obj=True) else: current_container = None current_object = None @@ -372,6 +492,9 @@ if current_container: menu_open = True UI.OpenContextMenu(self.__objs_context_menu()) + else: + menu_open = True + UI.OpenContextMenu(self.__connect_context_menu()) elif ret == 'context_add_object': obj = NewObjDialog(self.conn, current_container).Show() if obj: @@ -385,9 +508,9 @@ current_object = UI.QueryWidget('items', 'Value') self.__setup_menus(obj=True) else: - self.__obj_properties(current_container) + self.__obj_properties(current_container, current_object) elif ret == 'properties': - self.__obj_properties(current_container) + self.__obj_properties(current_container, current_object) elif ret == 'next': break elif ret == 'refresh': @@ -395,18 +518,23 @@ elif str(ret) == 'delete': self.__delete_selected_obj(current_object) self.__refresh(current_container) + elif ret == 'connect': + self.conn = ConnectionSettings(self.creds, self.lp).Show() + if self.conn: + UI.ReplaceWidget('ldap_tree', self.__ldap_tree()) UI.SetApplicationTitle('ADSI Edit') return ret - def __obj_properties(self, current_container): - current_object = UI.QueryWidget('items', 'Value') + def __obj_properties(self, current_container, current_object): + if not current_object: + current_object = current_container obj = self.conn.obj(current_object)[-1] old_obj = copy.deepcopy(obj) obj = ObjAttrs(self.conn, obj).Show() if obj: obj = {key: obj[key] for key in obj.keys() if obj[key] != None} self.conn.mod_obj(current_object, old_obj, obj) - self.__refresh(current_container, current_object) + self.__refresh(current_container, current_object if current_object != current_container else None) def __objs_context_menu(self): return Term('menu', [ @@ -418,6 +546,12 @@ Item(Id('properties'), 'Properties'), ]) + def __connect_context_menu(self): + return Term('menu', [ + Item(Id('connect'), 'Connect to...'), + Item(Id('refresh'), 'Refresh'), + ]) + def __warn_delete(self, name): if six.PY3 and type(name) is bytes: name = name.decode('utf-8') @@ -463,15 +597,20 @@ return [Item(Id(e[0]), e[0].split(',')[0], e[0].lower() in expand.lower(), self.__fetch_children(e[0], expand)) for e in self.conn.containers(parent)] def __ldap_tree(self, expand=''): - top = self.conn.realm_to_dn(self.conn.realm) - items = self.__fetch_children(top, expand) + if self.conn and not self.conn.rootdse: + top = self.conn.naming_context + context = '%s [%s]' % (self.conn.naming_context_name, self.conn.dc_hostname) + items = self.__fetch_children(top, expand) + tree = [Item(context, True, [Item(Id(top), top, True, items)])] + elif self.conn and self.conn.rootdse: + context = 'RootDSE [%s]' % self.conn.dc_hostname + tree = [Item(context, True, [Item(Id('rootdse'), 'RootDSE', False, [])])] + else: + tree = [] - return Tree(Id('adsi_tree'), Opt('notify', 'immediate', 'notifyContextMenu'), 'ADSI Edit', [ - Item('Default naming context', True, [ - Item(Id(top), top, True, items) - ]) - ] - ) + return Tree(Id('adsi_tree'), Opt('notify', 'immediate', 'notifyContextMenu'), '', [ + Item(Id('adsi_edit'), 'ADSI Edit', True, tree) + ]) def __adsi_page(self): return HBox(
