← Index
NYTProf Performance Profile   « line view »
For ./view
  Run on Fri Jul 31 19:05:14 2015
Reported on Fri Jul 31 19:08:09 2015

Filename/var/www/foswiki11/lib/Foswiki/Users/TopicUserMapping.pm
StatementsExecuted 29957 statements in 61.4ms
Subroutines
Calls P F Exclusive
Time
Inclusive
Time
Subroutine
10691113.7ms30.3msFoswiki::Users::TopicUserMapping::::_cacheUserFoswiki::Users::TopicUserMapping::_cacheUser
747418.52ms39.7msFoswiki::Users::TopicUserMapping::::_loadMappingFoswiki::Users::TopicUserMapping::_loadMapping
595118.50ms52.3msFoswiki::Users::TopicUserMapping::::handlesUserFoswiki::Users::TopicUserMapping::handlesUser
381313.81ms43.6msFoswiki::Users::TopicUserMapping::::_userReallyExistsFoswiki::Users::TopicUserMapping::_userReallyExists
985433.67ms224msFoswiki::Users::TopicUserMapping::::isGroupFoswiki::Users::TopicUserMapping::isGroup
1066323.50ms12.9msFoswiki::Users::TopicUserMapping::::login2cUIDFoswiki::Users::TopicUserMapping::login2cUID
3111.82ms12.5msFoswiki::Users::TopicUserMapping::::_expandUserListFoswiki::Users::TopicUserMapping::_expandUserList
1111.55ms1.56msFoswiki::Users::TopicUserMapping::::finishFoswiki::Users::TopicUserMapping::finish
155111.28ms1.66msFoswiki::Users::TopicUserMapping::::findUserByWikiNameFoswiki::Users::TopicUserMapping::findUserByWikiName
111684µs751µsFoswiki::Users::TopicUserMapping::::BEGIN@35Foswiki::Users::TopicUserMapping::BEGIN@35
111564µs651µsFoswiki::Users::TopicUserMapping::::newFoswiki::Users::TopicUserMapping::new
17011418µs418µsFoswiki::Users::TopicUserMapping::::_collateGroupsFoswiki::Users::TopicUserMapping::_collateGroups
411237µs15.6msFoswiki::Users::TopicUserMapping::::eachGroupMemberFoswiki::Users::TopicUserMapping::eachGroupMember
31176µs218msFoswiki::Users::TopicUserMapping::::_getListOfGroupsFoswiki::Users::TopicUserMapping::_getListOfGroups
21139µs2.27msFoswiki::Users::TopicUserMapping::::isAdminFoswiki::Users::TopicUserMapping::isAdmin
31139µs218msFoswiki::Users::TopicUserMapping::::eachGroupFoswiki::Users::TopicUserMapping::eachGroup
11125µs61µsFoswiki::Users::TopicUserMapping::::getLoginNameFoswiki::Users::TopicUserMapping::getLoginName
11119µs22µsFoswiki::Users::TopicUserMapping::::getWikiNameFoswiki::Users::TopicUserMapping::getWikiName
11112µs14µsFoswiki::Users::TopicUserMapping::::BEGIN@218Foswiki::Users::TopicUserMapping::BEGIN@218
11112µs14µsFoswiki::Users::TopicUserMapping::::BEGIN@213Foswiki::Users::TopicUserMapping::BEGIN@213
11110µs15µsFoswiki::Users::TopicUserMapping::::BEGIN@32Foswiki::Users::TopicUserMapping::BEGIN@32
1119µs9µsFoswiki::Users::TopicUserMapping::::BEGIN@28Foswiki::Users::TopicUserMapping::BEGIN@28
1119µs27µsFoswiki::Users::TopicUserMapping::::BEGIN@31Foswiki::Users::TopicUserMapping::BEGIN@31
1118µs22µsFoswiki::Users::TopicUserMapping::::BEGIN@33Foswiki::Users::TopicUserMapping::BEGIN@33
1118µs115µsFoswiki::Users::TopicUserMapping::::BEGIN@34Foswiki::Users::TopicUserMapping::BEGIN@34
1114µs4µsFoswiki::Users::TopicUserMapping::::BEGIN@36Foswiki::Users::TopicUserMapping::BEGIN@36
111900ns900nsFoswiki::Users::TopicUserMapping::::supportsRegistrationFoswiki::Users::TopicUserMapping::supportsRegistration
0000s0sFoswiki::Users::TopicUserMapping::::__ANON__[:476]Foswiki::Users::TopicUserMapping::__ANON__[:476]
0000s0sFoswiki::Users::TopicUserMapping::::__ANON__[:484]Foswiki::Users::TopicUserMapping::__ANON__[:484]
0000s0sFoswiki::Users::TopicUserMapping::::__ANON__[:640]Foswiki::Users::TopicUserMapping::__ANON__[:640]
0000s0sFoswiki::Users::TopicUserMapping::::__ANON__[:791]Foswiki::Users::TopicUserMapping::__ANON__[:791]
0000s0sFoswiki::Users::TopicUserMapping::::_clearGroupCacheFoswiki::Users::TopicUserMapping::_clearGroupCache
0000s0sFoswiki::Users::TopicUserMapping::::_maintainUsersTopicFoswiki::Users::TopicUserMapping::_maintainUsersTopic
0000s0sFoswiki::Users::TopicUserMapping::::_writeGroupTopicFoswiki::Users::TopicUserMapping::_writeGroupTopic
0000s0sFoswiki::Users::TopicUserMapping::::addUserFoswiki::Users::TopicUserMapping::addUser
0000s0sFoswiki::Users::TopicUserMapping::::addUserToGroupFoswiki::Users::TopicUserMapping::addUserToGroup
0000s0sFoswiki::Users::TopicUserMapping::::checkPasswordFoswiki::Users::TopicUserMapping::checkPassword
0000s0sFoswiki::Users::TopicUserMapping::::eachMembershipFoswiki::Users::TopicUserMapping::eachMembership
0000s0sFoswiki::Users::TopicUserMapping::::eachUserFoswiki::Users::TopicUserMapping::eachUser
0000s0sFoswiki::Users::TopicUserMapping::::findUserByEmailFoswiki::Users::TopicUserMapping::findUserByEmail
0000s0sFoswiki::Users::TopicUserMapping::::getEmailsFoswiki::Users::TopicUserMapping::getEmails
0000s0sFoswiki::Users::TopicUserMapping::::groupAllowsChangeFoswiki::Users::TopicUserMapping::groupAllowsChange
0000s0sFoswiki::Users::TopicUserMapping::::groupAllowsViewFoswiki::Users::TopicUserMapping::groupAllowsView
0000s0sFoswiki::Users::TopicUserMapping::::mapper_getEmailsFoswiki::Users::TopicUserMapping::mapper_getEmails
0000s0sFoswiki::Users::TopicUserMapping::::mapper_setEmailsFoswiki::Users::TopicUserMapping::mapper_setEmails
0000s0sFoswiki::Users::TopicUserMapping::::passwordErrorFoswiki::Users::TopicUserMapping::passwordError
0000s0sFoswiki::Users::TopicUserMapping::::removeUserFoswiki::Users::TopicUserMapping::removeUser
0000s0sFoswiki::Users::TopicUserMapping::::removeUserFromGroupFoswiki::Users::TopicUserMapping::removeUserFromGroup
0000s0sFoswiki::Users::TopicUserMapping::::setEmailsFoswiki::Users::TopicUserMapping::setEmails
0000s0sFoswiki::Users::TopicUserMapping::::setPasswordFoswiki::Users::TopicUserMapping::setPassword
0000s0sFoswiki::Users::TopicUserMapping::::userExistsFoswiki::Users::TopicUserMapping::userExists
0000s0sFoswiki::Users::TopicUserMapping::::validateRegistrationFieldFoswiki::Users::TopicUserMapping::validateRegistrationField
Call graph for these subroutines as a Graphviz dot language file.
Line State
ments
Time
on line
Calls Time
in subs
Code
1# See bottom of file for license and copyright information
2
3=begin TML
4
5---+ package Foswiki::Users::TopicUserMapping @isa Foswiki::UserMapping');
6
7use
8
9The User mapping is the process by which Foswiki maps from a username (a login name)
10to a wikiname and back. It is also where groups are defined.
11
12By default Foswiki maintains user topics and group topics in the %MAINWEB% that
13define users and group. These topics are
14 * !WikiUsers - stores a mapping from usernames to Wiki names
15 * !WikiName - for each user, stores info about the user
16 * !GroupNameGroup - for each group, a topic ending with "Group" stores a list of users who are part of that group.
17
18Many sites will want to override this behaviour, for example to get users and groups from a corporate database.
19
20This class implements the basic Foswiki behaviour using topics to store users,
21but is also designed to be subclassed so that other services can be used.
22
23Subclasses should be named 'XxxxUserMapping' so that configure can find them.
24
25=cut
26
27package Foswiki::Users::TopicUserMapping;
28239µs19µs
# spent 9µs within Foswiki::Users::TopicUserMapping::BEGIN@28 which was called: # once (9µs+0s) by Foswiki::Users::new at line 28
use Foswiki::UserMapping ();
# spent 9µs making 1 call to Foswiki::Users::TopicUserMapping::BEGIN@28
2917µsour @ISA = ('Foswiki::UserMapping');
30
31228µs245µs
# spent 27µs (9+18) within Foswiki::Users::TopicUserMapping::BEGIN@31 which was called: # once (9µs+18µs) by Foswiki::Users::new at line 31
use strict;
# spent 27µs making 1 call to Foswiki::Users::TopicUserMapping::BEGIN@31 # spent 18µs making 1 call to strict::import
32224µs221µs
# spent 15µs (10+6) within Foswiki::Users::TopicUserMapping::BEGIN@32 which was called: # once (10µs+6µs) by Foswiki::Users::new at line 32
use warnings;
# spent 15µs making 1 call to Foswiki::Users::TopicUserMapping::BEGIN@32 # spent 6µs making 1 call to warnings::import
33225µs236µs
# spent 22µs (8+14) within Foswiki::Users::TopicUserMapping::BEGIN@33 which was called: # once (8µs+14µs) by Foswiki::Users::new at line 33
use Assert;
# spent 22µs making 1 call to Foswiki::Users::TopicUserMapping::BEGIN@33 # spent 14µs making 1 call to Assert::import
34228µs2222µs
# spent 115µs (8+107) within Foswiki::Users::TopicUserMapping::BEGIN@34 which was called: # once (8µs+107µs) by Foswiki::Users::new at line 34
use Error qw( :try );
# spent 115µs making 1 call to Foswiki::Users::TopicUserMapping::BEGIN@34 # spent 107µs making 1 call to Error::import
352101µs1751µs
# spent 751µs (684+67) within Foswiki::Users::TopicUserMapping::BEGIN@35 which was called: # once (684µs+67µs) by Foswiki::Users::new at line 35
use Foswiki::ListIterator ();
# spent 751µs making 1 call to Foswiki::Users::TopicUserMapping::BEGIN@35
362444µs14µs
# spent 4µs within Foswiki::Users::TopicUserMapping::BEGIN@36 which was called: # once (4µs+0s) by Foswiki::Users::new at line 36
use Foswiki::Func ();
# spent 4µs making 1 call to Foswiki::Users::TopicUserMapping::BEGIN@36
37
38#use Monitor;
39#Monitor::MonitorMethod('Foswiki::Users::TopicUserMapping');
40
41=begin TML
42
43---++ ClassMethod new ($session, $impl)
44
45Constructs a new user mapping handler of this type, referring to $session
46for any required Foswiki services.
47
48=cut
49
50# The null mapping name is reserved for Foswiki for backward-compatibility.
51# We declare this as a global variable so we can override it during testing.
521400nsour $FOSWIKI_USER_MAPPING_ID = '';
53
54#our $FOSWIKI_USER_MAPPING_ID = 'TestMapping_';
55
56
# spent 651µs (564+87) within Foswiki::Users::TopicUserMapping::new which was called: # once (564µs+87µs) by Foswiki::Users::new at line 104 of /var/www/foswiki11/lib/Foswiki/Users.pm
sub new {
571900ns my ( $class, $session ) = @_;
58
59114µs18µs my $this = $class->SUPER::new( $session, $FOSWIKI_USER_MAPPING_ID );
# spent 8µs making 1 call to Foswiki::UserMapping::new
60
6111µs my $implPasswordManager = $Foswiki::cfg{PasswordManager};
621700ns $implPasswordManager = 'Foswiki::Users::Password'
63 if ( $implPasswordManager eq 'none' );
64123µs eval "require $implPasswordManager";
# spent 75µs executing statements in string eval
651200ns die $@ if $@;
6614µs111µs $this->{passwords} = $implPasswordManager->new($session);
# spent 11µs making 1 call to Foswiki::Users::Password::new
67
68# if password manager says sorry, we're read only today
69# 'none' is a special case, as it means we're not actually using the password manager for
70# registration.
7112µs11µs if ( $this->{passwords}->readOnly()
# spent 1µs making 1 call to Foswiki::Users::Password::readOnly
72 && ( $Foswiki::cfg{PasswordManager} ne 'none' )
73 && $Foswiki::cfg{Register}{EnableNewUserRegistration} )
74 {
75 $session->logger->log( 'warning',
76'TopicUserMapping has TURNED OFF EnableNewUserRegistration, because the password file is read only.'
77 );
78 $Foswiki::cfg{Register}{EnableNewUserRegistration} = 0;
79 }
80
81 #SMELL: and this is a second user object
82 #TODO: combine with the one in Foswiki::Users
83 #$this->{U2L} = {};
841600ns $this->{L2U} = {};
851300ns $this->{U2W} = {};
861400ns $this->{W2U} = {};
871400ns $this->{eachGroupMember} = {};
881800ns $this->{singleGroupMembers} = ();
89
9014µs return $this;
91}
92
93=begin TML
94
95---++ ObjectMethod finish()
96Break circular references.
97
98=cut
99
100# Note to developers; please undef *all* fields in the object explicitly,
101# whether they are references or not. That way this method is "golden
102# documentation" of the live fields in the object.
103
# spent 1.56ms (1.55+9µs) within Foswiki::Users::TopicUserMapping::finish which was called: # once (1.55ms+9µs) by Foswiki::Users::finish at line 165 of /var/www/foswiki11/lib/Foswiki/Users.pm
sub finish {
1041800ns my $this = shift;
105
10615µs16µs $this->{passwords}->finish() if $this->{passwords};
# spent 6µs making 1 call to Foswiki::Users::Password::finish
1071357µs undef $this->{L2U};
1081555µs undef $this->{U2W};
1091515µs undef $this->{W2U};
11018µs undef $this->{passwords};
111180µs undef $this->{eachGroupMember};
1121800ns undef $this->{singleGroupMembers};
113119µs14µs $this->SUPER::finish();
# spent 4µs making 1 call to Foswiki::UserMapping::finish
114}
115
116=begin TML
117
118---++ ObjectMethod supportsRegistration () -> false
119return 1 if the UserMapper supports registration (ie can create new users)
120
121=cut
122
123
# spent 900ns within Foswiki::Users::TopicUserMapping::supportsRegistration which was called: # once (900ns+0s) by Foswiki::Users::supportsRegistration at line 235 of /var/www/foswiki11/lib/Foswiki/Users.pm
sub supportsRegistration {
12413µs return 1;
125}
126
127=begin TML
128
129---++ ObjectMethod handlesUser ( $cUID, $login, $wikiname) -> $boolean
130
131Called by the Foswiki::Users object to determine which loaded mapping
132to use for a given user.
133
134The user can be identified by any of $cUID, $login or $wikiname. Any of
135these parameters may be undef, and they should be tested in order; cUID
136first, then login, then wikiname. This mapping is special - for backwards
137compatibility, it assumes responsibility for _all_ non BaseMapping users.
138If you're needing to mix the TopicUserMapping with other mappings,
139define $this->{mapping_id} = 'TopicUserMapping_';
140
141=cut
142
143
# spent 52.3ms (8.50+43.8) within Foswiki::Users::TopicUserMapping::handlesUser which was called 595 times, avg 88µs/call: # 595 times (8.50ms+43.8ms) by Foswiki::Users::_getMapping at line 214 of /var/www/foswiki11/lib/Foswiki/Users.pm, avg 88µs/call
sub handlesUser {
144595419µs my ( $this, $cUID, $login, $wikiname ) = @_;
145595272µs if ( defined $cUID && !length( $this->{mapping_id} ) ) {
146
147 # Handle all cUIDs if the mapping ID is not defined
148 return 1;
149 }
150 else {
151
152 # Used when (if) TopicUserMapping is subclassed
153588137µs return 1 if ( defined $cUID && $cUID =~ /^($this->{mapping_id})/ );
154 }
155
156 # Check the login id to see if we know it
157588542µs37843.5ms return 1 if ( $login && $this->_userReallyExists($login) );
# spent 43.5ms making 378 calls to Foswiki::Users::TopicUserMapping::_userReallyExists, avg 115µs/call
158
159 # Or the wiki name
16058799µs if ($wikiname) {
161210182µs210245µs $this->_loadMapping(); # Sorry Sven, has to be done
# spent 245µs making 210 calls to Foswiki::Users::TopicUserMapping::_loadMapping, avg 1µs/call
162210463µs return 1 if defined $this->{W2U}->{$wikiname};
163 }
164
16543213.3ms return 0;
166}
167
168=begin TML
169
170---++ ObjectMethod login2cUID ($login, $dontcheck) -> $cUID
171
172Convert a login name to the corresponding canonical user name. The
173canonical name can be any string of 7-bit alphanumeric and underscore
174characters, and must correspond 1:1 to the login name.
175(undef on failure)
176
177(if dontcheck is true, return a cUID for a nonexistant user too.
178This is used for registration)
179
180=cut
181
182
# spent 12.9ms (3.50+9.38) within Foswiki::Users::TopicUserMapping::login2cUID which was called 1066 times, avg 12µs/call: # 1064 times (3.48ms+9.36ms) by Foswiki::Users::TopicUserMapping::_cacheUser at line 1598, avg 12µs/call # once (12µs+16µs) by Foswiki::Users::getCanonicalUserID at line 480 of /var/www/foswiki11/lib/Foswiki/Users.pm # once (6µs+11µs) by Foswiki::Users::TopicUserMapping::getLoginName at line 221
sub login2cUID {
1831066453µs my ( $this, $login, $dontcheck ) = @_;
184
1851066106µs211µs unless ($dontcheck) {
# spent 11µs making 2 calls to Foswiki::Users::TopicUserMapping::_userReallyExists, avg 6µs/call
186 return unless ( _userReallyExists( $this, $login ) );
187 }
188
18910662.80ms10669.37ms return $this->{mapping_id} . Foswiki::Users::mapLogin2cUID($login);
# spent 9.37ms making 1066 calls to Foswiki::Users::mapLogin2cUID, avg 9µs/call
190}
191
192=begin TML
193
194---++ ObjectMethod getLoginName ($cUID) -> login
195
196Converts an internal cUID to that user's login
197(undef on failure)
198
199=cut
200
201
# spent 61µs (25+37) within Foswiki::Users::TopicUserMapping::getLoginName which was called: # once (25µs+37µs) by Foswiki::Users::getLoginName at line 679 of /var/www/foswiki11/lib/Foswiki/Users.pm
sub getLoginName {
20211µs my ( $this, $cUID ) = @_;
20311µs11µs ASSERT($cUID) if DEBUG;
# spent 1µs making 1 call to Assert::ASSERTS_OFF
204
2051600ns my $login = $cUID;
206
207 #can't call userExists - its recursive
208 #return unless (userExists($this, $user));
209
210 # Remove the mapping id in case this is a subclass
2111900ns $login =~ s/$this->{mapping_id}// if $this->{mapping_id};
212
213253µs217µs
# spent 14µs (12+2) within Foswiki::Users::TopicUserMapping::BEGIN@213 which was called: # once (12µs+2µs) by Foswiki::Users::new at line 213
use bytes;
# spent 14µs making 1 call to Foswiki::Users::TopicUserMapping::BEGIN@213 # spent 2µs making 1 call to bytes::import
214
215 # Reverse the encoding used to generate cUIDs in login2cUID
216 # use bytes to ignore character encoding
21712µs $login =~ s/_([0-9a-f][0-9a-f])/chr(hex($1))/gei;
21824.85ms216µs
# spent 14µs (12+2) within Foswiki::Users::TopicUserMapping::BEGIN@218 which was called: # once (12µs+2µs) by Foswiki::Users::new at line 218
no bytes;
# spent 14µs making 1 call to Foswiki::Users::TopicUserMapping::BEGIN@218 # spent 2µs making 1 call to bytes::unimport
219
22012µs17µs return unless _userReallyExists( $this, $login );
22112µs117µs return unless ( $cUID eq $this->login2cUID($login) );
# spent 17µs making 1 call to Foswiki::Users::TopicUserMapping::login2cUID
222
223 # Validated
224110µs112µs return Foswiki::Sandbox::untaintUnchecked($login);
# spent 12µs making 1 call to Foswiki::Sandbox::untaintUnchecked
225}
226
227# test if the login is in the WikiUsers topic, or in the password file
228# depending on the AllowLoginNames setting
229
# spent 43.6ms (3.81+39.7) within Foswiki::Users::TopicUserMapping::_userReallyExists which was called 381 times, avg 114µs/call: # 378 times (3.80ms+39.7ms) by Foswiki::Users::TopicUserMapping::handlesUser at line 157, avg 115µs/call # 2 times (9µs+2µs) by Foswiki::Users::TopicUserMapping::login2cUID at line 185, avg 6µs/call # once (6µs+1µs) by Foswiki::Users::TopicUserMapping::getLoginName at line 220
sub _userReallyExists {
230381188µs my ( $this, $login ) = @_;
231
232381310µs if ( $Foswiki::cfg{Register}{AllowLoginName}
233 || $Foswiki::cfg{PasswordManager} eq 'none' )
234 {
235
236 # need to use the WikiUsers file
237381542µs38139.3ms $this->_loadMapping();
# spent 39.3ms making 381 calls to Foswiki::Users::TopicUserMapping::_loadMapping, avg 103µs/call
238381339µs return 1 if ( defined( $this->{L2U}->{$login} ) );
239 }
240
241377733µs377421µs if ( $this->{passwords}->canFetchUsers() ) {
# spent 421µs making 377 calls to Foswiki::Users::Password::canFetchUsers, avg 1µs/call
242
243 # AllowLoginName mapping failed, maybe the user is however
244 # present in the Wiki managed pwd file
245 # can use the password file if available
246 my $pass = $this->{passwords}->fetchPass($login);
247 return unless ( defined($pass) );
248 return if ( $pass eq '0' ); # login invalid... (SMELL: what
249 # does that really mean)
250 return 1;
251 }
252 else {
2533771.00ms return 0;
254 }
255
256 return 0;
257}
258
259=begin TML
260
261---++ ObjectMethod addUser ($login, $wikiname, $password, $emails) -> $cUID
262
263throws an Error::Simple
264
265Add a user to the persistant mapping that maps from usernames to wikinames
266and vice-versa. The default implementation uses a special topic called
267"WikiUsers" in the users web. Subclasses will provide other implementations
268(usually stubs if they have other ways of mapping usernames to wikinames).
269Names must be acceptable to $Foswiki::cfg{NameFilter}
270$login must *always* be specified. $wikiname may be undef, in which case
271the user mapper should make one up.
272This function must return a *canonical user id* that it uses to uniquely
273identify the user. This can be the login name, or the wikiname if they
274are all guaranteed unigue, or some other string consisting only of 7-bit
275alphanumerics and underscores.
276if you fail to create a new user (for eg your Mapper has read only access),
277 throw Error::Simple(
278 'Failed to add user: '.$ph->error());
279
280=cut
281
282sub addUser {
283 my ( $this, $login, $wikiname, $password, $emails ) = @_;
284
285 ASSERT($login) if DEBUG;
286
287 # SMELL: really ought to be smarter about this e.g. make a wikiword
288 $wikiname ||= $login;
289
290 if ( $this->{passwords}->fetchPass($login) ) {
291
292 # They exist; their password must match
293 unless ( $this->{passwords}->checkPassword( $login, $password ) ) {
294 throw Error::Simple(
295 $this->{session}->i18n->maketext(
296 'New password did not match existing password for this user'
297 )
298 );
299 }
300
301 # User exists, and the password was good.
302 }
303 else {
304
305 # add a new user
306
307 unless ( defined($password) ) {
308 require Foswiki::Users;
309 $password = Foswiki::Users::randomPassword();
310 }
311
312 unless ( $this->{passwords}->setPassword( $login, $password ) == 1 ) {
313
314 throw Error::Simple(
315 $this->{session}->i18n->maketext('Failed to add user: ')
316 . $this->{passwords}->error() );
317 }
318 }
319
320 $this->{CACHED} = 0;
321 my $user = $this->_maintainUsersTopic( 'add', $login, $wikiname );
322
323#can't call setEmails here - user may be in the process of being registered
324#TODO; when registration is moved into the mapping, setEmails will happend after the createUserTOpic
325#$this->setEmails( $user, $emails );
326
327 return $user;
328}
329
330=begin TML
331
332---++ ObjectMethod _maintainUsersTopic ( $action, $login, $wikiname )
333
334throws an Error::Simple
335
336Add or remove a user to/from the persistant mapping that maps from usernames to wikinames
337and vice-versa. The default implementation uses a special topic called
338"WikiUsers" in the users web. =cut
339
340=cut
341
342sub _maintainUsersTopic {
343 my ( $this, $action, $login, $wikiname ) = @_;
344
345 my $usersTopicObject;
346
347 if (
348 $this->{session}->topicExists(
349 $Foswiki::cfg{UsersWebName},
350 $Foswiki::cfg{UsersTopicName}
351 )
352 )
353 {
354
355 # Load existing users topic
356 $usersTopicObject = Foswiki::Meta->load(
357 $this->{session},
358 $Foswiki::cfg{UsersWebName},
359 $Foswiki::cfg{UsersTopicName}
360 );
361 }
362 else {
363
364 return undef if ( $action eq 'del' );
365
366 # Construct a new users topic from the template
367 my $templateTopicObject =
368 Foswiki::Meta->load( $this->{session}, $Foswiki::cfg{SystemWebName},
369 'UsersTemplate' );
370 $usersTopicObject = Foswiki::Meta->new(
371 $this->{session}, $Foswiki::cfg{UsersWebName},
372 $Foswiki::cfg{UsersTopicName}, $templateTopicObject->text()
373 );
374
375 $usersTopicObject->copyFrom($templateTopicObject);
376 }
377
378 my $entry = " * $wikiname - ";
379 $entry .= $login . " - " if $login;
380
381 require Foswiki::Time;
382 my $today =
383 Foswiki::Time::formatTime( time(), $Foswiki::cfg{DefaultDateFormat},
384 'gmtime' );
385
386 my $user;
387
388 # add to the mapping caches unless removing a user
389 unless ( $action eq 'del' ) {
390 $user = _cacheUser( $this, $wikiname, $login );
391 ASSERT($user) if DEBUG;
392 }
393
394 # add name alphabetically to list
395
396 # insidelist is used to see if we are before the first record or after the last
397 # 0 before, 1 inside, 2 after
398 my $insidelist = 0;
399 my $input = $usersTopicObject->text();
400 my $output = '';
401 foreach my $line ( split( /\r?\n/, $input || '' ) ) {
402
403 # TODO: I18N fix here once basic auth problem with 8-bit user names is
404 # solved
405 if ($entry) {
406 my ( $web, $name, $odate ) = ( '', '', '' );
407 if ( $line =~
408/^\s+\*\s($Foswiki::regex{webNameRegex}\.)?($Foswiki::regex{wikiWordRegex})\s*(?:-\s*\w+\s*)?-\s*(.*)/
409 )
410 {
411 $web = $1 || $Foswiki::cfg{UsersWebName};
412 $name = $2;
413 $odate = $3;
414
415 # Filter-in date format matching {DefaultDateFormat}
416 # The admin may have changed the format at some point of time
417 # so we test with a generic format that matches all 4 formats.
418 $odate = ''
419 unless $odate =~ /^\d+[- .\/]+[A-Za-z0-9]+[- .\/]+\d+$/;
420 $insidelist = 1;
421 }
422 elsif ( $line =~ /^\s+\*\s([A-Z]) - / ) {
423
424 # * A - <a name="A">- - - -</a>^M
425 $name = $1;
426 $insidelist = 1;
427 }
428 elsif ( $insidelist == 1 ) {
429
430 # After last entry we have a blank line or some comment
431 # We assume no blank lines inside the list of users
432 # We cannot look for last after Z because Z is not the last letter
433 # in all alphabets
434 $insidelist = 2;
435 $name = '';
436 }
437 if ( ( $name && ( $wikiname le $name ) ) || $insidelist == 2 ) {
438
439 # found alphabetical position or last record
440 if ( $wikiname eq $name ) {
441
442 next if ( $action eq 'del' );
443
444 # adjusting existing user - keep original registration date
445 $entry .= $odate;
446 }
447 else {
448 $entry .= $today . "\n" . $line;
449 }
450
451 # don't adjust if unchanged
452 return $user if ( $entry eq $line && $action eq 'add' );
453 $line = $entry if ( $action eq 'add' );
454 $entry = '';
455 }
456 }
457 $output .= $line . "\n";
458 }
459
460 if ( $entry && $action eq 'add' ) {
461
462 # brand new file - add to end
463 $output .= "$entry$today\n";
464 }
465 $usersTopicObject->text($output);
466
467 $this->{CACHED} = 0;
468 try {
469 $usersTopicObject->save(
470 author =>
471
472 # SMELL: why is this Admin and not the RegoAgent??
473 $this->{session}->{users}
474 ->getCanonicalUserID( $Foswiki::cfg{AdminUserLogin} )
475 );
476 }
477 catch Error::Simple with {
478
479 # Failed to add user; must remove them from the password system too,
480 # otherwise their next registration attempt will be blocked
481 my $e = shift;
482 $this->{passwords}->removeUser($login);
483 throw $e;
484 };
485
486 return $user;
487}
488
489=begin TML
490
491---++ ObjectMethod removeUser( $cUID ) -> $boolean
492
493Delete the users entry. Removes the user from the password
494manager and user mapping manager. Does *not* remove their personal
495topics, which may still be linked.
496
497Note that this must be called with the cUID. If any doubt, resolve the cUID
498by $this->{session}->{users}->getCanonicalUserID($identity).
499
500=cut
501
502sub removeUser {
503 my ( $this, $cUID ) = @_;
504
505 my $ln = $this->getLoginName($cUID);
506 my $wikiname = $this->getWikiName($cUID);
507 $this->{passwords}->removeUser($ln) if ($ln);
508
509 # SMELL: If for some reason the login or wikiname is not found in the mapping
510 # Then the WikiUsers topic will not be maintained.
511 $this->_maintainUsersTopic( 'del', $ln, $wikiname ) if ( $ln && $wikiname );
512
513 # SMELL: does not update the internal caches,
514 # needs someone to implement it
515 # Brutal update - invalidate them all
516
517 $this->{CACHED} = 0;
518 $this->{L2U} = {};
519 $this->{U2W} = {};
520 $this->{W2U} = {};
521 $this->{eachGroupMember} = {};
522 $this->{singleGroupMembers} = ();
523
524 return 1;
525
526}
527
528=begin TML
529
530---++ ObjectMethod getWikiName ($cUID) -> $wikiname
531
532Map a canonical user name to a wikiname. If it fails to find a
533WikiName, it will attempt to find a matching loginname, and use
534an escaped version of that.
535If there is no matching WikiName or LoginName, it returns undef.
536
537=cut
538
539
# spent 22µs (19+3) within Foswiki::Users::TopicUserMapping::getWikiName which was called: # once (19µs+3µs) by Foswiki::Users::getWikiName at line 710 of /var/www/foswiki11/lib/Foswiki/Users.pm
sub getWikiName {
54012µs my ( $this, $cUID ) = @_;
54111µs11µs ASSERT($cUID) if DEBUG;
# spent 1µs making 1 call to Assert::ASSERTS_OFF
54212µs1800ns ASSERT( $cUID =~ /^$this->{mapping_id}/ ) if DEBUG;
# spent 800ns making 1 call to Assert::ASSERTS_OFF
543
5441300ns my $wikiname;
545
5461900ns if ( $Foswiki::cfg{Register}{AllowLoginName} ) {
54711µs11µs $this->_loadMapping();
# spent 1µs making 1 call to Foswiki::Users::TopicUserMapping::_loadMapping
54812µs $wikiname = $this->{U2W}->{$cUID};
549 }
550 else {
551
552 # If the mapping isn't enabled there's no point in loading it
553 }
554
5551600ns unless ($wikiname) {
556 $wikiname = $this->getLoginName($cUID);
557 if ($wikiname) {
558
559 # sanitise the generated WikiName
560 $wikiname =~ s/$Foswiki::cfg{NameFilter}//go;
561 }
562 }
563
56415µs return $wikiname;
565}
566
567=begin TML
568
569---++ ObjectMethod userExists($cUID) -> $boolean
570
571Determine if the user already exists or not. Whether a user exists
572or not is determined by the password manager.
573
574=cut
575
576sub userExists {
577 my ( $this, $cUID ) = @_;
578 ASSERT($cUID) if DEBUG;
579
580 # Do this to avoid a password manager lookup
581 return 1 if $cUID eq $this->{session}->{user};
582
583 my $loginName = $this->getLoginName($cUID);
584 return 0 unless defined($loginName);
585
586 return 1 if ( $loginName eq $Foswiki::cfg{DefaultUserLogin} );
587
588 # Foswiki allows *groups* to log in
589 return 1 if ( $this->isGroup($loginName) );
590
591 # Look them up in the password manager (can be slow).
592 return 1
593 if ( $this->{passwords}->canFetchUsers()
594 && $this->{passwords}->fetchPass($loginName) );
595
596 unless ( $Foswiki::cfg{Register}{AllowLoginName}
597 && $this->{passwords}->canFetchUsers() )
598 {
599
600 #if there is no pwd file, then its external auth
601 #and if AllowLoginName is also off, then the only way to know if
602 #the user has registered is to test for user topic?
603 my $wikiname = $this->{session}->{users}->getWikiName($cUID);
604 if (
605 Foswiki::Func::topicExists(
606 $Foswiki::cfg{UsersWebName}, $wikiname
607 )
608 )
609 {
610 return 1;
611 }
612 }
613
614 return 0;
615}
616
617=begin TML
618
619---++ ObjectMethod eachUser () -> Foswiki::Iterator of cUIDs
620
621See baseclass for documentation
622
623=cut
624
625sub eachUser {
626 my ($this) = @_;
627
628 $this->_loadMapping();
629 my @list = keys( %{ $this->{U2W} } );
630 my $iter = new Foswiki::ListIterator( \@list );
631 $iter->{filter} = sub {
632
633 # don't claim users that are handled by the basemapping
634 my $cUID = $_[0] || '';
635 my $login = $this->{session}->{users}->getLoginName($cUID);
636 my $wikiname = $this->{session}->{users}->getWikiName($cUID);
637
638 return !( $this->{session}->{users}->{basemapping}
639 ->handlesUser( undef, $login, $wikiname ) );
640 };
641 return $iter;
642}
643
644=begin TML
645
646---++ ObjectMethod eachGroupMember ($group) -> listIterator of cUIDs
647
648See baseclass for documentation
649
650=cut
651
6521200nsmy %expanding; # Prevents loops in nested groups
653
654
# spent 15.6ms (237µs+15.4) within Foswiki::Users::TopicUserMapping::eachGroupMember which was called 4 times, avg 3.90ms/call: # 4 times (237µs+15.4ms) by Foswiki::UserMapping::isInGroup at line 419 of /var/www/foswiki11/lib/Foswiki/UserMapping.pm, avg 3.90ms/call
sub eachGroupMember {
65544µs my ( $this, $group, $options ) = @_;
656
65743µs my $expand = $options->{expand};
658
659440µs415µs if ( Scalar::Util::tainted($group) ) {
# spent 15µs making 4 calls to Scalar::Util::tainted, avg 4µs/call
660 $group = Foswiki::Sandbox::untaint( $group,
661 \&Foswiki::Sandbox::validateTopicName );
662 }
663
66442µs $expand = 1 unless ( defined $expand );
665
666 # print STDERR "eachGroupMember called for $group - expand $expand \n";
667
66841µs if ( !$expand && defined( $this->{singleGroupMembers}->{$group} ) ) {
669
670 # print STDERR "Returning cached unexpanded list for $group\n";
671 return new Foswiki::ListIterator(
672 $this->{singleGroupMembers}->{$group} );
673 }
674
675414µs113µs if ( $expand && defined( $this->{eachGroupMember}->{$group} ) ) {
# spent 13µs making 1 call to Foswiki::ListIterator::new
676
677 # print STDERR "Returning cached expanded list for $group\n";
678 return new Foswiki::ListIterator( $this->{eachGroupMember}->{$group} );
679 }
680
681 # print "Cache miss for $group expand $expand \n";
682
68332µs my $session = $this->{session};
68431µs my $users = $session->{users};
685
68632µs my $members = [];
68731µs my $singleGroupMembers = [];
688
689# Determine if we are called recursively, either directly, or by the _expandUserList routine
690356µs unless ( ( caller(1) )[3] eq ( caller(0) )[3]
691 || ( caller(2) )[3] eq ( caller(0) )[3] )
692 {
693
694 # print "eachGroupMember $group - TOP LEVEL \n";
695 %expanding = ();
696 }
697
698314µs3197µs if ( !$expanding{$group}
# spent 197µs making 3 calls to Foswiki::topicExists, avg 66µs/call
699 && $session->topicExists( $Foswiki::cfg{UsersWebName}, $group ) )
700 {
70137µs $expanding{$group} = 1;
702
703 # print "Expanding $group \n";
704315µs32.01ms my $groupTopicObject =
# spent 2.01ms making 3 calls to Foswiki::Meta::load, avg 669µs/call
705 Foswiki::Meta->load( $this->{session}, $Foswiki::cfg{UsersWebName},
706 $group );
707
70832µs if ( !$expand ) {
709 $singleGroupMembers =
710 _expandUserList( $this,
711 $groupTopicObject->getPreference('GROUP'), 0 );
712 $this->{singleGroupMembers}->{$group} = $singleGroupMembers;
713
714# print "Returning iterator for singleGroupMembers $group, members $singleGroupMembers \n";
715 return new Foswiki::ListIterator(
716 $this->{singleGroupMembers}->{$group} );
717 }
718 else {
719320µs613.1ms $members =
# spent 12.5ms making 3 calls to Foswiki::Users::TopicUserMapping::_expandUserList, avg 4.15ms/call # spent 618µs making 3 calls to Foswiki::Meta::getPreference, avg 206µs/call
720 _expandUserList( $this,
721 $groupTopicObject->getPreference('GROUP') );
72235µs $this->{eachGroupMember}->{$group} = $members;
723 }
724
72534µs delete $expanding{$group};
726 }
727
728 # print "Returning iterator for eachGroupMember $group \n";
729328µs356µs return new Foswiki::ListIterator( $this->{eachGroupMember}->{$group} );
# spent 56µs making 3 calls to Foswiki::ListIterator::new, avg 19µs/call
730}
731
732=begin TML
733
734---++ ObjectMethod isGroup ($user) -> boolean
735
736See baseclass for documentation
737
738=cut
739
740
# spent 224ms (3.67+220) within Foswiki::Users::TopicUserMapping::isGroup which was called 985 times, avg 228µs/call: # 374 times (2.44ms+220ms) by Foswiki::Users::isGroup at line 839 of /var/www/foswiki11/lib/Foswiki/Users.pm, avg 596µs/call # 300 times (689µs+0s) by Foswiki::UserMapping::isInGroup at line 426 of /var/www/foswiki11/lib/Foswiki/UserMapping.pm, avg 2µs/call # 156 times (313µs+0s) by Foswiki::Users::TopicUserMapping::_expandUserList at line 1730, avg 2µs/call # 155 times (228µs+0s) by Foswiki::Users::TopicUserMapping::findUserByWikiName at line 1451, avg 1µs/call
sub isGroup {
741985470µs my ( $this, $user ) = @_;
742
743 # Groups have the same username as wikiname as canonical name
744985397µs return 1 if $user eq $Foswiki::cfg{SuperAdminGroup};
745
7469852.79ms return 0 unless ( $user =~ /Group$/ );
747
748 #actually test for the existance of this group
749 #TODO: SMELL: this is still a lie, because it will claim that a
750 #Group which the currently logged in user does _not_
751 #have VIEW permission for simply is non-existant.
752 #however, this may be desirable for security reasons.
753 #SMELL: this is why we should not use topicExist to test for createability...
754310µs3218ms my $iterator = $this->eachGroup();
# spent 218ms making 3 calls to Foswiki::Users::TopicUserMapping::eachGroup, avg 72.8ms/call
75536µs316µs while ( $iterator->hasNext() ) {
# spent 16µs making 3 calls to Foswiki::ListIterator::hasNext, avg 5µs/call
756231296µs2311.27ms my $groupname = $iterator->next();
# spent 1.27ms making 231 calls to Foswiki::ListIterator::next, avg 5µs/call
757231357µs228830µs return 1 if ( $groupname eq $user );
# spent 830µs making 228 calls to Foswiki::ListIterator::hasNext, avg 4µs/call
758 }
759 return 0;
760}
761
762=begin TML
763
764---++ ObjectMethod eachGroup () -> ListIterator of groupnames
765
766See baseclass for documentation
767
768=cut
769
770
# spent 218ms (39µs+218) within Foswiki::Users::TopicUserMapping::eachGroup which was called 3 times, avg 72.8ms/call: # 3 times (39µs+218ms) by Foswiki::Users::TopicUserMapping::isGroup at line 754, avg 72.8ms/call
sub eachGroup {
77133µs my ($this) = @_;
77239µs3218ms _getListOfGroups($this);
# spent 218ms making 3 calls to Foswiki::Users::TopicUserMapping::_getListOfGroups, avg 72.8ms/call
773321µs329µs return new Foswiki::ListIterator( \@{ $this->{groupsList} } );
# spent 29µs making 3 calls to Foswiki::ListIterator::new, avg 10µs/call
774}
775
776=begin TML
777
778---++ ObjectMethod eachMembership ($cUID) -> ListIterator of groups this user is in
779
780See baseclass for documentation
781
782=cut
783
784sub eachMembership {
785 my ( $this, $user ) = @_;
786
787 _getListOfGroups($this);
788 my $it = new Foswiki::ListIterator( \@{ $this->{groupsList} } );
789 $it->{filter} = sub {
790 $this->isInGroup( $user, $_[0] );
791 };
792 return $it;
793}
794
795=begin TML
796
797---++ ObjectMethod groupAllowsView($group) -> boolean
798
799returns 1 if the group is able to be viewed by the current logged in user
800
801implemented using topic VIEW permissions
802
803=cut
804
805sub groupAllowsView {
806 my $this = shift;
807 my $Group = shift;
808
809 my $user = $this->{session}->{user};
810 return 1 if $this->{session}->{users}->isAdmin($user);
811
812 $Group = Foswiki::Sandbox::untaint( $Group,
813 \&Foswiki::Sandbox::validateTopicName );
814 my ( $groupWeb, $groupName ) =
815 $this->{session}
816 ->normalizeWebTopicName( $Foswiki::cfg{UsersWebName}, $Group );
817
818# If a Group or User topic normalized somewhere else, doesn't make sense, so ignore the Webname
819 $groupWeb = $Foswiki::cfg{UsersWebName};
820
821 $groupName = undef
822 if ( not $this->{session}->topicExists( $groupWeb, $groupName ) );
823
824 return Foswiki::Func::checkAccessPermission( 'VIEW', $user, undef,
825 $groupName, $groupWeb );
826}
827
828=begin TML
829
830---++ ObjectMethod groupAllowsChange($group, $cuid) -> boolean
831
832returns 1 if the group is able to be modified by $cuid
833
834implemented using topic CHANGE permissions
835
836=cut
837
838sub groupAllowsChange {
839 my $this = shift;
840 my $Group = shift;
841 my $user = shift;
842 ASSERT( defined $user ) if DEBUG;
843
844 $Group = Foswiki::Sandbox::untaint( $Group,
845 \&Foswiki::Sandbox::validateTopicName );
846 my ( $groupWeb, $groupName ) =
847 $this->{session}
848 ->normalizeWebTopicName( $Foswiki::cfg{UsersWebName}, $Group );
849
850 # SMELL: Should NobodyGroup be configurable?
851 return 0 if $groupName eq 'NobodyGroup';
852 return 1 if $this->{session}->{users}->isAdmin($user);
853
854# If a Group or User topic normalized somewhere else, doesn't make sense, so ignore the Webname
855 $groupWeb = $Foswiki::cfg{UsersWebName};
856
857 $groupName = undef
858 if ( not $this->{session}->topicExists( $groupWeb, $groupName ) );
859
860 return Foswiki::Func::checkAccessPermission( 'CHANGE', $user, undef,
861 $groupName, $groupWeb );
862}
863
864=begin TML
865
866---++ ObjectMethod addToGroup( $cuid, $group, $create ) -> $boolean
867adds the user specified by the cuid to the group.
868If the group does not exist, it will return false and do nothing, unless the create flag is set.
869
870cuid be a groupname which is added like it was an unknown user
871
872=cut
873
874sub addUserToGroup {
875 my ( $this, $cuid, $Group, $create ) = @_;
876 $Group = Foswiki::Sandbox::untaint( $Group,
877 \&Foswiki::Sandbox::validateTopicName );
878 my ( $groupWeb, $groupName ) =
879 $this->{session}
880 ->normalizeWebTopicName( $Foswiki::cfg{UsersWebName}, $Group );
881
882 throw Error::Simple( $this->{session}
883 ->i18n->maketext( 'Users cannot be added to [_1]', $Group ) )
884 if ( $Group eq 'NobodyGroup' || $Group eq 'BaseGroup' );
885
886 throw Error::Simple(
887 $this->{session}->i18n->maketext('Group names must end in Group') )
888 unless ( $Group =~ m/Group$/ );
889
890 # the registration code will call this function using the rego agent
891 my $user = $this->{session}->{user};
892
893 my $usersObj = $this->{session}->{users};
894
895 print STDERR "$user, aka("
896 . $usersObj->getWikiName($user)
897 . ") is TRYING to add $cuid aka("
898 . $usersObj->getWikiName($cuid)
899 . ") to $groupName\n"
900 if ( $cuid && DEBUG );
901
902 my $membersString = '';
903 my $allowChangeString;
904 my $groupTopicObject;
905
906 if ( $usersObj->isGroup($groupName) ) {
907
908 $groupTopicObject =
909 Foswiki::Meta->load( $this->{session}, $groupWeb, $groupName );
910
911 if ( !$groupTopicObject->haveAccess( 'CHANGE', $user ) ) {
912 throw Error::Simple( $this->{session}
913 ->i18n->maketext( 'CHANGE not permitted by [_1]', $user ) );
914 }
915
916 $membersString = $groupTopicObject->getPreference('GROUP') || '';
917
918 my @l;
919 foreach my $ident ( split( /[\,\s]+/, $membersString ) ) {
920 $ident =~ s/^($Foswiki::cfg{UsersWebName}|%USERSWEB%|%MAINWEB%)\.//;
921 push( @l, $ident ) if $ident;
922 }
923 $membersString = join( ', ', @l );
924
925 if ( $create and !defined($cuid) ) {
926
927 #upgrade group topic.
928 $this->_writeGroupTopic(
929 $groupTopicObject, $groupWeb, $groupName,
930 $membersString, $allowChangeString
931 );
932
933 return 1;
934 }
935 }
936 else {
937
938# see if we have permission to add a topic, or to edit the existing topic, etc..
939
940 throw Error::Simple( $this->{session}
941 ->i18n->maketext('Group does not exist and create not permitted')
942 ) unless ($create);
943
944 throw Error::Simple(
945 $this->{session}->i18n->maketext(
946 'CHANGE not permitted for [_1] by [_2]',
947 ( $groupName, $user )
948 )
949 )
950 unless (
951 Foswiki::Func::checkAccessPermission(
952 'CHANGE', $user, '', $groupName, $groupWeb
953 )
954 );
955
956 $groupTopicObject =
957 Foswiki::Meta->load( $this->{session}, $groupWeb, 'GroupTemplate' );
958
959 # expand the GroupTemplate as best we can.
960 $this->{session}->{request}
961 ->param( -name => 'topic', -value => $groupName );
962 $groupTopicObject->expandNewTopic();
963
964 $allowChangeString = $groupName;
965 }
966
967 my $wikiName = '';
968 $wikiName = $usersObj->getWikiName($cuid) if ($cuid);
969
970 if ( $membersString !~ m/\b$wikiName\b/ ) {
971 $membersString .= ', ' if ( $membersString ne '' );
972 $membersString .= $wikiName;
973 }
974
975 Foswiki::Func::writeEvent( 'addUserToGroup',
976 "$groupName: $wikiName added by $user" );
977
978 $this->_clearGroupCache($groupName);
979
980 $this->_writeGroupTopic(
981 $groupTopicObject, $groupWeb, $groupName,
982 $membersString, $allowChangeString
983 );
984
985 # reparse groups brute force :/
986 _getListOfGroups( $this, 1 ) if ($create);
987 return 1;
988}
989
990#start by just writing the new form.
991sub _writeGroupTopic {
992 my $this = shift;
993 my $groupTopicObject = shift;
994 my $groupWeb = shift;
995 my $groupName = shift;
996 my $membersString = shift;
997 my $allowChangeString = shift;
998
999 my $text = $groupTopicObject->text() || '';
1000
1001#TODO: do an attempt to convert existing old style topics - compare to 'normal' GroupTemplate? (I'm hoping to keep any user added descriptions for the group
1002 if (
1003 (
1004 !defined $groupTopicObject->getPreference('VIEW_TEMPLATE')
1005 or $groupTopicObject->getPreference('VIEW_TEMPLATE') ne 'GroupView'
1006 )
1007 or ( $text =~ /^---\+!! <nop>.*$/ )
1008 or ( $text =~ /^(\t| )+\* Set GROUP = .*$/ )
1009 or ( $text =~ /^(\t| )+\* Member list \(comma-separated list\):$/ )
1010 or ( $text =~ /^(\t| )+\* Persons\/group who can change the list:$/ )
1011 or ( $text =~ /^(\t| )+\* Set ALLOWTOPICCHANGE = .*$/ )
1012 or ( $text =~ /^\*%MAKETEXT{"Related topics:"}%.*$/ )
1013 )
1014 {
1015 if ( !defined($allowChangeString) ) {
1016 $allowChangeString =
1017 $groupTopicObject->getPreference('ALLOWTOPICCHANGE') || '';
1018 }
1019
1020 $text =~ s/^---\+!! <nop>.*$//s;
1021 $text =~ s/^(\t| )+\* Set GROUP = .*$//s;
1022 $text =~ s/^(\t| )+\* Member list \(comma-separated list\):$//s;
1023 $text =~ s/^(\t| )+\* Persons\/group who can change the list:$//s;
1024 $text =~ s/^(\t| )+\* Set ALLOWTOPICCHANGE = .*$//s;
1025 $text =~ s/^\*%MAKETEXT{"Related topics:"}%.*$//s;
1026
1027 $text .= "\nEdit this topic to add a description to the $groupName\n";
1028
1029#TODO: consider removing the VIEW_TEMPLATE that only very few people should ever have...
1030 }
1031
1032 $groupTopicObject->text($text);
1033
1034 $groupTopicObject->putKeyed(
1035 'PREFERENCE',
1036 {
1037 type => 'Set',
1038 name => 'GROUP',
1039 title => 'GROUP',
1040 value => $membersString
1041 }
1042 );
1043 if ( defined($allowChangeString) ) {
1044 $groupTopicObject->putKeyed(
1045 'PREFERENCE',
1046 {
1047 type => 'Set',
1048 name => 'ALLOWTOPICCHANGE',
1049 title => 'ALLOWTOPICCHANGE',
1050 value => $allowChangeString
1051 }
1052 );
1053 }
1054 $groupTopicObject->putKeyed(
1055 'PREFERENCE',
1056 {
1057 type => 'Set',
1058 name => 'VIEW_TEMPLATE',
1059 title => 'VIEW_TEMPLATE',
1060 value => 'GroupView'
1061 }
1062 );
1063
1064 #TODO: should also consider securing the new topic?
1065 my $user = $this->{session}->{user};
1066 $groupTopicObject->saveAs(
1067 $groupWeb, $groupName,
1068 author => $user,
1069 forcenewrevision => ( $groupName eq $Foswiki::cfg{SuperAdminGroup} )
1070 ? 1
1071 : 0
1072 );
1073
1074}
1075
1076=begin TML
1077
1078---++ ObjectMethod removeFromGroup( $cuid, $group ) -> $boolean
1079
1080=cut
1081
1082sub removeUserFromGroup {
1083 my ( $this, $cuid, $groupName ) = @_;
1084 $groupName = Foswiki::Sandbox::untaint( $groupName,
1085 \&Foswiki::Sandbox::validateTopicName );
1086 my ( $groupWeb, $groupTopic ) =
1087 $this->{session}
1088 ->normalizeWebTopicName( $Foswiki::cfg{UsersWebName}, $groupName );
1089
1090 throw Error::Simple( $this->{session}
1091 ->i18n->maketext( 'Users cannot be removed from [_1]', $groupName ) )
1092 if ( $groupName eq 'BaseGroup' );
1093
1094 throw Error::Simple(
1095 $this->{session}->i18n->maketext(
1096 '[_1] cannot be removed from [_2]',
1097 (
1098 $Foswiki::cfg{AdminUserWikiName}, $Foswiki::cfg{SuperAdminGroup}
1099 )
1100 )
1101 )
1102 if ( $groupName eq "$Foswiki::cfg{SuperAdminGroup}"
1103 && $cuid eq 'BaseUserMapping_333' );
1104
1105 my $user = $this->{session}->{user};
1106 my $usersObj = $this->{session}->{users};
1107
1108 if (
1109 $usersObj->isGroup($groupName)
1110 and ( $this->{session}
1111 ->topicExists( $Foswiki::cfg{UsersWebName}, $groupName ) )
1112 )
1113 {
1114 if ( !$usersObj->isInGroup( $cuid, $groupName, { expand => 0 } )
1115 && !$usersObj->isGroup($cuid) )
1116 {
1117
1118 throw Error::Simple(
1119 $this->{session}->i18n->maketext(
1120 'User [_1] not in group, cannot be removed', $cuid
1121 )
1122 );
1123 }
1124 my $groupTopicObject =
1125 Foswiki::Meta->load( $this->{session}, $Foswiki::cfg{UsersWebName},
1126 $groupName );
1127 if ( !$groupTopicObject->haveAccess( 'CHANGE', $user ) ) {
1128
1129 throw Error::Simple(
1130 $this->{session}->i18n->maketext(
1131 'User [_1] does not have CHANGE permission on [_2].',
1132 ( $user, $groupName )
1133 )
1134 );
1135 }
1136
1137 my $WikiName = $usersObj->getWikiName($cuid);
1138 my $LoginName = $usersObj->getLoginName($cuid) || '';
1139
1140 my $membersString = $groupTopicObject->getPreference('GROUP');
1141 my @l;
1142 foreach my $ident ( split( /[\,\s]+/, $membersString ) ) {
1143 $ident =~ s/^($Foswiki::cfg{UsersWebName}|%USERSWEB%|%MAINWEB%)\.//;
1144 next if ( $ident eq $WikiName );
1145 next if ( $ident eq $LoginName );
1146 next if ( $ident eq $cuid );
1147 push( @l, $ident );
1148 }
1149 $membersString = join( ', ', @l );
1150
1151 Foswiki::Func::writeEvent( 'removeUserFromGroup',
1152 "$groupTopic: $WikiName removed by $user" );
1153
1154 $this->_writeGroupTopic( $groupTopicObject, $groupWeb, $groupTopic,
1155 $membersString );
1156
1157 $this->_clearGroupCache($groupName);
1158
1159 return 1;
1160 }
1161
1162 return 0;
1163}
1164
1165=begin TML
1166
1167---++ ObjectMethod _clearGroupCache( $groupName )
1168
1169Removes the cache entries for unexpanded and expanded groups,
1170and searches un-expanded groups for any nesting group references
1171clearing them as well.
1172
1173Note: This is not recursive and does not attempt to handle
1174more than one level of nested groups.
1175
1176=cut
1177
1178sub _clearGroupCache {
1179 my ( $this, $groupName ) = @_;
1180
1181 delete $this->{eachGroupMember}->{$groupName};
1182 delete $this->{singleGroupMembers}->{$groupName};
1183
1184 #SMELL: This should probably be recursive.
1185 foreach my $groupKey ( keys( %{ $this->{singleGroupMembers} } ) ) {
1186 if ( $this->{singleGroupMembers}->{$groupKey} =~ m/$groupName/ ) {
1187
1188 # print STDERR "Deleting cache for $groupKey \n";
1189 delete $this->{eachGroupMember}->{$groupKey};
1190 delete $this->{singleGroupMembers}->{$groupKey};
1191 }
1192 }
1193}
1194
1195=begin TML
1196
1197---++ ObjectMethod isAdmin( $cUID ) -> $boolean
1198
1199True if the user is an admin
1200 * is $Foswiki::cfg{SuperAdminGroup}
1201 * is a member of the $Foswiki::cfg{SuperAdminGroup}
1202
1203=cut
1204
1205
# spent 2.27ms (39µs+2.23) within Foswiki::Users::TopicUserMapping::isAdmin which was called 2 times, avg 1.14ms/call: # 2 times (39µs+2.23ms) by Foswiki::Users::isAdmin at line 626 of /var/www/foswiki11/lib/Foswiki/Users.pm, avg 1.14ms/call
sub isAdmin {
120622µs my ( $this, $cUID ) = @_;
120721µs my $isAdmin = 0;
1208
1209 # TODO: this might not apply now that we have BaseUserMapping - test
121023µs if ( $cUID eq $Foswiki::cfg{SuperAdminGroup} ) {
1211 $isAdmin = 1;
1212 }
1213 else {
12141900ns my $sag = $Foswiki::cfg{SuperAdminGroup};
1215118µs12.23ms $isAdmin = $this->isInGroup( $cUID, $sag );
# spent 2.23ms making 1 call to Foswiki::UserMapping::isInGroup
1216 }
1217
1218218µs return $isAdmin;
1219}
1220
1221=begin TML
1222
1223---++ ObjectMethod findUserByEmail( $email ) -> \@cUIDs
1224 * =$email= - email address to look up
1225Return a list of canonical user names for the users that have this email
1226registered with the password manager or the user mapping manager.
1227
1228The password manager is asked first for whether it maps emails.
1229If it doesn't, then the user mapping manager is asked instead.
1230
1231=cut
1232
1233sub findUserByEmail {
1234 my ( $this, $email ) = @_;
1235 ASSERT($email) if DEBUG;
1236 my @users;
1237 if ( $this->{passwords}->isManagingEmails() ) {
1238 my $logins = $this->{passwords}->findUserByEmail($email);
1239 if ( defined $logins ) {
1240 foreach my $l (@$logins) {
1241 $l = $this->login2cUID($l);
1242 push( @users, $l ) if $l;
1243 }
1244 }
1245 }
1246 else {
1247
1248 # if the password manager didn't want to provide the service, ask
1249 # the user mapping manager
1250 unless ( $this->{_MAP_OF_EMAILS} ) {
1251 $this->{_MAP_OF_EMAILS} = {};
1252 my $it = $this->eachUser();
1253 while ( $it->hasNext() ) {
1254 my $uo = $it->next();
1255 map { push( @{ $this->{_MAP_OF_EMAILS}->{$_} }, $uo ); }
1256 $this->getEmails($uo);
1257 }
1258 }
1259 push( @users, @{ $this->{_MAP_OF_EMAILS}->{$email} } )
1260 if ( $this->{_MAP_OF_EMAILS}->{$email} );
1261 }
1262 return \@users;
1263}
1264
1265=begin TML
1266
1267---++ ObjectMethod getEmails($name) -> @emailAddress
1268
1269If $name is a user, return their email addresses. If it is a group,
1270return the addresses of everyone in the group.
1271
1272The password manager and user mapping manager are both consulted for emails
1273for each user (where they are actually found is implementation defined).
1274
1275Duplicates are removed from the list.
1276
1277=cut
1278
1279sub getEmails {
1280 my ( $this, $user, $seen ) = @_;
1281
1282 $seen ||= {};
1283
1284 my %emails = ();
1285
1286 if ( $seen->{$user} ) {
1287
1288 #print STDERR "preventing infinit recursion in getEmails($user)\n";
1289 }
1290 else {
1291 $seen->{$user} = 1;
1292
1293 if ( $this->isGroup($user) ) {
1294 my $it = $this->eachGroupMember($user);
1295 while ( $it->hasNext() ) {
1296 foreach ( $this->getEmails( $it->next(), $seen ) ) {
1297 $emails{$_} = 1;
1298 }
1299 }
1300 }
1301 else {
1302 if ( $this->{passwords}->isManagingEmails() ) {
1303
1304 # get emails from the password manager
1305 foreach ( $this->{passwords}
1306 ->getEmails( $this->getLoginName($user), $seen ) )
1307 {
1308 $emails{$_} = 1;
1309 }
1310 }
1311 else {
1312
1313 # And any on offer from the user mapping manager
1314 foreach ( mapper_getEmails( $this->{session}, $user ) ) {
1315 $emails{$_} = 1;
1316 }
1317 }
1318 }
1319 }
1320 return keys %emails;
1321}
1322
1323=begin TML
1324
1325---++ ObjectMethod setEmails($cUID, @emails) -> boolean
1326
1327Set the email address(es) for the given user.
1328The password manager is tried first, and if it doesn't want to know the
1329user mapping manager is tried.
1330
1331=cut
1332
1333sub setEmails {
1334 my $this = shift;
1335 my $user = shift;
1336
1337 if ( $this->{passwords}->isManagingEmails() ) {
1338 $this->{passwords}->setEmails( $this->getLoginName($user), @_ );
1339 }
1340 else {
1341 mapper_setEmails( $this->{session}, $user, @_ );
1342 }
1343}
1344
1345=begin TML
1346
1347---++ StaticMethod mapper_getEmails($session, $user)
1348
1349Only used if passwordManager->isManagingEmails= = =false
1350(The emails are stored in the user topics.
1351
1352Note: This method is PUBLIC because it is used by the tools/upgrade_emails.pl
1353script, which needs to kick down to the mapper to retrieve email addresses
1354from Wiki topics.
1355
1356=cut
1357
1358sub mapper_getEmails {
1359 my ( $session, $user ) = @_;
1360
1361 my $topicObject = Foswiki::Meta->load(
1362 $session,
1363 $Foswiki::cfg{UsersWebName},
1364 $session->{users}->getWikiName($user)
1365 );
1366
1367 my @addresses;
1368
1369 # Try the form first
1370 my $entry = $topicObject->get( 'FIELD', 'Email' );
1371 if ($entry) {
1372 push( @addresses, split( /;/, $entry->{value} ) );
1373 }
1374 elsif ( defined $topicObject->text ) {
1375
1376 # Now try the topic text
1377 foreach my $l ( split( /\r?\n/, $topicObject->text ) ) {
1378 if ( $l =~ /^\s+\*\s+E-?mail:\s*(.*)$/mi ) {
1379
1380 # SMELL: implicit unvalidated untaint
1381 push @addresses, split( /;/, $1 );
1382 }
1383 }
1384 }
1385
1386 return @addresses;
1387}
1388
1389=begin TML
1390
1391---++ StaticMethod mapper_setEmails ($session, $user, @emails)
1392
1393Only used if =passwordManager->isManagingEmails= = =false=.
1394(emails are stored in user topics
1395
1396=cut
1397
1398sub mapper_setEmails {
1399 my $session = shift;
1400 my $cUID = shift;
1401
1402 my $mails = join( ';', @_ );
1403
1404 my $user = $session->{users}->getWikiName($cUID);
1405
1406 my $topicObject =
1407 Foswiki::Meta->load( $session, $Foswiki::cfg{UsersWebName}, $user );
1408
1409 if ( $topicObject->get('FORM') ) {
1410
1411 # use the form if there is one
1412 $topicObject->putKeyed(
1413 'FIELD',
1414 {
1415 name => 'Email',
1416 value => $mails,
1417 title => 'Email',
1418 attributes => 'h'
1419 }
1420 );
1421 }
1422 else {
1423
1424 # otherwise use the topic text
1425 my $text = $topicObject->text() || '';
1426 unless ( $text =~ s/^(\s+\*\s+E-?mail:\s*).*$/$1$mails/mi ) {
1427 $text .= "\n * Email: $mails\n";
1428 }
1429 $topicObject->text($text);
1430 }
1431
1432 $topicObject->save();
1433}
1434
1435=begin TML
1436
1437---++ ObjectMethod findUserByWikiName ($wikiname) -> list of cUIDs associated with that wikiname
1438
1439See baseclass for documentation
1440
1441The $skipExistanceCheck parameter
1442is private to this module, and blocks the standard existence check
1443to avoid reading .htpasswd when checking group memberships).
1444
1445=cut
1446
1447
# spent 1.66ms (1.28+382µs) within Foswiki::Users::TopicUserMapping::findUserByWikiName which was called 155 times, avg 11µs/call: # 155 times (1.28ms+382µs) by Foswiki::Users::findUserByWikiName at line 535 of /var/www/foswiki11/lib/Foswiki/Users.pm, avg 11µs/call
sub findUserByWikiName {
144815571µs my ( $this, $wn, $skipExistanceCheck ) = @_;
144915542µs my @users = ();
1450
1451155194µs155228µs if ( $this->isGroup($wn) ) {
# spent 228µs making 155 calls to Foswiki::Users::TopicUserMapping::isGroup, avg 1µs/call
1452 push( @users, $wn );
1453 }
1454 elsif ( $Foswiki::cfg{Register}{AllowLoginName} ) {
1455
1456 # print STDERR "AllowLoginName discovered \n";
1457
1458 # Add additional mappings defined in WikiUsers
1459155138µs155154µs $this->_loadMapping();
# spent 154µs making 155 calls to Foswiki::Users::TopicUserMapping::_loadMapping, avg 993ns/call
1460155109µs if ( $this->{W2U}->{$wn} ) {
1461
1462 # Wikiname to UID mapping is defined
146315574µs my $user = $this->{W2U}->{$wn};
1464155103µs push( @users, $user ) if $user;
1465 }
1466 else {
1467
1468 # Bloody compatibility!
1469 # The wikiname is always a registered user for the purposes of this
1470 # mapping. We have to do this because Foswiki defines access controls
1471 # in terms of mapped users, and if a wikiname is *missing* from the
1472 # mapping there is "no such user".
1473 my $user = $this->login2cUID($wn);
1474 push( @users, $user ) if $user;
1475 }
1476 }
1477 else {
1478
1479 # print STDERR "NOT AllowLoginName \n";
1480
1481 # The wikiname is also the login name, so we can just convert
1482 # it directly to a cUID
1483 my $cUID = $this->login2cUID($wn);
1484
1485 # print STDERR "login2cUID for $wn returned $cUID \n";
1486
1487 # print STDERR "$wn EXISTS \n" if ( $cUID && $this->userExists($cUID) );
1488 if ( $skipExistanceCheck || ( $cUID && $this->userExists($cUID) ) ) {
1489 push( @users, $cUID );
1490 }
1491 }
1492155321µs return \@users;
1493}
1494
1495=begin TML
1496
1497---++ ObjectMethod checkPassword( $login, $password ) -> $boolean
1498
1499Finds if the password is valid for the given user.
1500
1501Returns 1 on success, undef on failure.
1502
1503=cut
1504
1505sub checkPassword {
1506 my ( $this, $login, $pw ) = @_;
1507
1508 # If we don't have a PasswordManager and use TemplateLogin, always allow login
1509 return 1
1510 if ( $Foswiki::cfg{PasswordManager} eq 'none'
1511 && $Foswiki::cfg{LoginManager} eq
1512 'Foswiki::LoginManager::TemplateLogin' );
1513
1514 return $this->{passwords}->checkPassword( $login, $pw );
1515}
1516
1517=begin TML
1518
1519---++ ObjectMethod setPassword( $cUID, $newPassU, $oldPassU ) -> $boolean
1520
1521BEWARE: $user should be a cUID, but is a login when the resetPassword
1522functionality is used.
1523The UserMapper needs to convert either one to a valid login for use by
1524the Password manager
1525
1526TODO: needs fixing
1527
1528If the $oldPassU matches matches the user's password, then it will
1529replace it with $newPassU.
1530
1531If $oldPassU is not correct and not 1, will return 0.
1532
1533If $oldPassU is 1, will force the change irrespective of
1534the existing password, adding the user if necessary.
1535
1536Otherwise returns 1 on success, undef on failure.
1537
1538=cut
1539
1540sub setPassword {
1541 my ( $this, $user, $newPassU, $oldPassU ) = @_;
1542 ASSERT($user) if DEBUG;
1543 my $login = $this->getLoginName($user) || $user;
1544 return $this->{passwords}->setPassword( $login, $newPassU, $oldPassU );
1545}
1546
1547=begin TML
1548
1549---++ ObjectMethod passwordError( ) -> $string
1550
1551returns a string indicating the error that happened in the password handlers
1552TODO: these delayed error's should be replaced with Exceptions.
1553
1554returns undef if no error
1555
1556=cut
1557
1558sub passwordError {
1559 my ($this) = @_;
1560 return $this->{passwords}->error();
1561}
1562
1563=begin TML
1564
1565---++ ObjectMethod validateRegistrationField($field, $value ) -> $string
1566
1567This method is called for every field submitted during registration. It is also used
1568to validate the username when adding a member to a group.
1569
1570Returns a string containing the sanitized registration field, or can throw an Error::Simple
1571if the field contains illegal data to block the registration.
1572
1573returns the string unchanged if no issue found.
1574
1575=cut
1576
1577sub validateRegistrationField {
1578
1579 #my ($this, $field, $value) = @_;
1580 my $this = shift;
1581
1582# For now just let Foswiki::UserMapping do the validation - nothing special needed.
1583 return $this->SUPER::validateRegistrationField(@_);
1584}
1585
1586# TODO: and probably flawed in light of multiple cUIDs mapping to one wikiname
1587
# spent 30.3ms (13.7+16.6) within Foswiki::Users::TopicUserMapping::_cacheUser which was called 1069 times, avg 28µs/call: # 1069 times (13.7ms+16.6ms) by Foswiki::Users::TopicUserMapping::_loadMapping at line 1694, avg 28µs/call
sub _cacheUser {
15881069950µs my ( $this, $wikiname, $login ) = @_;
15891069801µs1069697µs ASSERT($wikiname) if DEBUG;
# spent 697µs making 1069 calls to Assert::ASSERTS_OFF, avg 652ns/call
1590
15911069137µs $login ||= $wikiname;
1592
1593 #discard users that are the BaseUserMapper's responsibility
1594 return
159510691.55ms10692.32ms if ( $this->{session}->{users}->{basemapping}
# spent 2.32ms making 1069 calls to Foswiki::Users::BaseUserMapping::handlesUser, avg 2µs/call
1596 ->handlesUser( undef, $login, $wikiname ) );
1597
159810641.23ms106412.8ms my $cUID = $this->login2cUID( $login, 1 );
# spent 12.8ms making 1064 calls to Foswiki::Users::TopicUserMapping::login2cUID, avg 12µs/call
15991064105µs return unless ($cUID);
16001064909µs1064735µs ASSERT($cUID) if DEBUG;
# spent 735µs making 1064 calls to Assert::ASSERTS_OFF, avg 691ns/call
1601
1602 #$this->{U2L}->{$cUID} = $login;
160310641.01ms $this->{U2W}->{$cUID} = $wikiname;
16041064564µs $this->{L2U}->{$login} = $cUID;
160510641.04ms $this->{W2U}->{$wikiname} = $cUID;
1606
160710642.10ms return $cUID;
1608}
1609
1610# callback for search function to collate results
1611
# spent 418µs within Foswiki::Users::TopicUserMapping::_collateGroups which was called 170 times, avg 2µs/call: # 170 times (418µs+0s) by Foswiki::Search::__ANON__[/var/www/foswiki11/lib/Foswiki/Search.pm:1336] at line 1335 of /var/www/foswiki11/lib/Foswiki/Search.pm, avg 2µs/call
sub _collateGroups {
161217039µs my $ref = shift;
161317041µs my $group = shift;
1614170301µs return unless $group;
161585380µs push( @{ $ref->{list} }, $group );
1616}
1617
1618# get a list of groups defined in this Wiki
1619
# spent 218ms (76µs+218) within Foswiki::Users::TopicUserMapping::_getListOfGroups which was called 3 times, avg 72.8ms/call: # 3 times (76µs+218ms) by Foswiki::Users::TopicUserMapping::eachGroup at line 772, avg 72.8ms/call
sub _getListOfGroups {
162031µs my $this = shift;
162131µs my $reset = shift;
1622
162335µs34µs ASSERT( $this->isa('Foswiki::Users::TopicUserMapping') ) if DEBUG;
# spent 4µs making 3 calls to Assert::ASSERTS_OFF, avg 1µs/call
1624
162534µs if ( !$this->{groupsList} || $reset ) {
162611µs my $users = $this->{session}->{users};
162714µs $this->{groupsList} = [];
1628
1629 #create a MetaCache _before_ we do silly things with the session's users
1630113µs216µs $this->{session}->search->metacache();
# spent 8µs making 1 call to Foswiki::search # spent 8µs making 1 call to Foswiki::Search::metacache
1631
1632 # Temporarily set the user to admin, otherwise it cannot see groups
1633 # where %USERSWEB% is protected from view
163413µs local $this->{session}->{user} = $Foswiki::cfg{SuperAdminGroup};
1635
1636117µs2218ms $this->{session}->search->searchWeb(
# spent 218ms making 1 call to Foswiki::Search::searchWeb # spent 2µs making 1 call to Foswiki::search
1637 _callback => \&_collateGroups,
1638 _cbdata => {
1639 list => $this->{groupsList},
1640 users => $users
1641 },
1642 web => $Foswiki::cfg{UsersWebName},
1643 topic => "*Group",
1644 scope => 'topic',
1645 search => '1',
1646 type => 'query',
1647 nosummary => 'on',
1648 nosearch => 'on',
1649 noheader => 'on',
1650 nototal => 'on',
1651 noempty => 'on',
1652 format => '$topic',
1653 separator => '',
1654 );
1655 }
1656312µs return $this->{groupsList};
1657}
1658
1659# Build hash to translate between username (e.g. jsmith)
1660# and WikiName (e.g. Main.JaneSmith).
1661# PRIVATE subclasses should *not* implement this.
1662
# spent 39.7ms (8.52+31.2) within Foswiki::Users::TopicUserMapping::_loadMapping which was called 747 times, avg 53µs/call: # 381 times (8.12ms+31.2ms) by Foswiki::Users::TopicUserMapping::_userReallyExists at line 237, avg 103µs/call # 210 times (245µs+0s) by Foswiki::Users::TopicUserMapping::handlesUser at line 161, avg 1µs/call # 155 times (154µs+0s) by Foswiki::Users::TopicUserMapping::findUserByWikiName at line 1459, avg 993ns/call # once (1µs+0s) by Foswiki::Users::TopicUserMapping::getWikiName at line 547
sub _loadMapping {
1663747210µs my $this = shift;
1664
16657477.08ms return if $this->{CACHED};
16661400ns $this->{CACHED} = 1;
1667
1668 #TODO: should only really do this mapping IF the user is in the password file.
1669 # except if we can't 'fetchUsers' like in the Passord='none' case -
1670 # in which case the only time we
1671 # know a login is real, is when they are logged in :(
167215µs if ( ( $Foswiki::cfg{Register}{AllowLoginName} )
1673 || ( !$this->{passwords}->canFetchUsers() ) )
1674 {
16751200ns my $session = $this->{session};
167613µs1105µs if (
# spent 105µs making 1 call to Foswiki::topicExists
1677 $session->topicExists(
1678 $Foswiki::cfg{UsersWebName},
1679 $Foswiki::cfg{UsersTopicName}
1680 )
1681 )
1682 {
168315µs1745µs my $usersTopicObject = Foswiki::Meta->load(
# spent 745µs making 1 call to Foswiki::Meta::load
1684 $session,
1685 $Foswiki::cfg{UsersWebName},
1686 $Foswiki::cfg{UsersTopicName}
1687 );
168812µs125µs my $text = $usersTopicObject->text() || '';
# spent 25µs making 1 call to Foswiki::Meta::text
1689
1690 # Get the WikiNames and userids, and build hashes in both directions
1691 # This matches:
1692 # * WikiGuest - guest - 10 Mar 2005
1693 # * WikiGuest - 10 Mar 2005
169410691.20ms106930.3ms $text =~
# spent 30.3ms making 1069 calls to Foswiki::Users::TopicUserMapping::_cacheUser, avg 28µs/call
169515.35mss/^\s*\* (?:$Foswiki::regex{webNameRegex}\.)?($Foswiki::regex{wikiWordRegex})\s*(?:-\s*(\S+)\s*)?-.*$/(_cacheUser( $this, $1, $2)||'')/gome;
1696 }
1697 }
1698 else {
1699
1700 #loginnames _are_ WikiNames so ask the Password handler for list of users
1701 my $iter = $this->{passwords}->fetchUsers();
1702 while ( $iter->hasNext() ) {
1703 my $login = $iter->next();
1704 _cacheUser( $this, $login, $login );
1705 }
1706 }
1707}
1708
1709# Get a list of *canonical user ids* from a text string containing a
1710# list of user *wiki* names, *login* names, and *group ids*.
1711
# spent 12.5ms (1.82+10.6) within Foswiki::Users::TopicUserMapping::_expandUserList which was called 3 times, avg 4.15ms/call: # 3 times (1.82ms+10.6ms) by Foswiki::Users::TopicUserMapping::eachGroupMember at line 719, avg 4.15ms/call
sub _expandUserList {
171234µs my ( $this, $names, $expand ) = @_;
1713
171432µs $expand = 1 unless ( defined $expand );
1715
1716 # print STDERR "_expandUserList called $names - expand $expand \n";
1717
17183800ns $names ||= '';
1719
1720 # comma delimited list of users or groups
1721 # i.e.: "%MAINWEB%.UserA, UserB, Main.UserC # something else"
172236µs $names =~ s/(<[^>]*>)//go; # Remove HTML tags
1723
17243800ns my @l;
1725365µs foreach my $ident ( split( /[\,\s]+/, $names ) ) {
1726
1727 # Dump the web specifier if userweb
1728156197µs $ident =~ s/^($Foswiki::cfg{UsersWebName}|%USERSWEB%|%MAINWEB%)\.//;
172915617µs next unless $ident;
1730156257µs156313µs if ( $this->isGroup($ident) ) {
# spent 313µs making 156 calls to Foswiki::Users::TopicUserMapping::isGroup, avg 2µs/call
1731 if ( !$expand ) {
1732 push( @l, $ident );
1733 }
1734 else {
1735 my $it =
1736 $this->eachGroupMember( $ident, { expand => $expand } );
1737 while ( $it->hasNext() ) {
1738 push( @l, $it->next() );
1739 }
1740 }
1741 }
1742 else {
1743
1744 # Might be a wiki name (wiki names may map to several cUIDs)
1745 my %namelist =
1746 map { $_ => 1 }
1747156616µs15610.2ms @{ $this->{session}->{users}->findUserByWikiName($ident) };
# spent 10.2ms making 156 calls to Foswiki::Users::findUserByWikiName, avg 66µs/call
1748
1749 # If we were not successful in finding by WikiName we assumed it
1750 # may be a login name (login names map to a single cUID).
1751 # If user is unknown we return whatever was listed so we can
1752 # remove deleted or misspelled users
1753156161µs unless (%namelist) {
175412µs170µs my $cUID = $this->{session}->{users}->getCanonicalUserID($ident)
# spent 70µs making 1 call to Foswiki::Users::getCanonicalUserID
1755 || $ident;
175611µs $namelist{$cUID} = 1 if $cUID;
1757 }
1758156174µs push( @l, keys %namelist );
1759 }
1760 }
1761313µs return \@l;
1762}
1763
176414µs1;
1765__END__