From c15a4c0ec1c0563628974c4b332333b3ef54d0cd Mon Sep 17 00:00:00 2001 From: phil-levy Date: Tue, 19 Sep 2017 11:28:49 -0700 Subject: [PATCH 01/12] Update _layout.scss --- docs/_sass/minima/_layout.scss | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/_sass/minima/_layout.scss b/docs/_sass/minima/_layout.scss index 8e1e8b2d6..e2334dbd2 100644 --- a/docs/_sass/minima/_layout.scss +++ b/docs/_sass/minima/_layout.scss @@ -209,6 +209,10 @@ margin-left: 30px; /* border: black 1px solid; */ padding: 5px; + /* settings for scrollable nav area */ + position: fixed; + height: calc(100% - 110px); + overflow-y: auto; } .dummy-nav-area { width: 8000px; /* just needs to exceed max-width in nav-area */ From 26b796bd640dc2724eaafcdaefd981b007a9fbf9 Mon Sep 17 00:00:00 2001 From: Philip Levy Date: Tue, 19 Sep 2017 15:38:15 -0700 Subject: [PATCH 02/12] Changes to template and css for better width compression in presence of fixed position nav area. --- docs/_layouts/default.html | 5 +++++ docs/_sass/minima/_layout.scss | 1 + 2 files changed, 6 insertions(+) diff --git a/docs/_layouts/default.html b/docs/_layouts/default.html index 81ef38e83..a4b5377f3 100644 --- a/docs/_layouts/default.html +++ b/docs/_layouts/default.html @@ -41,6 +41,11 @@ {% include post-nav-area.html %} +
+ +

                                                                             

+
+
{{ content }}
diff --git a/docs/_sass/minima/_layout.scss b/docs/_sass/minima/_layout.scss index e2334dbd2..d8778ac5c 100644 --- a/docs/_sass/minima/_layout.scss +++ b/docs/_sass/minima/_layout.scss @@ -213,6 +213,7 @@ position: fixed; height: calc(100% - 110px); overflow-y: auto; + overflow-x: hidden; } .dummy-nav-area { width: 8000px; /* just needs to exceed max-width in nav-area */ From 42f7e547219878132cb26ed893740927f0876b10 Mon Sep 17 00:00:00 2001 From: Lucian <14054859+Luci2015@users.noreply.github.com> Date: Tue, 26 Sep 2017 12:31:57 +0300 Subject: [PATCH 03/12] Update connector-ldap.yml include some more comments for all_users_filter --- examples/config files - basic/3 connector-ldap.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/examples/config files - basic/3 connector-ldap.yml b/examples/config files - basic/3 connector-ldap.yml index c84a85299..b723029b4 100755 --- a/examples/config files - basic/3 connector-ldap.yml +++ b/examples/config files - basic/3 connector-ldap.yml @@ -52,7 +52,10 @@ search_page_size: 200 require_tls_cert: False # (optional) all_users_filter (default value given below) -# all_users_filter specifies the query used to find all users in the directory. +# In order to obtain a more fine-tunned list of LDAP users, adapt the filter to your needs. +# Only resulted users from this filter predicate are ever considered by the User Sync tool; +# that is, all LDAP queries issued by User Sync include this predicate to filter out LDAP resources +# that are not subject of synchronization. # The default value specified here is appropriate for Active Directory, which has a # special field that is used to enable and disable users. The value for OpenLDAP # directories might be much simpler: "(&(objectClass=person)(objectClass=top))" From 770beab221305cda8808825c2cf24fdafc2ce6af Mon Sep 17 00:00:00 2001 From: Lucian <14054859+Luci2015@users.noreply.github.com> Date: Wed, 27 Sep 2017 12:17:04 +0300 Subject: [PATCH 04/12] Update connector-ldap.yml Wording change for all_users_filter --- examples/config files - basic/3 connector-ldap.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/config files - basic/3 connector-ldap.yml b/examples/config files - basic/3 connector-ldap.yml index b723029b4..f425653dc 100755 --- a/examples/config files - basic/3 connector-ldap.yml +++ b/examples/config files - basic/3 connector-ldap.yml @@ -52,10 +52,10 @@ search_page_size: 200 require_tls_cert: False # (optional) all_users_filter (default value given below) -# In order to obtain a more fine-tunned list of LDAP users, adapt the filter to your needs. -# Only resulted users from this filter predicate are ever considered by the User Sync tool; -# that is, all LDAP queries issued by User Sync include this predicate to filter out LDAP resources -# that are not subject of synchronization. +# Use this filter to control exactly which LDAP resources are considered for synchronization. +# If a user is not accepted by this filter he will never be seen by the User Sync tool, even if +# your command line specifies --users all or he is in a group you have specified in your +# command line (--users group g1) or in your configuration file (for group mapping). # The default value specified here is appropriate for Active Directory, which has a # special field that is used to enable and disable users. The value for OpenLDAP # directories might be much simpler: "(&(objectClass=person)(objectClass=top))" From d9716128f5df69d7a824fb46bbfabf2c5583347a Mon Sep 17 00:00:00 2001 From: phil-levy Date: Wed, 11 Oct 2017 13:42:46 -0700 Subject: [PATCH 05/12] Update default.html Fix TOC indent when level drops by more than one level --- docs/_layouts/default.html | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/docs/_layouts/default.html b/docs/_layouts/default.html index a4b5377f3..7d670fcda 100644 --- a/docs/_layouts/default.html +++ b/docs/_layouts/default.html @@ -28,8 +28,11 @@
    {% assign current_level = my_page.nav_level %} {% elsif my_page.nav_level < current_level %} -
- {% assign current_level = my_page.nav_level %} + {% assign ub = current_level | minus: 1 %} + {% for i in (my_page.nav_level .. ub) %} + + {% endfor %} + {% assign current_level = my_page.nav_level %} {% endif %} {% if my_page.url == page.url %}
  • {{ my_page.nav_link | escape }}
  • From a2a9ee7412db14c8acb6f3386dc354c259f1842a Mon Sep 17 00:00:00 2001 From: Daniel Brotsky Date: Wed, 18 Oct 2017 15:38:40 -0700 Subject: [PATCH 06/12] fix #286: allow attribute management on Adobe IDs --- setup.py | 2 +- user_sync/connector/umapi.py | 12 +----------- 2 files changed, 2 insertions(+), 12 deletions(-) diff --git a/setup.py b/setup.py index 98be9ce81..4e287b022 100644 --- a/setup.py +++ b/setup.py @@ -48,7 +48,7 @@ 'pycryptodome', 'pyldap==2.4.37', 'PyYAML', - 'umapi-client>=2.7', + 'umapi-client>=2.8', 'psutil', 'keyring', 'six' diff --git a/user_sync/connector/umapi.py b/user_sync/connector/umapi.py index 2ed87f61d..3a7cc01eb 100644 --- a/user_sync/connector/umapi.py +++ b/user_sync/connector/umapi.py @@ -178,9 +178,6 @@ def update_user(self, attributes): """ :type attributes: dict """ - if self.identity_type == user_sync.identity_type.ADOBEID_IDENTITY_TYPE: - # shouldn't happen, but ignore it if it does - return if attributes is not None and len(attributes) > 0: params = self.convert_user_attributes_to_params(attributes) self.do_list.append(('update', params)) @@ -212,14 +209,7 @@ def add_user(self, attributes): """ :type attributes: dict """ - if self.identity_type == user_sync.identity_type.ADOBEID_IDENTITY_TYPE: - email = self.email if self.email else self.username - if not email: - error_message = "ERROR: you must specify an email with an Adobe ID" - raise AssertionException(error_message) - params = self.convert_user_attributes_to_params({'email': email}) - else: - params = self.convert_user_attributes_to_params(attributes) + params = self.convert_user_attributes_to_params(attributes) on_conflict_value = None option = params.pop('option', None) From 00ce2329eba9eaaca9599985d4ac69629ca9f69f Mon Sep 17 00:00:00 2001 From: Andrew Dorton Date: Wed, 25 Oct 2017 14:33:33 -0600 Subject: [PATCH 07/12] escape special chars for strings that will be formatted into ldap queries --- user_sync/connector/directory_ldap.py | 43 +++++++++++++++++++++++++-- 1 file changed, 41 insertions(+), 2 deletions(-) diff --git a/user_sync/connector/directory_ldap.py b/user_sync/connector/directory_ldap.py index 9479094b0..296d1bc3c 100755 --- a/user_sync/connector/directory_ldap.py +++ b/user_sync/connector/directory_ldap.py @@ -133,7 +133,7 @@ def load_users_and_groups(self, groups, extended_attributes, all_users): if not group_dn: self.logger.warning("No group found for: %s", group) continue - group_member_subfilter = group_member_filter_format.format(group_dn=group_dn) + group_member_subfilter = self.format_ldap_query_string(group_member_filter_format, group_dn=group_dn) if not group_member_subfilter.startswith('('): group_member_subfilter = six.text_type('(') + group_member_subfilter + six.text_type(')') user_subfilter = all_users_filter @@ -179,7 +179,7 @@ def find_ldap_group_dn(self, group): group_filter_format = six.text_type(options['group_filter_format']) try: res = connection.search_s(base_dn, ldap.SCOPE_SUBTREE, - filterstr=group_filter_format.format(group=group), attrsonly=1) + filterstr=self.format_ldap_query_string(group_filter_format, group=group), attrsonly=1) except Exception as e: raise AssertionException('Unexpected LDAP failure reading group info: %s' % e) group_dn = None @@ -324,6 +324,45 @@ def iter_search_result(self, base_dn, scope, filter_string, attributes): connection.abandon(msgid) raise + @staticmethod + def format_ldap_query_string(query, **kwargs): + """ + To be used with any string that will be injected into a LDAP query - this escapes a few special characters that + may appear in DNs, group names, etc. + :param query: + :param kwargs: + :return: + """ + escape_chars = { + six.text_type('*'): six.text_type('\\2A'), + six.text_type('('): six.text_type('\\28'), + six.text_type(')'): six.text_type('\\29'), + six.text_type('\\'): six.text_type('\\5C'), + } + escaped_args = {} + # kwargs is a dict that would normally be passed to string.format + for k, v in six.iteritems(kwargs): + # python 2 and 3 both support string translation, which would make this process easier + # unfortunately, they are not compatible, and six does not provide a wrapper. + # additionally, the py2 version does not work with multi-char replacement values + # here, we're walking through the format replacement string char by char and replacing + # with the escape chars if needed. since strings are immutable, we build a list of chars + # for the escaped string, and join it together after translating the string + escaped_list = [] + replace = six.text_type('') + for c in v: + for s, r in six.iteritems(escape_chars): + if c == s: + replace = r + break + if replace: + escaped_list.append(replace) + replace = six.text_type('') + else: + escaped_list.append(c) + escaped_args[k] = six.text_type('').join(escaped_list) + return query.format(**escaped_args) + class LDAPValueFormatter(object): encoding = 'utf8' From 1cbabf22f982f556b1889bf409c800f0c640090b Mon Sep 17 00:00:00 2001 From: Andrew Dorton Date: Wed, 25 Oct 2017 15:01:20 -0600 Subject: [PATCH 08/12] expand escape char list and get hex value for escape char replacement --- user_sync/connector/directory_ldap.py | 16 +++------------- 1 file changed, 3 insertions(+), 13 deletions(-) diff --git a/user_sync/connector/directory_ldap.py b/user_sync/connector/directory_ldap.py index 296d1bc3c..200ee79a9 100755 --- a/user_sync/connector/directory_ldap.py +++ b/user_sync/connector/directory_ldap.py @@ -333,12 +333,7 @@ def format_ldap_query_string(query, **kwargs): :param kwargs: :return: """ - escape_chars = { - six.text_type('*'): six.text_type('\\2A'), - six.text_type('('): six.text_type('\\28'), - six.text_type(')'): six.text_type('\\29'), - six.text_type('\\'): six.text_type('\\5C'), - } + escape_chars = six.text_type('*()\\&|<>~!:') escaped_args = {} # kwargs is a dict that would normally be passed to string.format for k, v in six.iteritems(kwargs): @@ -349,15 +344,10 @@ def format_ldap_query_string(query, **kwargs): # with the escape chars if needed. since strings are immutable, we build a list of chars # for the escaped string, and join it together after translating the string escaped_list = [] - replace = six.text_type('') for c in v: - for s, r in six.iteritems(escape_chars): - if c == s: - replace = r - break - if replace: + if c in escape_chars: + replace = six.text_type(hex(ord(c))).replace('0x', '\\') escaped_list.append(replace) - replace = six.text_type('') else: escaped_list.append(c) escaped_args[k] = six.text_type('').join(escaped_list) From fc0ee128814ca46b2e748468d999ccd230e907c1 Mon Sep 17 00:00:00 2001 From: Andrew Dorton Date: Wed, 25 Oct 2017 15:11:24 -0600 Subject: [PATCH 09/12] revise the comments to reflect changes to character replacement logic --- user_sync/connector/directory_ldap.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/user_sync/connector/directory_ldap.py b/user_sync/connector/directory_ldap.py index 200ee79a9..08141a669 100755 --- a/user_sync/connector/directory_ldap.py +++ b/user_sync/connector/directory_ldap.py @@ -337,12 +337,10 @@ def format_ldap_query_string(query, **kwargs): escaped_args = {} # kwargs is a dict that would normally be passed to string.format for k, v in six.iteritems(kwargs): - # python 2 and 3 both support string translation, which would make this process easier - # unfortunately, they are not compatible, and six does not provide a wrapper. - # additionally, the py2 version does not work with multi-char replacement values - # here, we're walking through the format replacement string char by char and replacing - # with the escape chars if needed. since strings are immutable, we build a list of chars - # for the escaped string, and join it together after translating the string + # LDAP special characters are escaped in the general format '\' + hex(char) + # we need to run through the string char by char and if the char exists in + # the escape_char list, get the ord of it (decimal ascii value), convert it to hex, and + # replace the '0x' with '\' escaped_list = [] for c in v: if c in escape_chars: From 49d9588ccc85502f689b4c2b9079bbbba1ef04a7 Mon Sep 17 00:00:00 2001 From: Andrew Dorton Date: Wed, 25 Oct 2017 18:08:39 -0600 Subject: [PATCH 10/12] Readibility and comment clarity. Fixes #288 --- user_sync/connector/directory_ldap.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/user_sync/connector/directory_ldap.py b/user_sync/connector/directory_ldap.py index 08141a669..c4628aa47 100755 --- a/user_sync/connector/directory_ldap.py +++ b/user_sync/connector/directory_ldap.py @@ -178,8 +178,9 @@ def find_ldap_group_dn(self, group): base_dn = six.text_type(options['base_dn']) group_filter_format = six.text_type(options['group_filter_format']) try: + filter_string = self.format_ldap_query_string(group_filter_format, group=group) res = connection.search_s(base_dn, ldap.SCOPE_SUBTREE, - filterstr=self.format_ldap_query_string(group_filter_format, group=group), attrsonly=1) + filterstr=filter_string, attrsonly=1) except Exception as e: raise AssertionException('Unexpected LDAP failure reading group info: %s' % e) group_dn = None @@ -327,12 +328,13 @@ def iter_search_result(self, base_dn, scope, filter_string, attributes): @staticmethod def format_ldap_query_string(query, **kwargs): """ - To be used with any string that will be injected into a LDAP query - this escapes a few special characters that - may appear in DNs, group names, etc. + Escape LDAP special characters that may appear in injected query strings + Should be used with any string that will be injected into an LDAP query. :param query: :param kwargs: :return: """ + # See http://www.rfc-editor.org/rfc/rfc4515.txt escape_chars = six.text_type('*()\\&|<>~!:') escaped_args = {} # kwargs is a dict that would normally be passed to string.format From 04614033e0320bc56432276c3730a333f7814e3d Mon Sep 17 00:00:00 2001 From: Daniel Brotsky Date: Wed, 25 Oct 2017 18:18:36 -0700 Subject: [PATCH 11/12] fix #283: don't import keyring unless needed. Now we only import the keyring right where we use it. This will be helpful on Linux servers that don't have keyrings. --- user_sync/config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/user_sync/config.py b/user_sync/config.py index bcbd24aae..be2f01824 100644 --- a/user_sync/config.py +++ b/user_sync/config.py @@ -25,7 +25,6 @@ import subprocess import types -import keyring import yaml import six @@ -600,6 +599,7 @@ def get_credential(self, name, user_name, none_allowed=False): raise AssertionException('%s: cannot contain setting for both "%s" and "%s"' % (scope, name, keyring_name)) if secure_value_key: try: + import keyring value = keyring.get_password(service_name=secure_value_key, username=user_name) except Exception as e: raise AssertionException('%s: Error accessing secure storage: %s' % (scope, e)) From f368d29b5496e919cc21a593ecd6fd7ada9cb6ea Mon Sep 17 00:00:00 2001 From: Daniel Brotsky Date: Wed, 25 Oct 2017 21:09:20 -0700 Subject: [PATCH 12/12] Prepare for 2.2.2rc1 release. --- RELEASE_NOTES.md | 27 ++++++++++----------------- user_sync/version.py | 2 +- 2 files changed, 11 insertions(+), 18 deletions(-) diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 5078ae263..e3acdac8a 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -1,34 +1,27 @@ -# Release Notes for User Sync Tool Version 2.2.1 +# Release Notes for User Sync Tool Version 2.2.2 -These notes apply to v2.2.1 of 2017-08-30. +These notes apply to v2.2.2rc1 of 2017-10-25. ## New Features -[#266](https://github.com/adobe-apiplatform/user-sync.py/issues/266): Extended attribute values (defined in extensions) can now be multi-valued. The type of the attribute value in the `source_attributes` dictionary will be: -* `None` if the attribute has no value; -* a `str` (or `unicode` in py2) if the attribute has one value; -* a `list` of `str` (or `unicode` in py2) if the attribute has multiple values. - -[#268](https://github.com/adobe-apiplatform/user-sync.py/issues/268): To make sure users get all the right overlapping entitlements associated with mapped user groups, `--strategy push` now does group removals before group adds. +None. ## Bug Fixes -[#257](https://github.com/adobe-apiplatform/user-sync.py/issues/257): Catch exceptions thrown by umapi-client when creating actions. - -[#258](https://github.com/adobe-apiplatform/user-sync.py/issues/258): Correctly decrypt private keys in py3. +[#283](https://github.com/adobe-apiplatform/user-sync.py/issues/283): Don't import keyring unless needed. -[#260](https://github.com/adobe-apiplatform/user-sync.py/issues/260): Make sure the requests library is loaded when using pex on Windows. +[#286](https://github.com/adobe-apiplatform/user-sync.py/issues/286): Allow specifying attributes for Adobe IDs. -[#265](https://github.com/adobe-apiplatform/user-sync.py/issues/265): Extended attributes in extensions couldn't be fetched unless they had non-ascii names. - -[#269](https://github.com/adobe-apiplatform/user-sync.py/issues/269): When using `--strategy sync`, new users created in secondary organizations were not being added to any groups. +[#288](https://github.com/adobe-apiplatform/user-sync.py/issues/288): Escape special characters in user input to LDAP queries. ## Compatibility with Prior Versions -There are no functional changes from prior versions. +There are no interface changes from prior versions. ## Known Issues -Because the release on Windows is built with a pre-compiled version of pyldap, we have to specify a specific version to be used in each release. This not always be the latest version. +Because the release on Windows is built with a pre-compiled version of pyldap, we have to specify a specific version to be used in each release. This may not always be the latest version. On the Win64 platform, there are very long pathnames embedded in the released build artifact `user-sync.pex`, which will cause problems unless you are on Windows 10 and are either running Python 3.6 or have enabled long pathnames system-wide (as described in this [Microsoft Dev Center article](https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx)). To work around this issue on older platforms, set the `PEX_ROOT` environment variable (as described [in the docs here](https://adobe-apiplatform.github.io/user-sync.py/en/user-manual/setup_and_installation.html)) to be a very short path (e.g., `set PEX_ROOT=C:\pex`). + +Each release on each platform is built with a specific version of Python. Typically this is the latest available (from the OS vendor, if they provide one) for that platform. In general, and especially on Windows, you should use the same Python to run User Sync as it was built with. diff --git a/user_sync/version.py b/user_sync/version.py index 6e911ff7c..4a40c8ef0 100644 --- a/user_sync/version.py +++ b/user_sync/version.py @@ -18,4 +18,4 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. -__version__ = '2.2.1' +__version__ = '2.2.2rc1'