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/docs/_layouts/default.html b/docs/_layouts/default.html
index 81ef38e83..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 }}
@@ -41,6 +44,11 @@
{% include post-nav-area.html %}
+
+
{{ content }}
diff --git a/docs/_sass/minima/_layout.scss b/docs/_sass/minima/_layout.scss
index 8e1e8b2d6..d8778ac5c 100644
--- a/docs/_sass/minima/_layout.scss
+++ b/docs/_sass/minima/_layout.scss
@@ -209,6 +209,11 @@
margin-left: 30px;
/* border: black 1px solid; */
padding: 5px;
+ /* settings for scrollable nav area */
+ 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 */
diff --git a/examples/config files - basic/3 connector-ldap.yml b/examples/config files - basic/3 connector-ldap.yml
index c84a85299..f425653dc 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.
+# 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))"
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/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))
diff --git a/user_sync/connector/directory_ldap.py b/user_sync/connector/directory_ldap.py
index 9479094b0..c4628aa47 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
@@ -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=group_filter_format.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
@@ -324,6 +325,34 @@ def iter_search_result(self, base_dn, scope, filter_string, attributes):
connection.abandon(msgid)
raise
+ @staticmethod
+ def format_ldap_query_string(query, **kwargs):
+ """
+ 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
+ for k, v in six.iteritems(kwargs):
+ # 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:
+ replace = six.text_type(hex(ord(c))).replace('0x', '\\')
+ escaped_list.append(replace)
+ else:
+ escaped_list.append(c)
+ escaped_args[k] = six.text_type('').join(escaped_list)
+ return query.format(**escaped_args)
+
class LDAPValueFormatter(object):
encoding = 'utf8'
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)
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'