Filename | /usr/local/src/github.com/foswiki/core/lib/Foswiki/Users/HtPasswdUser.pm |
Statements | Executed 5759 statements in 31.3ms |
Calls | P | F | Exclusive Time |
Inclusive Time |
Subroutine |
---|---|---|---|---|---|
64 | 2 | 1 | 18.2ms | 20.1ms | _readPasswd | Foswiki::Users::HtPasswdUser::
63 | 2 | 1 | 2.73ms | 27.7ms | fetchPass | Foswiki::Users::HtPasswdUser::
63 | 1 | 1 | 1.18ms | 21.2ms | __ANON__[:484] | Foswiki::Users::HtPasswdUser::
402 | 1 | 1 | 971µs | 971µs | CORE:readline (opcode) | Foswiki::Users::HtPasswdUser::
1 | 1 | 1 | 918µs | 1.05ms | BEGIN@18 | Foswiki::Users::HtPasswdUser::
397 | 1 | 1 | 787µs | 787µs | CORE:match (opcode) | Foswiki::Users::HtPasswdUser::
1 | 1 | 1 | 415µs | 662µs | fetchUsers | Foswiki::Users::HtPasswdUser::
64 | 3 | 1 | 240µs | 240µs | canFetchUsers | Foswiki::Users::HtPasswdUser::
1 | 1 | 1 | 201µs | 9.08ms | new | Foswiki::Users::HtPasswdUser::
1 | 1 | 1 | 190µs | 190µs | CORE:sort (opcode) | Foswiki::Users::HtPasswdUser::
1 | 1 | 1 | 54µs | 100µs | readOnly | Foswiki::Users::HtPasswdUser::
1 | 1 | 1 | 46µs | 63µs | finish | Foswiki::Users::HtPasswdUser::
1 | 1 | 1 | 35µs | 60µs | _lockPasswdFile | Foswiki::Users::HtPasswdUser::
3 | 2 | 1 | 35µs | 35µs | CORE:ftis (opcode) | Foswiki::Users::HtPasswdUser::
1 | 1 | 1 | 27µs | 36µs | BEGIN@15 | Foswiki::Users::HtPasswdUser::
1 | 1 | 1 | 21µs | 134µs | BEGIN@26 | Foswiki::Users::HtPasswdUser::
1 | 1 | 1 | 20µs | 44µs | BEGIN@16 | Foswiki::Users::HtPasswdUser::
1 | 1 | 1 | 20µs | 20µs | CORE:sysopen (opcode) | Foswiki::Users::HtPasswdUser::
1 | 1 | 1 | 20µs | 20µs | CORE:open (opcode) | Foswiki::Users::HtPasswdUser::
1 | 1 | 1 | 18µs | 1.73ms | BEGIN@23 | Foswiki::Users::HtPasswdUser::
1 | 1 | 1 | 18µs | 55µs | BEGIN@21 | Foswiki::Users::HtPasswdUser::
1 | 1 | 1 | 17µs | 416µs | BEGIN@22 | Foswiki::Users::HtPasswdUser::
2 | 2 | 1 | 15µs | 15µs | CORE:close (opcode) | Foswiki::Users::HtPasswdUser::
1 | 1 | 1 | 15µs | 22µs | _unlockPasswdFile | Foswiki::Users::HtPasswdUser::
1 | 1 | 1 | 10µs | 10µs | BEGIN@30 | Foswiki::Users::HtPasswdUser::
1 | 1 | 1 | 5µs | 5µs | CORE:fteread (opcode) | Foswiki::Users::HtPasswdUser::
1 | 1 | 1 | 5µs | 5µs | CORE:ftdir (opcode) | Foswiki::Users::HtPasswdUser::
1 | 1 | 1 | 5µs | 5µs | CORE:ftewrite (opcode) | Foswiki::Users::HtPasswdUser::
1 | 1 | 1 | 4µs | 4µs | CORE:flock (opcode) | Foswiki::Users::HtPasswdUser::
0 | 0 | 0 | 0s | 0s | __ANON__[:487] | Foswiki::Users::HtPasswdUser::
0 | 0 | 0 | 0s | 0s | __ANON__[:528] | Foswiki::Users::HtPasswdUser::
0 | 0 | 0 | 0s | 0s | __ANON__[:536] | Foswiki::Users::HtPasswdUser::
0 | 0 | 0 | 0s | 0s | __ANON__[:539] | Foswiki::Users::HtPasswdUser::
0 | 0 | 0 | 0s | 0s | __ANON__[:564] | Foswiki::Users::HtPasswdUser::
0 | 0 | 0 | 0s | 0s | __ANON__[:567] | Foswiki::Users::HtPasswdUser::
0 | 0 | 0 | 0s | 0s | __ANON__[:570] | Foswiki::Users::HtPasswdUser::
0 | 0 | 0 | 0s | 0s | __ANON__[:645] | Foswiki::Users::HtPasswdUser::
0 | 0 | 0 | 0s | 0s | __ANON__[:648] | Foswiki::Users::HtPasswdUser::
0 | 0 | 0 | 0s | 0s | _dumpPasswd | Foswiki::Users::HtPasswdUser::
0 | 0 | 0 | 0s | 0s | _savePasswd | Foswiki::Users::HtPasswdUser::
0 | 0 | 0 | 0s | 0s | checkPassword | Foswiki::Users::HtPasswdUser::
0 | 0 | 0 | 0s | 0s | encrypt | Foswiki::Users::HtPasswdUser::
0 | 0 | 0 | 0s | 0s | findUserByEmail | Foswiki::Users::HtPasswdUser::
0 | 0 | 0 | 0s | 0s | getEmails | Foswiki::Users::HtPasswdUser::
0 | 0 | 0 | 0s | 0s | isManagingEmails | Foswiki::Users::HtPasswdUser::
0 | 0 | 0 | 0s | 0s | removeUser | Foswiki::Users::HtPasswdUser::
0 | 0 | 0 | 0s | 0s | setEmails | Foswiki::Users::HtPasswdUser::
0 | 0 | 0 | 0s | 0s | setPassword | Foswiki::Users::HtPasswdUser::
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::HtPasswdUser | ||||
6 | |||||
7 | Support for htpasswd and htdigest format password files. | ||||
8 | |||||
9 | Subclass of =[[%SCRIPTURL{view}%/%SYSTEMWEB%/PerlDoc?module=Foswiki::Users::Password][Foswiki::Users::Password]]=. | ||||
10 | See documentation of that class for descriptions of the methods of this class. | ||||
11 | |||||
12 | =cut | ||||
13 | |||||
14 | package Foswiki::Users::HtPasswdUser; | ||||
15 | 2 | 50µs | 2 | 46µs | # spent 36µs (27+10) within Foswiki::Users::HtPasswdUser::BEGIN@15 which was called:
# once (27µs+10µs) by Foswiki::Users::TopicUserMapping::new at line 15 # spent 36µs making 1 call to Foswiki::Users::HtPasswdUser::BEGIN@15
# spent 10µs making 1 call to strict::import |
16 | 2 | 47µs | 2 | 69µs | # spent 44µs (20+24) within Foswiki::Users::HtPasswdUser::BEGIN@16 which was called:
# once (20µs+24µs) by Foswiki::Users::TopicUserMapping::new at line 16 # spent 44µs making 1 call to Foswiki::Users::HtPasswdUser::BEGIN@16
# spent 24µs making 1 call to warnings::import |
17 | |||||
18 | 2 | 168µs | 1 | 1.05ms | # spent 1.05ms (918µs+135µs) within Foswiki::Users::HtPasswdUser::BEGIN@18 which was called:
# once (918µs+135µs) by Foswiki::Users::TopicUserMapping::new at line 18 # spent 1.05ms making 1 call to Foswiki::Users::HtPasswdUser::BEGIN@18 |
19 | 1 | 13µs | our @ISA = ('Foswiki::Users::Password'); | ||
20 | |||||
21 | 2 | 49µs | 2 | 92µs | # spent 55µs (18+37) within Foswiki::Users::HtPasswdUser::BEGIN@21 which was called:
# once (18µs+37µs) by Foswiki::Users::TopicUserMapping::new at line 21 # spent 55µs making 1 call to Foswiki::Users::HtPasswdUser::BEGIN@21
# spent 37µs making 1 call to Assert::import |
22 | 2 | 54µs | 2 | 816µs | # spent 416µs (17+400) within Foswiki::Users::HtPasswdUser::BEGIN@22 which was called:
# once (17µs+400µs) by Foswiki::Users::TopicUserMapping::new at line 22 # spent 416µs making 1 call to Foswiki::Users::HtPasswdUser::BEGIN@22
# spent 400µs making 1 call to Error::import |
23 | 2 | 65µs | 2 | 3.44ms | # spent 1.73ms (18µs+1.71) within Foswiki::Users::HtPasswdUser::BEGIN@23 which was called:
# once (18µs+1.71ms) by Foswiki::Users::TopicUserMapping::new at line 23 # spent 1.73ms making 1 call to Foswiki::Users::HtPasswdUser::BEGIN@23
# spent 1.71ms making 1 call to Exporter::import |
24 | |||||
25 | # Set TRACE to 1 to enable detailed trace of password activity | ||||
26 | 2 | 97µs | 2 | 246µs | # spent 134µs (21+112) within Foswiki::Users::HtPasswdUser::BEGIN@26 which was called:
# once (21µs+112µs) by Foswiki::Users::TopicUserMapping::new at line 26 # spent 134µs making 1 call to Foswiki::Users::HtPasswdUser::BEGIN@26
# spent 112µs making 1 call to constant::import |
27 | |||||
28 | # 'Use locale' for internationalisation of Perl sorting in getTopicNames | ||||
29 | # and other routines - main locale settings are done in Foswiki::setupLocale | ||||
30 | # spent 10µs within Foswiki::Users::HtPasswdUser::BEGIN@30 which was called:
# once (10µs+0s) by Foswiki::Users::TopicUserMapping::new at line 37 | ||||
31 | |||||
32 | # Do a dynamic 'use locale' for this module | ||||
33 | 1 | 10µs | if ( $Foswiki::cfg{UseLocale} ) { | ||
34 | require locale; | ||||
35 | import locale(); | ||||
36 | } | ||||
37 | 1 | 5.49ms | 1 | 10µs | } # spent 10µs making 1 call to Foswiki::Users::HtPasswdUser::BEGIN@30 |
38 | |||||
39 | # spent 9.08ms (201µs+8.88) within Foswiki::Users::HtPasswdUser::new which was called:
# once (201µs+8.88ms) by Foswiki::Users::TopicUserMapping::new at line 66 of /usr/local/src/github.com/foswiki/core/lib/Foswiki/Users/TopicUserMapping.pm | ||||
40 | 1 | 3µs | my ( $class, $session ) = @_; | ||
41 | 1 | 25µs | 1 | 22µs | my $this = bless( $class->SUPER::new($session), $class ); # spent 22µs making 1 call to Foswiki::Users::Password::new |
42 | 1 | 2µs | $this->{error} = undef; | ||
43 | |||||
44 | 1 | 4µs | if ( $Foswiki::cfg{Htpasswd}{AutoDetect} ) { | ||
45 | |||||
46 | # For autodetect, soft errors are allowed. If the .htpasswd file contains | ||||
47 | # a password for an unsupported encoding, it will not match. | ||||
48 | 1 | 60µs | eval 'use Digest::SHA'; # spent 172µs executing statements in string eval # includes 5.00ms spent executing 1 call to 1 sub defined therein. | ||
49 | 1 | 3µs | $this->{SHA} = 1 unless ($@); | ||
50 | 1 | 64µs | eval 'use Crypt::PasswdMD5'; # spent 247µs executing statements in string eval # includes 1.26ms spent executing 1 call to 1 sub defined therein. | ||
51 | 1 | 3µs | $this->{APR} = 1 unless ($@); | ||
52 | |||||
53 | } | ||||
54 | |||||
55 | 1 | 6µs | if ( $Foswiki::cfg{Htpasswd}{Encoding} eq 'md5' | ||
56 | || $Foswiki::cfg{Htpasswd}{Encoding} eq 'htdigest-md5' ) | ||||
57 | { | ||||
58 | require Digest::MD5; | ||||
59 | if ( $Foswiki::cfg{AuthRealm} =~ /\:/ ) { | ||||
60 | print STDERR | ||||
61 | "ERROR: the AuthRealm cannot contain a ':' (colon) as it corrupts the password file\n"; | ||||
62 | throw Error::Simple( | ||||
63 | "ERROR: the AuthRealm cannot contain a ':' (colon) as it corrupts the password file" | ||||
64 | ); | ||||
65 | } | ||||
66 | } | ||||
67 | elsif ( $Foswiki::cfg{Htpasswd}{Encoding} eq 'crypt' ) { | ||||
68 | } | ||||
69 | elsif ( $Foswiki::cfg{Htpasswd}{Encoding} eq 'plain' ) { | ||||
70 | } | ||||
71 | elsif ( $Foswiki::cfg{Htpasswd}{Encoding} eq 'sha1' ) { | ||||
72 | 1 | 2µs | require Digest::SHA; | ||
73 | 1 | 2µs | $this->{SHA} = 1; | ||
74 | } | ||||
75 | elsif ( $Foswiki::cfg{Htpasswd}{Encoding} eq 'apache-md5' ) { | ||||
76 | require Crypt::PasswdMD5; | ||||
77 | $this->{APR} = 1; | ||||
78 | } | ||||
79 | elsif ( $Foswiki::cfg{Htpasswd}{Encoding} eq 'crypt-md5' ) { | ||||
80 | eval 'use Crypt::PasswdMD5'; | ||||
81 | $this->{APR} = 1 unless ($@); | ||||
82 | } | ||||
83 | else { | ||||
84 | print STDERR "ERROR: unknown {Htpasswd}{Encoding} setting : " | ||||
85 | . $Foswiki::cfg{Htpasswd}{Encoding} . "\n"; | ||||
86 | throw Error::Simple( "ERROR: unknown {Htpasswd}{Encoding} setting : " | ||||
87 | . $Foswiki::cfg{Htpasswd}{Encoding} | ||||
88 | . "\n" ); | ||||
89 | } | ||||
90 | |||||
91 | 1 | 9µs | return $this; | ||
92 | } | ||||
93 | |||||
94 | =begin TML | ||||
95 | |||||
96 | ---++ ObjectMethod finish() | ||||
97 | Break circular references. | ||||
98 | |||||
99 | =cut | ||||
100 | |||||
101 | # Note to developers; please undef *all* fields in the object explicitly, | ||||
102 | # whether they are references or not. That way this method is "golden | ||||
103 | # documentation" of the live fields in the object. | ||||
104 | # spent 63µs (46+18) within Foswiki::Users::HtPasswdUser::finish which was called:
# once (46µs+18µs) by Foswiki::Users::TopicUserMapping::finish at line 106 of /usr/local/src/github.com/foswiki/core/lib/Foswiki/Users/TopicUserMapping.pm | ||||
105 | 1 | 3µs | my $this = shift; | ||
106 | 1 | 31µs | 1 | 18µs | $this->SUPER::finish(); # spent 18µs making 1 call to Foswiki::Users::Password::finish |
107 | 1 | 11µs | undef $this->{passworddata}; | ||
108 | } | ||||
109 | |||||
110 | =begin TML | ||||
111 | |||||
112 | ---++ ObjectMethod readOnly( ) -> boolean | ||||
113 | |||||
114 | returns true if the password file is not currently modifyable | ||||
115 | |||||
116 | =cut | ||||
117 | |||||
118 | # spent 100µs (54+46) within Foswiki::Users::HtPasswdUser::readOnly which was called:
# once (54µs+46µs) by Foswiki::Users::TopicUserMapping::new at line 71 of /usr/local/src/github.com/foswiki/core/lib/Foswiki/Users/TopicUserMapping.pm | ||||
119 | 1 | 2µs | my $this = shift; | ||
120 | 1 | 3µs | my $path = $Foswiki::cfg{Htpasswd}{FileName}; | ||
121 | |||||
122 | #TODO: what if the data dir is also read only? | ||||
123 | 1 | 68µs | 5 | 34µs | if ( ( !-e $path ) || ( -e $path && -r $path && !-d $path && -w $path ) ) { # spent 19µs making 2 calls to Foswiki::Users::HtPasswdUser::CORE:ftis, avg 9µs/call
# spent 5µs making 1 call to Foswiki::Users::HtPasswdUser::CORE:fteread
# spent 5µs making 1 call to Foswiki::Users::HtPasswdUser::CORE:ftdir
# spent 5µs making 1 call to Foswiki::Users::HtPasswdUser::CORE:ftewrite |
124 | 1 | 7µs | 1 | 12µs | $this->{session}->enterContext('passwords_modifyable'); # spent 12µs making 1 call to Foswiki::enterContext |
125 | 1 | 7µs | return 0; | ||
126 | } | ||||
127 | return 1; | ||||
128 | } | ||||
129 | |||||
130 | # spent 240µs within Foswiki::Users::HtPasswdUser::canFetchUsers which was called 64 times, avg 4µs/call:
# 61 times (229µs+0s) by Foswiki::Users::TopicUserMapping::_userReallyExists at line 241 of /usr/local/src/github.com/foswiki/core/lib/Foswiki/Users/TopicUserMapping.pm, avg 4µs/call
# 2 times (7µs+0s) by Foswiki::Users::TopicUserMapping::userExists at line 539 of /usr/local/src/github.com/foswiki/core/lib/Foswiki/Users/TopicUserMapping.pm, avg 4µs/call
# once (4µs+0s) by Foswiki::Users::TopicUserMapping::_loadMapping at line 1568 of /usr/local/src/github.com/foswiki/core/lib/Foswiki/Users/TopicUserMapping.pm | ||||
131 | 64 | 362µs | return 1; | ||
132 | } | ||||
133 | |||||
134 | # spent 662µs (415+247) within Foswiki::Users::HtPasswdUser::fetchUsers which was called:
# once (415µs+247µs) by Foswiki::Users::TopicUserMapping::_loadMapping at line 1597 of /usr/local/src/github.com/foswiki/core/lib/Foswiki/Users/TopicUserMapping.pm | ||||
135 | 1 | 2µs | my $this = shift; | ||
136 | |||||
137 | # Read passwords with shared lock | ||||
138 | 1 | 7µs | 1 | 8µs | my $db = $this->_readPasswd(1); # spent 8µs making 1 call to Foswiki::Users::HtPasswdUser::_readPasswd |
139 | 1 | 568µs | 1 | 190µs | my @users = sort keys %$db; # spent 190µs making 1 call to Foswiki::Users::HtPasswdUser::CORE:sort |
140 | 1 | 3µs | require Foswiki::ListIterator; | ||
141 | 1 | 22µs | 1 | 49µs | return new Foswiki::ListIterator( \@users ); # spent 49µs making 1 call to Foswiki::ListIterator::new |
142 | } | ||||
143 | |||||
144 | # Lock the htpasswd semaphore file (create if it does not exist) | ||||
145 | # Returns a file handle that you can later simply close with _unlockPasswdFile | ||||
146 | # spent 60µs (35+24) within Foswiki::Users::HtPasswdUser::_lockPasswdFile which was called:
# once (35µs+24µs) by Foswiki::Users::HtPasswdUser::_readPasswd at line 184 | ||||
147 | 1 | 2µs | my $operator = @_; | ||
148 | 1 | 4µs | my $lockFileName = $Foswiki::cfg{WorkingDir} . '/htpasswd.lock'; | ||
149 | |||||
150 | 1 | 35µs | 1 | 20µs | sysopen( my $fh, $lockFileName, O_RDWR | O_CREAT, 0666 ) # spent 20µs making 1 call to Foswiki::Users::HtPasswdUser::CORE:sysopen |
151 | || throw Error::Simple( $lockFileName | ||||
152 | . ' open or create password lock file failed -' | ||||
153 | . 'check access rights: ' | ||||
154 | . $! ); | ||||
155 | 1 | 13µs | 1 | 4µs | flock $fh, $operator; # spent 4µs making 1 call to Foswiki::Users::HtPasswdUser::CORE:flock |
156 | |||||
157 | 1 | 8µs | return $fh; | ||
158 | } | ||||
159 | |||||
160 | # Unlock the semaphore file. You must pass the filehandle for the lock file | ||||
161 | # which was returned by _lockPasswdFile | ||||
162 | # spent 22µs (15+7) within Foswiki::Users::HtPasswdUser::_unlockPasswdFile which was called:
# once (15µs+7µs) by Foswiki::Users::HtPasswdUser::_readPasswd at line 284 | ||||
163 | 1 | 2µs | my $fh = shift; | ||
164 | 1 | 21µs | 1 | 7µs | close($fh); # spent 7µs making 1 call to Foswiki::Users::HtPasswdUser::CORE:close |
165 | } | ||||
166 | |||||
167 | # Read the password file. The content of the file is cached in | ||||
168 | # the password object. | ||||
169 | # We put a shared lock while reading if requested to prevent | ||||
170 | # other processes from writing while we read but still allows | ||||
171 | # parallel reading. The caller must never request a shared lock | ||||
172 | # if there is already an exclusive lock. | ||||
173 | # spent 20.1ms (18.2+1.88) within Foswiki::Users::HtPasswdUser::_readPasswd which was called 64 times, avg 313µs/call:
# 63 times (18.2ms+1.88ms) by Foswiki::Users::HtPasswdUser::__ANON__[/usr/local/src/github.com/foswiki/core/lib/Foswiki/Users/HtPasswdUser.pm:484] at line 475, avg 318µs/call
# once (8µs+0s) by Foswiki::Users::HtPasswdUser::fetchUsers at line 138 | ||||
174 | 64 | 110µs | my ( $this, $lockShared ) = @_; | ||
175 | |||||
176 | 64 | 471µs | return $this->{passworddata} if ( defined( $this->{passworddata} ) ); | ||
177 | |||||
178 | 1 | 2µs | my $data = {}; | ||
179 | 1 | 25µs | 1 | 16µs | if ( !-e $Foswiki::cfg{Htpasswd}{FileName} ) { # spent 16µs making 1 call to Foswiki::Users::HtPasswdUser::CORE:ftis |
180 | return $data; | ||||
181 | } | ||||
182 | |||||
183 | 1 | 2µs | $lockShared |= 0; | ||
184 | 1 | 7µs | 1 | 60µs | my $lockHandle = _lockPasswdFile(LOCK_SH) if $lockShared; # spent 60µs making 1 call to Foswiki::Users::HtPasswdUser::_lockPasswdFile |
185 | 1 | 1µs | my $IN_FILE; | ||
186 | |||||
187 | 1 | 34µs | 1 | 20µs | open( $IN_FILE, '<', "$Foswiki::cfg{Htpasswd}{FileName}" ) # spent 20µs making 1 call to Foswiki::Users::HtPasswdUser::CORE:open |
188 | || throw Error::Simple( | ||||
189 | $Foswiki::cfg{Htpasswd}{FileName} . ' open failed: ' . $! ); | ||||
190 | 1 | 2µs | my $line = ''; | ||
191 | 1 | 2µs | my $tID; | ||
192 | 1 | 3.14ms | 402 | 971µs | while ( defined( $line = <$IN_FILE> ) ) { # spent 971µs making 402 calls to Foswiki::Users::HtPasswdUser::CORE:readline, avg 2µs/call |
193 | 401 | 751µs | chomp $line; | ||
194 | 401 | 2.38ms | my @fields = split( /:/, $line, 5 ); | ||
195 | |||||
196 | if (TRACE) { | ||||
197 | print "\nSplit LINE $line\n"; | ||||
198 | foreach my $f (@fields) { print "split: $f\n"; } | ||||
199 | } | ||||
200 | |||||
201 | 401 | 799µs | my $hID = shift @fields; | ||
202 | |||||
203 | 401 | 777µs | if ( $Foswiki::cfg{Htpasswd}{AutoDetect} ) { | ||
204 | 401 | 782µs | my $tPass = shift @fields; | ||
205 | |||||
206 | # tPass is either a password or a realm | ||||
207 | 401 | 1.37ms | if ( | ||
208 | $tPass eq $Foswiki::cfg{AuthRealm} | ||||
209 | || ( defined $fields[0] | ||||
210 | && length( $fields[0] ) eq 32 | ||||
211 | && defined $fields[1] | ||||
212 | && $fields[1] =~ m/@/ ) | ||||
213 | ) | ||||
214 | { | ||||
215 | $data->{$hID}->{enc} = 'htdigest-md5'; | ||||
216 | $data->{$hID}->{realm} = $tPass; | ||||
217 | $data->{$hID}->{pass} = shift @fields; | ||||
218 | $data->{$hID}->{emails} = shift @fields || ''; | ||||
219 | print STDERR "Auto ENCODING-1 $data->{$hID}->{enc} \n" | ||||
220 | if (TRACE); | ||||
221 | next; | ||||
222 | } | ||||
223 | |||||
224 | 401 | 4.15ms | 397 | 787µs | if ( length($tPass) eq 33 && $tPass =~ m/^\{SHA\}/ ) { # spent 787µs making 397 calls to Foswiki::Users::HtPasswdUser::CORE:match, avg 2µs/call |
225 | $data->{$hID}->{enc} = 'sha1'; | ||||
226 | } | ||||
227 | elsif ( length($tPass) eq 34 && $tPass =~ m/^\$1\$/ ) { | ||||
228 | $data->{$hID}->{enc} = 'crypt-md5'; | ||||
229 | } | ||||
230 | elsif ( length($tPass) eq 37 && $tPass =~ m/^\$apr1\$/ ) { | ||||
231 | $data->{$hID}->{enc} = 'apache-md5'; | ||||
232 | } | ||||
233 | elsif ( length($tPass) eq 13 | ||||
234 | && ( !$fields[0] || $fields[0] =~ m/@/ ) ) | ||||
235 | { | ||||
236 | 401 | 1.57ms | $data->{$hID}->{enc} = 'crypt'; | ||
237 | } | ||||
238 | elsif ( length($tPass) gt 0 && !$fields[0] | ||||
239 | || $fields[0] =~ m/@/ ) | ||||
240 | { | ||||
241 | $data->{$hID}->{enc} = 'plain'; | ||||
242 | } | ||||
243 | elsif ( length($tPass) eq 0 && !$fields[0] | ||||
244 | || $fields[0] =~ m/@/ ) | ||||
245 | { | ||||
246 | $data->{$hID}->{enc} = 'sha'; | ||||
247 | } | ||||
248 | |||||
249 | 401 | 740µs | if ( $data->{$hID}->{enc} ) { | ||
250 | 401 | 1.01ms | $data->{$hID}->{pass} = $tPass; | ||
251 | 401 | 1.20ms | $data->{$hID}->{emails} = shift @fields || ''; | ||
252 | print STDERR "Auto ENCODING-2 $data->{$hID}->{enc} \n" | ||||
253 | if (TRACE); | ||||
254 | 401 | 782µs | next; | ||
255 | } | ||||
256 | |||||
257 | print STDERR "Fell through - must be htdigest-md5 " | ||||
258 | . length($tPass) | ||||
259 | . "--$tPass \n" | ||||
260 | if (TRACE); | ||||
261 | |||||
262 | # Fell through - only thing left is digest encoding | ||||
263 | $data->{$hID}->{enc} = 'htdigest-md5'; | ||||
264 | $data->{$hID}->{realm} = $tPass; | ||||
265 | $data->{$hID}->{pass} = shift @fields; | ||||
266 | $data->{$hID}->{emails} = shift @fields || ''; | ||||
267 | print STDERR "Auto ENCODING-3 $data->{$hID}->{enc} \n" if (TRACE); | ||||
268 | } | ||||
269 | |||||
270 | # Static configuration | ||||
271 | else { | ||||
272 | $data->{$hID}->{enc} = $Foswiki::cfg{Htpasswd}{Encoding}; | ||||
273 | $data->{$hID}->{realm} = shift @fields | ||||
274 | if ( $Foswiki::cfg{Htpasswd}{Encoding} eq 'md5' | ||||
275 | || $Foswiki::cfg{Htpasswd}{Encoding} eq 'htdigest-md5' ); | ||||
276 | $data->{$hID}->{pass} = shift @fields; | ||||
277 | $data->{$hID}->{emails} = shift @fields || ''; | ||||
278 | print STDERR | ||||
279 | "Static Encoding - $hID: $data->{$hID}->{enc} pass $data->{$hID}->{pass} emails $data->{$hID}->{emails} \n" | ||||
280 | if (TRACE); | ||||
281 | } | ||||
282 | } | ||||
283 | 1 | 21µs | 1 | 8µs | close($IN_FILE); # spent 8µs making 1 call to Foswiki::Users::HtPasswdUser::CORE:close |
284 | 1 | 8µs | 1 | 22µs | _unlockPasswdFile($lockHandle) if $lockShared; # spent 22µs making 1 call to Foswiki::Users::HtPasswdUser::_unlockPasswdFile |
285 | |||||
286 | 1 | 3µs | $this->{passworddata} = $data; | ||
287 | 1 | 29µs | return $data; | ||
288 | } | ||||
289 | |||||
290 | # Dumps the memory password database to a newline separated string | ||||
291 | sub _dumpPasswd { | ||||
292 | my $db = shift; | ||||
293 | my @entries; | ||||
294 | foreach my $login ( sort( keys(%$db) ) ) { | ||||
295 | |||||
296 | my $entry = "$login:"; | ||||
297 | if ( | ||||
298 | $db->{$login}->{pass} | ||||
299 | && $db->{$login}->{enc} | ||||
300 | && ( $db->{$login}->{enc} eq 'md5' | ||||
301 | || $db->{$login}->{enc} eq 'htdigest-md5' ) | ||||
302 | ) | ||||
303 | { | ||||
304 | print STDERR | ||||
305 | "Writing realm - $db->{$login}->{enc} for $login pass ($db->{$login}->{pass})\n" | ||||
306 | if (TRACE); | ||||
307 | |||||
308 | # htdigest format | ||||
309 | $entry .= "$Foswiki::cfg{AuthRealm}:"; | ||||
310 | } | ||||
311 | $db->{$login}->{pass} ||= ''; | ||||
312 | $db->{$login}->{emails} ||= ''; | ||||
313 | $entry .= $db->{$login}->{pass} . ':' . $db->{$login}->{emails}; | ||||
314 | push( @entries, $entry ); | ||||
315 | } | ||||
316 | return join( "\n", @entries ) . "\n"; | ||||
317 | } | ||||
318 | |||||
319 | sub _savePasswd { | ||||
320 | my $db = shift; | ||||
321 | |||||
322 | unless ( -e "$Foswiki::cfg{Htpasswd}{FileName}" ) { | ||||
323 | |||||
324 | # Item4544: Document special format used in .htpasswd for email addresses | ||||
325 | open( my $readme, '>', "$Foswiki::cfg{Htpasswd}{FileName}.README" ) | ||||
326 | or throw Error::Simple( | ||||
327 | $Foswiki::cfg{Htpasswd}{FileName} . '.README open failed: ' . $! ); | ||||
328 | |||||
329 | print $readme <<'EoT'; | ||||
330 | Foswiki uses a specially crafted .htpasswd file format that should not be | ||||
331 | manipulated using a standard htpasswd utility or loss of registered emails might occur. | ||||
332 | (3rd-party utilities do not support the email address format used by Foswiki). | ||||
333 | |||||
334 | More information available at: http://foswiki.org/System/UserAuthentication. | ||||
335 | EoT | ||||
336 | close($readme); | ||||
337 | } | ||||
338 | |||||
339 | my $content = _dumpPasswd($db); | ||||
340 | print STDERR "CONTENT $content\n" if (TRACE); | ||||
341 | |||||
342 | my $oldMask = umask(077); # Access only by owner | ||||
343 | my $fh; | ||||
344 | |||||
345 | open( $fh, '>', $Foswiki::cfg{Htpasswd}{FileName} ) | ||||
346 | || throw Error::Simple( | ||||
347 | "$Foswiki::cfg{Htpasswd}{FileName} open failed: $!"); | ||||
348 | print $fh $content; | ||||
349 | |||||
350 | close($fh); | ||||
351 | umask($oldMask); # Restore original umask | ||||
352 | } | ||||
353 | |||||
354 | sub encrypt { | ||||
355 | my ( $this, $login, $passwd, $fresh, $entry ) = @_; | ||||
356 | |||||
357 | $passwd ||= ''; | ||||
358 | |||||
359 | my $enc = $entry->{enc}; | ||||
360 | $enc ||= $Foswiki::cfg{Htpasswd}{Encoding}; | ||||
361 | |||||
362 | if ( $enc eq 'sha1' ) { | ||||
363 | |||||
364 | unless ( $this->{SHA} ) { | ||||
365 | $this->{error} = "Unsupported Encoding"; | ||||
366 | return 0; | ||||
367 | } | ||||
368 | |||||
369 | my $encodedPassword = '{SHA}' . Digest::SHA::sha1_base64($passwd) . '='; | ||||
370 | |||||
371 | # don't use chomp, it relies on $/ | ||||
372 | $encodedPassword =~ s/\s+$//; | ||||
373 | return $encodedPassword; | ||||
374 | |||||
375 | } | ||||
376 | elsif ( $enc eq 'crypt' ) { | ||||
377 | |||||
378 | # by David Levy, Internet Channel, 1997 | ||||
379 | # found at http://world.inch.com/Scripts/htpasswd.pl.html | ||||
380 | |||||
381 | my $salt; | ||||
382 | $salt = $this->fetchPass($login) unless $fresh; | ||||
383 | if ( $fresh || !$salt ) { | ||||
384 | my @saltchars = ( 'a' .. 'z', 'A' .. 'Z', '0' .. '9', '.', '/' ); | ||||
385 | $salt = | ||||
386 | $saltchars[ int( rand( $#saltchars + 1 ) ) ] | ||||
387 | . $saltchars[ int( rand( $#saltchars + 1 ) ) ]; | ||||
388 | } | ||||
389 | return crypt( $passwd, substr( $salt, 0, 2 ) ); | ||||
390 | |||||
391 | } | ||||
392 | elsif ( $enc eq 'md5' || $enc eq 'htdigest-md5' ) { | ||||
393 | |||||
394 | # SMELL: what does this do if we are using a htpasswd file? | ||||
395 | my $realm = $entry->{realm} || $Foswiki::cfg{AuthRealm}; | ||||
396 | my $toEncode = "$login:$realm:$passwd"; | ||||
397 | return Digest::MD5::md5_hex($toEncode); | ||||
398 | |||||
399 | } | ||||
400 | elsif ( $enc eq 'apache-md5' ) { | ||||
401 | |||||
402 | unless ( $this->{APR} ) { | ||||
403 | $this->{error} = "Unsupported Encoding"; | ||||
404 | return 0; | ||||
405 | } | ||||
406 | |||||
407 | my $salt; | ||||
408 | $salt = $this->fetchPass($login) unless $fresh; | ||||
409 | if ( $fresh || !$salt ) { | ||||
410 | $salt = '$apr1$'; | ||||
411 | my @saltchars = ( '.', '/', 0 .. 9, 'A' .. 'Z', 'a' .. 'z' ); | ||||
412 | foreach my $i ( 0 .. 7 ) { | ||||
413 | |||||
414 | # generate a salt not only from rand() but also mixing | ||||
415 | # in the users login name: unecessary | ||||
416 | $salt .= $saltchars[ | ||||
417 | ( | ||||
418 | int( rand( $#saltchars + 1 ) ) + | ||||
419 | $i + | ||||
420 | ord( substr( $login, $i % length($login), 1 ) ) ) | ||||
421 | % ( $#saltchars + 1 ) | ||||
422 | ]; | ||||
423 | } | ||||
424 | } | ||||
425 | return Crypt::PasswdMD5::apache_md5_crypt( $passwd, | ||||
426 | substr( $salt, 0, 14 ) ); | ||||
427 | } | ||||
428 | elsif ( $enc eq 'crypt-md5' ) { | ||||
429 | my $salt; | ||||
430 | $salt = $this->fetchPass($login) unless $fresh; | ||||
431 | if ( $fresh || !$salt ) { | ||||
432 | $salt = '$1$'; | ||||
433 | my @saltchars = ( '.', '/', 0 .. 9, 'A' .. 'Z', 'a' .. 'z' ); | ||||
434 | foreach my $i ( 0 .. 7 ) { | ||||
435 | |||||
436 | # generate a salt not only from rand() but also mixing | ||||
437 | # in the users login name: unecessary | ||||
438 | $salt .= $saltchars[ | ||||
439 | ( | ||||
440 | int( rand( $#saltchars + 1 ) ) + | ||||
441 | $i + | ||||
442 | ord( substr( $login, $i % length($login), 1 ) ) ) | ||||
443 | % ( $#saltchars + 1 ) | ||||
444 | ]; | ||||
445 | } | ||||
446 | } | ||||
447 | |||||
448 | # crypt is not cross-plaform, so use Crypt::PasswdMD5 if it's available | ||||
449 | if ( $this->{APR} ) { | ||||
450 | return Crypt::PasswdMD5::unix_md5_crypt( $passwd, | ||||
451 | substr( $salt, 0, 11 ) ); | ||||
452 | } | ||||
453 | else { | ||||
454 | return crypt( $passwd, substr( $salt, 0, 11 ) ); | ||||
455 | } | ||||
456 | |||||
457 | } | ||||
458 | elsif ( $enc eq 'plain' ) { | ||||
459 | return $passwd; | ||||
460 | |||||
461 | } | ||||
462 | die 'Unsupported password encoding ' . $enc; | ||||
463 | } | ||||
464 | |||||
465 | # spent 27.7ms (2.73+25.0) within Foswiki::Users::HtPasswdUser::fetchPass which was called 63 times, avg 440µs/call:
# 61 times (2.66ms+24.9ms) by Foswiki::Users::TopicUserMapping::_userReallyExists at line 246 of /usr/local/src/github.com/foswiki/core/lib/Foswiki/Users/TopicUserMapping.pm, avg 451µs/call
# 2 times (79µs+157µs) by Foswiki::Users::TopicUserMapping::userExists at line 539 of /usr/local/src/github.com/foswiki/core/lib/Foswiki/Users/TopicUserMapping.pm, avg 118µs/call | ||||
466 | 63 | 111µs | my ( $this, $login ) = @_; | ||
467 | 63 | 82µs | my $ret = 0; | ||
468 | 63 | 82µs | my $enc = ''; | ||
469 | 63 | 79µs | my $db; | ||
470 | |||||
471 | 63 | 529µs | if ($login) { | ||
472 | # spent 21.2ms (1.18+20.0) within Foswiki::Users::HtPasswdUser::__ANON__[/usr/local/src/github.com/foswiki/core/lib/Foswiki/Users/HtPasswdUser.pm:484] which was called 63 times, avg 337µs/call:
# 63 times (1.18ms+20.0ms) by Error::subs::try at line 416 of Error.pm, avg 337µs/call | ||||
473 | |||||
474 | # Read passwords with shared lock | ||||
475 | 63 | 328µs | 63 | 20.0ms | $db = $this->_readPasswd(1); # spent 20.0ms making 63 calls to Foswiki::Users::HtPasswdUser::_readPasswd, avg 318µs/call |
476 | 63 | 471µs | if ( exists $db->{$login} ) { | ||
477 | 19 | 68µs | $ret = $db->{$login}->{pass}; | ||
478 | 19 | 36µs | $enc = $db->{$login}->{enc}; | ||
479 | } | ||||
480 | else { | ||||
481 | 44 | 137µs | $this->{error} = "Login $login invalid"; | ||
482 | 44 | 65µs | $ret = undef; | ||
483 | } | ||||
484 | } | ||||
485 | catch Error::Simple with { | ||||
486 | $this->{error} = $!; | ||||
487 | 63 | 1.32ms | 189 | 1.15ms | }; # spent 882µs making 63 calls to Error::catch, avg 14µs/call
# spent 265µs making 63 calls to Error::subs::with, avg 4µs/call
# spent 23.9ms making 63 calls to Error::subs::try, avg 379µs/call, recursion: max depth 2, sum of overlapping time 23.9ms |
488 | } | ||||
489 | else { | ||||
490 | $this->{error} = 'No user'; | ||||
491 | } | ||||
492 | 63 | 432µs | return (wantarray) ? ( $ret, $db->{$login} ) : $ret; | ||
493 | } | ||||
494 | |||||
495 | sub setPassword { | ||||
496 | my ( $this, $login, $newUserPassword, $oldUserPassword ) = @_; | ||||
497 | ASSERT($login) if DEBUG; | ||||
498 | |||||
499 | if ( defined($oldUserPassword) ) { | ||||
500 | unless ( $oldUserPassword eq '1' ) { | ||||
501 | return 0 unless $this->checkPassword( $login, $oldUserPassword ); | ||||
502 | } | ||||
503 | } | ||||
504 | elsif ( $this->fetchPass($login) ) { | ||||
505 | $this->{error} = $login . ' already exists'; | ||||
506 | return 0; | ||||
507 | } | ||||
508 | |||||
509 | my $lockHandle; | ||||
510 | try { | ||||
511 | $lockHandle = _lockPasswdFile(LOCK_EX); | ||||
512 | |||||
513 | # Read password without shared lock as we have already exclusive lock | ||||
514 | my $db = $this->_readPasswd(0); | ||||
515 | |||||
516 | $db->{$login}->{pass} = $this->encrypt( $login, $newUserPassword, 1 ); | ||||
517 | $db->{$login}->{enc} = $Foswiki::cfg{Htpasswd}{Encoding}; | ||||
518 | $db->{$login}->{realm} = | ||||
519 | ( $Foswiki::cfg{Htpasswd}{Encoding} eq 'md5' | ||||
520 | || $Foswiki::cfg{Htpasswd}{Encoding} eq 'htdigest-md5' ) | ||||
521 | ? $Foswiki::cfg{AuthRealm} | ||||
522 | : ''; | ||||
523 | $db->{$login}->{emails} ||= ''; | ||||
524 | print STDERR | ||||
525 | "setPassword login $login pass $db->{$login}->{pass} enc $db->{$login}->{enc} realm $db->{$login}->{realm} emails $db->{$login}->{emails}\n" | ||||
526 | if (TRACE); | ||||
527 | _savePasswd($db); | ||||
528 | } | ||||
529 | catch Error::Simple with { | ||||
530 | my $e = shift; | ||||
531 | $this->{error} = $!; | ||||
532 | print STDERR "ERROR: failed to resetPassword - $! ($e)"; | ||||
533 | $this->{error} = 'unknown error in resetPassword' | ||||
534 | unless ( $this->{error} && length( $this->{error} ) ); | ||||
535 | return undef; | ||||
536 | } | ||||
537 | finally { | ||||
538 | _unlockPasswdFile($lockHandle) if $lockHandle; | ||||
539 | }; | ||||
540 | |||||
541 | $this->{error} = undef; | ||||
542 | return 1; | ||||
543 | } | ||||
544 | |||||
545 | sub removeUser { | ||||
546 | my ( $this, $login ) = @_; | ||||
547 | my $result = undef; | ||||
548 | $this->{error} = undef; | ||||
549 | |||||
550 | my $lockHandle; | ||||
551 | try { | ||||
552 | $lockHandle = _lockPasswdFile(LOCK_EX); | ||||
553 | |||||
554 | # Read password without shared lock as we have already exclusive lock | ||||
555 | my $db = $this->_readPasswd(0); | ||||
556 | unless ( $db->{$login} ) { | ||||
557 | $this->{error} = 'No such user ' . $login; | ||||
558 | } | ||||
559 | else { | ||||
560 | delete $db->{$login}; | ||||
561 | _savePasswd($db); | ||||
562 | $result = 1; | ||||
563 | } | ||||
564 | } | ||||
565 | catch Error::Simple with { | ||||
566 | $this->{error} = shift->{-text}; | ||||
567 | } | ||||
568 | finally { | ||||
569 | _unlockPasswdFile($lockHandle) if $lockHandle; | ||||
570 | }; | ||||
571 | |||||
572 | return $result; | ||||
573 | } | ||||
574 | |||||
575 | sub checkPassword { | ||||
576 | my ( $this, $login, $password ) = @_; | ||||
577 | my ( $pw, $entry ) = $this->fetchPass($login); | ||||
578 | |||||
579 | # $pw will be 0 if there is no pw | ||||
580 | return 0 unless defined $pw; | ||||
581 | |||||
582 | my $encryptedPassword = $this->encrypt( $login, $password, 0, $entry ); | ||||
583 | return 0 unless ($encryptedPassword); | ||||
584 | |||||
585 | $this->{error} = undef; | ||||
586 | |||||
587 | #print STDERR "Checking $pw against $encryptedPassword\n" if (TRACE); | ||||
588 | |||||
589 | if ( length($pw) != length($encryptedPassword) ) { | ||||
590 | |||||
591 | #print STDERR "Fail on length mismatch ($pw) vs enc ($encryptedPassword)\n"; | ||||
592 | $this->{error} = 'Invalid user/password'; | ||||
593 | return 0; | ||||
594 | } | ||||
595 | return 1 if ( $pw && ( $encryptedPassword eq $pw ) ); | ||||
596 | |||||
597 | # pw may validly be '', and must match an unencrypted ''. This is | ||||
598 | # to allow for sysadmins removing the password field in .htpasswd in | ||||
599 | # order to reset the password. | ||||
600 | return 1 if ( defined $password && $pw eq '' && $password eq '' ); | ||||
601 | |||||
602 | $this->{error} = 'Invalid user/password'; | ||||
603 | return 0; | ||||
604 | } | ||||
605 | |||||
606 | sub isManagingEmails { | ||||
607 | return 1; | ||||
608 | } | ||||
609 | |||||
610 | sub getEmails { | ||||
611 | my ( $this, $login ) = @_; | ||||
612 | |||||
613 | # first try the mapping cache | ||||
614 | # read passwords with shared lock | ||||
615 | my $db = $this->_readPasswd(1); | ||||
616 | if ( $db->{$login}->{emails} ) { | ||||
617 | return split( /;/, $db->{$login}->{emails} ); | ||||
618 | } | ||||
619 | |||||
620 | return; | ||||
621 | } | ||||
622 | |||||
623 | sub setEmails { | ||||
624 | my $this = shift; | ||||
625 | my $login = shift; | ||||
626 | my $emails = join( ';', @_ ); | ||||
627 | ASSERT($login) if DEBUG; | ||||
628 | my $lockHandle; | ||||
629 | |||||
630 | try { | ||||
631 | $lockHandle = _lockPasswdFile(LOCK_EX); | ||||
632 | |||||
633 | # Read password without shared lock as we have already exclusive lock | ||||
634 | my $db = $this->_readPasswd(0); | ||||
635 | unless ( $db->{$login} ) { | ||||
636 | |||||
637 | # Make sure the user is in the auth system, by adding them with | ||||
638 | # a null password if not. | ||||
639 | $db->{$login}->{pass} = ''; | ||||
640 | } | ||||
641 | |||||
642 | $db->{$login}->{emails} = $emails; | ||||
643 | |||||
644 | _savePasswd($db); | ||||
645 | } | ||||
646 | finally { | ||||
647 | _unlockPasswdFile($lockHandle) if $lockHandle; | ||||
648 | }; | ||||
649 | return 1; | ||||
650 | } | ||||
651 | |||||
652 | # Searches the password DB for users who have set this email. | ||||
653 | sub findUserByEmail { | ||||
654 | my ( $this, $email ) = @_; | ||||
655 | my $logins = []; | ||||
656 | |||||
657 | # read passwords with shared lock | ||||
658 | my $db = $this->_readPasswd(1); | ||||
659 | while ( my ( $k, $v ) = each %$db ) { | ||||
660 | my %ems = map { $_ => 1 } split( ';', $v->{emails} ); | ||||
661 | if ( $ems{$email} ) { | ||||
662 | push( @$logins, $k ); | ||||
663 | } | ||||
664 | } | ||||
665 | return $logins; | ||||
666 | } | ||||
667 | |||||
668 | 1 | 6µs | 1; | ||
669 | __END__ | ||||
sub Foswiki::Users::HtPasswdUser::CORE:close; # opcode | |||||
# spent 4µs within Foswiki::Users::HtPasswdUser::CORE:flock which was called:
# once (4µs+0s) by Foswiki::Users::HtPasswdUser::_lockPasswdFile at line 155 | |||||
# spent 5µs within Foswiki::Users::HtPasswdUser::CORE:ftdir which was called:
# once (5µs+0s) by Foswiki::Users::HtPasswdUser::readOnly at line 123 | |||||
# spent 5µs within Foswiki::Users::HtPasswdUser::CORE:fteread which was called:
# once (5µs+0s) by Foswiki::Users::HtPasswdUser::readOnly at line 123 | |||||
# spent 5µs within Foswiki::Users::HtPasswdUser::CORE:ftewrite which was called:
# once (5µs+0s) by Foswiki::Users::HtPasswdUser::readOnly at line 123 | |||||
sub Foswiki::Users::HtPasswdUser::CORE:ftis; # opcode | |||||
# spent 787µs within Foswiki::Users::HtPasswdUser::CORE:match which was called 397 times, avg 2µs/call:
# 397 times (787µs+0s) by Foswiki::Users::HtPasswdUser::_readPasswd at line 224, avg 2µs/call | |||||
# spent 20µs within Foswiki::Users::HtPasswdUser::CORE:open which was called:
# once (20µs+0s) by Foswiki::Users::HtPasswdUser::_readPasswd at line 187 | |||||
# spent 971µs within Foswiki::Users::HtPasswdUser::CORE:readline which was called 402 times, avg 2µs/call:
# 402 times (971µs+0s) by Foswiki::Users::HtPasswdUser::_readPasswd at line 192, avg 2µs/call | |||||
# spent 190µs within Foswiki::Users::HtPasswdUser::CORE:sort which was called:
# once (190µs+0s) by Foswiki::Users::HtPasswdUser::fetchUsers at line 139 | |||||
# spent 20µs within Foswiki::Users::HtPasswdUser::CORE:sysopen which was called:
# once (20µs+0s) by Foswiki::Users::HtPasswdUser::_lockPasswdFile at line 150 |