lib/TWiki/Users.pm
changeset 0 414e01d06fd5
child 1 e2915a7cbdfa
equal deleted inserted replaced
-1:000000000000 0:414e01d06fd5
       
     1 # Users Module of TWiki Enterprise Collaboration Platform, http://TWiki.org/
       
     2 # Copyright (C) 1999-2007 TWiki Contributors.
       
     3 # All Rights Reserved. TWiki Contributors
       
     4 # are listed in the AUTHORS file in the root of this distribution.
       
     5 # NOTE: Please extend that file, not this notice.
       
     6 #
       
     7 # This program is free software; you can redistribute it and/or
       
     8 # modify it under the terms of the GNU General Public License
       
     9 # as published by the Free Software Foundation; either version 2
       
    10 # of the License, or (at your option) any later version. For
       
    11 # more details read LICENSE in the root of this distribution.
       
    12 #
       
    13 # This program is distributed in the hope that it will be useful,
       
    14 # but WITHOUT ANY WARRANTY; without even the implied warranty of
       
    15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
       
    16 #
       
    17 # As per the GPL, removal of this notice is prohibited.
       
    18 
       
    19 =pod
       
    20 
       
    21 ---+ package TWiki::Users
       
    22 This package provides services for the lookup and manipulation of login and
       
    23 wiki names of users, and their authentication.
       
    24 
       
    25 It is a Facade that presents a common interface to the User Mapping
       
    26 and Password modules. The rest of the core should *only* use the methods
       
    27 of this package, and should *never* call the mapping or password managers
       
    28 directly.
       
    29 
       
    30 TWiki uses the concept of a _login name_ which is used to authenticate a
       
    31 user. A login name maps to a _wiki name_ that is used to identify the user
       
    32 for display. Each login name is unique to a single user, though several
       
    33 login names may map to the same wiki name.
       
    34 
       
    35 Using this module (and the associated plug-in user mapper) TWiki supports
       
    36 the concept of _groups_. Groups are sets of login names that are treated
       
    37 equally for the purposes of access control. Group names do not have to be
       
    38 wiki names, though it is helpful for display if they are.
       
    39 
       
    40 Internally in the code TWiki uses something referred to as a _canonical user
       
    41 id_ or just _user id_. The user id is also used externally to uniquely identify
       
    42 the user when (for example) recording topic histories. The user id is *usually*
       
    43 just the login name, but it doesn't need to be. It just has to be a unique
       
    44 7-bit alphanumeric and underscore string that can be mapped to/from login
       
    45 and wiki names by the user mapper.
       
    46 
       
    47 The canonical user id should *never* be seen by a user. On the other hand,
       
    48 core code should never use anything *but* a canonical user id to refer
       
    49 to a user.
       
    50 
       
    51 *Terminology*
       
    52    * A *login name* is the name used to log in to TWiki. Each login name is
       
    53      assumed to be unique to a human. The Password module is responsible for
       
    54      authenticating and manipulating login names.
       
    55    * A *canonical user id* is an internal TWiki representation of a user. Each
       
    56      canonical user id maps 1:1 to a login name.
       
    57    * A *wikiname* is how a user is displayed. Many user ids may map to a
       
    58      single wikiname. The user mapping module is responsible for mapping
       
    59      the user id to a wikiname.
       
    60    * A *group id* represents a group of users and other groups.
       
    61      The user mapping module is responsible for mapping from a group id to
       
    62      a list of canonical user ids for the users in that group.
       
    63    * An *email* is an email address asscoiated with a *login name*. A single
       
    64      login name may have many emails.
       
    65 	 
       
    66 *NOTE:* 
       
    67    * wherever the code references $user, its a canonical_id
       
    68    * wherever the code references $group, its a group_name
       
    69 
       
    70 =cut
       
    71 
       
    72 package TWiki::Users;
       
    73 
       
    74 use strict;
       
    75 use Assert;
       
    76 
       
    77 require TWiki::AggregateIterator;
       
    78 #use Monitor;
       
    79 #Monitor::MonitorMethod('TWiki::Users');
       
    80 
       
    81 BEGIN {
       
    82     # Do a dynamic 'use locale' for this module
       
    83     if( $TWiki::cfg{UseLocale} ) {
       
    84         require locale;
       
    85         import locale();
       
    86     }
       
    87 }
       
    88 
       
    89 =pod
       
    90 
       
    91 ---++ ClassMethod new ($session)
       
    92 Construct the user management object that is the facade to the BaseUserMapping
       
    93 and the user mapping chosen in the configuration.
       
    94 
       
    95 =cut
       
    96 
       
    97 sub new {
       
    98     my ( $class, $session ) = @_;
       
    99     my $this = bless( { session => $session }, $class );
       
   100 
       
   101     require TWiki::LoginManager;
       
   102     $this->{loginManager} = TWiki::LoginManager::makeLoginManager( $session );
       
   103     # setup the cgi session, from a cookie or the url. this may return
       
   104     # the login, but even if it does, plugins will get the chance to
       
   105     # override (in TWiki.pm)
       
   106     $this->{remoteUser} = $this->{loginManager}->loadSession(
       
   107         $session->{remoteUser} );
       
   108     $this->{remoteUser} = $TWiki::cfg{DefaultUserLogin}
       
   109       unless (defined($this->{remoteUser}));
       
   110 
       
   111     #making basemapping
       
   112     my $implBaseUserMappingManager = $TWiki::cfg{BaseUserMappingManager} || 'TWiki::Users::BaseUserMapping';
       
   113     eval "require $implBaseUserMappingManager";
       
   114     die $@ if $@;
       
   115     $this->{basemapping} = $implBaseUserMappingManager->new( $session );
       
   116     $implBaseUserMappingManager =~ /^TWiki::Users::(.*)$/;
       
   117 
       
   118     my $implUserMappingManager = $TWiki::cfg{UserMappingManager};
       
   119     $implUserMappingManager = 'TWiki::Users::TWikiUserMapping' if( $implUserMappingManager eq 'none' );
       
   120 
       
   121 	if ( $implUserMappingManager eq 'TWiki::Users::BaseUserMapping') {
       
   122 		$this->{mapping} = $this->{basemapping};   #TODO: probly make undef..
       
   123 	} else {
       
   124     	eval "require $implUserMappingManager";
       
   125         die $@ if $@;
       
   126     	$this->{mapping} = $implUserMappingManager->new( $session );
       
   127     }
       
   128     #the UI for rego supported/not is different from rego temporarily turned off
       
   129     if ($this->supportsRegistration()) {
       
   130         $session->enterContext('registration_supported');
       
   131         $session->enterContext('registration_enabled') if $TWiki::cfg{Register}{EnableNewUserRegistration};
       
   132     }
       
   133     $implUserMappingManager =~ /^TWiki::Users::(.*)$/;
       
   134 
       
   135 	#caches - not only used for speedup, but also for authenticated but unregistered users
       
   136     #SMELL: this is basically the user object reborn
       
   137 	$this->{getWikiName} = {};
       
   138     $this->{getLoginName} = {};
       
   139     $this->{isAdmin} = {};
       
   140 	
       
   141     return $this;
       
   142 }
       
   143 
       
   144 =begin twiki
       
   145 
       
   146 ---++ ObjectMethod finish()
       
   147 Break circular references.
       
   148 
       
   149 =cut
       
   150 
       
   151 # Note to developers; please undef *all* fields in the object explicitly,
       
   152 # whether they are references or not. That way this method is "golden
       
   153 # documentation" of the live fields in the object.
       
   154 sub finish {
       
   155     my $this = shift;
       
   156 
       
   157     $this->{loginManager}->finish() if $this->{loginManager};
       
   158     $this->{basemapping}->finish() if $this->{basemapping};
       
   159     $this->{mapping}->finish() if $this->{mapping};
       
   160     undef $this->{loginManager};
       
   161     undef $this->{basemapping};
       
   162     undef $this->{mapping};
       
   163     undef $this->{session};
       
   164 	undef $this->{getWikiName};
       
   165     undef $this->{getLoginName};
       
   166     undef $this->{isAdmin};
       
   167 
       
   168 }
       
   169 
       
   170 =pod
       
   171 
       
   172 ---++ ObjectMethod loginTemplateName () -> templateFile
       
   173 
       
   174 allows UserMappings to come with customised login screens - that should preffereably only over-ride the UI function
       
   175 
       
   176 =cut
       
   177 
       
   178 sub loginTemplateName {
       
   179     my $this = shift;
       
   180 
       
   181     #use login.sudo.tmpl for admin logins
       
   182     return $this->{basemapping}->loginTemplateName() if ($this->{session}->inContext('sudo_login'));
       
   183     return $this->{mapping}->loginTemplateName() || 'login';
       
   184 }
       
   185 
       
   186 # ($cUID, $login, $wikiname, $noFallBack) -> usermapping object
       
   187 sub _getMapping {
       
   188 	my ($this, $cUID, $login, $wikiname, $noFallBack) = @_;
       
   189 
       
   190 	$cUID = '' unless defined $cUID;
       
   191 	$cUID = forceCUID($cUID);
       
   192 	$login = '' unless defined $login;
       
   193 	$wikiname = '' unless defined $wikiname;
       
   194 	
       
   195 	$wikiname =~ s/^$TWiki::cfg{UsersWebName}\.//;
       
   196 	
       
   197 	$noFallBack = 0 unless (defined($noFallBack));
       
   198 
       
   199 	return $this->{basemapping}
       
   200       if ($this->{basemapping}->handlesUser($cUID, $login, $wikiname));
       
   201 	return $this->{mapping}
       
   202       if ($this->{mapping}->handlesUser($cUID, $login, $wikiname));
       
   203 	#THIS MUS BE basemapping, otherwise the mapper implementations need to
       
   204 	#implement fallback / defaults for users they don't know about (which is
       
   205 	#BaseMappings job)
       
   206 	return $this->{basemapping} unless ($noFallBack);
       
   207 	return undef;
       
   208 }
       
   209 
       
   210 =pod
       
   211 
       
   212 ---++ ObjectMethod supportsRegistration () -> boolean
       
   213 
       
   214 #return 1 if the  main UserMapper supports registration (ie can create new users)
       
   215 
       
   216 =cut
       
   217 
       
   218 sub supportsRegistration {
       
   219     my( $this ) = @_;
       
   220     return $this->{mapping}->supportsRegistration();
       
   221 }
       
   222 
       
   223 =pod
       
   224 
       
   225 ---++ ObjectMethod initialiseUser ($login) -> cUID
       
   226 
       
   227 
       
   228 
       
   229 =cut
       
   230 
       
   231 sub initialiseUser {
       
   232     my( $this, $login ) = @_;
       
   233 
       
   234     # For compatibility with older ways of building login managers,
       
   235     # plugins can provide an alternate login name.
       
   236     my $plogin = $this->{session}->{plugins}->load( $TWiki::cfg{DisableAllPlugins} );
       
   237     $login = $plogin if $plogin;
       
   238 
       
   239     my $cUID;
       
   240     if (defined($login) && ($login ne '')) {
       
   241         $cUID = $this->getCanonicalUserID( $login );
       
   242         
       
   243         #see BugsItem4771 - it seems that authenticated, but unmapped users have rights too
       
   244         if (!defined($cUID)) {
       
   245             $cUID = forceCUID($login);
       
   246             $this->{getLoginName}->{$cUID} = $login;
       
   247             $this->{getWikiName}->{$cUID} = $login;
       
   248             #needs to be WikiName safe
       
   249             $this->{getWikiName}->{$cUID} =~ s/$TWiki::cfg{NameFilter}//go;
       
   250             $this->{getWikiName}->{$cUID} =~ s/\.//go;
       
   251             #I wonder what the risk is..
       
   252             $this->{getCanonicalUserID}->{$login} = $cUID;
       
   253             $this->{getCanonicalUserID}->{$this->{getWikiName}->{$cUID}} = $cUID;
       
   254         }
       
   255     }
       
   256 
       
   257     # if we get here without a login id, we are a guest
       
   258     $cUID = $cUID || $this->getCanonicalUserID( $TWiki::cfg{DefaultUserLogin} );
       
   259 	return $cUID;
       
   260 }
       
   261 
       
   262 # global used by test harness to give predictable results
       
   263 use vars qw( $password );
       
   264 
       
   265 =pod
       
   266 
       
   267 ---++ randomPassword()
       
   268 Static function that returns a random password
       
   269 
       
   270 =cut
       
   271 
       
   272 sub randomPassword {
       
   273     return $password || int( rand(9999999999) );
       
   274 }
       
   275 
       
   276 =pod
       
   277 
       
   278 ---++ ObjectMethod addUser($login, $wikiname, $password, $emails) -> $cUID
       
   279 
       
   280    * =$login= - user login name. If =undef=, =$wikiname= will be used as the login name.
       
   281    * =$wikiname= - user wikiname. If =undef=, the user mapper will be asked
       
   282      to provide it.
       
   283    * =$password= - password. If undef, a password will be generated.
       
   284 
       
   285 Add a new TWiki user identity, returning the canonical user id for the new
       
   286 user. Used ONLY for user registration.
       
   287 
       
   288 The user is added to the password system (if there is one, and if it accepts
       
   289 changes). If the user already exists in the password system, then the password
       
   290 is checked and an exception thrown if it doesn't match. If there is no
       
   291 existing user, and no password is given, a random password is generated.
       
   292 
       
   293 $login can be undef; $wikiname must always have a value.
       
   294 
       
   295 The return value is the canonical user id that is used
       
   296 by TWiki to identify the user.
       
   297 
       
   298 =cut
       
   299 
       
   300 sub addUser {
       
   301     my( $this, $login, $wikiname, $password, $emails) = @_;
       
   302     my $removeOnFail = 0;
       
   303 
       
   304 	$this->ASSERT_IS_USER_LOGIN_ID($login) if DEBUG;
       
   305     ASSERT($login || $wikiname) if DEBUG; # must have one
       
   306 
       
   307     # create a new user and get the canonical user ID from the user mapping
       
   308     # manager.
       
   309     my $cUID = $this->{mapping}->addUser(
       
   310         $login, $wikiname, $password, $emails);
       
   311 	$this->ASSERT_IS_CANONICAL_USER_ID($cUID) if DEBUG;
       
   312     
       
   313     #update the cached values
       
   314     #see BugsItem4771 - it seems that authenticated, but unmapped users have rights too
       
   315     $this->{getLoginName}->{$cUID} = $login;
       
   316     $this->{getWikiName}->{$cUID} = $wikiname;
       
   317     #I wonder what the risk is..
       
   318     $this->{getCanonicalUserID}->{$login} = $cUID;
       
   319     $this->{getCanonicalUserID}->{$wikiname} = $cUID;
       
   320 
       
   321     return $cUID;
       
   322 
       
   323 }
       
   324 
       
   325 =pod
       
   326 
       
   327 ---++ StaticMethod forceCUID( $cUID ) -> $cUID
       
   328 
       
   329 This function ensures that any cUID's are able to be used for rcs, and other internals
       
   330 not capable of coping with user identifications that contain more than 7 bit ascii.
       
   331 
       
   332 repeated calls must result in the same result (sorry, can't spell the word for it)so the '_' must not be re-encoded
       
   333 
       
   334 Please, call this function in any custom Usermapper to simplifyyour mapping code.
       
   335 
       
   336 =cut
       
   337 
       
   338 sub forceCUID {
       
   339 	my $cUID = shift;
       
   340 	return unless (defined($cUID));
       
   341 	
       
   342 	use bytes;
       
   343     # use bytes to ignore character encoding
       
   344     $cUID =~ s/([^a-zA-Z0-9_])/'_'.sprintf('%02d', ord($1))/ge;
       
   345     no bytes;
       
   346 	return $cUID;
       
   347 }
       
   348 
       
   349 =pod
       
   350 
       
   351 ---++ ObjectMethod getCanonicalUserID( $login ) -> $user
       
   352 
       
   353 Works out the unique TWiki identifier for the user who logs in with the
       
   354 given login. The canonical user ID is an alphanumeric string that is unique
       
   355 to the login name, and can be mapped back to a login name and the
       
   356 corresponding wiki name using the methods of this class.
       
   357 
       
   358 returns undef if the user does not exist.
       
   359 
       
   360 =cut
       
   361 
       
   362 sub getCanonicalUserID {
       
   363     my( $this, $login ) = @_;
       
   364 	$this->ASSERT_IS_USER_LOGIN_ID($login) if DEBUG;
       
   365     
       
   366    	return $this->{getCanonicalUserID}->{$login} if (defined($this->{getCanonicalUserID}->{$login}));
       
   367 	
       
   368     # if its one of the known cUID's, then just return itself
       
   369     # SMELL: CC added $login as the second param, because otherwise the
       
   370     # usernames defined by the base mapper get intercepted by the
       
   371     # TWikiuserMapping, which *also* defines the guest user.
       
   372     # My understanding is that the base user mapper users must always
       
   373     # override those defined in the TWikiUserMapping, even though that makes
       
   374     # it impossible to retain 100% compatibility (guest user edits will get
       
   375     # saved as edits by BaseMapping_666).
       
   376     my $testMapping = $this->_getMapping($login, $login, undef, 1);
       
   377 #print STDERR "Users::getCanonicalUserID($login) $testMapping=>".$testMapping->getLoginName($login)."\n";
       
   378 
       
   379     my $cUID;
       
   380     # passed a valid cUID?
       
   381     if ($testMapping && $testMapping->getLoginName($login)){
       
   382         $cUID = $login;
       
   383     } else {
       
   384         my $mapping = $this->_getMapping(undef, $login, $login);
       
   385         if ($mapping) {
       
   386             $cUID = $mapping->getCanonicalUserID( $login );
       
   387             
       
   388             unless ($cUID) {	#must be a wikiname
       
   389                 my( $web, $topic ) = $this->{session}->normalizeWebTopicName( '', $login );
       
   390                 my $found = $this->findUserByWikiName($topic);
       
   391                 $cUID = $found->[0] if ($found && scalar(@$found));
       
   392     #			print STDERR "\nfindUserByWikiName";	
       
   393             }
       
   394         }
       
   395     }
       
   396 #	print STDERR "\nTWiki::Users::getCanonicalUserID($login) => ".($cUID || '(NO cUID)');	
       
   397 
       
   398     $this->{getCanonicalUserID}->{$login} = forceCUID($cUID) if ($cUID);
       
   399 
       
   400     return $this->{getCanonicalUserID}->{$login};
       
   401 }
       
   402 
       
   403 =pod
       
   404 
       
   405 ---++ ObjectMethod findUserByWikiName( $wn ) -> \@users
       
   406    * =$wn= - wikiname to look up
       
   407 Return a list of canonical user names for the users that have this wikiname.
       
   408 Since a single wikiname might be used by multiple login ids, we need a list.
       
   409 
       
   410 If $wn is the name of a group, the group will *not* be expanded.
       
   411 
       
   412 =cut
       
   413 
       
   414 sub findUserByWikiName {
       
   415     my( $this, $wn ) = @_;
       
   416     ASSERT($wn) if DEBUG;
       
   417     # Trim the (pointless) web, if present
       
   418     $wn =~ s#.*[\./]##;
       
   419     return $this->_getMapping(undef, undef, $wn)->findUserByWikiName( $wn );
       
   420 }
       
   421 
       
   422 =pod
       
   423 
       
   424 ---++ ObjectMethod findUserByEmail( $email ) -> \@users
       
   425    * =$email= - email address to look up
       
   426 Return a list of canonical user names for the users that have this email
       
   427 registered with the user mapping managers.
       
   428 
       
   429 =cut
       
   430 
       
   431 sub findUserByEmail {
       
   432     my( $this, $email ) = @_;
       
   433     ASSERT($email) if DEBUG;
       
   434     
       
   435     my $users = $this->{mapping}->findUserByEmail( $email );
       
   436     push @{$users}, @{$this->{basemapping}->findUserByEmail( $email )};
       
   437 
       
   438     return $users;
       
   439 }
       
   440 
       
   441 =pod
       
   442 
       
   443 ---++ ObjectMethod getEmails($user) -> @emailAddress
       
   444 
       
   445 If this is a user, return their email addresses. If it is a group,
       
   446 return the addresses of everyone in the group.
       
   447 
       
   448 The password manager and user mapping manager are both consulted for emails
       
   449 for each user (where they are actually found is implementation defined).
       
   450 
       
   451 Duplicates are removed from the list.
       
   452 
       
   453 =cut
       
   454 
       
   455 sub getEmails {
       
   456     my( $this, $user ) = @_;
       
   457 	$user = $this->getCanonicalUserID($user);
       
   458 	#$this->ASSERT_IS_CANONICAL_USER_ID($user) if DEBUG;
       
   459 	
       
   460 	return () unless ($user);
       
   461     if ($this->{mapping}->isGroup($user)) {
       
   462         return $this->{mapping}->getEmails($user);
       
   463     }
       
   464     #will fall back to basemapper if its a basemapper group
       
   465     return $this->_getMapping($user)->getEmails( $user );
       
   466 }
       
   467 
       
   468 =pod
       
   469 
       
   470 ---++ ObjectMethod setEmails($user, @emails)
       
   471 
       
   472 Set the email address(es) for the given user.
       
   473 The password manager is tried first, and if it doesn't want to know the
       
   474 user mapping manager is tried.
       
   475 
       
   476 =cut
       
   477 
       
   478 sub setEmails {
       
   479     my $this = shift;
       
   480     my $cUID = shift;
       
   481     my @emails = @_;
       
   482 	$this->ASSERT_IS_CANONICAL_USER_ID($cUID) if DEBUG;
       
   483     return $this->_getMapping($cUID)->setEmails( $cUID, @emails );
       
   484 }
       
   485 
       
   486 =pod
       
   487 
       
   488 ---++ ObjectMethod isAdmin( $cUID ) -> $boolean
       
   489 
       
   490 True if the user is an admin
       
   491    * is $TWiki::cfg{SuperAdminGroup}
       
   492    * is a member of the $TWiki::cfg{SuperAdminGroup}
       
   493 
       
   494 =cut
       
   495 
       
   496 sub isAdmin {
       
   497     my( $this, $cUID ) = @_;
       
   498     return $this->{isAdmin}->{$cUID} if (defined($this->{isAdmin}->{$cUID}));
       
   499     
       
   500 	$cUID = $this->getCanonicalUserID($cUID);
       
   501     return unless (defined($cUID));
       
   502 
       
   503     my $mapping = $this->_getMapping($cUID);
       
   504     my $otherMapping = ($mapping eq $this->{basemapping}) ? $this->{mapping} : $this->{basemapping};
       
   505 
       
   506     if ($mapping eq $otherMapping) {
       
   507         return $mapping->isAdmin( $cUID );
       
   508     }
       
   509 	
       
   510     $this->{isAdmin}->{$cUID} = ($mapping->isAdmin( $cUID ) || $otherMapping->isAdmin( $cUID ));
       
   511     return $this->{isAdmin}->{$cUID};
       
   512 }
       
   513 
       
   514 =pod
       
   515 
       
   516 ---++ ObjectMethod isInList( $user, $list ) -> $boolean
       
   517 
       
   518 Return true if $user is in a list of user *wikinames* and group ids.
       
   519 
       
   520 $list is a comma-separated wikiname and group list. The list may contain the
       
   521 conventional web specifiers (which are ignored).
       
   522 
       
   523 =cut
       
   524 
       
   525 sub isInList {
       
   526     my( $this, $user, $userlist ) = @_;
       
   527 	$this->ASSERT_IS_CANONICAL_USER_ID($user) if DEBUG;
       
   528 	
       
   529     return 0 unless $userlist;
       
   530 
       
   531     # comma delimited list of users or groups
       
   532     # i.e.: "%USERSWEB%.UserA, UserB, Main.UserC  # something else"
       
   533     $userlist =~ s/(<[^>]*>)//go;     # Remove HTML tags
       
   534 
       
   535     my $wn = getWikiName( $this, $user );
       
   536     my $umm = $this->_getMapping($user);
       
   537 
       
   538     foreach my $ident ( split( /[\,\s]+/, $userlist )) {
       
   539         $ident =~ s/^.*\.//;       # Dump the web specifier
       
   540         next unless $ident;
       
   541         return 1 if( $ident eq $wn );
       
   542         if( $this->isGroup( $ident )) {
       
   543             return 1 if( $this->isInGroup( $user, $ident ));
       
   544         }
       
   545     }
       
   546     return 0;
       
   547 }
       
   548 
       
   549 =pod
       
   550 
       
   551 ---++ ObjectMethod getLoginName($cUID) -> $string
       
   552 
       
   553 Get the login name of a user.
       
   554 
       
   555 =cut
       
   556 
       
   557 sub getLoginName {
       
   558     my( $this, $cUID) = @_;
       
   559 #	$this->ASSERT_IS_CANONICAL_USER_ID($cUID) if DEBUG;
       
   560 	return $this->{getLoginName}->{$cUID} if (defined($this->{getLoginName}->{$cUID}));
       
   561 
       
   562 
       
   563 	$cUID = $this->getCanonicalUserID($cUID);
       
   564     return 'unknown' unless (defined($cUID));
       
   565 	
       
   566 	my $login = $this->_getMapping($cUID)-> getLoginName($cUID) if ($cUID && $this->_getMapping($cUID));
       
   567     $this->{getLoginName}->{$cUID} = $login || 'unknown';
       
   568     $this->{getCanonicalUserID}->{$this->{getLoginName}->{$cUID}} = $cUID;
       
   569 
       
   570     
       
   571     return $this->{getLoginName}->{$cUID};
       
   572 }
       
   573 
       
   574 =pod
       
   575 
       
   576 ---++ ObjectMethod getWikiName($cUID) -> $wikiName
       
   577 Get the wikiname to display for a canonical user identifier.
       
   578 
       
   579 can return undef if the user is not in the mapping system
       
   580 (or the special case from initialiseUser)
       
   581 
       
   582 =cut
       
   583 
       
   584 sub getWikiName {
       
   585     my ($this, $cUID ) = @_;
       
   586     return 'UnknownUser' unless defined($cUID);
       
   587 	return $this->{getWikiName}->{$cUID} if (defined($this->{getWikiName}->{$cUID}));
       
   588 
       
   589 	my $legacycUID = $this->getCanonicalUserID($cUID);
       
   590 
       
   591     my $wikiname;
       
   592     $wikiname = $this->_getMapping($legacycUID)->getWikiName($legacycUID) if (defined($legacycUID) && ($legacycUID ne '') && $this->_getMapping($legacycUID));
       
   593     
       
   594 	if (!defined($wikiname)) {
       
   595         if (defined($TWiki::cfg{RenderLoggedInButUnknownUsers} ) &&
       
   596             ($TWiki::cfg{RenderLoggedInButUnknownUsers}) ) {
       
   597             $wikiname = "UnknownUser (<nop>$cUID)";
       
   598         } else {
       
   599             $wikiname = "$cUID";
       
   600         }
       
   601     }
       
   602     
       
   603     my ( $web, $topic ) = $this->{session}->normalizeWebTopicName( $TWiki::cfg{UsersWebName}, $wikiname );
       
   604     $this->{getWikiName}->{$cUID} = $topic;
       
   605     $this->{getCanonicalUserID}->{$topic} = $cUID;
       
   606     
       
   607     return $this->{getWikiName}->{$cUID};
       
   608 }
       
   609 
       
   610 =pod
       
   611 
       
   612 ---++ ObjectMethod webDotWikiName($user) -> $webDotWiki
       
   613 
       
   614 Return the fully qualified wikiname of the user
       
   615 
       
   616 =cut
       
   617 
       
   618 sub webDotWikiName {
       
   619     my( $this, $user ) = @_;
       
   620  	#$this->ASSERT_IS_CANONICAL_USER_ID($user) if DEBUG;
       
   621     return $TWiki::cfg{UsersWebName}.'.'
       
   622       .getWikiName( $this, $user );
       
   623 }
       
   624 
       
   625 =pod
       
   626 
       
   627 ---++ ObjectMethod userExists($cUID) -> $boolean
       
   628 
       
   629 Determine if the user already exists or not. A user exists if they are
       
   630 known to to the user mapper.
       
   631 
       
   632 =cut
       
   633 
       
   634 sub userExists {
       
   635     my( $this, $cUID ) = @_;
       
   636 	$this->ASSERT_IS_CANONICAL_USER_ID($cUID) if DEBUG;
       
   637 
       
   638     return $this->_getMapping($cUID)->userExists( $cUID );
       
   639 }
       
   640 
       
   641 =pod
       
   642 
       
   643 ---++ ObjectMethod eachUser() -> $iterator
       
   644 
       
   645 Get an iterator over the list of all the registered users *not* including
       
   646 groups.
       
   647 
       
   648 list of canonical_ids ???
       
   649 
       
   650 Use it as follows:
       
   651 <verbatim>
       
   652     my $iterator = $umm->eachUser();
       
   653     while ($iterator->hasNext()) {
       
   654         my $user = $iterator->next();
       
   655         ...
       
   656     }
       
   657 </verbatim>
       
   658 
       
   659 =cut
       
   660 
       
   661 sub eachUser {
       
   662 	my $this = shift;
       
   663 	my @list = ($this->{basemapping}->eachUser( @_ ), $this->{mapping}->eachUser( @_ ));
       
   664     return new TWiki::AggregateIterator(\@list, 1);
       
   665 
       
   666     return shift->{mapping}->eachUser( @_ );
       
   667 }
       
   668 
       
   669 =pod
       
   670 
       
   671 ---++ ObjectMethod eachGroup() -> $iterator
       
   672 
       
   673 Get an iterator over the list of all the groups.
       
   674 
       
   675 =cut
       
   676 
       
   677 sub eachGroup {
       
   678 	my $this = shift;
       
   679 	my @list = ($this->{basemapping}->eachGroup( @_ ), $this->{mapping}->eachGroup( @_ ));
       
   680     return new TWiki::AggregateIterator(\@list, 1);
       
   681 }
       
   682 
       
   683 =pod
       
   684 
       
   685 ---++ ObjectMethod eachGroupMember($group) -> $iterator
       
   686 
       
   687 Return a iterator of user ids that are members of this group.
       
   688 Should only be called on groups.
       
   689 
       
   690 Note that groups may be defined recursively, so a group may contain other
       
   691 groups. This method should *only* return users i.e. all contained groups
       
   692 should be fully expanded.
       
   693 
       
   694 =cut
       
   695 
       
   696 sub eachGroupMember {
       
   697 	my $this = shift;
       
   698 	my @list = ($this->{basemapping}->eachGroupMember( @_ ), $this->{mapping}->eachGroupMember( @_ ));
       
   699     return new TWiki::AggregateIterator(\@list, 1);
       
   700 }
       
   701 
       
   702 =pod
       
   703 
       
   704 ---++ ObjectMethod isGroup($user) -> boolean
       
   705 
       
   706 Establish if a user refers to a group or not.
       
   707 
       
   708 The default implementation is to check if the wikiname of the user ends with
       
   709 'Group'. Subclasses may override this behaviour to provide alternative
       
   710 interpretations. The $TWiki::cfg{SuperAdminGroup} is recognized as a
       
   711 group no matter what it's name is.
       
   712 
       
   713 QUESTION: is the $user parameter here a string, or a canonical_id??
       
   714 
       
   715 =cut
       
   716 
       
   717 sub isGroup {
       
   718     my $this = shift;
       
   719     return ($this->{basemapping}->isGroup( @_ )) || ($this->{mapping}->isGroup( @_ ));
       
   720 }
       
   721 
       
   722 =pod
       
   723 
       
   724 ---++ ObjectMethod isInGroup( $user, $group ) -> $boolean
       
   725 
       
   726 Test if user is in the given group.
       
   727 
       
   728 =cut
       
   729 
       
   730 sub isInGroup {
       
   731 	my ($this, $cUID, $group) = @_;
       
   732     return unless (defined($cUID));
       
   733     
       
   734     my $mapping = $this->_getMapping($cUID);
       
   735     my $otherMapping = ($mapping eq $this->{basemapping}) ? $this->{mapping} : $this->{basemapping};
       
   736     if ($mapping eq $otherMapping){
       
   737         return $mapping->isInGroup( $cUID, $group );
       
   738     }
       
   739 		
       
   740     return ($mapping->isInGroup( $cUID, $group ) || $otherMapping->isInGroup( $cUID, $group ));
       
   741 
       
   742 	
       
   743 #	return $this->_getMapping($cUID)->isInGroup( $cUID, $group );
       
   744 }
       
   745 
       
   746 =pod
       
   747 
       
   748 ---++ ObjectMethod eachMembership($cUID) -> $iterator
       
   749 
       
   750 Return an iterator over the groups that $cUID
       
   751 is a member of.
       
   752 
       
   753 =cut
       
   754 
       
   755 sub eachMembership {
       
   756 	my ($this, $cUID) = @_;
       
   757 
       
   758     my $mapping = $this->_getMapping($cUID);
       
   759     my $wikiname = $mapping->getWikiName($cUID);
       
   760     #stop if the user has no wikiname (generally means BugsItem4771)
       
   761     unless(defined($wikiname)) {
       
   762         require TWiki::ListIterator;
       
   763         return new TWiki::ListIterator(\()) ;
       
   764     }
       
   765     
       
   766     my $otherMapping = ($mapping eq $this->{basemapping}) ? $this->{mapping} : $this->{basemapping};
       
   767     if ($mapping eq $otherMapping) {
       
   768         # only using BaseMapping.
       
   769         return $mapping->eachMembership( $cUID );
       
   770     }
       
   771 
       
   772 	my @list = ($mapping->eachMembership( $cUID ), $otherMapping->eachMembership( $cUID ));
       
   773     return new TWiki::AggregateIterator(\@list, 1);
       
   774 }
       
   775 
       
   776 =pod
       
   777 
       
   778 ---++ ObjectMethod checkPassword( $userName, $passwordU ) -> $boolean
       
   779 
       
   780 Finds if the password is valid for the given user.
       
   781 
       
   782 Returns 1 on success, undef on failure.
       
   783 
       
   784 TODO: add special check for BaseMapping admin user's login, and if its there (and we're in sudo_context?) use that..
       
   785 
       
   786 =cut
       
   787 
       
   788 sub checkPassword {
       
   789     my( $this, $userName, $pw ) = @_;
       
   790 	$this->ASSERT_IS_USER_LOGIN_ID($userName) if DEBUG;
       
   791     return $this->_getMapping(undef, $userName)->checkPassword($userName, $pw);
       
   792 }
       
   793 
       
   794 =pod
       
   795 
       
   796 ---++ ObjectMethod setPassword( $user, $newPassU, $oldPassU ) -> $boolean
       
   797 
       
   798 If the $oldPassU matches matches the user's password, then it will
       
   799 replace it with $newPassU.
       
   800 
       
   801 If $oldPassU is not correct and not 1, will return 0.
       
   802 
       
   803 If $oldPassU is 1, will force the change irrespective of
       
   804 the existing password, adding the user if necessary.
       
   805 
       
   806 Otherwise returns 1 on success, undef on failure.
       
   807 
       
   808 =cut
       
   809 
       
   810 sub setPassword {
       
   811     my( $this, $user, $newPassU, $oldPassU ) = @_;
       
   812 	$this->ASSERT_IS_CANONICAL_USER_ID($user) if DEBUG;
       
   813     return $this->_getMapping($user)->setPassword($this->getLoginName( $user ), $newPassU, $oldPassU);
       
   814 }
       
   815 
       
   816 =pod
       
   817 
       
   818 ---++ ObjectMethod passwordError( ) -> $string
       
   819 
       
   820 returns a string indicating the error that happened in the password handlers
       
   821 TODO: these delayed error's should be replaced with Exceptions.
       
   822 
       
   823 returns undef if no error
       
   824 
       
   825 =cut
       
   826 
       
   827 sub passwordError {
       
   828     my( $this ) = @_;
       
   829     return $this->_getMapping()->passwordError();
       
   830 }
       
   831 
       
   832 =pod
       
   833 
       
   834 ---++ ObjectMethod removeUser( $user ) -> $boolean
       
   835 
       
   836 Delete the users entry. Removes the user from the password
       
   837 manager and user mapping manager. Does *not* remove their personal
       
   838 topics, which may still be linked.
       
   839 
       
   840 =cut
       
   841 
       
   842 sub removeUser {
       
   843     my( $this, $user ) = @_;
       
   844 	$this->ASSERT_IS_CANONICAL_USER_ID($user) if DEBUG;
       
   845     $this->_getMapping($user)->removeUser( $user);
       
   846 }
       
   847 
       
   848 =pod
       
   849 
       
   850 ---++ ObjectMethod ASSERT_IS_CANONICAL_USER_ID( $user_id ) -> $boolean
       
   851 
       
   852 used for debugging to ensure we are actually passing a canonical_id
       
   853 
       
   854 These ASSERTS have been disabled, as they have been made dangerous and misleading
       
   855 due to the legacy cUID code
       
   856 
       
   857 =cut
       
   858 
       
   859 sub ASSERT_IS_CANONICAL_USER_ID {
       
   860     my( $this, $user_id ) = @_;
       
   861 
       
   862     #$this->_getMapping($user_id)->ASSERT_IS_CANONICAL_USER_ID($user_id) if ($this->_getMapping($user_id));
       
   863 }
       
   864 
       
   865 =pod
       
   866 
       
   867 ---++ ObjectMethod ASSERT_IS_USER_LOGIN_ID( $user_login ) -> $boolean
       
   868 
       
   869 used for debugging to ensure we are actually passing a user login
       
   870 
       
   871 These ASSERTS have been disabled, as they have been made dangerous and misleading
       
   872 due to the legacy cUID code
       
   873 
       
   874 =cut
       
   875 
       
   876 sub ASSERT_IS_USER_LOGIN_ID {
       
   877     my( $this, $user_login ) = @_;
       
   878     #$this->_getMapping(undef, $user_login)->ASSERT_IS_USER_LOGIN_ID($user_login) if ($this->_getMapping(undef, $user_login));
       
   879 }
       
   880 
       
   881 
       
   882 =pod
       
   883 
       
   884 ---++ ObjectMethod ASSERT_IS_USER_DISPLAY_NAME( $user_display ) -> $boolean
       
   885 
       
   886 used for debugging to ensure we are actually passing a user display_name (commonly a WikiWord Name)
       
   887 
       
   888 These ASSERTS have been disabled, as they have been made dangerous and misleading
       
   889 due to the legacy cUID code
       
   890 
       
   891 =cut
       
   892 
       
   893 sub ASSERT_IS_USER_DISPLAY_NAME {
       
   894     my( $this, $user_display ) = @_;
       
   895     #$this->_getMapping(undef, undef, $user_display)->ASSERT_IS_USER_DISPLAY_NAME($user_display) if ($this->_getMapping(undef, undef, $user_display));
       
   896 }
       
   897 
       
   898 1;