Attachment 'auth.orig.py'
Download 1 # -*- coding: iso-8859-1 -*-
2 """
3 MoinMoin - modular authentication code
4
5 Here are some methods moin can use in cfg.auth authentication method list.
6 The methods from that list get called (from request.py) in that sequence.
7 They get request as first argument and also some more kw arguments:
8 name: the value we did get from a POST of the UserPreferences page
9 in the "name" form field (or None)
10 password: the value of the password form field (or None)
11 login: True if user has clicked on Login button
12 logout: True if user has clicked on Logout button
13 user_obj: the user_obj we have until now (user_obj returned from
14 previous auth method or None for first auth method)
15 (we maybe add some more here)
16
17 Use code like this to get them:
18 name = kw.get('name') or ''
19 password = kw.get('password') or ''
20 login = kw.get('login')
21 logout = kw.get('logout')
22 request.log("got name=%s len(password)=%d login=%r logout=%r" % (name, len(password), login, logout))
23
24 The called auth method then must return a tuple (user_obj, continue_flag).
25 user_obj can be one of:
26 * a (newly created) User object
27 * None if we want to inhibit log in from previous auth methods
28 * what we got as kw argument user_obj (meaning: no change).
29 continue_flag is a boolean indication whether the auth loop shall continue
30 trying other auth methods (or not).
31
32 The methods give a kw arg "auth_attribs" to User.__init__ that tells
33 which user attribute names are DETERMINED and set by this auth method and
34 must not get changed by the user using the UserPreferences form.
35 It also gives a kw arg "auth_method" that tells the name of the auth
36 method that authentified the user.
37
38 @copyright: 2005-2006 Bastian Blank, Florian Festi, Thomas Waldmann
39 @copyright: 2005-2006 MoinMoin:AlexanderSchremmer
40 @license: GNU GPL, see COPYING for details.
41 """
42
43 import time, Cookie
44 from MoinMoin import user
45
46 def log(request, **kw):
47 """ just log the call, do nothing else """
48 username = kw.get('name')
49 password = kw.get('password')
50 login = kw.get('login')
51 logout = kw.get('logout')
52 user_obj = kw.get('user_obj')
53 request.log("auth.log: name=%s login=%r logout=%r user_obj=%r" % (username, login, logout, user_obj))
54 return user_obj, True
55
56 # some cookie functions used by moin_cookie auth
57 def makeCookie(request, moin_id, maxage, expires):
58 """ calculate a MOIN_ID cookie """
59 c = Cookie.SimpleCookie()
60 cfg = request.cfg
61 c['MOIN_ID'] = moin_id
62 c['MOIN_ID']['max-age'] = maxage
63 if cfg.cookie_domain:
64 c['MOIN_ID']['domain'] = cfg.cookie_domain
65 if cfg.cookie_path:
66 c['MOIN_ID']['path'] = cfg.cookie_path
67 else:
68 path = request.getScriptname()
69 if not path:
70 path = '/'
71 c['MOIN_ID']['path'] = path
72 # Set expires for older clients
73 c['MOIN_ID']['expires'] = request.httpDate(when=expires, rfc='850')
74 return c.output()
75
76 def setCookie(request, u):
77 """ Set cookie for the user obj u
78
79 cfg.cookie_lifetime and the user 'remember_me' setting set the
80 lifetime of the cookie. lifetime in int hours, see table:
81
82 value cookie lifetime
83 ----------------------------------------------------------------
84 = 0 forever, ignoring user 'remember_me' setting
85 > 0 n hours, or forever if user checked 'remember_me'
86 < 0 -n hours, ignoring user 'remember_me' setting
87 """
88 # Calculate cookie maxage and expires
89 lifetime = int(request.cfg.cookie_lifetime) * 3600
90 forever = 10*365*24*3600 # 10 years
91 now = time.time()
92 if not lifetime:
93 maxage = forever
94 elif lifetime > 0:
95 if u.remember_me:
96 maxage = forever
97 else:
98 maxage = lifetime
99 elif lifetime < 0:
100 maxage = (-lifetime)
101 expires = now + maxage
102
103 cookie = makeCookie(request, u.id, maxage, expires)
104 # Set cookie
105 request.setHttpHeader(cookie)
106 # IMPORTANT: Prevent caching of current page and cookie
107 request.disableHttpCaching()
108
109 def deleteCookie(request):
110 """ Delete the user cookie by sending expired cookie with null value
111
112 According to http://www.cse.ohio-state.edu/cgi-bin/rfc/rfc2109.html#sec-4.2.2
113 Deleted cookie should have Max-Age=0. We also have expires
114 attribute, which is probably needed for older browsers.
115
116 Finally, delete the saved cookie and create a new user based on the new settings.
117 """
118 moin_id = ''
119 maxage = 0
120 # Set expires to one year ago for older clients
121 expires = time.time() - (3600 * 24 * 365) # 1 year ago
122 cookie = makeCookie(request, moin_id, maxage, expires)
123 # Set cookie
124 request.setHttpHeader(cookie)
125 # IMPORTANT: Prevent caching of current page and cookie
126 request.disableHttpCaching()
127
128 def moin_cookie(request, **kw):
129 """ authenticate via the MOIN_ID cookie """
130 username = kw.get('name')
131 password = kw.get('password')
132 login = kw.get('login')
133 logout = kw.get('logout')
134 user_obj = kw.get('user_obj')
135 #request.log("auth.moin_cookie: name=%s login=%r logout=%r user_obj=%r" % (username, login, logout, user_obj))
136 if login:
137 u = user.User(request, name=username, password=password,
138 auth_method='login_userpassword')
139 if u.valid:
140 setCookie(request, u)
141 return u, True # we make continuing possible, e.g. for smbmount
142 return user_obj, True
143
144 try:
145 cookie = Cookie.SimpleCookie(request.saved_cookie)
146 except Cookie.CookieError:
147 # ignore invalid cookies, else user can't relogin
148 cookie = None
149 if cookie and cookie.has_key('MOIN_ID'):
150 u = user.User(request, id=cookie['MOIN_ID'].value,
151 auth_method='moin_cookie', auth_attribs=())
152
153 if logout:
154 u.valid = 0 # just make user invalid, but remember him
155
156 if u.valid:
157 setCookie(request, u) # refreshes cookie lifetime
158 return u, True # use True to get other methods called, too
159 else: # logout or invalid user
160 deleteCookie(request)
161 return u, True # we return a invalidated user object, so that
162 # following auth methods can get the name of
163 # the user who logged out
164 return user_obj, True
165
166
167 def http(request, **kw):
168 """ authenticate via http basic/digest/ntlm auth """
169 from MoinMoin.request import RequestTwisted, RequestCLI
170 user_obj = kw.get('user_obj')
171 u = None
172 # check if we are running Twisted
173 if isinstance(request, RequestTwisted):
174 username = request.twistd.getUser()
175 password = request.twistd.getPassword()
176 # when using Twisted http auth, we use username and password from
177 # the moin user profile, so both can be changed by user.
178 u = user.User(request, auth_username=username, password=password,
179 auth_method='http', auth_attribs=())
180
181 elif not isinstance(request, RequestCLI):
182 env = request.env
183 auth_type = env.get('AUTH_TYPE','')
184 if auth_type in ['Basic', 'Digest', 'NTLM', 'Negotiate',]:
185 username = env.get('REMOTE_USER','')
186 if auth_type in ('NTLM', 'Negotiate',):
187 # converting to standard case so the user can even enter wrong case
188 # (added since windows does not distinguish between e.g.
189 # "Mike" and "mike")
190 username = username.split('\\')[-1] # split off domain e.g.
191 # from DOMAIN\user
192 # this "normalizes" the login name from {meier, Meier, MEIER} to Meier
193 # put a comment sign in front of next line if you don't want that:
194 username = username.title()
195 # when using http auth, we have external user name and password,
196 # we don't use the moin user profile for those attributes.
197 u = user.User(request, auth_username=username,
198 auth_method='http', auth_attribs=('name', 'password'))
199
200 if u:
201 u.create_or_update()
202 if u and u.valid:
203 return u, True # True to get other methods called, too
204 else:
205 return user_obj, True
206
207 def sslclientcert(request, **kw):
208 """ authenticate via SSL client certificate """
209 from MoinMoin.request import RequestTwisted
210 user_obj = kw.get('user_obj')
211 u = None
212 changed = False
213 # check if we are running Twisted
214 if isinstance(request, RequestTwisted):
215 return user_obj, True # not supported if we run twisted
216 # Addendum: this seems to need quite some twisted insight and coding.
217 # A pointer i got on #twisted: divmod's vertex.sslverify
218 # If you really need this, feel free to implement and test it and
219 # submit a patch if it works.
220 else:
221 env = request.env
222 if env.get('SSL_CLIENT_VERIFY', 'FAILURE') == 'SUCCESS':
223 # if we only want to accept some specific CA, do a check like:
224 # if env.get('SSL_CLIENT_I_DN_OU') == "http://www.cacert.org"
225 email = env.get('SSL_CLIENT_S_DN_Email', '')
226 email_lower = email.lower()
227 commonname = env.get('SSL_CLIENT_S_DN_CN', '')
228 commonname_lower = commonname.lower()
229 if email_lower or commonname_lower:
230 for uid in user.getUserList():
231 u = user.User(request, uid,
232 auth_method='sslclientcert', auth_attribs=())
233 if email_lower and u.email.lower() == email_lower:
234 u.auth_attribs = ('email', 'password')
235 #this is only useful if same name should be used, as
236 #commonname is likely no CamelCase WikiName
237 #if commonname_lower != u.name.lower():
238 # u.name = commonname
239 # changed = True
240 #u.auth_attribs = ('email', 'name', 'password')
241 break
242 if commonname_lower and u.name.lower() == commonname_lower:
243 u.auth_attribs = ('name', 'password')
244 #this is only useful if same email should be used as
245 #specified in certificate.
246 #if email_lower != u.email.lower():
247 # u.email = email
248 # changed = True
249 #u.auth_attribs = ('name', 'email', 'password')
250 break
251 else:
252 u = None
253
254 if u:
255 u.create_or_update(changed)
256 if u and u.valid:
257 return u, True
258 else:
259 return user_obj, True
260
261
262 def smb_mount(request, **kw):
263 """ (u)mount a SMB server's share for username (using username/password for
264 authentication at the SMB server). This can be used if you need access
265 to files on some share via the wiki, but needs more code to be useful.
266 If you don't need it, don't use it.
267 """
268 username = kw.get('name')
269 password = kw.get('password')
270 login = kw.get('login')
271 logout = kw.get('logout')
272 user_obj = kw.get('user_obj')
273 cfg = request.cfg
274 verbose = cfg.smb_verbose
275 if verbose: request.log("got name=%s login=%r logout=%r" % (username, login, logout))
276
277 # we just intercept login to mount and logout to umount the smb share
278 if login or logout:
279 import os, pwd, subprocess
280 web_username = cfg.smb_dir_user
281 web_uid = pwd.getpwnam(web_username)[2] # XXX better just use current uid?
282 if logout and user_obj: # logout -> we don't have username in form
283 username = user_obj.name # so we take it from previous auth method (moin_cookie e.g.)
284 mountpoint = cfg.smb_mountpoint % {
285 'username': username,
286 }
287 if login:
288 cmd = u"sudo mount -t cifs -o user=%(user)s,domain=%(domain)s,uid=%(uid)d,dir_mode=%(dir_mode)s,file_mode=%(file_mode)s,iocharset=%(iocharset)s //%(server)s/%(share)s %(mountpoint)s >>%(log)s 2>&1"
289 elif logout:
290 cmd = u"sudo umount %(mountpoint)s >>%(log)s 2>&1"
291
292 cmd = cmd % {
293 'user': username,
294 'uid': web_uid,
295 'domain': cfg.smb_domain,
296 'server': cfg.smb_server,
297 'share': cfg.smb_share,
298 'mountpoint': mountpoint,
299 'dir_mode': cfg.smb_dir_mode,
300 'file_mode': cfg.smb_file_mode,
301 'iocharset': cfg.smb_iocharset,
302 'log': cfg.smb_log,
303 }
304 env = os.environ.copy()
305 if login:
306 try:
307 os.makedirs(mountpoint) # the dir containing the mountpoint must be writeable for us!
308 except OSError, err:
309 pass
310 env['PASSWD'] = password.encode(cfg.smb_coding)
311 subprocess.call(cmd.encode(cfg.smb_coding), env=env, shell=True)
312 return user_obj, True
313
314
315 def ldap_login(request, **kw):
316 """ get authentication data from form, authenticate against LDAP (or Active Directory),
317 fetch some user infos from LDAP and create a user profile for that user that must
318 be used by subsequent auth plugins (like moin_cookie) as we never return a user
319 object from ldap_login.
320 """
321 username = kw.get('name')
322 password = kw.get('password')
323 login = kw.get('login')
324 logout = kw.get('logout')
325 user_obj = kw.get('user_obj')
326
327 cfg = request.cfg
328 verbose = cfg.ldap_verbose
329
330 if verbose: request.log("got name=%s login=%r logout=%r" % (username, login, logout))
331
332 # we just intercept login and logout for ldap, other requests have to be
333 # handled by another auth handler
334 if not login and not logout:
335 return user_obj, True
336
337 import sys, re
338 import ldap
339 import traceback
340
341 u = None
342 coding = cfg.ldap_coding
343 try:
344 if verbose: request.log("LDAP: Trying to initialize %s." % cfg.ldap_uri)
345 l = ldap.initialize(cfg.ldap_uri)
346 if verbose: request.log("LDAP: Connected to LDAP server %s." % cfg.ldap_uri)
347 # you can use %(username)s and %(password)s here to get the stuff entered in the form:
348 ldap_binddn = cfg.ldap_binddn % locals()
349 ldap_bindpw = cfg.ldap_bindpw % locals()
350 l.simple_bind_s(ldap_binddn.encode(coding), ldap_bindpw.encode(coding))
351 if verbose: request.log("LDAP: Bound with binddn %s" % ldap_binddn)
352
353 filterstr = "(%s=%s)" % (cfg.ldap_name_attribute, username)
354 if verbose: request.log("LDAP: Searching %s" % filterstr)
355 lusers = l.search_st(cfg.ldap_base, cfg.ldap_scope,
356 filterstr.encode(coding), timeout=cfg.ldap_timeout)
357 result_length = len(lusers)
358 if result_length != 1:
359 if result_length > 1:
360 request.log("LDAP: Search found more than one (%d) matches for %s." % (len(lusers), filterstr))
361 if result_length == 0:
362 if verbose: request.log("LDAP: Search found no matches for %s." % (filterstr, ))
363 return user_obj, True
364
365 dn, ldap_dict = lusers[0]
366 if verbose:
367 request.log("LDAP: debug lusers = %r" % lusers)
368 for key,val in ldap_dict.items():
369 request.log("LDAP: %s: %s" % (key, val))
370
371 try:
372 if verbose: request.log("LDAP: DN found is %s, trying to bind with pw" % dn)
373 l.simple_bind_s(dn, password.encode(coding))
374 if verbose: request.log("LDAP: Bound with dn %s (username: %s)" % (dn, username))
375
376 email = ldap_dict.get(cfg.ldap_email_attribute, [''])[0]
377 email = email.decode(coding)
378 sn, gn = ldap_dict.get('sn', [''])[0], ldap_dict.get('givenName', [''])[0]
379 aliasname = ''
380 if sn and gn:
381 aliasname = "%s, %s" % (sn, gn)
382 elif sn:
383 aliasname = sn
384 aliasname = aliasname.decode(coding)
385
386 u = user.User(request, auth_username=username, password=password, auth_method='ldap', auth_attribs=('name', 'password', 'email', 'mailto_author',))
387 u.name = username
388 u.aliasname = aliasname
389 u.email = email
390 u.remember_me = 0 # 0 enforces cookie_lifetime config param
391 if verbose: request.log("LDAP: creating userprefs with name %s email %s alias %s" % (username, email, aliasname))
392
393 except ldap.INVALID_CREDENTIALS, err:
394 request.log("LDAP: invalid credentials (wrong password?) for dn %s (username: %s)" % (dn, username))
395
396 except:
397 info = sys.exc_info()
398 request.log("LDAP: caught an exception, traceback follows...")
399 request.log(''.join(traceback.format_exception(*info)))
400
401 if u:
402 u.create_or_update(True)
403 return user_obj, True # moin_cookie has to set the cookie and return the user obj
404
405
406 def interwiki(request, **kw):
407 # TODO use auth_method and auth_attribs for User object
408 username = kw.get('name')
409 password = kw.get('password')
410 login = kw.get('login')
411 logout = kw.get('logout')
412 user_obj = kw.get('user_obj')
413
414 if login:
415 wikitag, wikiurl, wikitail, err = wikiutil.resolve_wiki(username)
416
417 if err or wikitag not in request.cfg.trusted_wikis:
418 return user_obj, True
419
420 if password:
421 import xmlrpclib
422 homewiki = xmlrpclib.Server(wikiurl + "?action=xmlrpc2")
423 account_data = homewiki.getUser(wikitail, password)
424 if isinstance(account_data, str):
425 # show error message
426 return user_obj, True
427
428 u = user.User(request, name=username)
429 for key, value in account_data.iteritems():
430 if key not in ["may", "id", "valid", "trusted"
431 "auth_username",
432 "name", "aliasname",
433 "enc_passwd"]:
434 setattr(u, key, value)
435 u.save()
436 setCookie(request, u)
437 return u, True
438 else:
439 pass
440 # XXX redirect to homewiki
441
442 return user_obj, True
443
444
445 class php_session:
446 """ Authentication module for PHP based frameworks
447 Authenticates via PHP session cookie. Currently supported systems:
448
449 * eGroupware 1.2 ("egw")
450 * You need to configure eGroupware in the "header setup" to use
451 "php sessions plus restore"
452
453 @copyright: 2005 by MoinMoin:AlexanderSchremmer
454 - Thanks to Spreadshirt
455 """
456
457 def __init__(self, apps=['egw'], s_path="/tmp", s_prefix="sess_"):
458 """ @param apps: A list of the enabled applications. See above for
459 possible keys.
460 @param s_path: The path where the PHP sessions are stored.
461 @param s_prefix: The prefix of the session files.
462 """
463
464 self.s_path = s_path
465 self.s_prefix = s_prefix
466 self.apps = apps
467
468 def __call__(self, request, **kw):
469 def handle_egroupware(session):
470 """ Extracts name, fullname and email from the session. """
471 username = session['egw_session']['session_lid'].split("@", 1)[0]
472 known_accounts = session['egw_info_cache']['accounts']['cache']['account_data']
473
474 # if the next line breaks, then the cache was not filled with the current
475 # user information
476 user_info = [value for key, value in known_accounts.items()
477 if value['account_lid'] == username][0]
478 name = user_info.get('fullname', '')
479 email = user_info.get('email', '')
480
481 dec = lambda x: x and x.decode("iso-8859-1")
482
483 return dec(username), dec(email), dec(name)
484
485 import Cookie, urllib
486 from MoinMoin.user import User
487 from MoinMoin.util import sessionParser
488
489 user_obj = kw.get('user_obj')
490 try:
491 cookie = Cookie.SimpleCookie(request.saved_cookie)
492 except Cookie.CookieError: # ignore invalid cookies
493 cookie = None
494 if cookie:
495 for cookiename in cookie.keys():
496 cookievalue = urllib.unquote(cookie[cookiename].value).decode('iso-8859-1')
497 session = sessionParser.loadSession(cookievalue, path=self.s_path, prefix=self.s_prefix)
498 if session:
499 if "egw" in self.apps and session.get('egw_session', None):
500 username, email, name = handle_egroupware(session)
501 break
502 else:
503 return user_obj, True
504
505 user = User(request, name=username, auth_username=username)
506
507 changed = False
508 if name != user.aliasname:
509 user.aliasname = name
510 changed = True
511 if email != user.email:
512 user.email = email
513 changed = True
514
515 if user:
516 user.create_or_update(changed)
517 if user and user.valid:
518 return user, True # True to get other methods called, too
519 return user_obj, True # continue with next method in auth list
Attached Files
To refer to attachments on a page, use attachment:filename, as shown below in the list of files. Do NOT use the URL of the [get] link, since this is subject to change and can break easily.You are not allowed to attach a file to this page.