|
@@ -0,0 +1,144 @@
|
|
1
|
+from wsgidav.http_authenticator import HTTPAuthenticator
|
|
2
|
+from wsgidav import util
|
|
3
|
+import re
|
|
4
|
+
|
|
5
|
+_logger = util.getModuleLogger(__name__, True)
|
|
6
|
+HOTFIX_WINXP_AcceptRootShareLogin = True
|
|
7
|
+
|
|
8
|
+class TracimHTTPAuthenticator(HTTPAuthenticator):
|
|
9
|
+ def __init__(self, application, config):
|
|
10
|
+ super(TracimHTTPAuthenticator, self).__init__(application, config)
|
|
11
|
+ self._headerfixparser = re.compile(r'([\w]+)=("[^"]*,[^"]*"),')
|
|
12
|
+
|
|
13
|
+ def authDigestAuthRequest(self, environ, start_response):
|
|
14
|
+ realmname = self._domaincontroller.getDomainRealm(environ["PATH_INFO"], environ)
|
|
15
|
+
|
|
16
|
+ isinvalidreq = False
|
|
17
|
+
|
|
18
|
+ authheaderdict = dict([])
|
|
19
|
+ authheaders = environ["HTTP_AUTHORIZATION"] + ","
|
|
20
|
+ if not authheaders.lower().strip().startswith("digest"):
|
|
21
|
+ isinvalidreq = True
|
|
22
|
+ # Hotfix for Windows file manager and OSX Finder:
|
|
23
|
+ # Some clients don't urlencode paths in auth header, so uri value may
|
|
24
|
+ # contain commas, which break the usual regex headerparser. Example:
|
|
25
|
+ # Digest username="user",realm="/",uri="a,b.txt",nc=00000001, ...
|
|
26
|
+ # -> [..., ('uri', '"a'), ('nc', '00000001'), ...]
|
|
27
|
+ # Override any such values with carefully extracted ones.
|
|
28
|
+ authheaderlist = self._headerparser.findall(authheaders)
|
|
29
|
+ authheaderfixlist = self._headerfixparser.findall(authheaders)
|
|
30
|
+ if authheaderfixlist:
|
|
31
|
+ _logger.info("Fixing authheader comma-parsing: extend %s with %s" \
|
|
32
|
+ % (authheaderlist, authheaderfixlist))
|
|
33
|
+ authheaderlist += authheaderfixlist
|
|
34
|
+ for authheader in authheaderlist:
|
|
35
|
+ authheaderkey = authheader[0]
|
|
36
|
+ authheadervalue = authheader[1].strip().strip("\"")
|
|
37
|
+ authheaderdict[authheaderkey] = authheadervalue
|
|
38
|
+
|
|
39
|
+ _logger.debug("authDigestAuthRequest: %s" % environ["HTTP_AUTHORIZATION"])
|
|
40
|
+ _logger.debug(" -> %s" % authheaderdict)
|
|
41
|
+
|
|
42
|
+ if "username" in authheaderdict:
|
|
43
|
+ req_username = authheaderdict["username"]
|
|
44
|
+ req_username_org = req_username
|
|
45
|
+ # Hotfix for Windows XP:
|
|
46
|
+ # net use W: http://127.0.0.1/dav /USER:DOMAIN\tester tester
|
|
47
|
+ # will send the name with double backslashes ('DOMAIN\\tester')
|
|
48
|
+ # but send the digest for the simple name ('DOMAIN\tester').
|
|
49
|
+ if r"\\" in req_username:
|
|
50
|
+ req_username = req_username.replace("\\\\", "\\")
|
|
51
|
+ _logger.info("Fixing Windows name with double backslash: '%s' --> '%s'" % (req_username_org, req_username))
|
|
52
|
+
|
|
53
|
+ if not self._domaincontroller.isRealmUser(realmname, req_username, environ):
|
|
54
|
+ isinvalidreq = True
|
|
55
|
+ else:
|
|
56
|
+ isinvalidreq = True
|
|
57
|
+
|
|
58
|
+ # TODO: Chun added this comments, but code was commented out
|
|
59
|
+ # Do not do realm checking - a hotfix for WinXP using some other realm's
|
|
60
|
+ # auth details for this realm - if user/password match
|
|
61
|
+ print(authheaderdict.get("realm"), realmname)
|
|
62
|
+ if 'realm' in authheaderdict:
|
|
63
|
+ if authheaderdict["realm"].upper() != realmname.upper():
|
|
64
|
+ if HOTFIX_WINXP_AcceptRootShareLogin:
|
|
65
|
+ # Hotfix: also accept '/'
|
|
66
|
+ if authheaderdict["realm"].upper() != "/":
|
|
67
|
+ isinvalidreq = True
|
|
68
|
+ else:
|
|
69
|
+ isinvalidreq = True
|
|
70
|
+
|
|
71
|
+ if "algorithm" in authheaderdict:
|
|
72
|
+ if authheaderdict["algorithm"].upper() != "MD5":
|
|
73
|
+ isinvalidreq = True # only MD5 supported
|
|
74
|
+
|
|
75
|
+ if "uri" in authheaderdict:
|
|
76
|
+ req_uri = authheaderdict["uri"]
|
|
77
|
+
|
|
78
|
+ if "nonce" in authheaderdict:
|
|
79
|
+ req_nonce = authheaderdict["nonce"]
|
|
80
|
+ else:
|
|
81
|
+ isinvalidreq = True
|
|
82
|
+
|
|
83
|
+ req_hasqop = False
|
|
84
|
+ if "qop" in authheaderdict:
|
|
85
|
+ req_hasqop = True
|
|
86
|
+ req_qop = authheaderdict["qop"]
|
|
87
|
+ if req_qop.lower() != "auth":
|
|
88
|
+ isinvalidreq = True # only auth supported, auth-int not supported
|
|
89
|
+ else:
|
|
90
|
+ req_qop = None
|
|
91
|
+
|
|
92
|
+ if "cnonce" in authheaderdict:
|
|
93
|
+ req_cnonce = authheaderdict["cnonce"]
|
|
94
|
+ else:
|
|
95
|
+ req_cnonce = None
|
|
96
|
+ if req_hasqop:
|
|
97
|
+ isinvalidreq = True
|
|
98
|
+
|
|
99
|
+ if "nc" in authheaderdict: # is read but nonce-count checking not implemented
|
|
100
|
+ req_nc = authheaderdict["nc"]
|
|
101
|
+ else:
|
|
102
|
+ req_nc = None
|
|
103
|
+ if req_hasqop:
|
|
104
|
+ isinvalidreq = True
|
|
105
|
+
|
|
106
|
+ if "response" in authheaderdict:
|
|
107
|
+ req_response = authheaderdict["response"]
|
|
108
|
+ else:
|
|
109
|
+ isinvalidreq = True
|
|
110
|
+
|
|
111
|
+ if not isinvalidreq:
|
|
112
|
+ left_digest_response_hash = self._domaincontroller.get_left_digest_response_hash(realmname, req_username, environ)
|
|
113
|
+
|
|
114
|
+ req_method = environ["REQUEST_METHOD"]
|
|
115
|
+
|
|
116
|
+ required_digest = self.tracim_compute_digest_response(left_digest_response_hash, req_method, req_uri, req_nonce,
|
|
117
|
+ req_cnonce, req_qop, req_nc)
|
|
118
|
+
|
|
119
|
+ if required_digest != req_response:
|
|
120
|
+ _logger.warning("computeDigestResponse('%s', '%s', ...): %s != %s" % (
|
|
121
|
+ realmname, req_username, required_digest, req_response))
|
|
122
|
+ isinvalidreq = True
|
|
123
|
+ else:
|
|
124
|
+ _logger.debug("digest succeeded for realm '%s', user '%s'" % (realmname, req_username))
|
|
125
|
+ pass
|
|
126
|
+
|
|
127
|
+ if isinvalidreq:
|
|
128
|
+ _logger.warning("Authentication failed for user '%s', realm '%s'" % (req_username, realmname))
|
|
129
|
+ return self.sendDigestAuthResponse(environ, start_response)
|
|
130
|
+
|
|
131
|
+ environ["http_authenticator.realm"] = realmname
|
|
132
|
+ environ["http_authenticator.username"] = req_username
|
|
133
|
+ return self._application(environ, start_response)
|
|
134
|
+
|
|
135
|
+ def tracim_compute_digest_response(self, left_digest_response_hash, method, uri, nonce, cnonce, qop, nc):
|
|
136
|
+ # A1 : username:realm:password
|
|
137
|
+ A2 = method + ":" + uri
|
|
138
|
+ if qop:
|
|
139
|
+ digestresp = self.md5kd(left_digest_response_hash, nonce + ":" + nc + ":" + cnonce + ":" + qop + ":" + self.md5h(A2))
|
|
140
|
+ else:
|
|
141
|
+ digestresp = self.md5kd(left_digest_response_hash, nonce + ":" + self.md5h(A2))
|
|
142
|
+ # print(A1, A2)
|
|
143
|
+ # print(digestresp)
|
|
144
|
+ return digestresp
|