Filename | /var/www/foswiki11/lib/Foswiki/Users/TopicUserMapping.pm |
Statements | Executed 29957 statements in 61.4ms |
Calls | P | F | Exclusive Time |
Inclusive Time |
Subroutine |
---|---|---|---|---|---|
1069 | 1 | 1 | 13.7ms | 30.3ms | _cacheUser | Foswiki::Users::TopicUserMapping::
747 | 4 | 1 | 8.52ms | 39.7ms | _loadMapping | Foswiki::Users::TopicUserMapping::
595 | 1 | 1 | 8.50ms | 52.3ms | handlesUser | Foswiki::Users::TopicUserMapping::
381 | 3 | 1 | 3.81ms | 43.6ms | _userReallyExists | Foswiki::Users::TopicUserMapping::
985 | 4 | 3 | 3.67ms | 224ms | isGroup | Foswiki::Users::TopicUserMapping::
1066 | 3 | 2 | 3.50ms | 12.9ms | login2cUID | Foswiki::Users::TopicUserMapping::
3 | 1 | 1 | 1.82ms | 12.5ms | _expandUserList | Foswiki::Users::TopicUserMapping::
1 | 1 | 1 | 1.55ms | 1.56ms | finish | Foswiki::Users::TopicUserMapping::
155 | 1 | 1 | 1.28ms | 1.66ms | findUserByWikiName | Foswiki::Users::TopicUserMapping::
1 | 1 | 1 | 684µs | 751µs | BEGIN@35 | Foswiki::Users::TopicUserMapping::
1 | 1 | 1 | 564µs | 651µs | new | Foswiki::Users::TopicUserMapping::
170 | 1 | 1 | 418µs | 418µs | _collateGroups | Foswiki::Users::TopicUserMapping::
4 | 1 | 1 | 237µs | 15.6ms | eachGroupMember | Foswiki::Users::TopicUserMapping::
3 | 1 | 1 | 76µs | 218ms | _getListOfGroups | Foswiki::Users::TopicUserMapping::
2 | 1 | 1 | 39µs | 2.27ms | isAdmin | Foswiki::Users::TopicUserMapping::
3 | 1 | 1 | 39µs | 218ms | eachGroup | Foswiki::Users::TopicUserMapping::
1 | 1 | 1 | 25µs | 61µs | getLoginName | Foswiki::Users::TopicUserMapping::
1 | 1 | 1 | 19µs | 22µs | getWikiName | Foswiki::Users::TopicUserMapping::
1 | 1 | 1 | 12µs | 14µs | BEGIN@218 | Foswiki::Users::TopicUserMapping::
1 | 1 | 1 | 12µs | 14µs | BEGIN@213 | Foswiki::Users::TopicUserMapping::
1 | 1 | 1 | 10µs | 15µs | BEGIN@32 | Foswiki::Users::TopicUserMapping::
1 | 1 | 1 | 9µs | 9µs | BEGIN@28 | Foswiki::Users::TopicUserMapping::
1 | 1 | 1 | 9µs | 27µs | BEGIN@31 | Foswiki::Users::TopicUserMapping::
1 | 1 | 1 | 8µs | 22µs | BEGIN@33 | Foswiki::Users::TopicUserMapping::
1 | 1 | 1 | 8µs | 115µs | BEGIN@34 | Foswiki::Users::TopicUserMapping::
1 | 1 | 1 | 4µs | 4µs | BEGIN@36 | Foswiki::Users::TopicUserMapping::
1 | 1 | 1 | 900ns | 900ns | supportsRegistration | Foswiki::Users::TopicUserMapping::
0 | 0 | 0 | 0s | 0s | __ANON__[:476] | Foswiki::Users::TopicUserMapping::
0 | 0 | 0 | 0s | 0s | __ANON__[:484] | Foswiki::Users::TopicUserMapping::
0 | 0 | 0 | 0s | 0s | __ANON__[:640] | Foswiki::Users::TopicUserMapping::
0 | 0 | 0 | 0s | 0s | __ANON__[:791] | Foswiki::Users::TopicUserMapping::
0 | 0 | 0 | 0s | 0s | _clearGroupCache | Foswiki::Users::TopicUserMapping::
0 | 0 | 0 | 0s | 0s | _maintainUsersTopic | Foswiki::Users::TopicUserMapping::
0 | 0 | 0 | 0s | 0s | _writeGroupTopic | Foswiki::Users::TopicUserMapping::
0 | 0 | 0 | 0s | 0s | addUser | Foswiki::Users::TopicUserMapping::
0 | 0 | 0 | 0s | 0s | addUserToGroup | Foswiki::Users::TopicUserMapping::
0 | 0 | 0 | 0s | 0s | checkPassword | Foswiki::Users::TopicUserMapping::
0 | 0 | 0 | 0s | 0s | eachMembership | Foswiki::Users::TopicUserMapping::
0 | 0 | 0 | 0s | 0s | eachUser | Foswiki::Users::TopicUserMapping::
0 | 0 | 0 | 0s | 0s | findUserByEmail | Foswiki::Users::TopicUserMapping::
0 | 0 | 0 | 0s | 0s | getEmails | Foswiki::Users::TopicUserMapping::
0 | 0 | 0 | 0s | 0s | groupAllowsChange | Foswiki::Users::TopicUserMapping::
0 | 0 | 0 | 0s | 0s | groupAllowsView | Foswiki::Users::TopicUserMapping::
0 | 0 | 0 | 0s | 0s | mapper_getEmails | Foswiki::Users::TopicUserMapping::
0 | 0 | 0 | 0s | 0s | mapper_setEmails | Foswiki::Users::TopicUserMapping::
0 | 0 | 0 | 0s | 0s | passwordError | Foswiki::Users::TopicUserMapping::
0 | 0 | 0 | 0s | 0s | removeUser | Foswiki::Users::TopicUserMapping::
0 | 0 | 0 | 0s | 0s | removeUserFromGroup | Foswiki::Users::TopicUserMapping::
0 | 0 | 0 | 0s | 0s | setEmails | Foswiki::Users::TopicUserMapping::
0 | 0 | 0 | 0s | 0s | setPassword | Foswiki::Users::TopicUserMapping::
0 | 0 | 0 | 0s | 0s | userExists | Foswiki::Users::TopicUserMapping::
0 | 0 | 0 | 0s | 0s | validateRegistrationField | Foswiki::Users::TopicUserMapping::
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 | |||||
7 | use | ||||
8 | |||||
9 | The User mapping is the process by which Foswiki maps from a username (a login name) | ||||
10 | to a wikiname and back. It is also where groups are defined. | ||||
11 | |||||
12 | By default Foswiki maintains user topics and group topics in the %MAINWEB% that | ||||
13 | define 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 | |||||
18 | Many sites will want to override this behaviour, for example to get users and groups from a corporate database. | ||||
19 | |||||
20 | This class implements the basic Foswiki behaviour using topics to store users, | ||||
21 | but is also designed to be subclassed so that other services can be used. | ||||
22 | |||||
23 | Subclasses should be named 'XxxxUserMapping' so that configure can find them. | ||||
24 | |||||
25 | =cut | ||||
26 | |||||
27 | package Foswiki::Users::TopicUserMapping; | ||||
28 | 2 | 39µs | 1 | 9µs | # spent 9µs within Foswiki::Users::TopicUserMapping::BEGIN@28 which was called:
# once (9µs+0s) by Foswiki::Users::new at line 28 # spent 9µs making 1 call to Foswiki::Users::TopicUserMapping::BEGIN@28 |
29 | 1 | 7µs | our @ISA = ('Foswiki::UserMapping'); | ||
30 | |||||
31 | 2 | 28µs | 2 | 45µ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 # spent 27µs making 1 call to Foswiki::Users::TopicUserMapping::BEGIN@31
# spent 18µs making 1 call to strict::import |
32 | 2 | 24µs | 2 | 21µ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 # spent 15µs making 1 call to Foswiki::Users::TopicUserMapping::BEGIN@32
# spent 6µs making 1 call to warnings::import |
33 | 2 | 25µs | 2 | 36µ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 # spent 22µs making 1 call to Foswiki::Users::TopicUserMapping::BEGIN@33
# spent 14µs making 1 call to Assert::import |
34 | 2 | 28µs | 2 | 222µ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 # spent 115µs making 1 call to Foswiki::Users::TopicUserMapping::BEGIN@34
# spent 107µs making 1 call to Error::import |
35 | 2 | 101µs | 1 | 751µ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 # spent 751µs making 1 call to Foswiki::Users::TopicUserMapping::BEGIN@35 |
36 | 2 | 444µs | 1 | 4µs | # spent 4µs within Foswiki::Users::TopicUserMapping::BEGIN@36 which was called:
# once (4µs+0s) by Foswiki::Users::new at line 36 # 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 | |||||
45 | Constructs a new user mapping handler of this type, referring to $session | ||||
46 | for 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. | ||||
52 | 1 | 400ns | our $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 | ||||
57 | 1 | 900ns | my ( $class, $session ) = @_; | ||
58 | |||||
59 | 1 | 14µs | 1 | 8µs | my $this = $class->SUPER::new( $session, $FOSWIKI_USER_MAPPING_ID ); # spent 8µs making 1 call to Foswiki::UserMapping::new |
60 | |||||
61 | 1 | 1µs | my $implPasswordManager = $Foswiki::cfg{PasswordManager}; | ||
62 | 1 | 700ns | $implPasswordManager = 'Foswiki::Users::Password' | ||
63 | if ( $implPasswordManager eq 'none' ); | ||||
64 | 1 | 23µs | eval "require $implPasswordManager"; # spent 75µs executing statements in string eval | ||
65 | 1 | 200ns | die $@ if $@; | ||
66 | 1 | 4µs | 1 | 11µ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. | ||||
71 | 1 | 2µs | 1 | 1µ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} = {}; | ||||
84 | 1 | 600ns | $this->{L2U} = {}; | ||
85 | 1 | 300ns | $this->{U2W} = {}; | ||
86 | 1 | 400ns | $this->{W2U} = {}; | ||
87 | 1 | 400ns | $this->{eachGroupMember} = {}; | ||
88 | 1 | 800ns | $this->{singleGroupMembers} = (); | ||
89 | |||||
90 | 1 | 4µs | return $this; | ||
91 | } | ||||
92 | |||||
93 | =begin TML | ||||
94 | |||||
95 | ---++ ObjectMethod finish() | ||||
96 | Break 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 | ||||
104 | 1 | 800ns | my $this = shift; | ||
105 | |||||
106 | 1 | 5µs | 1 | 6µs | $this->{passwords}->finish() if $this->{passwords}; # spent 6µs making 1 call to Foswiki::Users::Password::finish |
107 | 1 | 357µs | undef $this->{L2U}; | ||
108 | 1 | 555µs | undef $this->{U2W}; | ||
109 | 1 | 515µs | undef $this->{W2U}; | ||
110 | 1 | 8µs | undef $this->{passwords}; | ||
111 | 1 | 80µs | undef $this->{eachGroupMember}; | ||
112 | 1 | 800ns | undef $this->{singleGroupMembers}; | ||
113 | 1 | 19µs | 1 | 4µs | $this->SUPER::finish(); # spent 4µs making 1 call to Foswiki::UserMapping::finish |
114 | } | ||||
115 | |||||
116 | =begin TML | ||||
117 | |||||
118 | ---++ ObjectMethod supportsRegistration () -> false | ||||
119 | return 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 | ||||
124 | 1 | 3µs | return 1; | ||
125 | } | ||||
126 | |||||
127 | =begin TML | ||||
128 | |||||
129 | ---++ ObjectMethod handlesUser ( $cUID, $login, $wikiname) -> $boolean | ||||
130 | |||||
131 | Called by the Foswiki::Users object to determine which loaded mapping | ||||
132 | to use for a given user. | ||||
133 | |||||
134 | The user can be identified by any of $cUID, $login or $wikiname. Any of | ||||
135 | these parameters may be undef, and they should be tested in order; cUID | ||||
136 | first, then login, then wikiname. This mapping is special - for backwards | ||||
137 | compatibility, it assumes responsibility for _all_ non BaseMapping users. | ||||
138 | If you're needing to mix the TopicUserMapping with other mappings, | ||||
139 | define $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 | ||||
144 | 595 | 419µs | my ( $this, $cUID, $login, $wikiname ) = @_; | ||
145 | 595 | 272µ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 | ||||
153 | 588 | 137µs | return 1 if ( defined $cUID && $cUID =~ /^($this->{mapping_id})/ ); | ||
154 | } | ||||
155 | |||||
156 | # Check the login id to see if we know it | ||||
157 | 588 | 542µs | 378 | 43.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 | ||||
160 | 587 | 99µs | if ($wikiname) { | ||
161 | 210 | 182µs | 210 | 245µs | $this->_loadMapping(); # Sorry Sven, has to be done # spent 245µs making 210 calls to Foswiki::Users::TopicUserMapping::_loadMapping, avg 1µs/call |
162 | 210 | 463µs | return 1 if defined $this->{W2U}->{$wikiname}; | ||
163 | } | ||||
164 | |||||
165 | 432 | 13.3ms | return 0; | ||
166 | } | ||||
167 | |||||
168 | =begin TML | ||||
169 | |||||
170 | ---++ ObjectMethod login2cUID ($login, $dontcheck) -> $cUID | ||||
171 | |||||
172 | Convert a login name to the corresponding canonical user name. The | ||||
173 | canonical name can be any string of 7-bit alphanumeric and underscore | ||||
174 | characters, 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. | ||||
178 | This 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 | ||||
183 | 1066 | 453µs | my ( $this, $login, $dontcheck ) = @_; | ||
184 | |||||
185 | 1066 | 106µs | 2 | 11µ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 | |||||
189 | 1066 | 2.80ms | 1066 | 9.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 | |||||
196 | Converts 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 | ||||
202 | 1 | 1µs | my ( $this, $cUID ) = @_; | ||
203 | 1 | 1µs | 1 | 1µs | ASSERT($cUID) if DEBUG; # spent 1µs making 1 call to Assert::ASSERTS_OFF |
204 | |||||
205 | 1 | 600ns | 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 | ||||
211 | 1 | 900ns | $login =~ s/$this->{mapping_id}// if $this->{mapping_id}; | ||
212 | |||||
213 | 2 | 53µs | 2 | 17µ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 # 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 | ||||
217 | 1 | 2µs | $login =~ s/_([0-9a-f][0-9a-f])/chr(hex($1))/gei; | ||
218 | 2 | 4.85ms | 2 | 16µ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 # spent 14µs making 1 call to Foswiki::Users::TopicUserMapping::BEGIN@218
# spent 2µs making 1 call to bytes::unimport |
219 | |||||
220 | 1 | 2µs | 1 | 7µs | return unless _userReallyExists( $this, $login ); # spent 7µs making 1 call to Foswiki::Users::TopicUserMapping::_userReallyExists |
221 | 1 | 2µs | 1 | 17µs | return unless ( $cUID eq $this->login2cUID($login) ); # spent 17µs making 1 call to Foswiki::Users::TopicUserMapping::login2cUID |
222 | |||||
223 | # Validated | ||||
224 | 1 | 10µs | 1 | 12µ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 | ||||
230 | 381 | 188µs | my ( $this, $login ) = @_; | ||
231 | |||||
232 | 381 | 310µs | if ( $Foswiki::cfg{Register}{AllowLoginName} | ||
233 | || $Foswiki::cfg{PasswordManager} eq 'none' ) | ||||
234 | { | ||||
235 | |||||
236 | # need to use the WikiUsers file | ||||
237 | 381 | 542µs | 381 | 39.3ms | $this->_loadMapping(); # spent 39.3ms making 381 calls to Foswiki::Users::TopicUserMapping::_loadMapping, avg 103µs/call |
238 | 381 | 339µs | return 1 if ( defined( $this->{L2U}->{$login} ) ); | ||
239 | } | ||||
240 | |||||
241 | 377 | 733µs | 377 | 421µ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 { | ||||
253 | 377 | 1.00ms | return 0; | ||
254 | } | ||||
255 | |||||
256 | return 0; | ||||
257 | } | ||||
258 | |||||
259 | =begin TML | ||||
260 | |||||
261 | ---++ ObjectMethod addUser ($login, $wikiname, $password, $emails) -> $cUID | ||||
262 | |||||
263 | throws an Error::Simple | ||||
264 | |||||
265 | Add a user to the persistant mapping that maps from usernames to wikinames | ||||
266 | and 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). | ||||
269 | Names must be acceptable to $Foswiki::cfg{NameFilter} | ||||
270 | $login must *always* be specified. $wikiname may be undef, in which case | ||||
271 | the user mapper should make one up. | ||||
272 | This function must return a *canonical user id* that it uses to uniquely | ||||
273 | identify the user. This can be the login name, or the wikiname if they | ||||
274 | are all guaranteed unigue, or some other string consisting only of 7-bit | ||||
275 | alphanumerics and underscores. | ||||
276 | if 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 | |||||
282 | sub 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 | |||||
334 | throws an Error::Simple | ||||
335 | |||||
336 | Add or remove a user to/from the persistant mapping that maps from usernames to wikinames | ||||
337 | and vice-versa. The default implementation uses a special topic called | ||||
338 | "WikiUsers" in the users web. =cut | ||||
339 | |||||
340 | =cut | ||||
341 | |||||
342 | sub _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 | |||||
493 | Delete the users entry. Removes the user from the password | ||||
494 | manager and user mapping manager. Does *not* remove their personal | ||||
495 | topics, which may still be linked. | ||||
496 | |||||
497 | Note that this must be called with the cUID. If any doubt, resolve the cUID | ||||
498 | by $this->{session}->{users}->getCanonicalUserID($identity). | ||||
499 | |||||
500 | =cut | ||||
501 | |||||
502 | sub 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 | |||||
532 | Map a canonical user name to a wikiname. If it fails to find a | ||||
533 | WikiName, it will attempt to find a matching loginname, and use | ||||
534 | an escaped version of that. | ||||
535 | If 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 | ||||
540 | 1 | 2µs | my ( $this, $cUID ) = @_; | ||
541 | 1 | 1µs | 1 | 1µs | ASSERT($cUID) if DEBUG; # spent 1µs making 1 call to Assert::ASSERTS_OFF |
542 | 1 | 2µs | 1 | 800ns | ASSERT( $cUID =~ /^$this->{mapping_id}/ ) if DEBUG; # spent 800ns making 1 call to Assert::ASSERTS_OFF |
543 | |||||
544 | 1 | 300ns | my $wikiname; | ||
545 | |||||
546 | 1 | 900ns | if ( $Foswiki::cfg{Register}{AllowLoginName} ) { | ||
547 | 1 | 1µs | 1 | 1µs | $this->_loadMapping(); # spent 1µs making 1 call to Foswiki::Users::TopicUserMapping::_loadMapping |
548 | 1 | 2µ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 | |||||
555 | 1 | 600ns | 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 | |||||
564 | 1 | 5µs | return $wikiname; | ||
565 | } | ||||
566 | |||||
567 | =begin TML | ||||
568 | |||||
569 | ---++ ObjectMethod userExists($cUID) -> $boolean | ||||
570 | |||||
571 | Determine if the user already exists or not. Whether a user exists | ||||
572 | or not is determined by the password manager. | ||||
573 | |||||
574 | =cut | ||||
575 | |||||
576 | sub 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 | |||||
621 | See baseclass for documentation | ||||
622 | |||||
623 | =cut | ||||
624 | |||||
625 | sub 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 | |||||
648 | See baseclass for documentation | ||||
649 | |||||
650 | =cut | ||||
651 | |||||
652 | 1 | 200ns | my %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 | ||||
655 | 4 | 4µs | my ( $this, $group, $options ) = @_; | ||
656 | |||||
657 | 4 | 3µs | my $expand = $options->{expand}; | ||
658 | |||||
659 | 4 | 40µs | 4 | 15µ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 | |||||
664 | 4 | 2µs | $expand = 1 unless ( defined $expand ); | ||
665 | |||||
666 | # print STDERR "eachGroupMember called for $group - expand $expand \n"; | ||||
667 | |||||
668 | 4 | 1µ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 | |||||
675 | 4 | 14µs | 1 | 13µ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 | |||||
683 | 3 | 2µs | my $session = $this->{session}; | ||
684 | 3 | 1µs | my $users = $session->{users}; | ||
685 | |||||
686 | 3 | 2µs | my $members = []; | ||
687 | 3 | 1µs | my $singleGroupMembers = []; | ||
688 | |||||
689 | # Determine if we are called recursively, either directly, or by the _expandUserList routine | ||||
690 | 3 | 56µ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 | |||||
698 | 3 | 14µs | 3 | 197µ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 | { | ||||
701 | 3 | 7µs | $expanding{$group} = 1; | ||
702 | |||||
703 | # print "Expanding $group \n"; | ||||
704 | 3 | 15µs | 3 | 2.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 | |||||
708 | 3 | 2µ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 { | ||||
719 | 3 | 20µs | 6 | 13.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') ); | ||||
722 | 3 | 5µs | $this->{eachGroupMember}->{$group} = $members; | ||
723 | } | ||||
724 | |||||
725 | 3 | 4µs | delete $expanding{$group}; | ||
726 | } | ||||
727 | |||||
728 | # print "Returning iterator for eachGroupMember $group \n"; | ||||
729 | 3 | 28µs | 3 | 56µ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 | |||||
736 | See 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 | ||||
741 | 985 | 470µs | my ( $this, $user ) = @_; | ||
742 | |||||
743 | # Groups have the same username as wikiname as canonical name | ||||
744 | 985 | 397µs | return 1 if $user eq $Foswiki::cfg{SuperAdminGroup}; | ||
745 | |||||
746 | 985 | 2.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... | ||||
754 | 3 | 10µs | 3 | 218ms | my $iterator = $this->eachGroup(); # spent 218ms making 3 calls to Foswiki::Users::TopicUserMapping::eachGroup, avg 72.8ms/call |
755 | 3 | 6µs | 3 | 16µs | while ( $iterator->hasNext() ) { # spent 16µs making 3 calls to Foswiki::ListIterator::hasNext, avg 5µs/call |
756 | 231 | 296µs | 231 | 1.27ms | my $groupname = $iterator->next(); # spent 1.27ms making 231 calls to Foswiki::ListIterator::next, avg 5µs/call |
757 | 231 | 357µs | 228 | 830µ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 | |||||
766 | See 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 | ||||
771 | 3 | 3µs | my ($this) = @_; | ||
772 | 3 | 9µs | 3 | 218ms | _getListOfGroups($this); # spent 218ms making 3 calls to Foswiki::Users::TopicUserMapping::_getListOfGroups, avg 72.8ms/call |
773 | 3 | 21µs | 3 | 29µ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 | |||||
780 | See baseclass for documentation | ||||
781 | |||||
782 | =cut | ||||
783 | |||||
784 | sub 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 | |||||
799 | returns 1 if the group is able to be viewed by the current logged in user | ||||
800 | |||||
801 | implemented using topic VIEW permissions | ||||
802 | |||||
803 | =cut | ||||
804 | |||||
805 | sub 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 | |||||
832 | returns 1 if the group is able to be modified by $cuid | ||||
833 | |||||
834 | implemented using topic CHANGE permissions | ||||
835 | |||||
836 | =cut | ||||
837 | |||||
838 | sub 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 | ||||
867 | adds the user specified by the cuid to the group. | ||||
868 | If the group does not exist, it will return false and do nothing, unless the create flag is set. | ||||
869 | |||||
870 | cuid be a groupname which is added like it was an unknown user | ||||
871 | |||||
872 | =cut | ||||
873 | |||||
874 | sub 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. | ||||
991 | sub _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 | |||||
1082 | sub 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 | |||||
1169 | Removes the cache entries for unexpanded and expanded groups, | ||||
1170 | and searches un-expanded groups for any nesting group references | ||||
1171 | clearing them as well. | ||||
1172 | |||||
1173 | Note: This is not recursive and does not attempt to handle | ||||
1174 | more than one level of nested groups. | ||||
1175 | |||||
1176 | =cut | ||||
1177 | |||||
1178 | sub _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 | |||||
1199 | True 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 | ||||
1206 | 2 | 2µs | my ( $this, $cUID ) = @_; | ||
1207 | 2 | 1µs | my $isAdmin = 0; | ||
1208 | |||||
1209 | # TODO: this might not apply now that we have BaseUserMapping - test | ||||
1210 | 2 | 3µs | if ( $cUID eq $Foswiki::cfg{SuperAdminGroup} ) { | ||
1211 | $isAdmin = 1; | ||||
1212 | } | ||||
1213 | else { | ||||
1214 | 1 | 900ns | my $sag = $Foswiki::cfg{SuperAdminGroup}; | ||
1215 | 1 | 18µs | 1 | 2.23ms | $isAdmin = $this->isInGroup( $cUID, $sag ); # spent 2.23ms making 1 call to Foswiki::UserMapping::isInGroup |
1216 | } | ||||
1217 | |||||
1218 | 2 | 18µs | return $isAdmin; | ||
1219 | } | ||||
1220 | |||||
1221 | =begin TML | ||||
1222 | |||||
1223 | ---++ ObjectMethod findUserByEmail( $email ) -> \@cUIDs | ||||
1224 | * =$email= - email address to look up | ||||
1225 | Return a list of canonical user names for the users that have this email | ||||
1226 | registered with the password manager or the user mapping manager. | ||||
1227 | |||||
1228 | The password manager is asked first for whether it maps emails. | ||||
1229 | If it doesn't, then the user mapping manager is asked instead. | ||||
1230 | |||||
1231 | =cut | ||||
1232 | |||||
1233 | sub 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 | |||||
1269 | If $name is a user, return their email addresses. If it is a group, | ||||
1270 | return the addresses of everyone in the group. | ||||
1271 | |||||
1272 | The password manager and user mapping manager are both consulted for emails | ||||
1273 | for each user (where they are actually found is implementation defined). | ||||
1274 | |||||
1275 | Duplicates are removed from the list. | ||||
1276 | |||||
1277 | =cut | ||||
1278 | |||||
1279 | sub 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 | |||||
1327 | Set the email address(es) for the given user. | ||||
1328 | The password manager is tried first, and if it doesn't want to know the | ||||
1329 | user mapping manager is tried. | ||||
1330 | |||||
1331 | =cut | ||||
1332 | |||||
1333 | sub 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 | |||||
1349 | Only used if passwordManager->isManagingEmails= = =false | ||||
1350 | (The emails are stored in the user topics. | ||||
1351 | |||||
1352 | Note: This method is PUBLIC because it is used by the tools/upgrade_emails.pl | ||||
1353 | script, which needs to kick down to the mapper to retrieve email addresses | ||||
1354 | from Wiki topics. | ||||
1355 | |||||
1356 | =cut | ||||
1357 | |||||
1358 | sub 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 | |||||
1393 | Only used if =passwordManager->isManagingEmails= = =false=. | ||||
1394 | (emails are stored in user topics | ||||
1395 | |||||
1396 | =cut | ||||
1397 | |||||
1398 | sub 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 | |||||
1439 | See baseclass for documentation | ||||
1440 | |||||
1441 | The $skipExistanceCheck parameter | ||||
1442 | is private to this module, and blocks the standard existence check | ||||
1443 | to 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 | ||||
1448 | 155 | 71µs | my ( $this, $wn, $skipExistanceCheck ) = @_; | ||
1449 | 155 | 42µs | my @users = (); | ||
1450 | |||||
1451 | 155 | 194µs | 155 | 228µ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 | ||||
1459 | 155 | 138µs | 155 | 154µs | $this->_loadMapping(); # spent 154µs making 155 calls to Foswiki::Users::TopicUserMapping::_loadMapping, avg 993ns/call |
1460 | 155 | 109µs | if ( $this->{W2U}->{$wn} ) { | ||
1461 | |||||
1462 | # Wikiname to UID mapping is defined | ||||
1463 | 155 | 74µs | my $user = $this->{W2U}->{$wn}; | ||
1464 | 155 | 103µ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 | } | ||||
1492 | 155 | 321µs | return \@users; | ||
1493 | } | ||||
1494 | |||||
1495 | =begin TML | ||||
1496 | |||||
1497 | ---++ ObjectMethod checkPassword( $login, $password ) -> $boolean | ||||
1498 | |||||
1499 | Finds if the password is valid for the given user. | ||||
1500 | |||||
1501 | Returns 1 on success, undef on failure. | ||||
1502 | |||||
1503 | =cut | ||||
1504 | |||||
1505 | sub 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 | |||||
1521 | BEWARE: $user should be a cUID, but is a login when the resetPassword | ||||
1522 | functionality is used. | ||||
1523 | The UserMapper needs to convert either one to a valid login for use by | ||||
1524 | the Password manager | ||||
1525 | |||||
1526 | TODO: needs fixing | ||||
1527 | |||||
1528 | If the $oldPassU matches matches the user's password, then it will | ||||
1529 | replace it with $newPassU. | ||||
1530 | |||||
1531 | If $oldPassU is not correct and not 1, will return 0. | ||||
1532 | |||||
1533 | If $oldPassU is 1, will force the change irrespective of | ||||
1534 | the existing password, adding the user if necessary. | ||||
1535 | |||||
1536 | Otherwise returns 1 on success, undef on failure. | ||||
1537 | |||||
1538 | =cut | ||||
1539 | |||||
1540 | sub 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 | |||||
1551 | returns a string indicating the error that happened in the password handlers | ||||
1552 | TODO: these delayed error's should be replaced with Exceptions. | ||||
1553 | |||||
1554 | returns undef if no error | ||||
1555 | |||||
1556 | =cut | ||||
1557 | |||||
1558 | sub passwordError { | ||||
1559 | my ($this) = @_; | ||||
1560 | return $this->{passwords}->error(); | ||||
1561 | } | ||||
1562 | |||||
1563 | =begin TML | ||||
1564 | |||||
1565 | ---++ ObjectMethod validateRegistrationField($field, $value ) -> $string | ||||
1566 | |||||
1567 | This method is called for every field submitted during registration. It is also used | ||||
1568 | to validate the username when adding a member to a group. | ||||
1569 | |||||
1570 | Returns a string containing the sanitized registration field, or can throw an Error::Simple | ||||
1571 | if the field contains illegal data to block the registration. | ||||
1572 | |||||
1573 | returns the string unchanged if no issue found. | ||||
1574 | |||||
1575 | =cut | ||||
1576 | |||||
1577 | sub 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 | ||||
1588 | 1069 | 950µs | my ( $this, $wikiname, $login ) = @_; | ||
1589 | 1069 | 801µs | 1069 | 697µs | ASSERT($wikiname) if DEBUG; # spent 697µs making 1069 calls to Assert::ASSERTS_OFF, avg 652ns/call |
1590 | |||||
1591 | 1069 | 137µs | $login ||= $wikiname; | ||
1592 | |||||
1593 | #discard users that are the BaseUserMapper's responsibility | ||||
1594 | return | ||||
1595 | 1069 | 1.55ms | 1069 | 2.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 | |||||
1598 | 1064 | 1.23ms | 1064 | 12.8ms | my $cUID = $this->login2cUID( $login, 1 ); # spent 12.8ms making 1064 calls to Foswiki::Users::TopicUserMapping::login2cUID, avg 12µs/call |
1599 | 1064 | 105µs | return unless ($cUID); | ||
1600 | 1064 | 909µs | 1064 | 735µs | ASSERT($cUID) if DEBUG; # spent 735µs making 1064 calls to Assert::ASSERTS_OFF, avg 691ns/call |
1601 | |||||
1602 | #$this->{U2L}->{$cUID} = $login; | ||||
1603 | 1064 | 1.01ms | $this->{U2W}->{$cUID} = $wikiname; | ||
1604 | 1064 | 564µs | $this->{L2U}->{$login} = $cUID; | ||
1605 | 1064 | 1.04ms | $this->{W2U}->{$wikiname} = $cUID; | ||
1606 | |||||
1607 | 1064 | 2.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 | ||||
1612 | 170 | 39µs | my $ref = shift; | ||
1613 | 170 | 41µs | my $group = shift; | ||
1614 | 170 | 301µs | return unless $group; | ||
1615 | 85 | 380µ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 | ||||
1620 | 3 | 1µs | my $this = shift; | ||
1621 | 3 | 1µs | my $reset = shift; | ||
1622 | |||||
1623 | 3 | 5µs | 3 | 4µs | ASSERT( $this->isa('Foswiki::Users::TopicUserMapping') ) if DEBUG; # spent 4µs making 3 calls to Assert::ASSERTS_OFF, avg 1µs/call |
1624 | |||||
1625 | 3 | 4µs | if ( !$this->{groupsList} || $reset ) { | ||
1626 | 1 | 1µs | my $users = $this->{session}->{users}; | ||
1627 | 1 | 4µs | $this->{groupsList} = []; | ||
1628 | |||||
1629 | #create a MetaCache _before_ we do silly things with the session's users | ||||
1630 | 1 | 13µs | 2 | 16µ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 | ||||
1634 | 1 | 3µs | local $this->{session}->{user} = $Foswiki::cfg{SuperAdminGroup}; | ||
1635 | |||||
1636 | 1 | 17µs | 2 | 218ms | $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 | } | ||||
1656 | 3 | 12µ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 | ||||
1663 | 747 | 210µs | my $this = shift; | ||
1664 | |||||
1665 | 747 | 7.08ms | return if $this->{CACHED}; | ||
1666 | 1 | 400ns | $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 :( | ||||
1672 | 1 | 5µs | if ( ( $Foswiki::cfg{Register}{AllowLoginName} ) | ||
1673 | || ( !$this->{passwords}->canFetchUsers() ) ) | ||||
1674 | { | ||||
1675 | 1 | 200ns | my $session = $this->{session}; | ||
1676 | 1 | 3µs | 1 | 105µ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 | { | ||||
1683 | 1 | 5µs | 1 | 745µ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 | ); | ||||
1688 | 1 | 2µs | 1 | 25µ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 | ||||
1694 | 1069 | 1.20ms | 1069 | 30.3ms | $text =~ # spent 30.3ms making 1069 calls to Foswiki::Users::TopicUserMapping::_cacheUser, avg 28µs/call |
1695 | 1 | 5.35ms | s/^\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 | ||||
1712 | 3 | 4µs | my ( $this, $names, $expand ) = @_; | ||
1713 | |||||
1714 | 3 | 2µs | $expand = 1 unless ( defined $expand ); | ||
1715 | |||||
1716 | # print STDERR "_expandUserList called $names - expand $expand \n"; | ||||
1717 | |||||
1718 | 3 | 800ns | $names ||= ''; | ||
1719 | |||||
1720 | # comma delimited list of users or groups | ||||
1721 | # i.e.: "%MAINWEB%.UserA, UserB, Main.UserC # something else" | ||||
1722 | 3 | 6µs | $names =~ s/(<[^>]*>)//go; # Remove HTML tags | ||
1723 | |||||
1724 | 3 | 800ns | my @l; | ||
1725 | 3 | 65µs | foreach my $ident ( split( /[\,\s]+/, $names ) ) { | ||
1726 | |||||
1727 | # Dump the web specifier if userweb | ||||
1728 | 156 | 197µs | $ident =~ s/^($Foswiki::cfg{UsersWebName}|%USERSWEB%|%MAINWEB%)\.//; | ||
1729 | 156 | 17µs | next unless $ident; | ||
1730 | 156 | 257µs | 156 | 313µ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 } | ||||
1747 | 156 | 616µs | 156 | 10.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 | ||||
1753 | 156 | 161µs | unless (%namelist) { | ||
1754 | 1 | 2µs | 1 | 70µs | my $cUID = $this->{session}->{users}->getCanonicalUserID($ident) # spent 70µs making 1 call to Foswiki::Users::getCanonicalUserID |
1755 | || $ident; | ||||
1756 | 1 | 1µs | $namelist{$cUID} = 1 if $cUID; | ||
1757 | } | ||||
1758 | 156 | 174µs | push( @l, keys %namelist ); | ||
1759 | } | ||||
1760 | } | ||||
1761 | 3 | 13µs | return \@l; | ||
1762 | } | ||||
1763 | |||||
1764 | 1 | 4µs | 1; | ||
1765 | __END__ |