Attachment 'auth.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 # optional group membership verification - done with binddn credentials
372 # since user might not have permissions
373 try:
374 if (l.compare_s(dn, cfg.ldap_group_attribute , cfg.ldap_user_access_group) != True):
375 request.log("LDAP: username %s fails group membership check %s : %s ." % ( username, cfg.ldap_group_attribute, cfg.ldap_user_access_group))
376 return user_obj, True
377 except AttributeError, e: # group params not specified in cfg
378 request.log("LDAP: skipping group membership check because no group configuration options in config")
379
380 try:
381 if verbose: request.log("LDAP: DN found is %s, trying to bind with pw" % dn)
382 l.simple_bind_s(dn, password.encode(coding))
383 if verbose: request.log("LDAP: Bound with dn %s (username: %s)" % (dn, username))
384
385 email = ldap_dict.get(cfg.ldap_email_attribute, [''])[0]
386 email = email.decode(coding)
387 sn, gn = ldap_dict.get('sn', [''])[0], ldap_dict.get('givenName', [''])[0]
388 aliasname = ''
389 if sn and gn:
390 aliasname = "%s, %s" % (sn, gn)
391 elif sn:
392 aliasname = sn
393 aliasname = aliasname.decode(coding)
394
395 u = user.User(request, auth_username=username, password=password, auth_method='ldap', auth_attribs=('name', 'password', 'email', 'mailto_author',))
396 u.name = username
397 u.aliasname = aliasname
398 u.email = email
399 u.remember_me = 0 # 0 enforces cookie_lifetime config param
400 if verbose: request.log("LDAP: creating userprefs with name %s email %s alias %s" % (username, email, aliasname))
401
402 except ldap.INVALID_CREDENTIALS, err:
403 request.log("LDAP: invalid credentials (wrong password?) for dn %s (username: %s)" % (dn, username))
404
405 except:
406 info = sys.exc_info()
407 request.log("LDAP: caught an exception, traceback follows...")
408 request.log(''.join(traceback.format_exception(*info)))
409
410 if u:
411 u.create_or_update(True)
412 return user_obj, True # moin_cookie has to set the cookie and return the user obj
413
414
415 def interwiki(request, **kw):
416 # TODO use auth_method and auth_attribs for User object
417 username = kw.get('name')
418 password = kw.get('password')
419 login = kw.get('login')
420 logout = kw.get('logout')
421 user_obj = kw.get('user_obj')
422
423 if login:
424 wikitag, wikiurl, wikitail, err = wikiutil.resolve_wiki(username)
425
426 if err or wikitag not in request.cfg.trusted_wikis:
427 return user_obj, True
428
429 if password:
430 import xmlrpclib
431 homewiki = xmlrpclib.Server(wikiurl + "?action=xmlrpc2")
432 account_data = homewiki.getUser(wikitail, password)
433 if isinstance(account_data, str):
434 # show error message
435 return user_obj, True
436
437 u = user.User(request, name=username)
438 for key, value in account_data.iteritems():
439 if key not in ["may", "id", "valid", "trusted"
440 "auth_username",
441 "name", "aliasname",
442 "enc_passwd"]:
443 setattr(u, key, value)
444 u.save()
445 setCookie(request, u)
446 return u, True
447 else:
448 pass
449 # XXX redirect to homewiki
450
451 return user_obj, True
452
453
454 class php_session:
455 """ Authentication module for PHP based frameworks
456 Authenticates via PHP session cookie. Currently supported systems:
457
458 * eGroupware 1.2 ("egw")
459 * You need to configure eGroupware in the "header setup" to use
460 "php sessions plus restore"
461
462 @copyright: 2005 by MoinMoin:AlexanderSchremmer
463 - Thanks to Spreadshirt
464 """
465
466 def __init__(self, apps=['egw'], s_path="/tmp", s_prefix="sess_"):
467 """ @param apps: A list of the enabled applications. See above for
468 possible keys.
469 @param s_path: The path where the PHP sessions are stored.
470 @param s_prefix: The prefix of the session files.
471 """
472
473 self.s_path = s_path
474 self.s_prefix = s_prefix
475 self.apps = apps
476
477 def __call__(self, request, **kw):
478 def handle_egroupware(session):
479 """ Extracts name, fullname and email from the session. """
480 username = session['egw_session']['session_lid'].split("@", 1)[0]
481 known_accounts = session['egw_info_cache']['accounts']['cache']['account_data']
482
483 # if the next line breaks, then the cache was not filled with the current
484 # user information
485 user_info = [value for key, value in known_accounts.items()
486 if value['account_lid'] == username][0]
487 name = user_info.get('fullname', '')
488 email = user_info.get('email', '')
489
490 dec = lambda x: x and x.decode("iso-8859-1")
491
492 return dec(username), dec(email), dec(name)
493
494 import Cookie, urllib
495 from MoinMoin.user import User
496 from MoinMoin.util import sessionParser
497
498 user_obj = kw.get('user_obj')
499 try:
500 cookie = Cookie.SimpleCookie(request.saved_cookie)
501 except Cookie.CookieError: # ignore invalid cookies
502 cookie = None
503 if cookie:
504 for cookiename in cookie.keys():
505 cookievalue = urllib.unquote(cookie[cookiename].value).decode('iso-8859-1')
506 session = sessionParser.loadSession(cookievalue, path=self.s_path, prefix=self.s_prefix)
507 if session:
508 if "egw" in self.apps and session.get('egw_session', None):
509 username, email, name = handle_egroupware(session)
510 break
511 else:
512 return user_obj, True
513
514 user = User(request, name=username, auth_username=username)
515
516 changed = False
517 if name != user.aliasname:
518 user.aliasname = name
519 changed = True
520 if email != user.email:
521 user.email = email
522 changed = True
523
524 if user:
525 user.create_or_update(changed)
526 if user and user.valid:
527 return user, True # True to get other methods called, too
528 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.