lib/TWiki/Users/TWikiUserMapping.pm
changeset 0 414e01d06fd5
child 1 e2915a7cbdfa
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/lib/TWiki/Users/TWikiUserMapping.pm	Sat Jan 26 15:50:53 2008 +0100
     1.3 @@ -0,0 +1,1122 @@
     1.4 +# Module of TWiki Enterprise Collaboration Platform, http://TWiki.org/
     1.5 +#
     1.6 +# Copyright (C) 2007 Sven Dowideit, SvenDowideit@distributedINFORMATION.com
     1.7 +# and TWiki Contributors. All Rights Reserved. TWiki Contributors
     1.8 +# are listed in the AUTHORS file in the root of this distribution.
     1.9 +# NOTE: Please extend that file, not this notice.
    1.10 +#
    1.11 +# This program is free software; you can redistribute it and/or
    1.12 +# modify it under the terms of the GNU General Public License
    1.13 +# as published by the Free Software Foundation; either version 2
    1.14 +# of the License, or (at your option) any later version. For
    1.15 +# more details read LICENSE in the root of this distribution.
    1.16 +#
    1.17 +# This program is distributed in the hope that it will be useful,
    1.18 +# but WITHOUT ANY WARRANTY; without even the implied warranty of
    1.19 +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
    1.20 +#
    1.21 +# As per the GPL, removal of this notice is prohibited.
    1.22 +
    1.23 +=begin twiki
    1.24 +
    1.25 +---+ package TWiki::Users::TWikiUserMapping
    1.26 +
    1.27 +The User mapping is the process by which TWiki maps from a username (a login name)
    1.28 +to a wikiname and back. It is also where groups are defined.
    1.29 +
    1.30 +By default TWiki maintains user topics and group topics in the %MAINWEB% that
    1.31 +define users and group. These topics are
    1.32 +   * !TWikiUsers - stores a mapping from usernames to TWiki names
    1.33 +   * !WikiName - for each user, stores info about the user
    1.34 +   * !GroupNameGroup - for each group, a topic ending with "Group" stores a list of users who are part of that group.
    1.35 +
    1.36 +Many sites will want to override this behaviour, for example to get users and groups from a corporate database.
    1.37 +
    1.38 +This class implements the basic TWiki behaviour using topics to store users,
    1.39 +but is also designed to be subclassed so that other services can be used.
    1.40 +
    1.41 +Subclasses should be named 'XxxxUserMapping' so that configure can find them.
    1.42 + 
    1.43 +=cut
    1.44 +
    1.45 +package TWiki::Users::TWikiUserMapping;
    1.46 +use base 'TWiki::UserMapping';
    1.47 +
    1.48 +use strict;
    1.49 +use Assert;
    1.50 +use Error qw( :try );
    1.51 +
    1.52 +#use Monitor;
    1.53 +#Monitor::MonitorMethod('TWiki::Users::TWikiUserMapping');
    1.54 +
    1.55 +=begin twiki
    1.56 +
    1.57 +---++ ClassMethod new ($session, $impl)
    1.58 +
    1.59 +Constructs a new user mapping handler of this type, referring to $session
    1.60 +for any required TWiki services.
    1.61 +
    1.62 +=cut
    1.63 +
    1.64 +sub new {
    1.65 +    my( $class, $session ) = @_;
    1.66 +
    1.67 +    # The null mapping name is reserved for TWiki for backward-compatibility
    1.68 +    my $this = $class->SUPER::new( $session, '' );
    1.69 +
    1.70 +    my $implPasswordManager = $TWiki::cfg{PasswordManager};
    1.71 +    $implPasswordManager = 'TWiki::Users::Password'
    1.72 +      if( $implPasswordManager eq 'none' );
    1.73 +    eval "require $implPasswordManager";
    1.74 +    die $@ if $@;
    1.75 +    $this->{passwords} = $implPasswordManager->new( $session );
    1.76 +    
    1.77 +    #if password manager says sorry, we're read only today
    1.78 +    #'none' is a special case, as it means we're not actually using the password manager for
    1.79 +    # registration.
    1.80 +    if ($this->{passwords}->readOnly() && ($TWiki::cfg{PasswordManager} ne 'none')) {
    1.81 +        $session->writeWarning( 'TWikiUserMapping has TURNED OFF EnableNewUserRegistration, because the password file is read only.' );
    1.82 +        $TWiki::cfg{Register}{EnableNewUserRegistration} = 0;
    1.83 +    }
    1.84 +
    1.85 +	#SMELL: and this is a second user object
    1.86 +	#TODO: combine with the one in TWiki::Users
    1.87 +    #$this->{U2L} = {};
    1.88 +    $this->{L2U} = {};
    1.89 +    $this->{U2W} = {};
    1.90 +    $this->{W2U} = {};
    1.91 +    $this->{eachGroupMember} = {};
    1.92 +
    1.93 +    return $this;
    1.94 +}
    1.95 +
    1.96 +=begin twiki
    1.97 +
    1.98 +---++ ObjectMethod finish()
    1.99 +Break circular references.
   1.100 +
   1.101 +=cut
   1.102 +
   1.103 +# Note to developers; please undef *all* fields in the object explicitly,
   1.104 +# whether they are references or not. That way this method is "golden
   1.105 +# documentation" of the live fields in the object.
   1.106 +sub finish {
   1.107 +    my $this = shift;
   1.108 +
   1.109 +    $this->{passwords}->finish() if $this->{passwords};
   1.110 +    undef $this->{L2U};
   1.111 +    undef $this->{U2W};
   1.112 +    undef $this->{W2U};
   1.113 +    undef $this->{passwords};
   1.114 +    undef $this->{eachGroupMember};
   1.115 +    $this->SUPER::finish();
   1.116 +}
   1.117 +
   1.118 +=begin twiki
   1.119 +
   1.120 +---++ ObjectMethod supportsRegistration () -> false
   1.121 +return 1 if the UserMapper supports registration (ie can create new users)
   1.122 +
   1.123 +=cut
   1.124 +
   1.125 +sub supportsRegistration {
   1.126 +    return 1;
   1.127 +}
   1.128 +
   1.129 +=begin twiki
   1.130 +
   1.131 +---++ ObjectMethod handlesUser ( $cUID, $login, $wikiname) -> $boolean
   1.132 +
   1.133 +Called by the TWiki::Users object to determine which loaded mapping
   1.134 +to use for a given user (must be fast).
   1.135 +
   1.136 +=cut
   1.137 +
   1.138 +sub handlesUser {
   1.139 +	my ($this, $cUID, $login, $wikiname) = @_;
   1.140 +	
   1.141 +    if (defined $cUID && !length($this->{mapping_id})) {
   1.142 +        # TWikiUserMapping is special - for backwards compatibility, it assumes
   1.143 +        # responsibility for _all_ non BaseMapping users
   1.144 +        # if you're needing to mix the TWikiuserMapping with others, 
   1.145 +        # define $this->{mapping_id} = 'TWikiUserMapping_';
   1.146 +        return 1;
   1.147 +    } else {
   1.148 +        # Used when (if) TWikiUserMapping is subclassed
   1.149 +        return 1 if ( defined $cUID && $cUID =~ /^($this->{mapping_id})/ );
   1.150 +    }
   1.151 +	return 1 if ($login && $this->getLoginName( $login ));
   1.152 +#	return 1 if ($wikiname && $this->findUserByWikiName( $wikiname ));
   1.153 +	return 0;
   1.154 +}
   1.155 +
   1.156 +
   1.157 +=begin twiki
   1.158 +
   1.159 +---++ ObjectMethod getCanonicalUserID ($login, $dontcheck) -> cUID
   1.160 +
   1.161 +Convert a login name to the corresponding canonical user name. The
   1.162 +canonical name can be any string of 7-bit alphanumeric and underscore
   1.163 +characters, and must correspond 1:1 to the login name.
   1.164 +(undef on failure)
   1.165 +
   1.166 +(if dontcheck is true, return a cUID for a nonexistant user too - used for registration)
   1.167 +
   1.168 +=cut
   1.169 +
   1.170 +sub getCanonicalUserID {
   1.171 +    my( $this, $login, $dontcheck ) = @_;
   1.172 +#    print STDERR "\nTWikiUserMapping::getCanonicalUserID($login, ".($dontcheck||'undef').")";
   1.173 +
   1.174 +    unless ($dontcheck) {
   1.175 +        return unless (_userReallyExists($this, $login));
   1.176 +    }
   1.177 +
   1.178 +    $login = TWiki::Users::forceCUID($login);
   1.179 +    $login = $this->{mapping_id}.$login;
   1.180 +#print STDERR " OK ($login)";
   1.181 +    return $login;
   1.182 +}
   1.183 +
   1.184 +=begin twiki
   1.185 +
   1.186 +---++ ObjectMethod getLoginName ($cUID) -> login
   1.187 +
   1.188 +converts an internal cUID to that user's login
   1.189 +(undef on failure)
   1.190 +
   1.191 +=cut
   1.192 +
   1.193 +sub getLoginName {
   1.194 +    my( $this, $user ) = @_;
   1.195 +    ASSERT($user) if DEBUG;
   1.196 +	
   1.197 +	#can't call userExists - its recursive
   1.198 +	#return unless (userExists($this, $user));
   1.199 +	
   1.200 +    # Remove the mapping id in case this is a subclass
   1.201 +    $user =~ s/$this->{mapping_id}// if $this->{mapping_id};
   1.202 +
   1.203 +    use bytes;
   1.204 +    # use bytes to ignore character encoding
   1.205 +    $user =~ s/_(\d\d)/chr($1)/ge;
   1.206 +    no bytes;
   1.207 +
   1.208 +    return unless (_userReallyExists($this, $user));
   1.209 +    
   1.210 +    return $user;
   1.211 +}
   1.212 +
   1.213 +=begin twiki
   1.214 +
   1.215 +---++ ObjectMethod _userReallyExists ($login) -> boolean
   1.216 +
   1.217 +test if the login is in the TWikiUsers topic, or in the password file
   1.218 +depending on the AllowLoginNames setting
   1.219 +
   1.220 +=cut
   1.221 +
   1.222 +sub _userReallyExists {
   1.223 +    my( $this, $login ) = @_;
   1.224 +    
   1.225 +    if ($TWiki::cfg{Register}{AllowLoginName}) {
   1.226 +        #need to use the TWikiUsers file
   1.227 +        _loadMapping($this);
   1.228 +        return 1 if (defined($this->{L2U}->{$login}));
   1.229 +    }
   1.230 +    
   1.231 +    if ($this->{passwords}->canFetchUsers()) {
   1.232 +        #AllowLoginName mapping failed, maybe the user is however present in the TWiki managed pwd file
   1.233 +        #can use the password file if available 
   1.234 +        my $pass = $this->{passwords}->fetchPass( $login );
   1.235 +        return unless (defined($pass));
   1.236 +        return if ("$pass" eq "0"); #login invalid... (TODO: what does that really mean)
   1.237 +        return 1;
   1.238 +     } else {
   1.239 +        #passwd==none case generally assumes any login given exists... (not positive if that makes sense for rego..)
   1.240 +        return 1;
   1.241 +     }
   1.242 +
   1.243 +    return 0;
   1.244 +}
   1.245 +
   1.246 +=begin twiki
   1.247 +
   1.248 +---++ ObjectMethod addUser ($login, $wikiname, $password, $emails) -> cUID
   1.249 +
   1.250 +throws an Error::Simple 
   1.251 +
   1.252 +Add a user to the persistant mapping that maps from usernames to wikinames
   1.253 +and vice-versa. The default implementation uses a special topic called
   1.254 +"TWikiUsers" in the users web. Subclasses will provide other implementations
   1.255 +(usually stubs if they have other ways of mapping usernames to wikinames).
   1.256 +Names must be acceptable to $TWiki::cfg{NameFilter}
   1.257 +$login must *always* be specified. $wikiname may be undef, in which case
   1.258 +the user mapper should make one up.
   1.259 +This function must return a *canonical user id* that it uses to uniquely
   1.260 +identify the user. This can be the login name, or the wikiname if they
   1.261 +are all guaranteed unigue, or some other string consisting only of 7-bit
   1.262 +alphanumerics and underscores.
   1.263 +if you fail to create a new user (for eg your Mapper has read only access), 
   1.264 +            throw Error::Simple(
   1.265 +               'Failed to add user: '.$ph->error());
   1.266 +
   1.267 +=cut
   1.268 +
   1.269 +sub addUser {
   1.270 +    my ( $this, $login, $wikiname, $password, $emails ) = @_;
   1.271 +
   1.272 +    ASSERT($login) if DEBUG;
   1.273 +
   1.274 +    # SMELL: really ought to be smarter about this e.g. make a wikiword
   1.275 +    $wikiname ||= $login;
   1.276 +
   1.277 +    if( $this->{passwords}->fetchPass( $login )) {
   1.278 +        # They exist; their password must match
   1.279 +        unless( $this->{passwords}->checkPassword( $login, $password )) {
   1.280 +            throw Error::Simple(
   1.281 +                'New password did not match existing password for this user');
   1.282 +        }
   1.283 +        # User exists, and the password was good.
   1.284 +    } else {
   1.285 +        # add a new user
   1.286 +
   1.287 +        unless( defined( $password )) {
   1.288 +            require TWiki::Users;
   1.289 +            $password = TWiki::Users::randomPassword();
   1.290 +        }
   1.291 +
   1.292 +        unless( $this->{passwords}->setPassword( $login, $password )) {
   1.293 +        	#print STDERR "\n Failed to add user:  ".$this->{passwords}->error();
   1.294 +            throw Error::Simple(
   1.295 +                'Failed to add user: '.$this->{passwords}->error());
   1.296 +        }
   1.297 +    }
   1.298 +
   1.299 +    my $store = $this->{session}->{store};
   1.300 +    my( $meta, $text );
   1.301 +
   1.302 +    if( $store->topicExists( $TWiki::cfg{UsersWebName},
   1.303 +                             $TWiki::cfg{UsersTopicName} )) {
   1.304 +        ( $meta, $text ) = $store->readTopic(
   1.305 +            undef, $TWiki::cfg{UsersWebName}, $TWiki::cfg{UsersTopicName} );
   1.306 +    } else {
   1.307 +        ( $meta, $text ) = $store->readTopic(
   1.308 +            undef, $TWiki::cfg{SystemWebName}, 'TWikiUsersTemplate' );
   1.309 +    }
   1.310 +
   1.311 +    my $result = '';
   1.312 +    my $entry = "   * $wikiname - ";
   1.313 +    $entry .= $login . " - " if $login;
   1.314 +
   1.315 +    require TWiki::Time;
   1.316 +    my $today = TWiki::Time::formatTime(time(), $TWiki::cfg{DefaultDateFormat}, 'gmtime');
   1.317 +
   1.318 +    # add to the mapping caches
   1.319 +    my $user = _cacheUser( $this, $wikiname, $login );
   1.320 +    ASSERT($user) if DEBUG;
   1.321 +
   1.322 +    # add name alphabetically to list
   1.323 +    foreach my $line ( split( /\r?\n/, $text) ) {
   1.324 +        # TODO: I18N fix here once basic auth problem with 8-bit user names is
   1.325 +        # solved
   1.326 +        if ( $entry ) {
   1.327 +            my ( $web, $name, $odate ) = ( '', '', '' );
   1.328 +            if ( $line =~ /^\s+\*\s($TWiki::regex{webNameRegex}\.)?($TWiki::regex{wikiWordRegex})\s*(?:-\s*\w+\s*)?-\s*(.*)/ ) {
   1.329 +                $web = $1 || $TWiki::cfg{UsersWebName};
   1.330 +                $name = $2;
   1.331 +                $odate = $3;
   1.332 +            } elsif ( $line =~ /^\s+\*\s([A-Z]) - / ) {
   1.333 +                #	* A - <a name="A">- - - -</a>^M
   1.334 +                $name = $1;
   1.335 +            }
   1.336 +            if( $name && ( $wikiname le $name ) ) {
   1.337 +                # found alphabetical position
   1.338 +                if( $wikiname eq $name ) {
   1.339 +                    # adjusting existing user - keep original registration date
   1.340 +                    $entry .= $odate;
   1.341 +                } else {
   1.342 +                    $entry .= $today."\n".$line;
   1.343 +                }
   1.344 +                # don't adjust if unchanged
   1.345 +                return $user if( $entry eq $line );
   1.346 +                $line = $entry;
   1.347 +                $entry = '';
   1.348 +            }
   1.349 +        }
   1.350 +
   1.351 +        $result .= $line."\n";
   1.352 +    }
   1.353 +    if( $entry ) {
   1.354 +        # brand new file - add to end
   1.355 +        $result .= "$entry$today\n";
   1.356 +    }
   1.357 +    $store->saveTopic( 
   1.358 +    			#TODO: why is this Admin and not the RegoAgent??
   1.359 +    			$this->{session}->{users}->getCanonicalUserID($TWiki::cfg{AdminUserLogin}),
   1.360 +                       $TWiki::cfg{UsersWebName},
   1.361 +                       $TWiki::cfg{UsersTopicName},
   1.362 +                       $result, $meta );
   1.363 +
   1.364 +    #can't call setEmails here - user may be in the process of being registered
   1.365 +    #TODO; when registration is moved into the mapping, setEmails will happend after the createUserTOpic
   1.366 +    #$this->setEmails( $user, $emails );
   1.367 +
   1.368 +    return $user;
   1.369 +}
   1.370 +
   1.371 +
   1.372 +=begin twiki
   1.373 +
   1.374 +---++ ObjectMethod removeUser( $user ) -> $boolean
   1.375 +
   1.376 +Delete the users entry. Removes the user from the password
   1.377 +manager and user mapping manager. Does *not* remove their personal
   1.378 +topics, which may still be linked.
   1.379 +
   1.380 +=cut
   1.381 +
   1.382 +sub removeUser {
   1.383 +    my( $this, $user ) = @_;
   1.384 +	$this->ASSERT_IS_CANONICAL_USER_ID($user) if DEBUG;
   1.385 +    my $ln = $this->getLoginName( $user );
   1.386 +    $this->{passwords}->removeUser($ln);
   1.387 +    # SMELL: currently a nop, needs someone to implement it
   1.388 +}
   1.389 +
   1.390 +
   1.391 +=begin twiki
   1.392 +
   1.393 +---++ ObjectMethod getWikiName ($cUID) -> wikiname
   1.394 +
   1.395 +Map a canonical user name to a wikiname. If it fails to find a WikiName, it will
   1.396 +attempt to find a matching loginname, and use an escaped version of that.
   1.397 +If there is no matching WikiName or LoginName, it returns undef.
   1.398 +
   1.399 +=cut
   1.400 +
   1.401 +sub getWikiName {
   1.402 +    my ($this, $cUID) = @_;
   1.403 +	ASSERT($cUID) if DEBUG;
   1.404 +	ASSERT($cUID =~ /^$this->{mapping_id}/) if DEBUG;
   1.405 +	
   1.406 +	my $wikiname;
   1.407 +#    $cUID =~ s/^$this->{mapping_id}//;
   1.408 +    if( $TWiki::cfg{Register}{AllowLoginName} ) {
   1.409 +        _loadMapping( $this );
   1.410 +        $wikiname = $this->{U2W}->{$cUID}
   1.411 +    } else {
   1.412 +        # If the mapping isn't enabled there's no point in loading it
   1.413 +    }
   1.414 +    
   1.415 +    unless ($wikiname) {
   1.416 +        #sanitise the generated WikiName - fix up email addresses and stuff
   1.417 +        $wikiname = getLoginName( $this, $cUID );
   1.418 +        if ($wikiname) {
   1.419 +            $wikiname =~ s/$TWiki::cfg{NameFilter}//go;
   1.420 +            $wikiname =~ s/\.//go;
   1.421 +        }
   1.422 +    }
   1.423 +
   1.424 +#print STDERR "--------------------------------------cUID : $cUID => $wikiname\n";	
   1.425 +    return $wikiname;
   1.426 + 
   1.427 +}
   1.428 +
   1.429 +=begin twiki
   1.430 +
   1.431 +---++ ObjectMethod userExists($cUID) -> $boolean
   1.432 +
   1.433 +Determine if the user already exists or not. Whether a user exists
   1.434 +or not is determined by the password manager.
   1.435 +
   1.436 +=cut
   1.437 +
   1.438 +sub userExists {
   1.439 +    my( $this, $cUID ) = @_;
   1.440 +    ASSERT($cUID) if DEBUG;
   1.441 +	$this->ASSERT_IS_CANONICAL_USER_ID($cUID) if DEBUG;
   1.442 +
   1.443 +    # Do this to avoid a password manager lookup
   1.444 +    return 1 if $cUID eq $this->{session}->{user};
   1.445 +
   1.446 +    my $loginName = $this->getLoginName( $cUID );
   1.447 +    return unless (defined($loginName) && ($loginName ne ''));
   1.448 +
   1.449 +    if( $loginName eq $TWiki::cfg{DefaultUserLogin} ) {
   1.450 +        return $loginName;
   1.451 +    }
   1.452 +
   1.453 +    # TWiki allows *groups* to log in
   1.454 +    if( $this->isGroup( $loginName )) {
   1.455 +        return $loginName;
   1.456 +    }
   1.457 +
   1.458 +    # Look them up in the password manager (can be slow).
   1.459 +    if( $this->{passwords}->canFetchUsers() &&
   1.460 +       $this->{passwords}->fetchPass( $loginName )) {
   1.461 +        return $loginName;
   1.462 +    }
   1.463 +    
   1.464 +    unless ( $TWiki::cfg{Register}{AllowLoginName} ||
   1.465 +            $this->{passwords}->canFetchUsers() ) {
   1.466 +        #if there is no pwd file, then its external auth
   1.467 +        #and if AllowLoginName is also off, then the only way to know if
   1.468 +        #the user has registered is to test for user topic?
   1.469 +        if (TWiki::Func::topicExists($TWiki::cfg{UsersWebName}, $loginName)) {
   1.470 +            return $loginName
   1.471 +        }
   1.472 +    }
   1.473 +
   1.474 +    return undef;
   1.475 +}
   1.476 +
   1.477 +=begin twiki
   1.478 +
   1.479 +---++ ObjectMethod eachUser () -> listIterator of cUIDs
   1.480 +
   1.481 +Called from TWiki::Users. See the documentation of the corresponding
   1.482 +method in that module for details.
   1.483 +
   1.484 +=cut
   1.485 +
   1.486 +sub eachUser {
   1.487 +    my( $this ) = @_;
   1.488 +
   1.489 +    _loadMapping( $this );
   1.490 +    my @list = keys(%{$this->{U2W}});
   1.491 +    require TWiki::ListIterator;
   1.492 +    my $iter = new TWiki::ListIterator( \@list );
   1.493 +    $iter->{filter} = sub {
   1.494 +        #don't claim users that are handled by the basemapping
   1.495 +        my $cUID = $_[0] || '';
   1.496 +        my $login = $this->{session}->{users}->getLoginName($cUID);
   1.497 +        my $wikiname =  $this->{session}->{users}->getWikiName($cUID);
   1.498 +        #print STDERR "**** $cUID  $login  $wikiname \n";
   1.499 +        require TWiki::Plugins;
   1.500 +        return !($TWiki::Plugins::SESSION->{users}->{basemapping}->handlesUser ( undef, $login, $wikiname) );
   1.501 +    };
   1.502 +    return $iter;
   1.503 +}
   1.504 +
   1.505 +my %expanding;
   1.506 +
   1.507 +=begin twiki
   1.508 +
   1.509 +---++ ObjectMethod eachGroupMember ($group) ->  listIterator of cUIDs
   1.510 +
   1.511 +Called from TWiki::Users. See the documentation of the corresponding
   1.512 +method in that module for details.
   1.513 +
   1.514 +=cut
   1.515 +
   1.516 +sub eachGroupMember {
   1.517 +    my $this = shift;
   1.518 +    my $group = shift;
   1.519 +    
   1.520 +    return new TWiki::ListIterator( $this->{eachGroupMember}->{$group} )
   1.521 +            if (defined($this->{eachGroupMember}->{$group}));
   1.522 +    
   1.523 +    my $store = $this->{session}->{store};
   1.524 +    my $users = $this->{session}->{users};
   1.525 +
   1.526 +    my $members = [];
   1.527 +
   1.528 +    if( !$expanding{$group} &&
   1.529 +          $store->topicExists( $TWiki::cfg{UsersWebName}, $group )) {
   1.530 +
   1.531 +        $expanding{$group} = 1;
   1.532 +        my $text =
   1.533 +          $store->readTopicRaw( undef,
   1.534 +                                $TWiki::cfg{UsersWebName}, $group,
   1.535 +                                undef );
   1.536 +
   1.537 +        foreach( split( /\r?\n/, $text ) ) {
   1.538 +            if( /$TWiki::regex{setRegex}GROUP\s*=\s*(.+)$/ ) {
   1.539 +                next unless( $1 eq 'Set' );
   1.540 +                # Note: if there are multiple GROUP assignments in the
   1.541 +                # topic, only the last will be taken.
   1.542 +                my $f = $2;
   1.543 +                $members = _expandUserList( $this, $f );
   1.544 +            }
   1.545 +        }
   1.546 +        delete $expanding{$group};
   1.547 +    }
   1.548 +
   1.549 +    require TWiki::ListIterator;
   1.550 +    $this->{eachGroupMember}->{$group} = $members;
   1.551 +    return new TWiki::ListIterator( $this->{eachGroupMember}->{$group} );
   1.552 +}
   1.553 +
   1.554 +
   1.555 +=begin twiki
   1.556 +
   1.557 +---++ ObjectMethod isGroup ($user) -> boolean
   1.558 +TODO: what is $user - wikiname, UID ??
   1.559 +Called from TWiki::Users. See the documentation of the corresponding
   1.560 +method in that module for details.
   1.561 +
   1.562 +=cut
   1.563 +
   1.564 +sub isGroup {
   1.565 +    my ($this, $user) = @_;
   1.566 +
   1.567 +    # Groups have the same username as wikiname as canonical name
   1.568 +    return 1 if $user eq $TWiki::cfg{SuperAdminGroup};
   1.569 +
   1.570 +    return $user =~ /Group$/;
   1.571 +}
   1.572 +
   1.573 +=begin twiki
   1.574 +
   1.575 +---++ ObjectMethod eachGroup () -> ListIterator of groupnames
   1.576 +
   1.577 +Called from TWiki::Users. See the documentation of the corresponding
   1.578 +method in that module for details.
   1.579 +
   1.580 +=cut
   1.581 +
   1.582 +sub eachGroup {
   1.583 +    my ( $this ) = @_;
   1.584 +    _getListOfGroups( $this );
   1.585 +    require TWiki::ListIterator;
   1.586 +    return new TWiki::ListIterator( \@{$this->{groupsList}} );
   1.587 +}
   1.588 +
   1.589 +
   1.590 +=begin twiki
   1.591 +
   1.592 +---++ ObjectMethod eachMembership ($cUID) -> ListIterator of groups this user is in
   1.593 +
   1.594 +Called from TWiki::Users. See the documentation of the corresponding
   1.595 +method in that module for details.
   1.596 +
   1.597 +=cut
   1.598 +
   1.599 +sub eachMembership {
   1.600 +    my ($this, $user) = @_;
   1.601 +    my @groups = ();
   1.602 +
   1.603 +    _getListOfGroups( $this );
   1.604 +    require TWiki::ListIterator;
   1.605 +    my $it = new TWiki::ListIterator( \@{$this->{groupsList}} );
   1.606 +    $it->{filter} = sub {
   1.607 +        $this->isInGroup($user, $_[0]);
   1.608 +    };
   1.609 +    return $it;
   1.610 +}
   1.611 +
   1.612 +=begin twiki
   1.613 +
   1.614 +---++ ObjectMethod isAdmin( $user ) -> $boolean
   1.615 +
   1.616 +True if the user is an admin
   1.617 +   * is $TWiki::cfg{SuperAdminGroup}
   1.618 +   * is a member of the $TWiki::cfg{SuperAdminGroup}
   1.619 +
   1.620 +=cut
   1.621 +
   1.622 +sub isAdmin {
   1.623 +    my( $this, $user ) = @_;
   1.624 +    my $isAdmin = 0;
   1.625 +	$this->ASSERT_IS_CANONICAL_USER_ID($user) if DEBUG;
   1.626 +#TODO: this might not apply now that we have BaseUserMapping - test
   1.627 +    if ($user eq $TWiki::cfg{SuperAdminGroup}) {
   1.628 +        $isAdmin = 1;
   1.629 +    } else {
   1.630 +        my $sag = $TWiki::cfg{SuperAdminGroup};
   1.631 +        $isAdmin = $this->isInGroup( $user, $sag );
   1.632 +    }
   1.633 +
   1.634 +    return $isAdmin;
   1.635 +}
   1.636 +
   1.637 +
   1.638 +=begin twiki
   1.639 +
   1.640 +---++ ObjectMethod isInGroup ($user, $group, $scanning) -> bool
   1.641 +
   1.642 +Called from TWiki::Users. See the documentation of the corresponding
   1.643 +method in that module for details.
   1.644 +
   1.645 +=cut
   1.646 +
   1.647 +sub isInGroup {
   1.648 +    my( $this, $user, $group, $scanning ) = @_;
   1.649 +    ASSERT($user) if DEBUG;
   1.650 +
   1.651 +    my @users;
   1.652 +    my $it = $this->eachGroupMember($group);
   1.653 +    while ($it->hasNext()) {
   1.654 +        my $u = $it->next();
   1.655 +        next if $scanning->{$u};
   1.656 +        $scanning->{$u} = 1;
   1.657 +        return 1 if $u eq $user;
   1.658 +        if( $this->isGroup($u) ) {
   1.659 +            return 1 if $this->isInGroup( $user, $u, $scanning);
   1.660 +        }
   1.661 +    }
   1.662 +    return 0;
   1.663 +}
   1.664 +
   1.665 +=begin twiki
   1.666 +
   1.667 +---++ ObjectMethod findUserByEmail( $email ) -> \@users
   1.668 +   * =$email= - email address to look up
   1.669 +Return a list of canonical user names for the users that have this email
   1.670 +registered with the password manager or the user mapping manager.
   1.671 +
   1.672 +The password manager is asked first for whether it maps emails.
   1.673 +If it doesn't, then the user mapping manager is asked instead.
   1.674 +
   1.675 +=cut
   1.676 +
   1.677 +sub findUserByEmail {
   1.678 +    my( $this, $email ) = @_;
   1.679 +    ASSERT($email) if DEBUG;
   1.680 +    my @users;
   1.681 +    if( $this->{passwords}->isManagingEmails()) {
   1.682 +        my $logins = $this->{passwords}->findLoginByEmail( $email );
   1.683 +        if (defined $logins) {
   1.684 +            foreach my $l ( @$logins ) {
   1.685 +                $l = $this->getLoginName( $l );
   1.686 +                push( @users, $l ) if $l;
   1.687 +            }
   1.688 +        }
   1.689 +    } else {
   1.690 +        # if the password manager didn't want to provide the service, ask
   1.691 +        # the user mapping manager
   1.692 +        unless( $this->{_MAP_OF_EMAILS} ) {
   1.693 +            $this->{_MAP_OF_EMAILS} = {};
   1.694 +            my $it = $this->eachUser();
   1.695 +            while( $it->hasNext() ) {
   1.696 +                my $uo = $it->next();
   1.697 +                map { push( @{$this->{_MAP_OF_EMAILS}->{$_}}, $uo); }
   1.698 +                  $this->getEmails( $uo );
   1.699 +            }
   1.700 +        }
   1.701 +        push( @users, $this->{_MAP_OF_EMAILS}->{$email});
   1.702 +    }
   1.703 +    return \@users;
   1.704 +}
   1.705 +
   1.706 +=begin twiki
   1.707 +
   1.708 +---++ ObjectMethod getEmails($user) -> @emailAddress
   1.709 +
   1.710 +If this is a user, return their email addresses. If it is a group,
   1.711 +return the addresses of everyone in the group.
   1.712 +
   1.713 +The password manager and user mapping manager are both consulted for emails
   1.714 +for each user (where they are actually found is implementation defined).
   1.715 +
   1.716 +Duplicates are removed from the list.
   1.717 +
   1.718 +=cut
   1.719 +
   1.720 +sub getEmails {
   1.721 +    my( $this, $user ) = @_;
   1.722 +	$this->ASSERT_IS_CANONICAL_USER_ID($user) if DEBUG;
   1.723 +
   1.724 +    my %emails;
   1.725 +    if ( $this->isGroup($user) ) {
   1.726 +        my $it = $this->eachGroupMember( $user );
   1.727 +        while( $it->hasNext() ) {
   1.728 +            foreach ($this->getEmails( $it->next())) {
   1.729 +                $emails{$_} = 1;
   1.730 +            }
   1.731 +        }
   1.732 +    } else {
   1.733 +        if ($this->{passwords}->isManagingEmails()) {
   1.734 +            # get emails from the password manager
   1.735 +            foreach ($this->{passwords}->getEmails( $this->getLoginName( $user ))) {
   1.736 +                $emails{$_} = 1;
   1.737 +            }
   1.738 +        } else {
   1.739 +            # And any on offer from the user mapping manager
   1.740 +            foreach (mapper_getEmails( $this->{session}, $user )) {
   1.741 +                $emails{$_} = 1;
   1.742 +            }
   1.743 +        }
   1.744 +    }
   1.745 +
   1.746 +    return keys %emails;
   1.747 +}
   1.748 +
   1.749 +=begin twiki
   1.750 +
   1.751 +---++ ObjectMethod setEmails($user, @emails) -> boolean
   1.752 +
   1.753 +Set the email address(es) for the given user.
   1.754 +The password manager is tried first, and if it doesn't want to know the
   1.755 +user mapping manager is tried.
   1.756 +
   1.757 +=cut
   1.758 +
   1.759 +sub setEmails {
   1.760 +    my $this = shift;
   1.761 +    my $user = shift;
   1.762 +	$this->ASSERT_IS_CANONICAL_USER_ID($user) if DEBUG;
   1.763 +
   1.764 +    if( $this->{passwords}->isManagingEmails()) {
   1.765 +        $this->{passwords}->setEmails( $this->getLoginName( $user ), @_ );
   1.766 +    } else {
   1.767 +        mapper_setEmails( $this->{session}, $user, @_ );
   1.768 +    }
   1.769 +}
   1.770 +
   1.771 +
   1.772 +=begin twiki
   1.773 +
   1.774 +---++ StaticMethod mapper_getEmails($session, $user)
   1.775 +
   1.776 +Only used if passwordManager->isManagingEmails= = =false
   1.777 +(The emails are stored in the user topics.
   1.778 +
   1.779 +Note: This method is PUBLIC because it is used by the tools/upgrade_emails.pl
   1.780 +script, which needs to kick down to the mapper to retrieve email addresses
   1.781 +from TWiki topics.
   1.782 +
   1.783 +=cut
   1.784 +
   1.785 +sub mapper_getEmails {
   1.786 +    my( $session, $user ) = @_;
   1.787 +
   1.788 +    my ($meta, $text) =
   1.789 +      $session->{store}->readTopic(
   1.790 +          undef, $TWiki::cfg{UsersWebName},
   1.791 +          $session->{users}->getWikiName($user) );
   1.792 +
   1.793 +    my @addresses;
   1.794 +
   1.795 +    # Try the form first
   1.796 +    my $entry = $meta->get('FIELD', 'Email');
   1.797 +    if ($entry) {
   1.798 +        push( @addresses, split( /;/, $entry->{value} ) );
   1.799 +    } else {
   1.800 +        # Now try the topic text
   1.801 +        foreach my $l (split ( /\r?\n/, $text  )) {
   1.802 +            if ($l =~ /^\s+\*\s+E-?mail:\s*(.*)$/mi) {
   1.803 +                push @addresses, split( /;/, $1 );
   1.804 +            }
   1.805 +        }
   1.806 +    }
   1.807 +
   1.808 +    return @addresses;
   1.809 +}
   1.810 +
   1.811 +=begin twiki
   1.812 +
   1.813 +---++ StaticMethod mapper_setEmails ($session, $user, @emails)
   1.814 +
   1.815 +Only used if =passwordManager->isManagingEmails= = =false=.
   1.816 +(emails are stored in user topics
   1.817 +
   1.818 +=cut
   1.819 +
   1.820 +sub mapper_setEmails {
   1.821 +    my $session = shift;
   1.822 +    my $cUID = shift;
   1.823 +
   1.824 +    my $mails = join( ';', @_ );
   1.825 +
   1.826 +    my $user = $session->{users}->getWikiName( $cUID );
   1.827 +
   1.828 +    my ($meta, $text) =
   1.829 +      $session->{store}->readTopic(
   1.830 +          undef, $TWiki::cfg{UsersWebName},
   1.831 +          $user);
   1.832 +
   1.833 +    if ($meta->get('FORM')) {
   1.834 +        # use the form if there is one
   1.835 +        $meta->putKeyed( 'FIELD',
   1.836 +                         { name => 'Email',
   1.837 +                           value => $mails,
   1.838 +                           title => 'Email',
   1.839 +                           attributes=> 'h' } );
   1.840 +    } else {
   1.841 +        # otherwise use the topic text
   1.842 +        unless( $text =~ s/^(\s+\*\s+E-?mail:\s*).*$/$1$mails/mi ) {
   1.843 +            $text .= "\n   * Email: $mails\n";
   1.844 +        }
   1.845 +    }
   1.846 +
   1.847 +    $session->{store}->saveTopic(
   1.848 +        $cUID, $TWiki::cfg{UsersWebName}, $user, $text, $meta );
   1.849 +}
   1.850 +
   1.851 +
   1.852 +=begin twiki
   1.853 +
   1.854 +---++ ObjectMethod findUserByWikiName ($wikiname) -> list of cUIDs associated with that wikiname
   1.855 +
   1.856 +Called from TWiki::Users. See the documentation of the corresponding
   1.857 +method in that module for details. The $skipExistanceCheck parameter
   1.858 +is private to this module, and blocks the standard existence check
   1.859 +to avoid reading .htpasswd when checking group memberships).
   1.860 +
   1.861 +=cut
   1.862 +
   1.863 +sub findUserByWikiName {
   1.864 +    my( $this, $wn, $skipExistanceCheck ) = @_;
   1.865 +    my @users = ();
   1.866 +
   1.867 +    if( $this->isGroup( $wn )) {
   1.868 +        push( @users, $wn);
   1.869 +    } elsif( $TWiki::cfg{Register}{AllowLoginName} ) {
   1.870 +        # Add additional mappings defined in TWikiUsers
   1.871 +        _loadMapping( $this );
   1.872 +        if( $this->{W2U}->{$wn} ) {
   1.873 +            push( @users, $this->{W2U}->{$wn} );
   1.874 +        } else {
   1.875 +            # Bloody compatibility!
   1.876 +            # The wikiname is always a registered user for the purposes of this
   1.877 +            # mapping. We have to do this because TWiki defines access controls
   1.878 +            # in terms of mapped users, and if a wikiname is *missing* from the
   1.879 +            # mapping there is "no such user".
   1.880 +            push( @users, getCanonicalUserID( $this, $wn ));
   1.881 +        }
   1.882 +    } else {
   1.883 +        # The wikiname is also the login name, so we can just convert
   1.884 +        # it directly to a cUID
   1.885 +        my $cUID = getCanonicalUserID( $this, $wn );
   1.886 +        if( $skipExistanceCheck || ($cUID && $this->userExists( $cUID )) ) {
   1.887 +            push( @users, getCanonicalUserID( $this, $wn ));
   1.888 +        }
   1.889 +    }
   1.890 +    return \@users;
   1.891 +}
   1.892 +
   1.893 +=begin twiki
   1.894 +
   1.895 +---++ ObjectMethod checkPassword( $userName, $passwordU ) -> $boolean
   1.896 +
   1.897 +Finds if the password is valid for the given user.
   1.898 +
   1.899 +Returns 1 on success, undef on failure.
   1.900 +
   1.901 +=cut
   1.902 +
   1.903 +sub checkPassword {
   1.904 +    my( $this, $userName, $pw ) = @_;
   1.905 +	$this->ASSERT_IS_USER_LOGIN_ID($userName) if DEBUG;
   1.906 +    return $this->{passwords}->checkPassword(
   1.907 +        $userName, $pw);
   1.908 +}
   1.909 +
   1.910 +=begin twiki
   1.911 +
   1.912 +---++ ObjectMethod setPassword( $user, $newPassU, $oldPassU ) -> $boolean
   1.913 +
   1.914 +If the $oldPassU matches matches the user's password, then it will
   1.915 +replace it with $newPassU.
   1.916 +
   1.917 +If $oldPassU is not correct and not 1, will return 0.
   1.918 +
   1.919 +If $oldPassU is 1, will force the change irrespective of
   1.920 +the existing password, adding the user if necessary.
   1.921 +
   1.922 +Otherwise returns 1 on success, undef on failure.
   1.923 +
   1.924 +=cut
   1.925 +
   1.926 +sub setPassword {
   1.927 +    my( $this, $user, $newPassU, $oldPassU ) = @_;
   1.928 +	$this->ASSERT_IS_CANONICAL_USER_ID($user) if DEBUG;
   1.929 +    return $this->{passwords}->setPassword(
   1.930 +        $this->getLoginName( $user ), $newPassU, $oldPassU);
   1.931 +}
   1.932 +
   1.933 +=begin twiki
   1.934 +
   1.935 +---++ ObjectMethod passwordError( ) -> $string
   1.936 +
   1.937 +returns a string indicating the error that happened in the password handlers
   1.938 +TODO: these delayed error's should be replaced with Exceptions.
   1.939 +
   1.940 +returns undef if no error
   1.941 +
   1.942 +=cut
   1.943 +
   1.944 +sub passwordError {
   1.945 +    my( $this ) = @_;
   1.946 +    return $this->{passwords}->error();
   1.947 +}
   1.948 +
   1.949 +=begin twiki
   1.950 +
   1.951 +---++ ObjectMethod ASSERT_IS_CANONICAL_USER_ID( $user_id ) -> $boolean
   1.952 +
   1.953 +used for debugging to ensure we are actually passing a canonical_id
   1.954 +
   1.955 +=cut
   1.956 +
   1.957 +sub ASSERT_IS_CANONICAL_USER_ID {
   1.958 +    # NOP because there is no mapping_id
   1.959 +}
   1.960 +
   1.961 +=begin twiki
   1.962 +
   1.963 +---++ ObjectMethod _cacheUser ($wikiname, $login) => cUID
   1.964 +
   1.965 +# PRIVATE
   1.966 +
   1.967 +TODO: and probably flawed in light of multiple cUIDs mapping to one wikiname
   1.968 +
   1.969 +=cut
   1.970 +
   1.971 +
   1.972 +sub _cacheUser {
   1.973 +    my($this, $wikiname, $login) = @_;
   1.974 +    ASSERT($wikiname) if DEBUG;
   1.975 +
   1.976 +    $login ||= $wikiname;
   1.977 +
   1.978 +    my $cUID = getCanonicalUserID( $this, $login, 1 );
   1.979 +    return unless ($cUID);
   1.980 +    ASSERT($cUID) if DEBUG;
   1.981 +
   1.982 +    #$this->{U2L}->{$cUID}     = $login;
   1.983 +    $this->{U2W}->{$cUID}     = $wikiname;
   1.984 +    $this->{L2U}->{$login}    = $cUID;
   1.985 +    $this->{W2U}->{$wikiname} = $cUID;
   1.986 +
   1.987 +    return $cUID;
   1.988 +}
   1.989 +
   1.990 +
   1.991 +=begin twiki
   1.992 +
   1.993 +---++ ClassMethod _collateGroups ($ref, $group)
   1.994 +
   1.995 +PRIVATE callback for search function to collate results
   1.996 +
   1.997 +=cut
   1.998 +
   1.999 +sub _collateGroups {
  1.1000 +    my $ref = shift;
  1.1001 +    my $group = shift;
  1.1002 +    return unless $group;
  1.1003 +    push (@{$ref->{list}}, $group);
  1.1004 +}
  1.1005 +
  1.1006 +
  1.1007 +=begin twiki
  1.1008 +
  1.1009 +---++ ObjectMethod _getListOfGroups ()
  1.1010 +
  1.1011 +PRIVATE get a list of groups defined in this TWiki
  1.1012 +
  1.1013 +=cut
  1.1014 +
  1.1015 +sub _getListOfGroups {
  1.1016 +    my $this = shift;
  1.1017 +    ASSERT(ref($this) eq 'TWiki::Users::TWikiUserMapping') if DEBUG;
  1.1018 +
  1.1019 +    unless( $this->{groupsList} ) {
  1.1020 +        my $users = $this->{session}->{users};
  1.1021 +        $this->{groupsList} = [];
  1.1022 +
  1.1023 +        $this->{session}->search->searchWeb
  1.1024 +          (
  1.1025 +              _callback     => \&_collateGroups,
  1.1026 +              _cbdata       =>  { list => $this->{groupsList},
  1.1027 +                                  users => $users },
  1.1028 +              inline        => 1,
  1.1029 +              search        => "Set GROUP =",
  1.1030 +              web           => $TWiki::cfg{UsersWebName},
  1.1031 +              topic         => "*Group",
  1.1032 +              type          => 'regex',
  1.1033 +              nosummary     => 'on',
  1.1034 +              nosearch      => 'on',
  1.1035 +              noheader      => 'on',
  1.1036 +              nototal       => 'on',
  1.1037 +              noempty       => 'on',
  1.1038 +              format	     => '$topic',
  1.1039 +              separator     => '',
  1.1040 +             );
  1.1041 +    }
  1.1042 +    return $this->{groupsList};
  1.1043 +}
  1.1044 +
  1.1045 +=begin twiki
  1.1046 +
  1.1047 +---++ ClassMethod _loadMapping ($session, $impl)
  1.1048 +Build hash to translate between username (e.g. jsmith)
  1.1049 +and WikiName (e.g. Main.JaneSmith).
  1.1050 +PRIVATE subclasses should *not* implement this.
  1.1051 +
  1.1052 +
  1.1053 +=cut
  1.1054 +
  1.1055 +sub _loadMapping {
  1.1056 +    my $this = shift;
  1.1057 +    return if $this->{CACHED};
  1.1058 +    $this->{CACHED} = 1;
  1.1059 +
  1.1060 +    #TODO: should only really do this mapping IF the user is in the password file.
  1.1061 +    #       except if we can't 'fetchUsers' like in the Passord='none' case - 
  1.1062 +    #       in which case the only time we
  1.1063 +    #       know a login is real, is when they are logged in :(
  1.1064 +    if (($TWiki::cfg{Register}{AllowLoginName}) ||
  1.1065 +        (!$this->{passwords}->canFetchUsers())
  1.1066 +        ) {
  1.1067 +        my $store = $this->{session}->{store};
  1.1068 +        if( $store->topicExists($TWiki::cfg{UsersWebName},
  1.1069 +                                $TWiki::cfg{UsersTopicName} )) {
  1.1070 +            my $text = $store->readTopicRaw( undef,
  1.1071 +                                          $TWiki::cfg{UsersWebName},
  1.1072 +                                          $TWiki::cfg{UsersTopicName},
  1.1073 +                                          undef );
  1.1074 +            # Get the WikiNames and userids, and build hashes in both directions
  1.1075 +            # This matches:
  1.1076 +            #   * TWikiGuest - guest - 10 Mar 2005
  1.1077 +            #   * TWikiGuest - 10 Mar 2005
  1.1078 +            $text =~ s/^\s*\* (?:$TWiki::regex{webNameRegex}\.)?($TWiki::regex{wikiWordRegex})\s*(?:-\s*(\S+)\s*)?-.*$/(_cacheUser( $this, $1, $2)||'')/gome;
  1.1079 +        }
  1.1080 +    } else {
  1.1081 +        #loginnames _are_ WikiNames so ask the Password handler for list of users
  1.1082 +        my $iter = $this->{passwords}->fetchUsers();
  1.1083 +        while ($iter->hasNext()) {
  1.1084 +            my $login = $iter->next();
  1.1085 +            _cacheUser($this, $login, $login);
  1.1086 +        }
  1.1087 +    }
  1.1088 +}
  1.1089 +
  1.1090 +
  1.1091 +=begin twiki
  1.1092 +
  1.1093 +---++ ObjectMethod _expandUserList ($names )
  1.1094 +
  1.1095 +Get a list of *canonical user ids* from a text string containing a
  1.1096 +list of user *wiki* names and *group ids*.
  1.1097 +
  1.1098 +=cut
  1.1099 +
  1.1100 +sub _expandUserList {
  1.1101 +    my( $this, $names ) = @_;
  1.1102 +
  1.1103 +    $names ||= '';
  1.1104 +    # comma delimited list of users or groups
  1.1105 +    # i.e.: "%MAINWEB%.UserA, UserB, Main.UserC # something else"
  1.1106 +    $names =~ s/(<[^>]*>)//go;     # Remove HTML tags
  1.1107 +
  1.1108 +    my @l;
  1.1109 +    foreach my $ident ( split( /[\,\s]+/, $names )) {
  1.1110 +        $ident =~ s/^.*\.//;       # Dump the web specifier
  1.1111 +        next unless $ident;
  1.1112 +        if( $this->isGroup( $ident )) {
  1.1113 +            my $it = $this->eachGroupMember( $ident );
  1.1114 +            while( $it->hasNext() ) {
  1.1115 +                push( @l, $it->next() );
  1.1116 +            }
  1.1117 +        } else {
  1.1118 +	        my @list = @{$this->{session}->{users}->findUserByWikiName( $ident, 1 )};
  1.1119 +            push( @l, @list );
  1.1120 +        }
  1.1121 +    }
  1.1122 +    return \@l;
  1.1123 +}
  1.1124 +
  1.1125 +1;