1
# See bottom of file for license and copyright information
5
---+ package Foswiki::UI::Register
7
User registration handling.
11
package Foswiki::UI::Register;
18
require Foswiki::OopsException;
19
require Foswiki::Sandbox;
21
# Keys from the user data that should *not* be included in
34
---++ StaticMethod register_cgi( $session )
36
=register= command handler.
37
This method is designed to be
38
invoked via the =UI::run= method.
45
# absolute URL context for email generation
46
$session->enterContext('absolute_urls');
50
# Register -> Verify -> Approve -> Finish
52
# NB. bulkRegister invoked from ManageCgiScript.
54
my $query = $session->{request};
55
my $action = $query->param('action') || '';
57
if ( $action ne 'verify' && $query && $query->method() &&
58
uc($query->method()) ne 'POST') {
59
throw Foswiki::OopsException(
61
web => $session->{webName},
62
topic => $session->{topicName},
63
def => 'post_method_only',
64
params => [ 'upload' ]
68
if ( $action eq 'register' ) {
69
if ( !$session->inContext('registration_supported') ) {
70
throw Foswiki::OopsException(
72
web => $session->{webName},
73
topic => $session->{topicName},
74
def => 'registration_not_supported'
77
if ( !$Foswiki::cfg{Register}{EnableNewUserRegistration} ) {
78
throw Foswiki::OopsException(
80
web => $session->{webName},
81
topic => $session->{topicName},
82
def => 'registration_disabled'
85
registerAndNext($session);
87
elsif ( $action eq 'verify' ) {
88
verifyEmailAddress($session);
90
throw Error::Simple('Approval code has not been written!');
94
elsif ( $action eq 'resetPassword' ) {
95
resetPassword($session);
97
elsif ( $action eq 'approve' ) {
101
registerAndNext($session);
104
$session->leaveContext('absolute_urls');
106
# Output of register:
107
# UnsavedUser, accessible by username.$verificationCode
109
# Output of reset password:
110
# unaffected user, accessible by username.$verificationCode
113
# UnsavedUser, accessible by username.$approvalCode (only sent to administrator)
116
# RegisteredUser, all related UnsavedUsers deleted
124
---++ StaticMethod bulkRegister($session)
126
Called by ManageCgiScript::bulkRegister (requires authentication) with topic = the page with the entries on it.
133
my $user = $session->{user};
134
my $topic = $session->{topicName};
135
my $web = $session->{webName};
136
my $userweb = $Foswiki::cfg{UsersWebName};
137
my $query = $session->{request};
139
# absolute URL context for email generation
140
$session->enterContext('absolute_urls');
144
# This gets set from the value in the BulkRegistrations topic
145
$settings->{doOverwriteTopics} = $query->param('OverwriteHomeTopics') || 0;
146
$settings->{doEmailUserDetails} = $query->param('EmailUsersWithDetails')
149
unless ( $session->{users}->isAdmin($user) ) {
150
throw Foswiki::OopsException(
151
'accessdenied', status => 403,
155
params => [ $Foswiki::cfg{SuperAdminGroup} ]
159
#-- Read the topic containing the table of people to be registered
161
my ( $meta, $text ) =
162
$session->{store}->readTopic( undef, $web, $topic, undef );
166
foreach my $line ( split( /\r?\n/, $text ) ) {
167
# unchecked implicit untaint OK - this function is for admins only
168
if ( $line =~ /^\s*\|\s*(.*?)\s*\|\s*$/ ) {
171
my %row = map { $fields[ $i++ ] => $_ } split( /\s*\|\s*/, $1 );
172
push( @data, \%row );
175
foreach my $field ( split( /\s*\|\s*/, $1 ) ) {
176
$field =~ s/^[\s*]*(.*?)[\s*]*$/$1/;
177
push( @fields, $field );
184
my $log = "---+ Report for Bulk Register\n";
186
#-- Process each row, generate a log as we go
187
for ( my $n = 0 ; $n < scalar(@data) ; $n++ ) {
189
$row->{webName} = $userweb;
191
unless ( $row->{WikiName} ) {
192
$log .= "---++ Failed to register user on row $n: no !WikiName\n";
195
$row->{LoginName} = $row->{WikiName} unless $row->{LoginName};
197
$log .= _registerSingleBulkUser( $session, \@fields, $row, $settings );
203
my $logTopic = $query->param('LogTopic') || $topic . 'Result';
204
( $logWeb, $logTopic ) = $session->normalizeWebTopicName( '', $logTopic );
206
#-- Save the LogFile as designated, link back to the source topic
207
$meta->put( 'TOPICPARENT', { name => $web . '.' . $topic } );
209
$session->{store}->saveTopic( $user, $logWeb, $logTopic, $log, $meta );
211
$session->leaveContext('absolute_urls');
213
my $nurl = $session->getScriptUrl( 1, 'view', $web, $logTopic );
214
$session->redirect( $nurl );
217
# Register a single user during a bulk registration process
218
sub _registerSingleBulkUser {
219
my ( $session, $fieldNames, $row, $settings ) = @_;
220
ASSERT($row) if DEBUG;
222
my $doOverwriteTopics = defined $settings->{doOverwriteTopics}
223
|| throw Error::Simple('No doOverwriteTopics');
225
my $log = "---++ Registering $row->{WikiName}\n";
228
_validateRegistration( $session, $row, 0 );
230
catch Foswiki::OopsException with {
232
$log .= '<blockquote>' . $e->stringify($session) . "</blockquote>\n";
233
return $log . "$b1 Registration failed\n";
236
#-- call to the registrationHandler (to amend fields) should
237
# really happen in here.
239
#-- Ensure every required field exists
240
# NB. LoginName is OPTIONAL
241
my @requiredFields = qw(WikiName FirstName LastName);
242
if ( _missingElements( $fieldNames, \@requiredFields ) ) {
245
. join( ' ', @{ $settings->{fieldNames} } )
246
. ' does not contain the full set of required fields '
247
. join( ' ', @requiredFields ) . "\n";
248
return ( undef, $log );
251
#-- Generation of the page is done from the {form} subhash,
253
$row->{form} = _makeFormFieldOrderMatch( $fieldNames, $row );
255
my $users = $session->{users};
259
# Add the user to the user management system. May throw an exception
260
my $cUID = $users->addUser(
261
$row->{LoginName}, $row->{WikiName},
262
$row->{Password}, $row->{Email}
265
"$b1 $row->{WikiName} has been added to the password and user mapping managers\n";
267
if ( $settings->{doOverwriteTopics}
268
|| !$session->{store}
269
->topicExists( $row->{webName}, $row->{WikiName} ) )
271
$log .= _createUserTopic( $session, $row );
274
$log .= "$b1 Not writing user topic $row->{WikiName}\n";
276
$users->setEmails( $cUID, $row->{Email} );
278
$session->logEvent( 'bulkregister',
279
$row->{webName} . '.' . $row->{WikiName},
280
$row->{Email}, $row->{WikiName} );
282
catch Error::Simple with {
284
$log .= "$b1 Failed to add user: " . $e->stringify() . "\n";
287
#if ($Foswiki::cfg{EmailUserDetails}) {
288
# If you want it, write it.
289
# _sendEmail($session, 'registernotifybulk', $data );
290
# $log .= $b1.' Password email disabled\n';
296
#ensures all named fields exist in hash
297
#returns array containing any that are missing
298
sub _missingElements {
299
my ( $presentArrRef, $requiredArrRef ) = @_;
303
$present{$_} = 1 for @$presentArrRef;
304
foreach my $required (@$requiredArrRef) {
305
if ( !$present{$required} ) {
306
push @missing, $required;
312
# rearranges the fields in $data so that they match settings
313
# returns a new ordered form
314
sub _makeFormFieldOrderMatch {
315
my ( $fieldNames, $data ) = @_;
317
foreach my $field (@$fieldNames) {
318
push @form, { name => $field, value => $data->{$field} };
325
---++ StaticMethod registerAndNext($session)
327
This is called when action = register or action = ""
329
It calls register and either Verify or Finish.
331
Hopefully we will get workflow integrated and rewrite this to be table driven
335
sub registerAndNext {
338
if ( $Foswiki::cfg{Register}{NeedVerification} ) {
339
_requireVerification($session);
348
---++ StaticMethod register($session)
350
This is called through: UserRegistration -> RegisterCgiScript -> here
357
my $query = $session->{request};
358
my $data = _getDataFromQuery( $query, $query->param() );
360
$data->{webName} = $session->{webName};
363
# SMELL: should perform basic checks that we have e.g. a WikiName
365
_validateRegistration( $session, $data, 1 );
368
# Generate a registration record, and mail the registrant with the code.
369
# Redirects the browser to the confirmation screen.
370
sub _requireVerification {
373
my $query = $session->{request};
374
my $topic = $session->{topicName};
375
my $web = $session->{webName};
377
my $data = _getDataFromQuery( $query, $query->param() );
378
$data->{LoginName} ||= $data->{WikiName};
379
$data->{webName} = $web;
381
$data->{VerificationCode} = $data->{WikiName} . '.' . int( rand(99999999) );
383
#SMELL: used for Register unit tests
384
$session->{DebugVerificationCode} = $data->{VerificationCode};
386
require Data::Dumper;
388
my $file = _codeFile( $data->{VerificationCode} );
389
open( F, ">$file" ) or throw Error::Simple( 'Failed to open file: ' . $! );
390
print F '# Verification code', "\n";
392
# SMELL: wierd jiggery-pokery required, otherwise Data::Dumper screws
393
# up the form fields when it saves. Perl bug? Probably to do with
394
# chucking around arrays, instead of references to them.
395
my $form = $data->{form};
396
$data->{form} = undef;
397
print F Data::Dumper->Dump( [ $data, $form ], [ 'data', 'form' ] );
398
$data->{form} = $form;
401
$session->logEvent('regstart',
402
$Foswiki::cfg{UsersWebName} . '.' . $data->{WikiName},
403
$data->{Email}, $data->{WikiName} );
405
my $em = $data->{Email};
407
if ( $Foswiki::cfg{EnableEmail} ) {
408
my $err = _sendEmail( $session, 'registerconfirm', $data );
411
throw Foswiki::OopsException(
413
def => 'registration_mail_failed',
414
web => $data->{webName},
416
params => [ $em, $err ]
421
my $err = $session->i18n->maketext(
422
'Email has been disabled for this Foswiki installation');
424
throw Foswiki::OopsException(
426
def => 'send_mail_error',
427
web => $data->{webName},
429
params => [ $em, $err ]
433
throw Foswiki::OopsException(
437
web => $data->{webName},
445
---++ StaticMethod resetPassword($session)
447
Generates a password. Mails it to them and asks them to change it. Entry
448
point intended to be called from UI::run
454
my $query = $session->{request};
455
my $topic = $session->{topicName};
456
my $web = $session->{webName};
457
my $user = $session->{user};
459
unless ( $Foswiki::cfg{EnableEmail} ) {
460
my $err = $session->i18n->maketext(
461
'Email has been disabled for this Foswiki installation');
462
throw Foswiki::OopsException(
464
topic => $Foswiki::cfg{HomeTopicName},
470
my @userNames = $query->param('LoginName');
471
unless (@userNames) {
472
throw Foswiki::OopsException( 'attention', def => 'no_users_to_reset' );
474
my $introduction = $query->param('Introduction') || '';
476
# need admin priv if resetting bulk, or resetting another user
477
my $isBulk = ( scalar(@userNames) > 1 );
481
# Only admin is able to reset more than one password or
482
# another user's password.
483
unless ( $session->{users}->isAdmin($user) ) {
484
throw Foswiki::OopsException(
485
'accessdenied', status => 403,
489
params => [ $Foswiki::cfg{SuperAdminGroup} ]
495
# Anyone can reset a single password - important because by definition
496
# the user cannot authenticate
497
# Note that the passwd script must NOT authenticate!
500
# Collect all messages into one string
503
foreach my $userName (@userNames) {
505
&& _resetUsersPassword( $session, $userName, $introduction,
511
# Redirect to a page that tells what happened
515
# one user; refine the change password link to include their
516
# username (can't use logged in user - by definition this won't
518
$action = '?username=' . $userNames[0];
521
throw Foswiki::OopsException(
524
topic => $Foswiki::cfg{HomeTopicName},
530
throw Foswiki::OopsException(
532
topic => $Foswiki::cfg{HomeTopicName},
540
sub _resetUsersPassword {
541
my ( $session, $login, $introduction, $pMess ) = @_;
543
my $users = $session->{users};
546
$$pMess .= $session->inlineAlert( 'alertsnohtml', 'bad_user', '' );
550
my $user = $users->getCanonicalUserID($login);
552
unless ( $user && $users->userExists($user) ) {
556
$session->inlineAlert( 'alertsnohtml', 'missing_user', $login );
560
require Foswiki::Users;
561
my $password = Foswiki::Users::randomPassword();
563
unless ( $users->setPassword( $user, $password, 1 ) ) {
564
$$pMess .= $session->inlineAlert( 'alertsnohtml', 'reset_bad', $user );
568
# Now that we have successfully reset the password we log the event
569
$session->logEvent('resetpasswd', $login);
571
# absolute URL context for email generation
572
$session->enterContext('absolute_urls');
574
my @em = $users->getEmails($user);
576
if ( !scalar(@em) ) {
578
$session->inlineAlert( 'alertsnohtml', 'no_email_for', $user );
581
my $ln = $users->getLoginName($user);
582
my $wn = $users->getWikiName($user);
583
foreach my $email (@em) {
584
my $err = _sendEmail(
588
webName => $Foswiki::cfg{UsersWebName},
590
Name => Foswiki::spaceOutWikiWord($wn),
593
PasswordA => $password,
594
Introduction => $introduction,
600
$session->inlineAlert(
601
'alertsnohtml', 'generic', $err );
609
$session->leaveContext('absolute_urls');
612
$$pMess .= $session->inlineAlert(
613
'alertsnohtml', 'new_sys_pass',
614
$users->getLoginName($user),
615
$users->getWikiName($user)
624
---++ StaticMethod changePassword( $session )
626
Change the user's password and/or email. Details of the user and password
627
are passed in CGI parameters.
629
1 Checks required fields have values
630
2 get wikiName and userName from getUserByEitherLoginOrWikiName(username)
631
3 check passwords match each other, and that the password is correct, otherwise 'wrongpassword'
632
4 Foswiki::User::updateUserPassword
635
The NoPasswdUser case is not handled.
637
An admin user can change other user's passwords.
644
my $topic = $session->{topicName};
645
my $webName = $session->{webName};
646
my $query = $session->{request};
647
my $requestUser = $session->{user};
649
my $oldpassword = $query->param('oldpassword');
650
my $login = $query->param('username');
651
my $passwordA = $query->param('password');
652
my $passwordB = $query->param('passwordA');
653
my $email = $query->param('email');
654
my $topicName = $query->param('TopicName');
656
# check if required fields are filled in
658
throw Foswiki::OopsException(
662
def => 'missing_fields',
663
params => ['username']
667
my $users = $session->{users};
670
throw Foswiki::OopsException(
680
if ( defined $passwordA || defined $passwordB ) {
681
unless ( defined $passwordA ) {
682
throw Foswiki::OopsException(
686
def => 'missing_fields',
687
params => ['password']
691
# check if passwords are identical
692
if ( $passwordA ne $passwordB ) {
693
throw Foswiki::OopsException(
697
def => 'password_mismatch'
703
# check if required fields are filled in
704
unless ( defined $oldpassword || $users->isAdmin($requestUser) ) {
705
throw Foswiki::OopsException(
709
def => 'missing_fields',
710
params => ['oldpassword']
714
unless ( $users->isAdmin($requestUser)
715
|| $users->checkPassword( $login, $oldpassword ) )
717
throw Foswiki::OopsException(
721
def => 'wrong_password'
725
my $cUID = $users->getCanonicalUserID($login);
726
if ( defined $email ) {
727
my $return = $users->setEmails( $cUID, split( /\s+/, $email ) );
730
# OK - password may be changed
732
if ( length($passwordA) < $Foswiki::cfg{MinPasswordLength} ) {
733
throw Foswiki::OopsException(
737
def => 'bad_password',
738
params => [ $Foswiki::cfg{MinPasswordLength} ]
742
unless ( $users->setPassword( $cUID, $passwordA, $oldpassword ) ) {
743
throw Foswiki::OopsException(
747
def => 'password_not_changed'
751
$session->logEvent('changepasswd', $login );
754
# OK - password changed
755
throw Foswiki::OopsException(
760
def => 'password_changed'
765
throw Foswiki::OopsException(
770
def => 'email_changed',
777
---++ StaticMethod verifyEmailAddress($session)
779
This is called: on receipt of the activation password -> RegisterCgiScript -> here
780
1 calls _loadPendingRegistration(activation password)
781
2 throws oops if appropriate
782
3 calls emailRegistrationConfirmations
783
4 still calls 'oopssendmailerr' if a problem, but this is not done uniformly
787
sub verifyEmailAddress {
790
my $code = $session->{request}->param('code');
792
throw Error::Simple('verifyEmailAddress: no verification code!');
794
my $data = _loadPendingRegistration( $session, $code );
796
if ( !exists $data->{Email} ) {
797
throw Error::Simple('verifyEmailAddress: no email address!');
800
my $topic = $session->{topicName};
801
my $web = $session->{webName};
807
---++ StaticMethod deleteUser($session)
808
CGI function that deletes the current user
809
Renames the *current* user's topic (with renaming all links) and
810
removes user entry from passwords.
817
my $webName = $session->{webName};
818
my $topic = $session->{topicName};
819
my $query = $session->{request};
820
my $cUID = $session->{user};
822
my $password = $query->param('password');
824
# check if user entry exists
825
my $users = $session->{users};
826
if ( !$users->userExists($cUID) ) {
827
throw Foswiki::OopsException(
832
params => [ $session->{users}->getWikiName($cUID) ]
836
#check to see it the user we are trying to remove is a member of a group.
837
#initially we refuse to delete the user
838
#in a later implementation we will remove the from the group
839
#(if Access.pm implements it..)
840
my $git = $users->eachMembership($cUID);
841
if ( $git->hasNext() ) {
843
while ( $git->hasNext() ) {
844
$list .= ' ' . $git->next();
846
throw Foswiki::OopsException(
851
params => [ $session->{users}->getWikiName($cUID), $list ]
856
$users->checkPassword(
857
$session->{users}->getLoginName($cUID), $password
861
throw Foswiki::OopsException(
865
def => 'wrong_password'
869
$users->removeUser($cUID);
871
throw Foswiki::OopsException(
872
'attention', status => 200,
873
def => 'remove_user_done',
876
params => [ $users->getWikiName($cUID) ]
880
# Complete a registration
884
my $topic = $session->{topicName};
885
my $web = $session->{webName};
886
my $query = $session->{request};
887
my $code = $query->param('code');
890
if ( $Foswiki::cfg{Register}{NeedVerification} ) {
891
$data = _loadPendingRegistration( $session, $code );
892
_clearPendingRegistrationsForUser($code);
895
$data = _getDataFromQuery( $query, $query->param() );
896
$data->{webName} = $web;
899
$data->{WikiName} = Foswiki::Sandbox::untaint(
900
$data->{WikiName}, \&Foswiki::Sandbox::validateTopicName);
901
throw Error::Simple('bad WikiName after reload') unless $data->{WikiName};
903
if ( !exists $data->{LoginName} ) {
904
if ( $Foswiki::cfg{Register}{AllowLoginName} ) {
906
# This should have been populated
907
throw Error::Simple('no LoginName after reload');
909
$data->{LoginName} ||= $data->{WikiName};
912
my $users = $session->{users};
914
unless ( defined($data->{Password}) ) {
915
#SMELL: should give consideration to disabling $Foswiki::cfg{Register}{HidePasswd}
916
#though that may reduce the conf options an admin has..
917
#OR, a better option would be that the rego email would thus point the user to the resetPasswd url.
918
$data->{Password} = Foswiki::Users::randomPassword();
919
#add data to the form so it can go out in the registration emails.
920
push(@{ $data->{form} }, {name=>'Password', value=>$data->{Password} });
923
my $cUID = $users->addUser(
924
$data->{LoginName}, $data->{WikiName},
925
$data->{Password}, $data->{Email}
927
my $log = _createUserTopic( $session, $data );
928
$users->setEmails( $cUID, $data->{Email} );
930
catch Error::Simple with {
934
$session->logger->log('warning', 'Registration failed: ' . $e->stringify() );
935
throw Foswiki::OopsException(
937
web => $data->{webName},
939
def => 'problem_adding',
940
params => [ $data->{WikiName}, $e->stringify() ]
944
# Plugin to do some other post processing of the user.
945
# for legacy, (callback to set cookies - now should use LoginHandler)
947
->dispatch( 'registrationHandler', $data->{WebName}, $data->{WikiName},
948
$data->{LoginName}, $data );
953
if ( $Foswiki::cfg{EnableEmail} ) {
955
# inform user and admin about the registration.
956
$status = _emailRegistrationConfirmations( $session, $data );
959
if ( $Foswiki::cfg{Log}{register} ) {
960
$session->logEvent('register',
961
$Foswiki::cfg{UsersWebName} . '.' . $data->{WikiName},
962
$data->{Email}, $data->{WikiName} );
966
$status = $session->i18n->maketext(
967
'Warning: Could not send confirmation email')
972
$status = $session->i18n->maketext(
973
'A confirmation e-mail has been sent to [_1]',
978
$status = $session->i18n->maketext(
979
'Warning: Could not send confirmation email, email has been disabled'
983
# Only change the session's identity _if_ the registration was done by
984
# WikiGuest, and an email was correctly sent.
986
&& $session->{user} eq
987
$session->{users}->getCanonicalUserID( $Foswiki::cfg{DefaultUserLogin} ) )
990
# let the client session know that we're logged in. (This probably
991
# eliminates the need for the registrationHandler call above,
992
# but we'll leave them both in here for now.)
993
$users->{loginManager}
994
->userLoggedIn( $data->{LoginName}, $data->{WikiName} );
997
# and finally display thank you page
998
throw Foswiki::OopsException(
1001
web => $Foswiki::cfg{UsersWebName},
1002
topic => $data->{WikiName},
1004
params => [ $status, $data->{WikiName} ]
1008
#Given a template and a hash, creates a new topic for a user
1009
# 1 reads the template topic
1010
# 2 calls RegistrationHandler::register with the row details, so that a plugin can augment/delete/change the entries
1012
#I use RegistrationHandler::register to prevent certain fields (like password)
1013
#appearing in the homepage and to fetch photos into the topic
1014
sub _createUserTopic {
1015
my ( $session, $row ) = @_;
1016
my $store = $session->{store};
1017
my $template = 'NewUserTemplate';
1018
my ( $meta, $text );
1019
if ( $store->topicExists( $Foswiki::cfg{UsersWebName}, $template ) ) {
1021
# Use the local customised version
1023
$store->readTopic( undef, $Foswiki::cfg{UsersWebName}, $template );
1027
# Use the default read-only version
1029
$store->readTopic( undef, $Foswiki::cfg{SystemWebName}, $template );
1035
. $Foswiki::cfg{UsersWebName} . '.'
1036
. $row->{WikiName} . "\n"
1037
. "$b1 !RegistrationHandler:\n"
1038
. _writeRegistrationDetailsToTopic( $session, $row, $meta, $text );
1042
# Writes the registration details passed as a hash to either BulletFields
1043
# or FormFields on the user's topic.
1045
sub _writeRegistrationDetailsToTopic {
1046
my ( $session, $data, $meta, $text ) = @_;
1048
ASSERT( $data->{WikiName} ) if DEBUG;
1050
# TODO - there should be some way of overwriting meta without
1051
# blatting the content.
1053
my ( $before, $repeat, $after ) = split( /%SPLIT%/, $text, 3 );
1054
$before = '' unless defined($before);
1055
$after = '' unless defined($after);
1059
my $form = $meta->get('FORM');
1061
( $meta, $addText ) =
1062
_populateUserTopicForm( $session, $form->{name}, $meta, $data );
1063
$log = "$b2 Using Form Fields\n";
1066
$addText = _getRegFormAsTopicContent($data);
1067
$log = "$b2 Using Bullet Fields\n";
1069
$text = $before . $addText . $after;
1071
my $user = $data->{WikiName};
1073
$session->expandVariablesOnTopicCreation( $text, $user,
1074
$Foswiki::cfg{UsersWebName}, $user );
1076
$meta->put( 'TOPICPARENT', { 'name' => $Foswiki::cfg{UsersTopicName} } );
1078
$session->{store}->saveTopic(
1079
$session->{users}->getCanonicalUserID($Foswiki::cfg{Register}{RegistrationAgentWikiName}),
1080
$Foswiki::cfg{UsersWebName},
1086
# Puts form fields into the topic form
1087
sub _populateUserTopicForm {
1088
my ( $session, $formName, $meta, $data ) = @_;
1091
require Foswiki::Form;
1094
new Foswiki::Form( $session, $Foswiki::cfg{UsersWebName}, $formName );
1096
return ( $meta, '' ) unless $form;
1098
foreach my $field ( @{ $form->getFields() } ) {
1099
foreach my $fd ( @{ $data->{form} } ) {
1100
next unless $fd->{name} eq $field->{name};
1101
next if $SKIPKEYS{ $fd->{name} };
1102
my $item = $meta->get( 'FIELD', $fd->{name} );
1103
$item->{value} = $fd->{value};
1104
$meta->putKeyed( 'FIELD', $item );
1105
$inform{ $fd->{name} } = 1;
1109
my $leftoverText = '';
1110
foreach my $fd ( @{ $data->{form} } ) {
1111
unless ( $inform{ $fd->{name} } || $SKIPKEYS{ $fd->{name} } ) {
1112
$leftoverText .= " * $fd->{name}: $fd->{value}\n";
1115
return ( $meta, $leftoverText );
1118
# Registers a user using the old bullet field code
1119
sub _getRegFormAsTopicContent {
1122
foreach my $fd ( @{ $data->{form} } ) {
1123
next if $SKIPKEYS{ $fd->{name} };
1124
my $title = $fd->{name};
1125
$title =~ s/([a-z0-9])([A-Z0-9])/$1 $2/go; # Spaced
1126
my $value = $fd->{value};
1127
$value =~ s/[\n\r]//go;
1128
$text .= " * $title\: $value\n";
1133
#Sends to both the WIKIWEBMASTER and the USER notice of the registration
1134
#emails both the admin 'registernotifyadmin' and the user 'registernotify',
1135
#in separate emails so they both get targeted information (and no password to the admin).
1136
sub _emailRegistrationConfirmations {
1137
my ( $session, $data ) = @_;
1139
my $skin = $session->getSkin();
1140
my $template = $session->templates->readTemplate( 'registernotify', $skin );
1142
_buildConfirmationEmail( $session, $data, $template,
1143
$Foswiki::cfg{Register}{HidePasswd} );
1145
my $warnings = $session->net->sendEmail($email);
1149
# Email address doesn't work, likely fraudulent registration
1151
my $users = $session->{users};
1152
my $cUID = $users->getCanonicalUserID( $data->{LoginName} );
1154
if ( $session->{users}->removeUser($cUID) ) {
1156
$session->templates->readTemplate( 'registerfailedremoved',
1161
$session->templates->readTemplate( 'registerfailedunremoved',
1165
catch Error::Simple with {
1167
# Most Mapping Managers don't support removeUser, unfortunately
1169
$session->templates->readTemplate( 'registerfailednoremove',
1175
$session->templates->readTemplate( 'registernotifyadmin', $skin );
1178
$email = _buildConfirmationEmail( $session, $data, $template, 1 );
1180
my $err = $session->net->sendEmail($email);
1183
# don't tell the user about this one
1184
$session->logger->log('warning', 'Could not confirm registration: ' . $err );
1190
#The template dictates the to: field
1191
sub _buildConfirmationEmail {
1192
my ( $session, $data, $templateText, $hidePassword ) = @_;
1194
$data->{Name} ||= $data->{WikiName};
1195
$data->{LoginName} = '' unless defined $data->{LoginName};
1197
$templateText =~ s/%FIRSTLASTNAME%/$data->{Name}/go;
1198
$templateText =~ s/%WIKINAME%/$data->{WikiName}/go;
1199
$templateText =~ s/%EMAILADDRESS%/$data->{Email}/go;
1202
$session->handleCommonTags( $templateText, $Foswiki::cfg{UsersWebName},
1203
$data->{WikiName} );
1205
#add LoginName to make it clear to new users
1206
my $loginName = $b1 . ' LoginName: ' . $data->{LoginName} . "\n";
1208
#SMELL: this means we fail hard if there are 2 FORMDATA vars -
1209
# like in multi-part mime - txt & html
1210
my ( $before, $after ) = split( /%FORMDATA%/, $templateText );
1211
$before .= $loginName;
1212
foreach my $fd ( @{ $data->{form} } ) {
1213
my $name = $fd->{name};
1214
my $value = $fd->{value};
1215
if ( ( $name eq 'Password' ) && ($hidePassword) ) {
1218
if ( ( $name ne 'Confirm' ) and
1219
( $name ne 'LoginName' ) ) { #skip LoginName - we've put it on top.
1220
$before .= $b1 . ' ' . $name . ': ' . $value . "\n";
1223
$templateText = $before . ( $after || '' );
1224
$templateText =~ s/( ?) *<\/?(nop|noautolink)\/?>\n?/$1/gois;
1226
# remove <nop> and <noautolink> tags
1228
return $templateText;
1231
# Throws an Oops exception if there is a problem.
1232
sub _validateRegistration {
1233
my ( $session, $data, $requireForm ) = @_;
1235
if ( !defined( $data->{LoginName} )
1236
&& $Foswiki::cfg{Register}{AllowLoginName} )
1239
# Login name is required, barf
1240
throw Foswiki::OopsException(
1242
web => $data->{webName},
1243
topic => $session->{topicName},
1244
def => 'bad_loginname',
1245
params => ['undefined']
1248
elsif ( !defined( $data->{LoginName} ) ) {
1250
# Login name is optional, default to the wikiname
1251
$data->{LoginName} = $data->{WikiName};
1254
# Check if login name matches expectations
1255
unless ( $session->{users}->{loginManager}->isValidLoginName(
1256
$data->{LoginName} )) {
1257
throw Foswiki::OopsException(
1259
web => $data->{webName},
1260
topic => $session->{topicName},
1261
def => 'bad_loginname',
1262
params => [ $data->{LoginName} ]
1266
# Check if the login name is already registered
1267
# luckily, we're only considering TopicUserMapping cfg's
1268
# there are several possible interpretations of 'already registered'
1269
# --- For setups with a PasswordManager...
1270
# on twiki.org, (allowloginname=off) means that if the user has an
1271
# entry in the htpasswd file, they are already registered.
1272
# onmost systems using (allowloginname=off) already registered could mean
1273
# user topic exists, or, Main.UserList mapping exists
1274
# on any system using (allowloginname=on) already registered could mean
1275
# user topic exists, or, Main.UserList mapping exists
1276
#NOTE: it is important that _any_ user can register any random third party
1277
# this is not only how WikiGuest registers as someone else, but often
1278
# how users pre-register others.
1279
my $users = $session->{users};
1280
my $user = $users->getCanonicalUserID( $data->{LoginName} );
1281
my $wikiname = $users->getWikiName($user);
1283
my $store = $session->{store};
1289
# OR already logged in (shortcircuit to reduce perf impact)
1290
# returns undef if passwordmgr=none
1291
( ( $users->userExists($user) ) )
1294
#user has an entry in the mapping system (if AllowLoginName == off, then entry is automatic)
1296
( !$Foswiki::cfg{Register}{AllowLoginName} )
1297
|| $store->topicExists( $Foswiki::cfg{UsersWebName},
1298
$wikiname ) #mapping from new login exists
1302
throw Foswiki::OopsException(
1304
web => $data->{webName},
1305
topic => $session->{topicName},
1306
def => 'already_exists',
1307
params => [ $data->{LoginName} ]
1311
#new user's topic already exists
1312
if ( $store->topicExists( $Foswiki::cfg{UsersWebName}, $data->{WikiName} ) ) {
1313
throw Foswiki::OopsException(
1315
web => $data->{webName},
1316
topic => $session->{topicName},
1317
def => 'already_exists',
1318
params => [ $data->{WikiName} ]
1322
# Check if WikiName is a WikiName
1323
if ( !Foswiki::isValidWikiWord( $data->{WikiName} ) ) {
1324
throw Foswiki::OopsException(
1326
web => $data->{webName},
1327
topic => $session->{topicName},
1328
def => 'bad_wikiname',
1329
params => [ $data->{WikiName} ]
1333
if ( exists $data->{Password} ) {
1335
# check password length
1336
my $doCheckPasswordLength =
1337
( $Foswiki::cfg{PasswordManager} ne 'none'
1338
&& $Foswiki::cfg{MinPasswordLength} );
1340
if ( $doCheckPasswordLength
1341
&& length( $data->{Password} ) < $Foswiki::cfg{MinPasswordLength} )
1343
throw Foswiki::OopsException(
1345
web => $data->{webName},
1346
topic => $session->{topicName},
1347
def => 'bad_password',
1348
params => [ $Foswiki::cfg{MinPasswordLength} ]
1352
# check if passwords are identical
1353
if ( $data->{Password} ne $data->{Confirm} ) {
1354
throw Foswiki::OopsException(
1356
web => $data->{webName},
1357
topic => $session->{topicName},
1358
def => 'password_mismatch'
1363
# check valid email address
1364
if ( $data->{Email} !~ $Foswiki::regex{emailAddrRegex} ) {
1365
throw Foswiki::OopsException(
1367
web => $data->{webName},
1368
topic => $session->{topicName},
1370
params => [ $data->{Email} ]
1374
return unless $requireForm;
1376
# check if required fields are filled in
1377
unless ( $data->{form} && ( $#{ $data->{form} } > 1 ) ) {
1378
throw Foswiki::OopsException(
1380
web => $data->{webName},
1381
topic => $session->{topicName},
1382
def => 'missing_fields',
1387
foreach my $fd ( @{ $data->{form} } ) {
1388
if ( ( $fd->{required} ) && ( !$fd->{value} ) ) {
1389
push( @missing, $fd->{name} );
1393
if ( scalar(@missing) ) {
1394
throw Foswiki::OopsException(
1396
web => $data->{webName},
1397
topic => $session->{topicName},
1398
def => 'missing_fields',
1399
params => [ join( ', ', @missing ) ]
1404
# sends $p->{template} to $p->{Email} with a bunch of substitutions.
1406
my ( $session, $template, $p ) = @_;
1408
my $text = $session->templates->readTemplate($template);
1409
$p->{Introduction} ||= '';
1410
$p->{Name} ||= $p->{WikiName};
1411
$text =~ s/%LOGINNAME%/$p->{LoginName}/geo;
1412
$text =~ s/%FIRSTLASTNAME%/$p->{Name}/go;
1413
$text =~ s/%WIKINAME%/$p->{WikiName}/geo;
1414
$text =~ s/%EMAILADDRESS%/$p->{Email}/go;
1415
$text =~ s/%INTRODUCTION%/$p->{Introduction}/go;
1416
$text =~ s/%VERIFICATIONCODE%/$p->{VerificationCode}/go;
1417
$text =~ s/%PASSWORD%/$p->{PasswordA}/go;
1418
$text = $session->handleCommonTags( $text, $Foswiki::cfg{UsersWebName},
1421
return $session->net->sendEmail($text);
1426
ASSERT($code) if DEBUG;
1427
throw Error::Simple("bad code") unless $code =~ /^(\w+)\.(\d+)$/;
1428
return "$Foswiki::cfg{WorkingDir}/registration_approvals/$1.$2";
1433
ASSERT($code) if DEBUG;
1434
$code =~ s/\.\d+$//;
1438
sub _clearPendingRegistrationsForUser {
1440
my $file = _codeFile($code);
1442
# Remove the integer code to leave just the wikiname
1443
$file =~ s/\.\d+$//;
1444
foreach my $f (<$file.*>) {
1445
# Read from disc, implictly validated
1446
unlink( Foswiki::Sandbox::untaintUnchecked($f) );
1450
use vars qw( $data $form );
1452
# Redirects user and dies if cannot load.
1453
# Dies if loads and does not match.
1454
# Returns the users data hash if succeeded.
1455
# Returns () if not found.
1456
# Assumptions: In error handling we assume that the verification code
1457
# starts with the wikiname under consideration, and that the
1458
# random code does not contain a '.'.
1459
sub _loadPendingRegistration {
1460
my ( $session, $code ) = @_;
1462
ASSERT($code) if DEBUG;
1466
$file = _codeFile($code);
1468
catch Error::Simple with {
1469
throw Foswiki::OopsException(
1471
def => 'bad_ver_code',
1472
params => [ $code, 'Invalid code' ],
1476
unless ( -f $file ) {
1477
my $wikiName = _codeWikiName($code);
1478
my $users = $session->{users}->findUserByWikiName($wikiName);
1479
if ( scalar( @{$users} )
1480
&& $session->{users}->userExists( $users->[0] ) )
1482
throw Foswiki::OopsException(
1484
def => 'duplicate_activation',
1485
params => [$wikiName],
1488
throw Foswiki::OopsException(
1490
def => 'bad_ver_code',
1491
params => [ $code, 'Code is not recognised' ],
1496
$data->{form} = $form if $form;
1497
throw Foswiki::OopsException(
1499
def => 'bad_ver_code',
1500
params => [ $code, 'Bad activation code' ]
1502
throw Foswiki::OopsException(
1504
def => 'bad_ver_code',
1505
params => [ $code, 'Invalid activation code ' ]
1506
) unless $data->{VerificationCode} eq $code;
1511
sub _getDataFromQuery {
1514
# get all parameters from the form
1516
foreach ( $query->param() ) {
1517
if (/^(Twk([0-9])(.*))/) {
1518
my @values = $query->param( $1 ) || ();
1521
# deal with multivalue fields like checkboxen
1522
my $value = join( ',', @values );
1524
# Note: field values are unvalidated (and therefore tainted).
1525
# This is because the registration code does not have enough
1526
# information to validate the data - for example, it cannot
1527
# know what the user mapper considers to be a valid login name.
1528
# It is the responsibility of the implementation code to untaint
1529
# these data before they are used in dangerous ways.
1530
# DO NOT UNTAINT THESE DATA HERE!
1531
$data->{$name} = $value;
1532
push( @{ $data->{form} },
1534
required => $required,
1542
&& defined $data->{FirstName}
1543
&& defined $data->{LastName} )
1545
$data->{Name} = $data->{FirstName} . ' ' . $data->{LastName};
1550
# We delete only the field in the {form} array - this leaves
1551
# the original value still there should we want it i.e. it must
1552
# still be available via $row->{$key} even though $row-{form}[]
1553
# does not contain it
1555
my ( $row, $key ) = @_;
1556
my @formArray = @{ $row->{form} };
1558
foreach my $index ( 0 .. $#formArray ) {
1559
my $a = $formArray[$index];
1560
my $name = $a->{name};
1561
my $value = $a->{value};
1562
if ( $name eq $key ) {
1563
splice( @{ $row->{form} }, $index, 1 );
1571
# Module of Foswiki - The Free and Open Source Wiki, http://foswiki.org/
1573
# Copyright (C) 2008-2009 Foswiki Contributors. Foswiki Contributors
1574
# are listed in the AUTHORS file in the root of this distribution.
1575
# NOTE: Please extend that file, not this notice.
1577
# Additional copyrights apply to some or all of the code in this
1579
# Copyright (c) 1999-2007 TWiki Contributors
1580
# (c) 1999-2007 Peter Thoeny, peter@thoeny.com
1581
# (c) 2001 Kevin Atkinson, kevin twiki at atkinson dhs org
1582
# (c) 2003-2008 SvenDowideit, SvenDowideit@home.org.au
1583
# (c) 2003 Graeme Pyle graeme@raspberry dot co dot za
1584
# (c) 2004 Martin Cleaver, Martin.Cleaver@BCS.org.uk
1585
# (c) 2004 Gilles-Eric Descamps twiki at descamps.org
1586
# (c) 2004-2007 Crawford Currie c-dot.co.uk
1588
# This program is free software; you can redistribute it and/or
1589
# modify it under the terms of the GNU General Public License
1590
# as published by the Free Software Foundation; either version 2
1591
# of the License, or (at your option) any later version. For
1592
# more details read LICENSE in the root of this distribution.
1594
# This program is distributed in the hope that it will be useful,
1595
# but WITHOUT ANY WARRANTY; without even the implied warranty of
1596
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
1598
# As per the GPL, removal of this notice is prohibited.