Attachment 'user_moin1-5-4-1_patched.py'

Download

   1 # -*- coding: iso-8859-1 -*-
   2 """
   3     MoinMoin - User Accounts
   4 
   5     @copyright: 2000-2004 by Jürgen Hermann <jh@web.de>
   6     @license: GNU GPL, see COPYING for details.
   7 """
   8 
   9 import os, time, sha, codecs
  10 
  11 try:
  12     import cPickle as pickle
  13 except ImportError:
  14     import pickle
  15 
  16 # Set pickle protocol, see http://docs.python.org/lib/node64.html
  17 PICKLE_PROTOCOL = pickle.HIGHEST_PROTOCOL
  18 
  19 from MoinMoin import config, caching, wikiutil
  20 from MoinMoin.util import filesys, timefuncs
  21 
  22 
  23 def getUserList(request):
  24     """ Get a list of all (numerical) user IDs.
  25 
  26     @param request: current request
  27     @rtype: list
  28     @return: all user IDs
  29     """
  30     import re, dircache
  31     user_re = re.compile(r'^\d+\.\d+(\.\d+)?$')
  32     files = dircache.listdir(request.cfg.user_dir)
  33     userlist = [f for f in files if user_re.match(f)]
  34     return userlist
  35 
  36 
  37 def getUserId(request, searchName):
  38     """
  39     Get the user ID for a specific user NAME.
  40 
  41     @param searchName: the user name to look up
  42     @rtype: string
  43     @return: the corresponding user ID or None
  44     """
  45     if not searchName:
  46         return None
  47     cfg = request.cfg
  48     try:
  49         _name2id = cfg._name2id
  50     except AttributeError:
  51         arena = 'user'
  52         key = 'name2id'
  53         cache = caching.CacheEntry(request, arena, key)
  54         try:
  55             _name2id = pickle.loads(cache.content())
  56         except (pickle.UnpicklingError, IOError, EOFError, ValueError):
  57             _name2id = {}
  58         cfg._name2id = _name2id
  59     id = _name2id.get(searchName, None)
  60     if id is None:
  61         for userid in getUserList(request):
  62             name = User(request, id=userid).name
  63             _name2id[name] = userid
  64         cfg._name2id = _name2id
  65         arena = 'user'
  66         key = 'name2id'
  67         cache = caching.CacheEntry(request, arena, key)
  68         cache.update(pickle.dumps(_name2id, PICKLE_PROTOCOL))
  69         id = _name2id.get(searchName, None)
  70     return id
  71 
  72 
  73 def getUserIdentification(request, username=None):
  74     """ 
  75     Return user name or IP or '<unknown>' indicator.
  76     
  77     @param request: the request object
  78     @param username: (optional) user name
  79     @rtype: string
  80     @return: user name or IP or unknown indicator
  81     """
  82     _ = request.getText
  83 
  84     if username is None:
  85         username = request.user.name
  86 
  87     return username or (request.cfg.show_hosts and request.remote_addr) or _("<unknown>")
  88 
  89 
  90 def encodePassword(pwd, charset='utf-8'):
  91     """ Encode a cleartext password
  92 
  93     Compatible to Apache htpasswd SHA encoding.
  94 
  95     When using different encoding than 'utf-8', the encoding might fail
  96     and raise UnicodeError.
  97 
  98     @param pwd: the cleartext password, (unicode)
  99     @param charset: charset used to encode password, used only for
 100         compatibility with old passwords generated on moin-1.2.
 101     @rtype: string
 102     @return: the password in apache htpasswd compatible SHA-encoding,
 103         or None
 104     """
 105     import base64
 106 
 107     # Might raise UnicodeError, but we can't do anything about it here,
 108     # so let the caller handle it.
 109     pwd = pwd.encode(charset)
 110 
 111     pwd = sha.new(pwd).digest()
 112     pwd = '{SHA}' + base64.encodestring(pwd).rstrip()
 113     return pwd
 114 
 115     
 116 def normalizeName(name):
 117     """ Make normalized user name
 118 
 119     Prevent impersonating another user with names containing leading,
 120     trailing or multiple whitespace, or using invisible unicode
 121     characters.
 122 
 123     Prevent creating user page as sub page, because '/' is not allowed
 124     in user names.
 125 
 126     Prevent using ':' and ',' which are reserved by acl.
 127     
 128     @param name: user name, unicode
 129     @rtype: unicode
 130     @return: user name that can be used in acl lines
 131     """
 132     name = name.replace('_', ' ') # we treat _ as a blank
 133     username_allowedchars = "'@.-" # ' for names like O'Brian or email addresses.
 134                                    # "," and ":" must not be allowed (ACL delimiters).
 135     # Strip non alpha numeric characters (except username_allowedchars), keep white space
 136     name = ''.join([c for c in name if c.isalnum() or c.isspace() or c in username_allowedchars])
 137 
 138     # Normalize white space. Each name can contain multiple 
 139     # words separated with only one space.
 140     name = ' '.join(name.split())
 141 
 142     return name
 143 
 144     
 145 def isValidName(request, name):
 146     """ Validate user name
 147 
 148     @param name: user name, unicode
 149     """
 150     normalized = normalizeName(name)
 151     name = name.replace('_', ' ') # we treat _ as a blank
 152     return (name == normalized) and not wikiutil.isGroupPage(request, name)
 153 
 154 
 155 def encodeList(items):
 156     """ Encode list of items in user data file
 157 
 158     Items are separated by '\t' characters.
 159     
 160     @param items: list unicode strings
 161     @rtype: unicode
 162     @return: list encoded as unicode
 163     """
 164     line = []
 165     for item in items:
 166         item = item.strip()
 167         if not item:
 168             continue
 169         line.append(item)
 170         
 171     line = '\t'.join(line)
 172     return line
 173 
 174 
 175 def decodeList(line):
 176     """ Decode list of items from user data file
 177     
 178     @param line: line containing list of items, encoded with encodeList
 179     @rtype: list of unicode strings
 180     @return: list of items in encoded in line
 181     """
 182     items = []
 183     for item in line.split('\t'):
 184         item = item.strip()
 185         if not item:
 186             continue
 187         items.append(item)
 188     return items
 189 
 190     
 191 class User:
 192     """A MoinMoin User"""
 193 
 194     def __init__(self, request, id=None, name="", password=None, auth_username="", **kw):
 195         """ Initialize User object
 196         
 197         @param request: the request object
 198         @param id: (optional) user ID
 199         @param name: (optional) user name
 200         @param password: (optional) user password
 201         @param auth_username: (optional) already authenticated user name
 202                               (e.g. when using http basic auth)
 203         @keyword auth_method: method that was used for authentication,
 204                               default: 'internal'
 205         @keyword auth_attribs: tuple of user object attribute names that are
 206                                determined by auth method and should not be
 207                                changed by UserPreferences form, default: ().
 208                                First tuple element was used for authentication.
 209         """
 210         self._cfg = request.cfg
 211         self.valid = 0
 212         self.trusted = 0
 213         self.id = id
 214         self.auth_username = auth_username
 215         self.auth_method = kw.get('auth_method', 'internal')
 216         self.auth_attribs = kw.get('auth_attribs', ())
 217                                        
 218         # create some vars automatically
 219         for tuple in self._cfg.user_form_fields:
 220             key = tuple[0]
 221             default = self._cfg.user_form_defaults.get(key, '')
 222             setattr(self, key, default)
 223        
 224         if name:
 225             self.name = name
 226         elif auth_username: # this is needed for user_autocreate
 227             self.name = auth_username
 228 
 229         # create checkbox fields (with default 0)
 230         for key, label in self._cfg.user_checkbox_fields:
 231             setattr(self, key, self._cfg.user_checkbox_defaults.get(key, 0))
 232 
 233         self.enc_password = ""
 234         if password:
 235             if password.startswith('{SHA}'):
 236                 self.enc_password = password
 237             else:
 238                 try:
 239                     self.enc_password = encodePassword(password)
 240                 except UnicodeError:
 241                     pass # Should never happen
 242 
 243         #self.edit_cols = 80
 244         self.tz_offset = int(float(self._cfg.tz_offset) * 3600)
 245         self.language = ""
 246         self.date_fmt = ""
 247         self.datetime_fmt = ""
 248         self.quicklinks = []
 249         self.subscribed_pages = []
 250         self.theme_name = self._cfg.theme_default
 251         self.editor_default = self._cfg.editor_default
 252         self.editor_ui = self._cfg.editor_ui
 253         self.last_saved = str(time.time())
 254         
 255         # attrs not saved to profile
 256         self._request = request
 257         self._trail = []
 258 
 259         # we got an already authenticated username:
 260         check_pass = 0
 261         if not self.id and self.auth_username:
 262             self.id = getUserId(request, self.auth_username)
 263             if not password is None:
 264                 check_pass = 1
 265         if self.id:
 266             self.load_from_id(check_pass)
 267             if self.name == self.auth_username:
 268                 self.trusted = 1
 269         elif self.name:
 270             self.id = getUserId(self._request, self.name)
 271             if self.id:
 272                 self.load_from_id(1)
 273             else:
 274                 self.id = self.make_id()
 275         else:
 276             self.id = self.make_id()
 277 
 278         # "may" so we can say "if user.may.read(pagename):"
 279         if self._cfg.SecurityPolicy:
 280             self.may = self._cfg.SecurityPolicy(self)
 281         else:
 282             from security import Default
 283             self.may = Default(self)
 284         
 285         from MoinMoin.i18n.meta import languages
 286         if self.language and not languages.has_key(self.language):
 287             self.language = 'en'
 288 
 289     def __repr__(self):
 290         return "<%s.%s at 0x%x name:%r id:%s valid:%r>" % (
 291             self.__class__.__module__, self.__class__.__name__,
 292             id(self), self.name, self.id, self.valid)
 293 
 294     def make_id(self):
 295         """ make a new unique user id """
 296         #!!! this should probably be a hash of REMOTE_ADDR, HTTP_USER_AGENT
 297         # and some other things identifying remote users, then we could also
 298         # use it reliably in edit locking
 299         from random import randint
 300         return "%s.%d" % (str(time.time()), randint(0,65535))
 301 
 302     def create_or_update(self, changed=False):
 303         """ Create or update a user profile
 304 
 305         @param changed: bool, set this to True if you updated the user profile values
 306         """
 307         if self._cfg.user_autocreate:
 308             if not self.valid and not self.disabled or changed: # do we need to save/update?
 309                 self.save() # yes, create/update user profile
 310                                 
 311     def __filename(self):
 312         """ Get filename of the user's file on disk
 313         
 314         @rtype: string
 315         @return: full path and filename of user account file
 316         """
 317         return os.path.join(self._cfg.user_dir, self.id or "...NONE...")
 318 
 319     def __bookmark_filename(self):
 320         if self._cfg.interwikiname:
 321             return (self.__filename() + "." + self._cfg.interwikiname +
 322                     ".bookmark")
 323         else:
 324             return self.__filename() + ".bookmark"
 325 
 326     def exists(self):
 327         """ Do we have a user account for this user?
 328         
 329         @rtype: bool
 330         @return: true, if we have a user account
 331         """
 332         return os.path.exists(self.__filename())
 333 
 334     def load_from_id(self, check_pass=0):
 335         """ Load user account data from disk.
 336 
 337         Can only load user data if the id number is already known.
 338 
 339         This loads all member variables, except "id" and "valid" and
 340         those starting with an underscore.
 341         
 342         @param check_pass: If 1, then self.enc_password must match the
 343                            password in the user account file.
 344         """
 345         if not self.exists():
 346             return
 347 
 348         data = codecs.open(self.__filename(), "r", config.charset).readlines()
 349         user_data = {'enc_password': ''}
 350         for line in data:
 351             if line[0] == '#':
 352                 continue
 353 
 354             try:
 355                 key, val = line.strip().split('=', 1)
 356                 if key not in self._cfg.user_transient_fields and key[0] != '_':
 357                     # Decode list values
 358                     if key in ['quicklinks', 'subscribed_pages']:
 359                         val = decodeList(val)
 360                     user_data[key] = val
 361             except ValueError:
 362                 pass
 363 
 364         # Validate data from user file. In case we need to change some
 365         # values, we set 'changed' flag, and later save the user data.
 366         changed = 0
 367 
 368         if check_pass:
 369             # If we have no password set, we don't accept login with username
 370             if not user_data['enc_password']:
 371                 return
 372             # Check for a valid password, possibly changing encoding
 373             valid, changed = self._validatePassword(user_data)
 374             if not valid: 
 375                 return
 376             else:
 377                 self.trusted = 1
 378 
 379         # Remove ignored checkbox values from user data
 380         for key, label in self._cfg.user_checkbox_fields:
 381             if user_data.has_key(key) and key in self._cfg.user_checkbox_disable:
 382                 del user_data[key]
 383 
 384         # Copy user data into user object
 385         for key, val in user_data.items():
 386             vars(self)[key] = val
 387 
 388         self.tz_offset = int(self.tz_offset)
 389 
 390         # Remove old unsupported attributes from user data file.
 391         remove_attributes = ['passwd', 'show_emoticons']
 392         for attr in remove_attributes:
 393             if hasattr(self, attr):
 394                 delattr(self, attr)
 395                 changed = 1
 396         
 397         # make sure checkboxes are boolean
 398         for key, label in self._cfg.user_checkbox_fields:
 399             try:
 400                 setattr(self, key, int(getattr(self, key)))
 401             except ValueError:
 402                 setattr(self, key, 0)
 403 
 404         # convert (old) hourly format to seconds
 405         if -24 <= self.tz_offset and self.tz_offset <= 24:
 406             self.tz_offset = self.tz_offset * 3600
 407 
 408         # clear trail
 409         self._trail = []
 410 
 411         if not self.disabled:
 412             self.valid = 1
 413 
 414         # If user data has been changed, save fixed user data.
 415         if changed:
 416             self.save()
 417 
 418     def _validatePassword(self, data):
 419         """ Try to validate user password
 420 
 421         This is a private method and should not be used by clients.
 422 
 423         In pre 1.3, the wiki used some 8 bit charset. The user password
 424         was entered in this 8 bit password and passed to
 425         encodePassword. So old passwords can use any of the charset
 426         used.
 427 
 428         In 1.3, we use unicode internally, so we encode the password in
 429         encodePassword using utf-8.
 430 
 431         When we compare passwords we must compare with same encoding, or
 432         the passwords will not match. We don't know what encoding the
 433         password on the user file uses. We may ask the wiki admin to put
 434         this into the config, but he may be wrong.
 435 
 436         The way chosen is to try to encode and compare passwords using
 437         all the encoding that were available on 1.2, until we get a
 438         match, which means that the user is valid.
 439 
 440         If we get a match, we replace the user password hash with the
 441         utf-8 encoded version, and next time it will match on first try
 442         as before. The user password did not change, this change is
 443         completely transparent for the user. Only the sha digest will
 444         change.
 445 
 446         @param data: dict with user data
 447         @rtype: 2 tuple (bool, bool)
 448         @return: password is valid, password did change
 449         """
 450         # First try with default encoded password. Match only non empty
 451         # passwords. (require non empty enc_password)
 452         if self.enc_password and self.enc_password == data['enc_password']:
 453             return True, False
 454 
 455         # Try to match using one of pre 1.3 8 bit charsets
 456 
 457         # Get the clear text password from the form (require non empty
 458         # password)
 459         password = self._request.form.get('password',[None])[0]
 460         if not password:
 461             return False, False 
 462                 
 463         # First get all available pre13 charsets on this system
 464         pre13 = ['iso-8859-1', 'iso-8859-2', 'euc-jp', 'gb2312', 'big5',]
 465         available = []
 466         for charset in pre13:
 467             try:
 468                 encoder = codecs.getencoder(charset)
 469                 available.append(charset)
 470             except LookupError:
 471                 pass # missing on this system
 472                 
 473         # Now try to match the password
 474         for charset in available:
 475             # Try to encode, failure is expected
 476             try:
 477                 enc_password = encodePassword(password, charset=charset)
 478             except UnicodeError:
 479                 continue
 480 
 481             # And match (require non empty enc_password)
 482             if enc_password and enc_password == data['enc_password']:
 483                 # User password match - replace the user password in the
 484                 # file with self.password
 485                 data['enc_password'] = self.enc_password
 486                 return True, True
 487 
 488         # No encoded password match, this must be wrong password
 489         return False, False
 490 
 491     def save(self):
 492         """ Save user account data to user account file on disk.
 493 
 494         This saves all member variables, except "id" and "valid" and
 495         those starting with an underscore.
 496         """
 497         if not self.id:
 498             return
 499 
 500         user_dir = self._cfg.user_dir
 501         filesys.makeDirs(user_dir)
 502 
 503         self.last_saved = str(time.time())
 504 
 505         # !!! should write to a temp file here to avoid race conditions,
 506         # or even better, use locking
 507         
 508         data = codecs.open(self.__filename(), "w", config.charset)
 509         data.write("# Data saved '%s' for id '%s'\n" % (
 510             time.strftime(self._cfg.datetime_fmt, time.localtime(time.time())),
 511             self.id))
 512         attrs = vars(self).items()
 513         attrs.sort()
 514         for key, value in attrs:
 515             if key not in self._cfg.user_transient_fields and key[0] != '_':
 516                 # Encode list values
 517                 if key in ['quicklinks', 'subscribed_pages']:
 518                     value = encodeList(value)
 519                 line = u"%s=%s\n" % (key, unicode(value))
 520                 data.write(line)
 521         data.close()
 522 
 523         try:
 524             os.chmod(self.__filename(), 0666 & config.umask)
 525         except OSError:
 526             pass
 527 
 528         if not self.disabled:
 529             self.valid = 1
 530 
 531     # -----------------------------------------------------------------
 532     # Time and date formatting
 533 
 534     def getTime(self, tm):
 535         """ Get time in user's timezone.
 536         
 537         @param tm: time (UTC UNIX timestamp)
 538         @rtype: int
 539         @return: tm tuple adjusted for user's timezone
 540         """
 541         return timefuncs.tmtuple(tm + self.tz_offset)
 542 
 543 
 544     def getFormattedDate(self, tm):
 545         """ Get formatted date adjusted for user's timezone.
 546 
 547         @param tm: time (UTC UNIX timestamp)
 548         @rtype: string
 549         @return: formatted date, see cfg.date_fmt
 550         """
 551         date_fmt = self.date_fmt or self._cfg.date_fmt
 552         return time.strftime(date_fmt, self.getTime(tm))
 553 
 554 
 555     def getFormattedDateTime(self, tm):
 556         """ Get formatted date and time adjusted for user's timezone.
 557 
 558         @param tm: time (UTC UNIX timestamp)
 559         @rtype: string
 560         @return: formatted date and time, see cfg.datetime_fmt
 561         """
 562         datetime_fmt = self.datetime_fmt or self._cfg.datetime_fmt
 563         return time.strftime(datetime_fmt, self.getTime(tm))
 564 
 565     # -----------------------------------------------------------------
 566     # Bookmark
 567 
 568     def setBookmark(self, tm):
 569         """ Set bookmark timestamp.
 570         
 571         @param tm: timestamp
 572         """
 573         if self.valid:
 574             bm_fn = self.__bookmark_filename()
 575             bmfile = open(bm_fn, "w")
 576             bmfile.write(str(tm)+"\n")
 577             bmfile.close()
 578             try:
 579                 os.chmod(bm_fn, 0666 & config.umask)
 580             except OSError:
 581                 pass
 582 
 583     def getBookmark(self):
 584         """ Get bookmark timestamp.
 585         
 586         @rtype: int
 587         @return: bookmark timestamp or None
 588         """
 589         bm = None
 590         bm_fn = self.__bookmark_filename()
 591 
 592         if self.valid and os.path.exists(bm_fn):
 593             try:
 594                 bm = long(open(bm_fn, 'r').readline()) # must be long for py 2.2
 595             except (OSError, ValueError):
 596                 pass
 597         return bm
 598 
 599     def delBookmark(self):
 600         """ Removes bookmark timestamp.
 601 
 602         @rtype: int
 603         @return: 0 on success, 1 on failure
 604         """
 605         bm_fn = self.__bookmark_filename()
 606         if self.valid:
 607             if os.path.exists(bm_fn):
 608                 try:
 609                     os.unlink(bm_fn)
 610                 except OSError:
 611                     return 1
 612             return 0
 613         return 1
 614 
 615     # -----------------------------------------------------------------
 616     # Subscribe
 617 
 618     def getSubscriptionList(self):
 619         """ Get list of pages this user has subscribed to
 620         
 621         @rtype: list
 622         @return: pages this user has subscribed to
 623         """
 624         return self.subscribed_pages
 625         
 626     def isSubscribedTo(self, pagelist):
 627         """ Check if user subscription matches any page in pagelist.
 628         
 629         The subscription list may contain page names or interwiki page
 630         names. e.g 'Page Name' or 'WikiName:Page_Name'
 631         
 632         TODO: check if its fast enough when calling with many users
 633         from page.getSubscribersList()
 634         
 635         @param pagelist: list of pages to check for subscription
 636         @rtype: bool
 637         @return: if user is subscribed any page in pagelist
 638         """
 639         if not self.valid:
 640             return False
 641         
 642         import re        
 643         # Create a new list with both names and interwiki names.
 644         pages = pagelist[:]
 645         if self._cfg.interwikiname:
 646             pages += [self._interWikiName(pagename) for pagename in pagelist]
 647         # Create text for regular expression search
 648         text = '\n'.join(pages)
 649         
 650         for pattern in self.getSubscriptionList():
 651             # Try simple match first
 652             if pattern in pages:
 653                 return True
 654             # Try regular expression search, skipping bad patterns
 655             try:
 656                 pattern = re.compile(r'^%s$' % pattern, re.M)
 657             except re.error:
 658                 continue
 659             if pattern.search(text):
 660                 return True
 661 
 662         return False
 663 
 664     def subscribe(self, pagename):
 665         """ Subscribe to a wiki page.
 666 
 667         To enable shared farm users, if the wiki has an interwiki name,
 668         page names are saved as interwiki names.
 669 
 670         @param pagename: name of the page to subscribe
 671         @type pagename: unicode
 672         @rtype: bool
 673         @return: if page was subscribed
 674         """        
 675         if self._cfg.interwikiname:
 676             pagename = self._interWikiName(pagename)
 677         
 678         if pagename not in self.subscribed_pages:
 679             self.subscribed_pages.append(pagename)
 680             self.save()
 681             return True
 682         
 683         return False
 684 
 685     def unsubscribe(self, pagename):
 686         """ Unsubscribe a wiki page.
 687 
 688         Try to unsubscribe by removing non-interwiki name (leftover
 689         from old use files) and interwiki name from the subscription
 690         list.
 691 
 692         Its possible that the user will be subscribed to a page by more
 693         then one pattern. It can be both pagename and interwiki name,
 694         or few patterns that all of them match the page. Therefore, we
 695         must check if the user is still subscribed to the page after we
 696         try to remove names from the list.
 697 
 698         TODO: should we remove non-interwiki subscription? what if the
 699         user want to subscribe to the same page in multiple wikis?
 700 
 701         @param pagename: name of the page to subscribe
 702         @type pagename: unicode
 703         @rtype: bool
 704         @return: if unsubscrieb was successful. If the user has a
 705             regular expression that match, it will always fail.
 706         """
 707         changed = False
 708         if pagename in self.subscribed_pages:
 709             self.subscribed_pages.remove(pagename)
 710             changed = True
 711         
 712         interWikiName = self._interWikiName(pagename)        
 713         if interWikiName and interWikiName in self.subscribed_pages:
 714             self.subscribed_pages.remove(interWikiName)
 715             changed = True
 716     
 717         if changed:
 718             self.save()
 719         return not self.isSubscribedTo([pagename])
 720         
 721     # -----------------------------------------------------------------
 722     # Quicklinks
 723 
 724     def getQuickLinks(self):
 725         """ Get list of pages this user wants in the navibar
 726 
 727         @rtype: list
 728         @return: quicklinks from user account
 729         """
 730         return self.quicklinks
 731 
 732     def isQuickLinkedTo(self, pagelist):
 733         """ Check if user quicklink matches any page in pagelist.
 734         
 735         @param pagelist: list of pages to check for quicklinks
 736         @rtype: bool
 737         @return: if user has quicklinked any page in pagelist
 738         """
 739         if not self.valid:
 740             return False
 741             
 742         for pagename in pagelist:
 743             if pagename in self.quicklinks:
 744                 return True
 745             interWikiName = self._interWikiName(pagename)
 746             if interWikiName and interWikiName in self.quicklinks:
 747                 return True
 748         
 749         return False
 750 
 751     def addQuicklink(self, pagename):
 752         """ Adds a page to the user quicklinks 
 753         
 754         If the wiki has an interwiki name, all links are saved as
 755         interwiki names. If not, as simple page name.
 756 
 757         @param pagename: page name
 758         @type pagename: unicode
 759         @rtype: bool
 760         @return: if pagename was added
 761         """
 762         changed = False
 763         interWikiName = self._interWikiName(pagename)
 764         if interWikiName:
 765             if pagename in self.quicklinks:
 766                 self.quicklinks.remove(pagename)
 767                 changed = True
 768             if interWikiName not in self.quicklinks:
 769                 self.quicklinks.append(interWikiName)
 770                 changed = True
 771         else:
 772             if pagename not in self.quicklinks:
 773                 self.quicklinks.append(pagename)
 774                 changed = True
 775 
 776         if changed:
 777             self.save()
 778         return changed
 779 
 780     def removeQuicklink(self, pagename):
 781         """ Remove a page from user quicklinks 
 782         
 783         Remove both interwiki and simple name from quicklinks.
 784         
 785         @param pagename: page name
 786         @type pagename: unicode
 787         @rtype: bool
 788         @return: if pagename was removed
 789         """
 790         changed = False
 791         interWikiName = self._interWikiName(pagename)
 792         if interWikiName and interWikiName in self.quicklinks:
 793             self.quicklinks.remove(interWikiName)
 794             changed = True
 795         if pagename in self.quicklinks:
 796             self.quicklinks.remove(pagename)
 797             changed = True        
 798         
 799         if changed:
 800             self.save()
 801         return changed
 802 
 803     def _interWikiName(self, pagename):
 804         """ Return the inter wiki name of a page name 
 805 
 806         @param pagename: page name
 807         @type pagename: unicode
 808         """
 809         if not self._cfg.interwikiname:
 810             return None
 811             
 812         # Interwiki links must use _ e.g Wiki:Main_Page
 813         pagename = pagename.replace(" ", "_")
 814         return "%s:%s" % (self._cfg.interwikiname, pagename)
 815 
 816     # -----------------------------------------------------------------
 817     # Trail
 818 
 819     def addTrail(self, pagename):
 820         """ Add page to trail.
 821         
 822         @param pagename: the page name to add to the trail
 823         """
 824         # TODO: acquire lock here, so multiple processes don't clobber
 825         # each one trail.
 826 
 827         if self.valid and (self.show_page_trail or self.remember_last_visit):
 828             # load trail if not known
 829             self.getTrail()      
 830 
 831             # Add only existing pages that the user may read
 832             if self._request:
 833                 from MoinMoin.Page import Page
 834                 page = Page(self._request, pagename)
 835                 if not (page.exists() and
 836                         self._request.user.may.read(page.page_name)):
 837                     return
 838 
 839             # Save interwiki links internally
 840             if self._cfg.interwikiname:
 841                 pagename = self._interWikiName(pagename)
 842 
 843             # Don't append tail to trail ;)
 844             if self._trail and self._trail[-1] == pagename:
 845                 return
 846 
 847             # Append new page, limiting the length
 848             self._trail = [p for p in self._trail if p != pagename]
 849             self._trail = self._trail[-(self._cfg.trail_size-1):]
 850             self._trail.append(pagename)
 851             self.saveTrail()
 852 
 853         # TODO: release lock here
 854             
 855     def saveTrail(self):
 856         """ Save trail file
 857 
 858         Save using one write call, which should be fine in most cases,
 859         but will fail in rare cases without real file locking.
 860         """
 861         data = '\n'.join(self._trail) + '\n'
 862         path = self.__filename() + ".trail"
 863         try:
 864             file = codecs.open(path, "w", config.charset)
 865             try:
 866                 file.write(data)
 867             finally:
 868                 file.close()
 869 
 870             try:
 871                 os.chmod(path, 0666 & config.umask)
 872             except OSError, err:
 873                 self._request.log("Can't change mode of trail file: %s" %
 874                                   str(err))
 875         except (IOError, OSError), err:
 876             self._request.log("Can't save trail file: %s" % str(err))
 877 
 878     def getTrail(self):
 879         """ Return list of recently visited pages.
 880         
 881         @rtype: list
 882         @return: pages in trail
 883         """
 884         if self.valid and (self.show_page_trail or self.remember_last_visit) \
 885                 and not self._trail \
 886                 and os.path.exists(self.__filename() + ".trail"):
 887             try:
 888                 trail = codecs.open(self.__filename() + ".trail", 'r', config.charset).readlines()
 889             except (OSError, ValueError):
 890                 trail = []
 891             trail = [t.strip() for t in trail]
 892             trail = [t for t in trail if t]
 893             self._trail = trail[-self._cfg.trail_size:]
 894 
 895         return self._trail
 896 
 897     # -----------------------------------------------------------------
 898     # Other
 899 
 900     def isCurrentUser(self):
 901         return self._request.user.name == self.name
 902 
 903     def isSuperUser(self):
 904         superusers = self._request.cfg.superuser
 905         assert isinstance(superusers, (list, tuple))
 906         return self.valid and self.name and self.name in superusers
 907 
 908     def host(self):
 909         """ Return user host """
 910         _ = self._request.getText
 911         host = self.isCurrentUser() and self._cfg.show_hosts and self._request.remote_addr
 912         return host or _("<unknown>")
 913 
 914     def signature(self):
 915         """ Return user signature using markup
 916         
 917         Users sign with a link to their homepage, or with text if they
 918         don't have one. The text may be parsed as a link if it's using
 919         CamelCase. Visitors return their host address.
 920         
 921         TODO: The signature use wiki format only, for example, it will
 922         not create a link when using rst format. It will also break if
 923         we change wiki syntax.
 924         """
 925         if not self.name:
 926             return self.host()
 927         
 928         wikiname, pagename = wikiutil.getInterwikiHomePage(self._request,
 929                                                            self.name)
 930         if wikiname == 'Self':
 931             if not wikiutil.isStrictWikiname(self.name):
 932                 markup = '["%s"]' % pagename
 933             else:
 934                 markup = pagename
 935         else:
 936             markup = '%s:%s' % (wikiname, pagename.replace(" ","_")) 
 937         return markup
 938 
 939     def mailAccountData(self, cleartext_passwd=None, invitation=False):
 940         from MoinMoin.util import mail
 941         from MoinMoin.wikiutil import getSysPage
 942         _ = self._request.getText
 943 
 944         if not self.enc_password: # generate pw if there is none yet
 945             from random import randint
 946             import base64
 947     
 948             charset = 'utf-8'
 949             pwd = "%s%d" % (str(time.time()), randint(0, 65535))
 950             pwd = pwd.encode(charset)
 951 
 952             pwd = sha.new(pwd).digest()
 953             pwd = '{SHA}%s' % base64.encodestring(pwd).rstrip()
 954     
 955             self.enc_password = pwd
 956             self.save()
 957 
 958         if not invitation:
 959 
 960             text = '\n' + _("""\
 961 Login Name: %s
 962 
 963 Login Password: %s
 964 
 965 Login URL: %s/%s?action=login
 966 """, formatted=False) % (
 967                         self.name, self.enc_password, self._request.getBaseURL(), getSysPage(self._request, 'UserPreferences').page_name)
 968 
 969             text = _("""\
 970 Somebody has requested to submit your account data to this email address.
 971 
 972 If you lost your password, please use the data below and just enter the
 973 password AS SHOWN into the wiki's password form field (use copy and paste
 974 for that).
 975 
 976 After successfully logging in, it is of course a good idea to set a new and known password.
 977 """, formatted=False) + text
 978 
 979         else:
 980 
 981             text = '\n' + _("""\
 982 Login Name: %s
 983 
 984 Login Password: %s
 985 
 986 Login URL: %s/%s?action=login
 987 """, formatted=False) % (
 988                         self.name, cleartext_passwd, self._request.getBaseURL(), getSysPage(self._request, 'UserPreferences').page_name)
 989 
 990             text = _("""\
 991 Welcome to the %(sitename)s Wiki!
 992 
 993 You are invited to join our wiki community and share your ideas with us.
 994 Please use the login name and password below to log in. To ease login, you can
 995 also use copy and paste.
 996 Please do also make sure that in your browser's settings cookies are enabled
 997 for the login url so that authentications does really work and is kept while
 998 navigating in the wiki.
 999 
1000 After successfully logging in, it is of course a good idea to set a new password
1001 of your choice.
1002 """, formatted=False) % {'sitename': self._cfg.sitename or "Wiki"}  + text
1003 
1004         
1005 
1006         subject = _('[%(sitename)s] Your wiki account data',
1007                     formatted=False) % {'sitename': self._cfg.sitename or "Wiki"}
1008         mailok, msg = mail.sendmail(self._request, [self.email], subject,
1009                                     text, mail_from=self._cfg.mail_from)
1010         return msg

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.
  • [get | view] (2006-11-25 20:10:35, 1.4 KB) [[attachment:MakeInvitation.py]]
  • [get | view] (2006-12-10 22:20:31, 1.1 KB) [[attachment:MakeInvitation.txt]]
  • [get | view] (2006-12-10 22:20:06, 2.8 KB) [[attachment:MakeInvitation1-6-dev.py]]
  • [get | view] (2008-11-20 08:40:46, 9.3 KB) [[attachment:MakeInvitation_v1.7.2.gif]]
  • [get | view] (2008-11-20 07:33:50, 3.0 KB) [[attachment:MakeInvitation_v1.7.2.py]]
  • [get | view] (2008-11-20 07:34:07, 1.2 KB) [[attachment:MakeInvitation_v1.7.2.txt]]
  • [get | view] (2006-12-10 22:19:33, 2.5 KB) [[attachment:mailAccountData.py]]
  • [get | view] (2006-11-25 20:10:53, 2.3 KB) [[attachment:moin1-5-4-1user.diff]]
  • [get | view] (2006-11-25 20:13:16, 4.1 KB) [[attachment:moin1-5-4-1userform.diff]]
  • [get | view] (2006-12-10 22:19:03, 2.2 KB) [[attachment:user-1-6-dev.diff]]
  • [get | view] (2006-11-25 20:12:10, 33.3 KB) [[attachment:user_moin1-5-4-1_patched.py]]
  • [get | view] (2006-12-10 22:18:31, 1.0 KB) [[attachment:userform.diff]]
  • [get | view] (2006-11-25 20:12:52, 31.5 KB) [[attachment:userform_moin1-5-4-1_patched.py]]
 All files | Selected Files: delete move to page copy to page

You are not allowed to attach a file to this page.