Fixed LDAP Auth (#2333)

This commit is contained in:
charlesdhdt 2017-05-12 15:37:00 +02:00 committed by Thomas Kriechbaumer
parent 1c6b33f5af
commit 9f8e83259e
3 changed files with 57 additions and 50 deletions

View File

@ -47,12 +47,13 @@ class ProxyAuth:
self.nonanonymous = False self.nonanonymous = False
self.htpasswd = None self.htpasswd = None
self.singleuser = None self.singleuser = None
self.ldapconn = None
self.ldapserver = None self.ldapserver = None
self.authenticated = weakref.WeakKeyDictionary() # type: MutableMapping[connections.ClientConnection, Tuple[str, str]] self.authenticated = weakref.WeakKeyDictionary() # type: MutableMapping[connections.ClientConnection, Tuple[str, str]]
"""Contains all connections that are permanently authenticated after an HTTP CONNECT""" """Contains all connections that are permanently authenticated after an HTTP CONNECT"""
def enabled(self) -> bool: def enabled(self) -> bool:
return any([self.nonanonymous, self.htpasswd, self.singleuser, self.ldapserver]) return any([self.nonanonymous, self.htpasswd, self.singleuser, self.ldapconn, self.ldapserver])
def is_proxy_auth(self) -> bool: def is_proxy_auth(self) -> bool:
""" """
@ -101,19 +102,17 @@ class ProxyAuth:
elif self.htpasswd: elif self.htpasswd:
if self.htpasswd.check_password(username, password): if self.htpasswd.check_password(username, password):
return username, password return username, password
elif self.ldapserver: elif self.ldapconn:
if not username or not password: if not username or not password:
return None return None
dn = ctx.options.proxyauth.split(":")[2] self.ldapconn.search(ctx.options.proxyauth.split(':')[4], '(cn=' + username + ')')
parts = dn.split("?") if self.ldapconn.response:
conn = ldap3.Connection( conn = ldap3.Connection(
self.ldapserver, self.ldapserver,
parts[0] + username + parts[1], self.ldapconn.response[0]['dn'],
password, password,
auto_bind=True) auto_bind=True)
if conn: if conn:
conn.search(parts[1][1:], '(' + parts[0] + username + ')', attributes=['objectclass'])
if ctx.options.proxyauth.split(":")[3] in conn.entries[0]['objectclass']:
return username, password return username, password
return None return None
@ -146,19 +145,29 @@ class ProxyAuth:
"Could not open htpasswd file: %s" % p "Could not open htpasswd file: %s" % p
) )
elif ctx.options.proxyauth.startswith("ldap"): elif ctx.options.proxyauth.startswith("ldap"):
parts = ctx.options.proxyauth.split(":") parts = ctx.options.proxyauth.split(':')
if len(parts) != 4: security = parts[0]
ldap_server = parts[1]
dn_baseauth = parts[2]
password_baseauth = parts[3]
if len(parts) != 5:
raise exceptions.OptionsError( raise exceptions.OptionsError(
"Invalid ldap specification" "Invalid ldap specification"
) )
if parts[0] == "ldaps": if security == "ldaps":
server = ldap3.Server(parts[1], use_ssl=True) server = ldap3.Server(ldap_server, use_ssl=True)
elif parts[0] == "ldap": elif security == "ldap":
server = ldap3.Server(parts[1]) server = ldap3.Server(ldap_server)
else: else:
raise exceptions.OptionsError( raise exceptions.OptionsError(
"Invalid ldap specfication on the first part" "Invalid ldap specfication on the first part"
) )
conn = ldap3.Connection(
server,
dn_baseauth,
password_baseauth,
auto_bind=True)
self.ldapconn = conn
self.ldapserver = server self.ldapserver = server
else: else:
parts = ctx.options.proxyauth.split(':') parts = ctx.options.proxyauth.split(':')

View File

@ -206,10 +206,11 @@ class Options(optmanager.OptManager):
authenticaiton but accept any credentials, start with "@" to specify authenticaiton but accept any credentials, start with "@" to specify
a path to an Apache htpasswd file, be of the form a path to an Apache htpasswd file, be of the form
"username:password", or be of the form "username:password", or be of the form
"ldap[s]:url_server_ldap:dn:group", the dn must include "?", which will be "ldap[s]:url_server_ldap:dn_auth:password:dn_subtree",
the username prompted, and the group is the group the user must belong to the dn_auth & password is the dn/pass used to authenticate
the dn subtree is the subtree that we will search to find the username
an example would be an example would be
"ldap:ldap.forumsys.com:uid=?,dc=example,dc=com:person". "ldap:localhost:cn=default,dc=example,dc=com:password:ou=application,dc=example,dc=com".
""" """
) )
self.add_option( self.add_option(

View File

@ -1,8 +1,8 @@
import binascii import binascii
import ldap3
import pytest import pytest
from unittest import mock
from mitmproxy import exceptions from mitmproxy import exceptions
from mitmproxy.addons import proxyauth from mitmproxy.addons import proxyauth
from mitmproxy.test import taddons from mitmproxy.test import taddons
@ -42,19 +42,21 @@ def test_configure():
ctx.configure(up, proxyauth=None) ctx.configure(up, proxyauth=None)
assert not up.nonanonymous assert not up.nonanonymous
ctx.configure(up, proxyauth="ldap:fake_server:fake_dn:fake_group") with mock.patch('ldap3.Server', return_value="ldap://fake_server:389 - cleartext"):
assert up.ldapserver with mock.patch('ldap3.Connection', return_value="test"):
ctx.configure(up, proxyauth="ldap:localhost:cn=default,dc=cdhdt,dc=com:password:ou=application,dc=cdhdt,dc=com")
ctx.configure(up, proxyauth="ldap:fake_server:uid=?,dc=example,dc=com:person") assert up.ldapserver
assert up.ldapserver ctx.configure(up, proxyauth="ldaps:localhost:cn=default,dc=cdhdt,dc=com:password:ou=application,dc=cdhdt,dc=com")
ctx.configure(up, proxyauth="ldaps:fake_server.com:uid=?,dc=example,dc=com:person") assert up.ldapserver
assert up.ldapserver
with pytest.raises(exceptions.OptionsError): with pytest.raises(exceptions.OptionsError):
ctx.configure(up, proxyauth="ldap:test:test:test")
with pytest.raises(IndexError):
ctx.configure(up, proxyauth="ldap:fake_serveruid=?dc=example,dc=com:person") ctx.configure(up, proxyauth="ldap:fake_serveruid=?dc=example,dc=com:person")
with pytest.raises(exceptions.OptionsError): with pytest.raises(exceptions.OptionsError):
ctx.configure(up, proxyauth="ldapssssssss:fake_server.com:uid=?,dc=example,dc=com:person") ctx.configure(up, proxyauth="ldapssssssss:fake_server:dn:password:tree")
with pytest.raises(exceptions.OptionsError): with pytest.raises(exceptions.OptionsError):
ctx.configure( ctx.configure(
@ -124,26 +126,21 @@ def test_check(monkeypatch):
) )
assert not up.check(f) assert not up.check(f)
ctx.configure( with mock.patch('ldap3.Server', return_value="ldap://fake_server:389 - cleartext"):
up, with mock.patch('ldap3.Connection', search="test"):
proxyauth="ldap:fake-server:cn=?,ou=test,o=lab:test" with mock.patch('ldap3.Connection.search', return_value="test"):
) ctx.configure(
conn = ldap3.Connection("fake-server", user="cn=user0,ou=test,o=lab", password="password", client_strategy=ldap3.MOCK_SYNC) up,
conn.bind() proxyauth="ldap:localhost:cn=default,dc=cdhdt,dc=com:password:ou=application,dc=cdhdt,dc=com"
conn.strategy.add_entry('cn=user0,ou=test,o=lab', {'userPassword': 'test0', 'sn': 'user0_sn', 'revision': 0, 'objectClass': 'test'}) )
f.request.headers["Proxy-Authorization"] = proxyauth.mkauth(
def conn_mp(ldap, user, password, **kwargs): "test", "test"
return conn )
assert up.check(f)
monkeypatch.setattr(ldap3, "Connection", conn_mp) f.request.headers["Proxy-Authorization"] = proxyauth.mkauth(
f.request.headers["Proxy-Authorization"] = proxyauth.mkauth( "", ""
"user0", "test0" )
) assert not up.check(f)
assert up.check(f)
f.request.headers["Proxy-Authorization"] = proxyauth.mkauth(
"", ""
)
assert not up.check(f)
def test_authenticate(): def test_authenticate():