5 from samba.gensec import Security
6 from samba.auth import AuthContext
9 __all__ = ['NTLMAuthHandler']
11 COOKIE_NAME="ocs-ntlm-auth"
14 class NTLMAuthHandler(object):
16 HTTP/1.0 ``NTLM`` authentication middleware
18 Parameters: application -- the application object that is called only upon
19 successful authentication.
23 def __init__(self, application):
24 # TODO: client expiration and/or cleanup
25 self.client_status = {}
26 self.application = application
28 def _in_progress_response(self, start_response, ntlm_data=None, client_id=None):
29 status = "401 %s" % httplib.responses[401]
30 content = "More data needed..."
31 headers = [("Content-Type", "text/plain"),
32 ("Content-Length", "%d" % len(content))]
34 www_auth_value = "NTLM"
36 enc_ntlm_data = ntlm_data.encode("base64")
37 www_auth_value = ("NTLM %s"
38 % enc_ntlm_data.strip().replace("\n", ""))
39 if client_id is not None:
40 # MUST occur when ntlm_data is None, can still occur otherwise
41 headers.append(("Set-Cookie", "%s=%s" % (COOKIE_NAME, client_id)))
43 headers.append(("WWW-Authenticate", www_auth_value))
44 start_response(status, headers)
48 def _failure_response(self, start_response, explanation=None):
49 status = "403 %s" % httplib.responses[403]
50 content = "Authentication failure"
51 if explanation is not None:
52 content = content + " (%s)" % explanation
53 headers = [("Content-Type", "text/plain"),
54 ("Content-Length", "%d" % len(content))]
55 start_response(status, headers)
59 def _get_cookies(self, env):
61 if "HTTP_COOKIE" in env:
62 cookie_str = env["HTTP_COOKIE"]
63 for pair in cookie_str.split(";"):
64 (key, value) = pair.strip().split("=")
69 def __call__(self, env, start_response):
70 cookies = self._get_cookies(env)
71 if COOKIE_NAME in cookies:
72 client_id = cookies[COOKIE_NAME]
76 # old model that only works with mod_wsgi:
77 # if "REMOTE_ADDR" in env and "REMOTE_PORT" in env:
78 # client_id = "%(REMOTE_ADDR)s:%(REMOTE_PORT)s".format(env)
80 if client_id is None or client_id not in self.client_status:
82 server = Security.start_server(auth_context=AuthContext())
83 server.start_mech_by_name("ntlmssp")
84 client_id = str(uuid.uuid4())
86 if "HTTP_AUTHORIZATION" in env:
87 # Outlook may directly have sent a NTLM payload
88 auth = env["HTTP_AUTHORIZATION"]
89 auth_msg = server.update(auth[5:].decode('base64'))
90 response = self._in_progress_response(start_response,
93 self.client_status[client_id] = {"stage": "stage1",
96 self.client_status[client_id] = {"stage": "stage0",
98 response = self._in_progress_response(start_response, None, client_id)
100 status_stage = self.client_status[client_id]["stage"]
102 if status_stage == "ok":
103 # client authenticated previously
104 response = self.application(env, start_response)
105 elif status_stage == "stage0":
106 # test whether client supports "NTLM"
107 if "HTTP_AUTHORIZATION" in env:
108 auth = env["HTTP_AUTHORIZATION"]
109 server = self.client_status[client_id]["server"]
110 auth_msg = server.update(auth[5:].decode('base64'))
111 response = self._in_progress_response(start_response,
113 self.client_status[client_id]["stage"] = "stage1"
115 del(self.client_status[client_id])
116 response = self._failure_response(start_response,
119 elif status_stage == "stage1":
120 if "HTTP_AUTHORIZATION" in env:
121 auth = env["HTTP_AUTHORIZATION"]
122 server = self.client_status[client_id]["server"]
124 auth_msg = server.update(auth[5:].decode('base64'))
125 except RuntimeError: # a bit violent...
129 # authentication completed
130 self.client_status[client_id]["stage"] = "ok"
131 del(self.client_status[client_id]["server"])
132 response = self.application(env, start_response)
134 # we start over with the whole process
136 server = Security.start_server(auth_context=AuthContext())
137 server.start_mech_by_name("ntlmssp")
138 self.client_status[client_id] = {"stage": "stage0",
140 response = self._in_progress_response(start_response)
142 del(self.client_status[client_id])
143 response = self._failure_response(start_response,
147 raise RuntimeError("none shall pass!")
152 middleware = NTLMAuthHandler