We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
LdapUserInfo  Avatar picture broken
Since I cannot find any reference to my custom code I assume it is an error in buildbot?

```
	Traceback (most recent call last):
	  File "python3.8/site-packages/buildbot/www/avatar.py", line 79, in render_GET
	    return self.asyncRenderHelper(request, self.renderAvatar)
	  File "python3.8/site-packages/buildbot/www/resource.py", line 72, in asyncRenderHelper
	    d = _callable(request)
	  File "python3.8/site-packages/twisted/internet/defer.py", line 1613, in unwindGenerator
	    return _cancellableInlineCallbacks(gen)
	  File "python3.8/site-packages/twisted/internet/defer.py", line 1529, in _cancellableInlineCallbacks
	    _inlineCallbacks(None, g, status)
	--- <exception caught here> ---
	  File "python3.8/site-packages/twisted/internet/defer.py", line 654, in _runCallbacks
	    current.result = callback(current.result, *args, **kw)
	  File "python3.8/site-packages/buildbot/www/resource.py", line 96, in failHttpError
	    f.trap(Error)
	  File "python3.8/site-packages/twisted/python/failure.py", line 460, in trap
	    self.raiseException()
	  File "python3.8/site-packages/twisted/python/failure.py", line 488, in raiseException
	    raise self.value.with_traceback(self.tb)
	  File "python3.8/site-packages/twisted/internet/defer.py", line 654, in _runCallbacks
	    current.result = callback(current.result, *args, **kw)
	  File "python3.8/site-packages/buildbot/www/resource.py", line 89, in failHttpRedirect
	    f.trap(Redirect)
	  File "python3.8/site-packages/twisted/python/failure.py", line 460, in trap
	    self.raiseException()
	  File "python3.8/site-packages/twisted/python/failure.py", line 488, in raiseException
	    raise self.value.with_traceback(self.tb)
	  File "python3.8/site-packages/twisted/internet/defer.py", line 1418, in _inlineCallbacks
	    result = g.send(result)
	  File "python3.8/site-packages/buildbot/www/avatar.py", line 91, in renderAvatar
	    res = yield method.getUserAvatar(email, username, size, self.defaultAvatarFullUrl)
	builtins.TypeError: getUserAvatar() takes 4 positional arguments but 5 were given
```

Buildbot master version: 2.10.1
buildbot worker version: 2020.08.31
Python version: 3.8.5

avatar plugin: buildbot.plugins.util.LdapUserInfo

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `master/buildbot/www/ldapuserinfo.py`
Content:
```
1 # This file is part of Buildbot.  Buildbot is free software: you can
2 # redistribute it and/or modify it under the terms of the GNU General Public
3 # License as published by the Free Software Foundation, version 2.
4 #
5 # This program is distributed in the hope that it will be useful, but WITHOUT
6 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
7 # FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
8 # details.
9 #
10 # You should have received a copy of the GNU General Public License along with
11 # this program; if not, write to the Free Software Foundation, Inc., 51
12 # Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
13 #
14 # Copyright Buildbot Team Members
15 
16 
17 # NOTE regarding LDAP encodings:
18 #
19 # By default the encoding used in ldap3 is utf-8. The encoding is user-configurable, though.
20 # For more information check ldap3's documentation on this topic:
21 # http://ldap3.readthedocs.io/encoding.html
22 #
23 # It is recommended to use ldap3's auto-decoded `attributes` values for
24 # `unicode` and `raw_*` attributes for `bytes`.
25 
26 
27 from urllib.parse import urlparse
28 
29 import ldap3
30 
31 from twisted.internet import threads
32 
33 from buildbot.util import bytes2unicode
34 from buildbot.util import flatten
35 from buildbot.www import auth
36 from buildbot.www import avatar
37 
38 
39 class LdapUserInfo(avatar.AvatarBase, auth.UserInfoProviderBase):
40     name = 'ldap'
41 
42     def __init__(self, uri, bindUser, bindPw,
43                  accountBase,
44                  accountPattern,
45                  accountFullName,
46                  accountEmail,
47                  groupBase=None,
48                  groupMemberPattern=None,
49                  groupName=None,
50                  avatarPattern=None,
51                  avatarData=None,
52                  accountExtraFields=None):
53         self.uri = uri
54         self.bindUser = bindUser
55         self.bindPw = bindPw
56         self.accountBase = accountBase
57         self.accountEmail = accountEmail
58         self.accountPattern = accountPattern
59         self.accountFullName = accountFullName
60         group_params = [p for p in (groupName, groupMemberPattern, groupBase)
61                         if p is not None]
62         if len(group_params) not in (0, 3):
63             raise ValueError(
64                 "Incomplete LDAP groups configuration. "
65                 "To use Ldap groups, you need to specify the three "
66                 "parameters (groupName, groupMemberPattern and groupBase). ")
67 
68         self.groupName = groupName
69         self.groupMemberPattern = groupMemberPattern
70         self.groupBase = groupBase
71         self.avatarPattern = avatarPattern
72         self.avatarData = avatarData
73         if accountExtraFields is None:
74             accountExtraFields = []
75         self.accountExtraFields = accountExtraFields
76         self.ldap_encoding = ldap3.get_config_parameter('DEFAULT_SERVER_ENCODING')
77 
78     def connectLdap(self):
79         server = urlparse(self.uri)
80         netloc = server.netloc.split(":")
81         # define the server and the connection
82         s = ldap3.Server(netloc[0], port=int(netloc[1]), use_ssl=server.scheme == 'ldaps',
83                          get_info=ldap3.ALL)
84 
85         auth = ldap3.SIMPLE
86         if self.bindUser is None and self.bindPw is None:
87             auth = ldap3.ANONYMOUS
88 
89         c = ldap3.Connection(s, auto_bind=True, client_strategy=ldap3.SYNC,
90                              user=self.bindUser, password=self.bindPw,
91                              authentication=auth)
92         return c
93 
94     def search(self, c, base, filterstr='f', attributes=None):
95         c.search(base, filterstr, ldap3.SUBTREE, attributes=attributes)
96         return c.response
97 
98     def getUserInfo(self, username):
99         username = bytes2unicode(username)
100 
101         def thd():
102             c = self.connectLdap()
103             infos = {'username': username}
104             pattern = self.accountPattern % dict(username=username)
105             res = self.search(c, self.accountBase, pattern,
106                               attributes=[
107                                   self.accountEmail, self.accountFullName] +
108                               self.accountExtraFields)
109             if len(res) != 1:
110                 raise KeyError("ldap search \"{}\" returned {} results".format(pattern, len(res)))
111             dn, ldap_infos = res[0]['dn'], res[0]['attributes']
112 
113             def getFirstLdapInfo(x):
114                 if isinstance(x, list):
115                     x = x[0] if x else None
116                 return x
117 
118             infos['full_name'] = getFirstLdapInfo(ldap_infos[self.accountFullName])
119             infos['email'] = getFirstLdapInfo(ldap_infos[self.accountEmail])
120             for f in self.accountExtraFields:
121                 if f in ldap_infos:
122                     infos[f] = getFirstLdapInfo(ldap_infos[f])
123 
124             if self.groupMemberPattern is None:
125                 infos['groups'] = []
126                 return infos
127 
128             # needs double quoting of backslashing
129             pattern = self.groupMemberPattern % dict(dn=ldap3.utils.conv.escape_filter_chars(dn))
130             res = self.search(c, self.groupBase, pattern,
131                               attributes=[self.groupName])
132             infos['groups'] = flatten([group_infos['attributes'][self.groupName]
133                                       for group_infos in res])
134 
135             return infos
136         return threads.deferToThread(thd)
137 
138     def findAvatarMime(self, data):
139         # http://en.wikipedia.org/wiki/List_of_file_signatures
140         if data.startswith(b"\xff\xd8\xff"):
141             return ("image/jpeg", data)
142         if data.startswith(b"\x89PNG"):
143             return ("image/png", data)
144         if data.startswith(b"GIF8"):
145             return ("image/gif", data)
146         # ignore unknown image format
147         return None
148 
149     def getUserAvatar(self, user_email, size, defaultAvatarUrl):
150         user_email = bytes2unicode(user_email)
151 
152         def thd():
153             c = self.connectLdap()
154             pattern = self.avatarPattern % dict(email=user_email)
155             res = self.search(c, self.accountBase, pattern,
156                               attributes=[self.avatarData])
157             if not res:
158                 return None
159             ldap_infos = res[0]['raw_attributes']
160             if self.avatarData in ldap_infos and ldap_infos[self.avatarData]:
161                 data = ldap_infos[self.avatarData][0]
162                 return self.findAvatarMime(data)
163             return None
164         return threads.deferToThread(thd)
165
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/master/buildbot/www/ldapuserinfo.py b/master/buildbot/www/ldapuserinfo.py
--- a/master/buildbot/www/ldapuserinfo.py
+++ b/master/buildbot/www/ldapuserinfo.py
@@ -138,20 +138,28 @@
     def findAvatarMime(self, data):
         # http://en.wikipedia.org/wiki/List_of_file_signatures
         if data.startswith(b"\xff\xd8\xff"):
-            return ("image/jpeg", data)
+            return (b"image/jpeg", data)
         if data.startswith(b"\x89PNG"):
-            return ("image/png", data)
+            return (b"image/png", data)
         if data.startswith(b"GIF8"):
-            return ("image/gif", data)
+            return (b"image/gif", data)
         # ignore unknown image format
         return None
 
-    def getUserAvatar(self, user_email, size, defaultAvatarUrl):
-        user_email = bytes2unicode(user_email)
+    def getUserAvatar(self, email, username, size, defaultAvatarUrl):
+        if username:
+            username = bytes2unicode(username)
+        if email:
+            email = bytes2unicode(email)
 
         def thd():
             c = self.connectLdap()
-            pattern = self.avatarPattern % dict(email=user_email)
+            if username:
+                pattern = self.accountPattern % dict(username=username)
+            elif email:
+                pattern = self.avatarPattern % dict(email=email)
+            else:
+                return None
             res = self.search(c, self.accountBase, pattern,
                               attributes=[self.avatarData])
             if not res:

{"golden_diff": "diff --git a/master/buildbot/www/ldapuserinfo.py b/master/buildbot/www/ldapuserinfo.py\n--- a/master/buildbot/www/ldapuserinfo.py\n+++ b/master/buildbot/www/ldapuserinfo.py\n@@ -138,20 +138,28 @@\n     def findAvatarMime(self, data):\n         # http://en.wikipedia.org/wiki/List_of_file_signatures\n         if data.startswith(b\"\\xff\\xd8\\xff\"):\n-            return (\"image/jpeg\", data)\n+            return (b\"image/jpeg\", data)\n         if data.startswith(b\"\\x89PNG\"):\n-            return (\"image/png\", data)\n+            return (b\"image/png\", data)\n         if data.startswith(b\"GIF8\"):\n-            return (\"image/gif\", data)\n+            return (b\"image/gif\", data)\n         # ignore unknown image format\n         return None\n \n-    def getUserAvatar(self, user_email, size, defaultAvatarUrl):\n-        user_email = bytes2unicode(user_email)\n+    def getUserAvatar(self, email, username, size, defaultAvatarUrl):\n+        if username:\n+            username = bytes2unicode(username)\n+        if email:\n+            email = bytes2unicode(email)\n \n         def thd():\n             c = self.connectLdap()\n-            pattern = self.avatarPattern % dict(email=user_email)\n+            if username:\n+                pattern = self.accountPattern % dict(username=username)\n+            elif email:\n+                pattern = self.avatarPattern % dict(email=email)\n+            else:\n+                return None\n             res = self.search(c, self.accountBase, pattern,\n                               attributes=[self.avatarData])\n             if not res:\n", "issue": "LdapUserInfo  Avatar picture broken\nSince I cannot find any reference to my custom code I assume it is an error in buildbot?\r\n\r\n```\r\n\tTraceback (most recent call last):\r\n\t  File \"python3.8/site-packages/buildbot/www/avatar.py\", line 79, in render_GET\r\n\t    return self.asyncRenderHelper(request, self.renderAvatar)\r\n\t  File \"python3.8/site-packages/buildbot/www/resource.py\", line 72, in asyncRenderHelper\r\n\t    d = _callable(request)\r\n\t  File \"python3.8/site-packages/twisted/internet/defer.py\", line 1613, in unwindGenerator\r\n\t    return _cancellableInlineCallbacks(gen)\r\n\t  File \"python3.8/site-packages/twisted/internet/defer.py\", line 1529, in _cancellableInlineCallbacks\r\n\t    _inlineCallbacks(None, g, status)\r\n\t--- <exception caught here> ---\r\n\t  File \"python3.8/site-packages/twisted/internet/defer.py\", line 654, in _runCallbacks\r\n\t    current.result = callback(current.result, *args, **kw)\r\n\t  File \"python3.8/site-packages/buildbot/www/resource.py\", line 96, in failHttpError\r\n\t    f.trap(Error)\r\n\t  File \"python3.8/site-packages/twisted/python/failure.py\", line 460, in trap\r\n\t    self.raiseException()\r\n\t  File \"python3.8/site-packages/twisted/python/failure.py\", line 488, in raiseException\r\n\t    raise self.value.with_traceback(self.tb)\r\n\t  File \"python3.8/site-packages/twisted/internet/defer.py\", line 654, in _runCallbacks\r\n\t    current.result = callback(current.result, *args, **kw)\r\n\t  File \"python3.8/site-packages/buildbot/www/resource.py\", line 89, in failHttpRedirect\r\n\t    f.trap(Redirect)\r\n\t  File \"python3.8/site-packages/twisted/python/failure.py\", line 460, in trap\r\n\t    self.raiseException()\r\n\t  File \"python3.8/site-packages/twisted/python/failure.py\", line 488, in raiseException\r\n\t    raise self.value.with_traceback(self.tb)\r\n\t  File \"python3.8/site-packages/twisted/internet/defer.py\", line 1418, in _inlineCallbacks\r\n\t    result = g.send(result)\r\n\t  File \"python3.8/site-packages/buildbot/www/avatar.py\", line 91, in renderAvatar\r\n\t    res = yield method.getUserAvatar(email, username, size, self.defaultAvatarFullUrl)\r\n\tbuiltins.TypeError: getUserAvatar() takes 4 positional arguments but 5 were given\r\n```\r\n\r\nBuildbot master version: 2.10.1\r\nbuildbot worker version: 2020.08.31\r\nPython version: 3.8.5\r\n\r\navatar plugin: buildbot.plugins.util.LdapUserInfo\n", "before_files": [{"content": "# This file is part of Buildbot.  Buildbot is free software: you can\n# redistribute it and/or modify it under the terms of the GNU General Public\n# License as published by the Free Software Foundation, version 2.\n#\n# This program is distributed in the hope that it will be useful, but WITHOUT\n# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS\n# FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more\n# details.\n#\n# You should have received a copy of the GNU General Public License along with\n# this program; if not, write to the Free Software Foundation, Inc., 51\n# Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.\n#\n# Copyright Buildbot Team Members\n\n\n# NOTE regarding LDAP encodings:\n#\n# By default the encoding used in ldap3 is utf-8. The encoding is user-configurable, though.\n# For more information check ldap3's documentation on this topic:\n# http://ldap3.readthedocs.io/encoding.html\n#\n# It is recommended to use ldap3's auto-decoded `attributes` values for\n# `unicode` and `raw_*` attributes for `bytes`.\n\n\nfrom urllib.parse import urlparse\n\nimport ldap3\n\nfrom twisted.internet import threads\n\nfrom buildbot.util import bytes2unicode\nfrom buildbot.util import flatten\nfrom buildbot.www import auth\nfrom buildbot.www import avatar\n\n\nclass LdapUserInfo(avatar.AvatarBase, auth.UserInfoProviderBase):\n    name = 'ldap'\n\n    def __init__(self, uri, bindUser, bindPw,\n                 accountBase,\n                 accountPattern,\n                 accountFullName,\n                 accountEmail,\n                 groupBase=None,\n                 groupMemberPattern=None,\n                 groupName=None,\n                 avatarPattern=None,\n                 avatarData=None,\n                 accountExtraFields=None):\n        self.uri = uri\n        self.bindUser = bindUser\n        self.bindPw = bindPw\n        self.accountBase = accountBase\n        self.accountEmail = accountEmail\n        self.accountPattern = accountPattern\n        self.accountFullName = accountFullName\n        group_params = [p for p in (groupName, groupMemberPattern, groupBase)\n                        if p is not None]\n        if len(group_params) not in (0, 3):\n            raise ValueError(\n                \"Incomplete LDAP groups configuration. \"\n                \"To use Ldap groups, you need to specify the three \"\n                \"parameters (groupName, groupMemberPattern and groupBase). \")\n\n        self.groupName = groupName\n        self.groupMemberPattern = groupMemberPattern\n        self.groupBase = groupBase\n        self.avatarPattern = avatarPattern\n        self.avatarData = avatarData\n        if accountExtraFields is None:\n            accountExtraFields = []\n        self.accountExtraFields = accountExtraFields\n        self.ldap_encoding = ldap3.get_config_parameter('DEFAULT_SERVER_ENCODING')\n\n    def connectLdap(self):\n        server = urlparse(self.uri)\n        netloc = server.netloc.split(\":\")\n        # define the server and the connection\n        s = ldap3.Server(netloc[0], port=int(netloc[1]), use_ssl=server.scheme == 'ldaps',\n                         get_info=ldap3.ALL)\n\n        auth = ldap3.SIMPLE\n        if self.bindUser is None and self.bindPw is None:\n            auth = ldap3.ANONYMOUS\n\n        c = ldap3.Connection(s, auto_bind=True, client_strategy=ldap3.SYNC,\n                             user=self.bindUser, password=self.bindPw,\n                             authentication=auth)\n        return c\n\n    def search(self, c, base, filterstr='f', attributes=None):\n        c.search(base, filterstr, ldap3.SUBTREE, attributes=attributes)\n        return c.response\n\n    def getUserInfo(self, username):\n        username = bytes2unicode(username)\n\n        def thd():\n            c = self.connectLdap()\n            infos = {'username': username}\n            pattern = self.accountPattern % dict(username=username)\n            res = self.search(c, self.accountBase, pattern,\n                              attributes=[\n                                  self.accountEmail, self.accountFullName] +\n                              self.accountExtraFields)\n            if len(res) != 1:\n                raise KeyError(\"ldap search \\\"{}\\\" returned {} results\".format(pattern, len(res)))\n            dn, ldap_infos = res[0]['dn'], res[0]['attributes']\n\n            def getFirstLdapInfo(x):\n                if isinstance(x, list):\n                    x = x[0] if x else None\n                return x\n\n            infos['full_name'] = getFirstLdapInfo(ldap_infos[self.accountFullName])\n            infos['email'] = getFirstLdapInfo(ldap_infos[self.accountEmail])\n            for f in self.accountExtraFields:\n                if f in ldap_infos:\n                    infos[f] = getFirstLdapInfo(ldap_infos[f])\n\n            if self.groupMemberPattern is None:\n                infos['groups'] = []\n                return infos\n\n            # needs double quoting of backslashing\n            pattern = self.groupMemberPattern % dict(dn=ldap3.utils.conv.escape_filter_chars(dn))\n            res = self.search(c, self.groupBase, pattern,\n                              attributes=[self.groupName])\n            infos['groups'] = flatten([group_infos['attributes'][self.groupName]\n                                      for group_infos in res])\n\n            return infos\n        return threads.deferToThread(thd)\n\n    def findAvatarMime(self, data):\n        # http://en.wikipedia.org/wiki/List_of_file_signatures\n        if data.startswith(b\"\\xff\\xd8\\xff\"):\n            return (\"image/jpeg\", data)\n        if data.startswith(b\"\\x89PNG\"):\n            return (\"image/png\", data)\n        if data.startswith(b\"GIF8\"):\n            return (\"image/gif\", data)\n        # ignore unknown image format\n        return None\n\n    def getUserAvatar(self, user_email, size, defaultAvatarUrl):\n        user_email = bytes2unicode(user_email)\n\n        def thd():\n            c = self.connectLdap()\n            pattern = self.avatarPattern % dict(email=user_email)\n            res = self.search(c, self.accountBase, pattern,\n                              attributes=[self.avatarData])\n            if not res:\n                return None\n            ldap_infos = res[0]['raw_attributes']\n            if self.avatarData in ldap_infos and ldap_infos[self.avatarData]:\n                data = ldap_infos[self.avatarData][0]\n                return self.findAvatarMime(data)\n            return None\n        return threads.deferToThread(thd)\n", "path": "master/buildbot/www/ldapuserinfo.py"}], "after_files": [{"content": "# This file is part of Buildbot.  Buildbot is free software: you can\n# redistribute it and/or modify it under the terms of the GNU General Public\n# License as published by the Free Software Foundation, version 2.\n#\n# This program is distributed in the hope that it will be useful, but WITHOUT\n# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS\n# FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more\n# details.\n#\n# You should have received a copy of the GNU General Public License along with\n# this program; if not, write to the Free Software Foundation, Inc., 51\n# Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.\n#\n# Copyright Buildbot Team Members\n\n\n# NOTE regarding LDAP encodings:\n#\n# By default the encoding used in ldap3 is utf-8. The encoding is user-configurable, though.\n# For more information check ldap3's documentation on this topic:\n# http://ldap3.readthedocs.io/encoding.html\n#\n# It is recommended to use ldap3's auto-decoded `attributes` values for\n# `unicode` and `raw_*` attributes for `bytes`.\n\n\nfrom urllib.parse import urlparse\n\nimport ldap3\n\nfrom twisted.internet import threads\n\nfrom buildbot.util import bytes2unicode\nfrom buildbot.util import flatten\nfrom buildbot.www import auth\nfrom buildbot.www import avatar\n\n\nclass LdapUserInfo(avatar.AvatarBase, auth.UserInfoProviderBase):\n    name = 'ldap'\n\n    def __init__(self, uri, bindUser, bindPw,\n                 accountBase,\n                 accountPattern,\n                 accountFullName,\n                 accountEmail,\n                 groupBase=None,\n                 groupMemberPattern=None,\n                 groupName=None,\n                 avatarPattern=None,\n                 avatarData=None,\n                 accountExtraFields=None):\n        self.uri = uri\n        self.bindUser = bindUser\n        self.bindPw = bindPw\n        self.accountBase = accountBase\n        self.accountEmail = accountEmail\n        self.accountPattern = accountPattern\n        self.accountFullName = accountFullName\n        group_params = [p for p in (groupName, groupMemberPattern, groupBase)\n                        if p is not None]\n        if len(group_params) not in (0, 3):\n            raise ValueError(\n                \"Incomplete LDAP groups configuration. \"\n                \"To use Ldap groups, you need to specify the three \"\n                \"parameters (groupName, groupMemberPattern and groupBase). \")\n\n        self.groupName = groupName\n        self.groupMemberPattern = groupMemberPattern\n        self.groupBase = groupBase\n        self.avatarPattern = avatarPattern\n        self.avatarData = avatarData\n        if accountExtraFields is None:\n            accountExtraFields = []\n        self.accountExtraFields = accountExtraFields\n        self.ldap_encoding = ldap3.get_config_parameter('DEFAULT_SERVER_ENCODING')\n\n    def connectLdap(self):\n        server = urlparse(self.uri)\n        netloc = server.netloc.split(\":\")\n        # define the server and the connection\n        s = ldap3.Server(netloc[0], port=int(netloc[1]), use_ssl=server.scheme == 'ldaps',\n                         get_info=ldap3.ALL)\n\n        auth = ldap3.SIMPLE\n        if self.bindUser is None and self.bindPw is None:\n            auth = ldap3.ANONYMOUS\n\n        c = ldap3.Connection(s, auto_bind=True, client_strategy=ldap3.SYNC,\n                             user=self.bindUser, password=self.bindPw,\n                             authentication=auth)\n        return c\n\n    def search(self, c, base, filterstr='f', attributes=None):\n        c.search(base, filterstr, ldap3.SUBTREE, attributes=attributes)\n        return c.response\n\n    def getUserInfo(self, username):\n        username = bytes2unicode(username)\n\n        def thd():\n            c = self.connectLdap()\n            infos = {'username': username}\n            pattern = self.accountPattern % dict(username=username)\n            res = self.search(c, self.accountBase, pattern,\n                              attributes=[\n                                  self.accountEmail, self.accountFullName] +\n                              self.accountExtraFields)\n            if len(res) != 1:\n                raise KeyError(\"ldap search \\\"{}\\\" returned {} results\".format(pattern, len(res)))\n            dn, ldap_infos = res[0]['dn'], res[0]['attributes']\n\n            def getFirstLdapInfo(x):\n                if isinstance(x, list):\n                    x = x[0] if x else None\n                return x\n\n            infos['full_name'] = getFirstLdapInfo(ldap_infos[self.accountFullName])\n            infos['email'] = getFirstLdapInfo(ldap_infos[self.accountEmail])\n            for f in self.accountExtraFields:\n                if f in ldap_infos:\n                    infos[f] = getFirstLdapInfo(ldap_infos[f])\n\n            if self.groupMemberPattern is None:\n                infos['groups'] = []\n                return infos\n\n            # needs double quoting of backslashing\n            pattern = self.groupMemberPattern % dict(dn=ldap3.utils.conv.escape_filter_chars(dn))\n            res = self.search(c, self.groupBase, pattern,\n                              attributes=[self.groupName])\n            infos['groups'] = flatten([group_infos['attributes'][self.groupName]\n                                      for group_infos in res])\n\n            return infos\n        return threads.deferToThread(thd)\n\n    def findAvatarMime(self, data):\n        # http://en.wikipedia.org/wiki/List_of_file_signatures\n        if data.startswith(b\"\\xff\\xd8\\xff\"):\n            return (b\"image/jpeg\", data)\n        if data.startswith(b\"\\x89PNG\"):\n            return (b\"image/png\", data)\n        if data.startswith(b\"GIF8\"):\n            return (b\"image/gif\", data)\n        # ignore unknown image format\n        return None\n\n    def getUserAvatar(self, email, username, size, defaultAvatarUrl):\n        if username:\n            username = bytes2unicode(username)\n        if email:\n            email = bytes2unicode(email)\n\n        def thd():\n            c = self.connectLdap()\n            if username:\n                pattern = self.accountPattern % dict(username=username)\n            elif email:\n                pattern = self.avatarPattern % dict(email=email)\n            else:\n                return None\n            res = self.search(c, self.accountBase, pattern,\n                              attributes=[self.avatarData])\n            if not res:\n                return None\n            ldap_infos = res[0]['raw_attributes']\n            if self.avatarData in ldap_infos and ldap_infos[self.avatarData]:\n                data = ldap_infos[self.avatarData][0]\n                return self.findAvatarMime(data)\n            return None\n        return threads.deferToThread(thd)\n", "path": "master/buildbot/www/ldapuserinfo.py"}]}