~kosova/+junk/tuxfamily-twiki

« back to all changes in this revision

Viewing changes to foswiki/lib/Foswiki/UI/Register.pm

  • Committer: James Michael DuPont
  • Date: 2009-07-18 19:58:49 UTC
  • Revision ID: jamesmikedupont@gmail.com-20090718195849-vgbmaht2ys791uo2
added foswiki

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# See bottom of file for license and copyright information
 
2
 
 
3
=begin TML
 
4
 
 
5
---+ package Foswiki::UI::Register
 
6
 
 
7
User registration handling.
 
8
 
 
9
=cut
 
10
 
 
11
package Foswiki::UI::Register;
 
12
 
 
13
use strict;
 
14
use Assert;
 
15
use Error qw( :try );
 
16
 
 
17
require Foswiki;
 
18
require Foswiki::OopsException;
 
19
require Foswiki::Sandbox;
 
20
 
 
21
# Keys from the user data that should *not* be included in
 
22
# the user topic.
 
23
my %SKIPKEYS = (
 
24
    'Photo'     => 1,
 
25
    'WikiName'  => 1,
 
26
    'LoginName' => 1,
 
27
    'Password'  => 1,
 
28
    'Confirm'   => 1,
 
29
    'Email'     => 1
 
30
);
 
31
 
 
32
=begin TML
 
33
 
 
34
---++ StaticMethod register_cgi( $session )
 
35
 
 
36
=register= command handler.
 
37
This method is designed to be
 
38
invoked via the =UI::run= method.
 
39
 
 
40
=cut
 
41
 
 
42
sub register_cgi {
 
43
    my $session = shift;
 
44
 
 
45
    # absolute URL context for email generation
 
46
    $session->enterContext('absolute_urls');
 
47
 
 
48
    my $needApproval = 0;
 
49
 
 
50
    # Register -> Verify -> Approve -> Finish
 
51
 
 
52
    # NB. bulkRegister invoked from ManageCgiScript.
 
53
 
 
54
    my $query = $session->{request};
 
55
    my $action = $query->param('action') || '';
 
56
    
 
57
    if ( $action ne 'verify' && $query && $query->method() &&
 
58
                                uc($query->method()) ne 'POST') {
 
59
        throw Foswiki::OopsException(
 
60
            'attention',
 
61
            web   => $session->{webName},
 
62
            topic => $session->{topicName},
 
63
            def   => 'post_method_only',
 
64
            params => [ 'upload' ]
 
65
        );    
 
66
    }
 
67
 
 
68
    if ( $action eq 'register' ) {
 
69
        if ( !$session->inContext('registration_supported') ) {
 
70
            throw Foswiki::OopsException(
 
71
                'attention',
 
72
                web   => $session->{webName},
 
73
                topic => $session->{topicName},
 
74
                def   => 'registration_not_supported'
 
75
            );
 
76
        }
 
77
        if ( !$Foswiki::cfg{Register}{EnableNewUserRegistration} ) {
 
78
            throw Foswiki::OopsException(
 
79
                'attention',
 
80
                web   => $session->{webName},
 
81
                topic => $session->{topicName},
 
82
                def   => 'registration_disabled'
 
83
            );
 
84
        }
 
85
        registerAndNext($session);
 
86
    }
 
87
    elsif ( $action eq 'verify' ) {
 
88
        verifyEmailAddress($session);
 
89
        if ($needApproval) {
 
90
            throw Error::Simple('Approval code has not been written!');
 
91
        }
 
92
        complete($session);
 
93
    }
 
94
    elsif ( $action eq 'resetPassword' ) {
 
95
        resetPassword($session);
 
96
    }
 
97
    elsif ( $action eq 'approve' ) {
 
98
        complete($session);
 
99
    }
 
100
    else {
 
101
        registerAndNext($session);
 
102
    }
 
103
 
 
104
    $session->leaveContext('absolute_urls');
 
105
 
 
106
    # Output of register:
 
107
    #    UnsavedUser, accessible by username.$verificationCode
 
108
 
 
109
    # Output of reset password:
 
110
    #    unaffected user, accessible by username.$verificationCode
 
111
 
 
112
# Output of verify:
 
113
#    UnsavedUser, accessible by username.$approvalCode (only sent to administrator)
 
114
 
 
115
    # Output of approve:
 
116
    #    RegisteredUser, all related UnsavedUsers deleted
 
117
}
 
118
 
 
119
my $b1 = "\t* ";
 
120
my $b2 = "\t$b1";
 
121
 
 
122
=begin TML
 
123
 
 
124
---++ StaticMethod bulkRegister($session)
 
125
 
 
126
  Called by ManageCgiScript::bulkRegister (requires authentication) with topic = the page with the entries on it.
 
127
 
 
128
=cut
 
129
 
 
130
sub bulkRegister {
 
131
    my $session = shift;
 
132
 
 
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};
 
138
 
 
139
    # absolute URL context for email generation
 
140
    $session->enterContext('absolute_urls');
 
141
 
 
142
    my $settings = {};
 
143
 
 
144
    # This gets set from the value in the BulkRegistrations topic
 
145
    $settings->{doOverwriteTopics} = $query->param('OverwriteHomeTopics') || 0;
 
146
    $settings->{doEmailUserDetails} = $query->param('EmailUsersWithDetails')
 
147
      || 0;
 
148
 
 
149
    unless ( $session->{users}->isAdmin($user) ) {
 
150
        throw Foswiki::OopsException(
 
151
            'accessdenied', status => 403,
 
152
            def    => 'only_group',
 
153
            web    => $web,
 
154
            topic  => $topic,
 
155
            params => [ $Foswiki::cfg{SuperAdminGroup} ]
 
156
        );
 
157
    }
 
158
 
 
159
    #-- Read the topic containing the table of people to be registered
 
160
 
 
161
    my ( $meta, $text ) =
 
162
      $session->{store}->readTopic( undef, $web, $topic, undef );
 
163
    my @fields;
 
164
    my @data;
 
165
    my $gotHdr = 0;
 
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*$/ ) {
 
169
            if ($gotHdr) {
 
170
                my $i = 0;
 
171
                my %row = map { $fields[ $i++ ] => $_ } split( /\s*\|\s*/, $1 );
 
172
                push( @data, \%row );
 
173
            }
 
174
            else {
 
175
                foreach my $field ( split( /\s*\|\s*/, $1 ) ) {
 
176
                    $field =~ s/^[\s*]*(.*?)[\s*]*$/$1/;
 
177
                    push( @fields, $field );
 
178
                }
 
179
                $gotHdr = 1;
 
180
            }
 
181
        }
 
182
    }
 
183
 
 
184
    my $log = "---+ Report for Bulk Register\n";
 
185
 
 
186
    #-- Process each row, generate a log as we go
 
187
    for ( my $n = 0 ; $n < scalar(@data) ; $n++ ) {
 
188
        my $row = $data[$n];
 
189
        $row->{webName} = $userweb;
 
190
 
 
191
        unless ( $row->{WikiName} ) {
 
192
            $log .= "---++ Failed to register user on row $n: no !WikiName\n";
 
193
            next;
 
194
        }
 
195
        $row->{LoginName} = $row->{WikiName} unless $row->{LoginName};
 
196
 
 
197
        $log .= _registerSingleBulkUser( $session, \@fields, $row, $settings );
 
198
    }
 
199
 
 
200
    $log .= "----\n";
 
201
 
 
202
    my $logWeb;
 
203
    my $logTopic = $query->param('LogTopic') || $topic . 'Result';
 
204
    ( $logWeb, $logTopic ) = $session->normalizeWebTopicName( '', $logTopic );
 
205
 
 
206
    #-- Save the LogFile as designated, link back to the source topic
 
207
    $meta->put( 'TOPICPARENT', { name => $web . '.' . $topic } );
 
208
    my $err =
 
209
      $session->{store}->saveTopic( $user, $logWeb, $logTopic, $log, $meta );
 
210
 
 
211
    $session->leaveContext('absolute_urls');
 
212
 
 
213
    my $nurl = $session->getScriptUrl( 1, 'view', $web, $logTopic );
 
214
    $session->redirect( $nurl );
 
215
}
 
216
 
 
217
# Register a single user during a bulk registration process
 
218
sub _registerSingleBulkUser {
 
219
    my ( $session, $fieldNames, $row, $settings ) = @_;
 
220
    ASSERT($row) if DEBUG;
 
221
 
 
222
    my $doOverwriteTopics = defined $settings->{doOverwriteTopics}
 
223
      || throw Error::Simple('No doOverwriteTopics');
 
224
 
 
225
    my $log = "---++ Registering $row->{WikiName}\n";
 
226
 
 
227
    try {
 
228
        _validateRegistration( $session, $row, 0 );
 
229
    }
 
230
    catch Foswiki::OopsException with {
 
231
        my $e = shift;
 
232
        $log .= '<blockquote>' . $e->stringify($session) . "</blockquote>\n";
 
233
        return $log . "$b1 Registration failed\n";
 
234
    };
 
235
 
 
236
    #-- call to the registrationHandler (to amend fields) should
 
237
    # really happen in here.
 
238
 
 
239
    #-- Ensure every required field exists
 
240
    # NB. LoginName is OPTIONAL
 
241
    my @requiredFields = qw(WikiName FirstName LastName);
 
242
    if ( _missingElements( $fieldNames, \@requiredFields ) ) {
 
243
        $log .=
 
244
            $b1
 
245
          . join( ' ', @{ $settings->{fieldNames} } )
 
246
          . ' does not contain the full set of required fields '
 
247
          . join( ' ', @requiredFields ) . "\n";
 
248
        return ( undef, $log );
 
249
    }
 
250
 
 
251
    #-- Generation of the page is done from the {form} subhash,
 
252
    # so align the two
 
253
    $row->{form} = _makeFormFieldOrderMatch( $fieldNames, $row );
 
254
 
 
255
    my $users = $session->{users};
 
256
 
 
257
    try {
 
258
 
 
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}
 
263
        );
 
264
        $log .=
 
265
"$b1 $row->{WikiName} has been added to the password and user mapping managers\n";
 
266
 
 
267
        if ( $settings->{doOverwriteTopics}
 
268
            || !$session->{store}
 
269
            ->topicExists( $row->{webName}, $row->{WikiName} ) )
 
270
        {
 
271
            $log .= _createUserTopic( $session, $row );
 
272
        }
 
273
        else {
 
274
            $log .= "$b1 Not writing user topic $row->{WikiName}\n";
 
275
        }
 
276
        $users->setEmails( $cUID, $row->{Email} );
 
277
 
 
278
        $session->logEvent( 'bulkregister',
 
279
            $row->{webName} . '.' . $row->{WikiName},
 
280
            $row->{Email}, $row->{WikiName} );
 
281
    }
 
282
    catch Error::Simple with {
 
283
        my $e = shift;
 
284
        $log .= "$b1 Failed to add user: " . $e->stringify() . "\n";
 
285
    };
 
286
 
 
287
    #if ($Foswiki::cfg{EmailUserDetails}) {
 
288
    # If you want it, write it.
 
289
    # _sendEmail($session, 'registernotifybulk', $data );
 
290
    #    $log .= $b1.' Password email disabled\n';
 
291
    #}
 
292
 
 
293
    return $log;
 
294
}
 
295
 
 
296
#ensures all named fields exist in hash
 
297
#returns array containing any that are missing
 
298
sub _missingElements {
 
299
    my ( $presentArrRef, $requiredArrRef ) = @_;
 
300
    my %present;
 
301
    my @missing;
 
302
 
 
303
    $present{$_} = 1 for @$presentArrRef;
 
304
    foreach my $required (@$requiredArrRef) {
 
305
        if ( !$present{$required} ) {
 
306
            push @missing, $required;
 
307
        }
 
308
    }
 
309
    return @missing;
 
310
}
 
311
 
 
312
# rearranges the fields in $data so that they match settings
 
313
# returns a new ordered form
 
314
sub _makeFormFieldOrderMatch {
 
315
    my ( $fieldNames, $data ) = @_;
 
316
    my @form = ();
 
317
    foreach my $field (@$fieldNames) {
 
318
        push @form, { name => $field, value => $data->{$field} };
 
319
    }
 
320
    return \@form;
 
321
}
 
322
 
 
323
=begin TML
 
324
 
 
325
---++ StaticMethod registerAndNext($session) 
 
326
 
 
327
This is called when action = register or action = ""
 
328
 
 
329
It calls register and either Verify or Finish.
 
330
 
 
331
Hopefully we will get workflow integrated and rewrite this to be table driven
 
332
 
 
333
=cut
 
334
 
 
335
sub registerAndNext {
 
336
    my ($session) = @_;
 
337
    register($session);
 
338
    if ( $Foswiki::cfg{Register}{NeedVerification} ) {
 
339
        _requireVerification($session);
 
340
    }
 
341
    else {
 
342
        complete($session);
 
343
    }
 
344
}
 
345
 
 
346
=begin TML
 
347
 
 
348
---++ StaticMethod register($session)
 
349
 
 
350
This is called through: UserRegistration -> RegisterCgiScript -> here
 
351
 
 
352
=cut
 
353
 
 
354
sub register {
 
355
    my ($session) = @_;
 
356
 
 
357
    my $query = $session->{request};
 
358
    my $data = _getDataFromQuery( $query, $query->param() );
 
359
 
 
360
    $data->{webName} = $session->{webName};
 
361
    $data->{debug}   = 1;
 
362
 
 
363
    # SMELL: should perform basic checks that we have e.g. a WikiName
 
364
 
 
365
    _validateRegistration( $session, $data, 1 );
 
366
}
 
367
 
 
368
# Generate a registration record, and mail the registrant with the code.
 
369
# Redirects the browser to the confirmation screen.
 
370
sub _requireVerification {
 
371
    my ($session) = @_;
 
372
 
 
373
    my $query = $session->{request};
 
374
    my $topic = $session->{topicName};
 
375
    my $web   = $session->{webName};
 
376
 
 
377
    my $data = _getDataFromQuery( $query, $query->param() );
 
378
    $data->{LoginName} ||= $data->{WikiName};
 
379
    $data->{webName} = $web;
 
380
 
 
381
    $data->{VerificationCode} = $data->{WikiName} . '.' . int( rand(99999999) );
 
382
 
 
383
    #SMELL: used for Register unit tests
 
384
    $session->{DebugVerificationCode} = $data->{VerificationCode};
 
385
 
 
386
    require Data::Dumper;
 
387
 
 
388
    my $file = _codeFile( $data->{VerificationCode} );
 
389
    open( F, ">$file" ) or throw Error::Simple( 'Failed to open file: ' . $! );
 
390
    print F '# Verification code', "\n";
 
391
 
 
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;
 
399
    close(F);
 
400
 
 
401
    $session->logEvent('regstart',
 
402
        $Foswiki::cfg{UsersWebName} . '.' . $data->{WikiName},
 
403
        $data->{Email}, $data->{WikiName} );
 
404
 
 
405
    my $em = $data->{Email};
 
406
 
 
407
    if ( $Foswiki::cfg{EnableEmail} ) {
 
408
        my $err = _sendEmail( $session, 'registerconfirm', $data );
 
409
 
 
410
        if ($err) {
 
411
            throw Foswiki::OopsException(
 
412
                'attention',
 
413
                def    => 'registration_mail_failed',
 
414
                web    => $data->{webName},
 
415
                topic  => $topic,
 
416
                params => [ $em, $err ]
 
417
            );
 
418
        }
 
419
    }
 
420
    else {
 
421
        my $err = $session->i18n->maketext(
 
422
            'Email has been disabled for this Foswiki installation');
 
423
 
 
424
        throw Foswiki::OopsException(
 
425
            'attention',
 
426
            def    => 'send_mail_error',
 
427
            web    => $data->{webName},
 
428
            topic  => $topic,
 
429
            params => [ $em, $err ]
 
430
        );
 
431
    }
 
432
 
 
433
    throw Foswiki::OopsException(
 
434
        'attention',
 
435
        status => 200,
 
436
        def    => 'confirm',
 
437
        web    => $data->{webName},
 
438
        topic  => $topic,
 
439
        params => [$em]
 
440
    );
 
441
}
 
442
 
 
443
=begin TML
 
444
 
 
445
---++ StaticMethod resetPassword($session)
 
446
 
 
447
Generates a password. Mails it to them and asks them to change it. Entry
 
448
point intended to be called from UI::run
 
449
 
 
450
=cut
 
451
 
 
452
sub resetPassword {
 
453
    my $session = shift;
 
454
    my $query   = $session->{request};
 
455
    my $topic   = $session->{topicName};
 
456
    my $web     = $session->{webName};
 
457
    my $user    = $session->{user};
 
458
 
 
459
    unless ( $Foswiki::cfg{EnableEmail} ) {
 
460
        my $err = $session->i18n->maketext(
 
461
            'Email has been disabled for this Foswiki installation');
 
462
        throw Foswiki::OopsException(
 
463
            'attention',
 
464
            topic  => $Foswiki::cfg{HomeTopicName},
 
465
            def    => 'reset_bad',
 
466
            params => [$err]
 
467
        );
 
468
    }
 
469
 
 
470
    my @userNames = $query->param('LoginName');
 
471
    unless (@userNames) {
 
472
        throw Foswiki::OopsException( 'attention', def => 'no_users_to_reset' );
 
473
    }
 
474
    my $introduction = $query->param('Introduction') || '';
 
475
 
 
476
    # need admin priv if resetting bulk, or resetting another user
 
477
    my $isBulk = ( scalar(@userNames) > 1 );
 
478
 
 
479
    if ($isBulk) {
 
480
 
 
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,
 
486
                def    => 'only_group',
 
487
                web    => $web,
 
488
                topic  => $topic,
 
489
                params => [ $Foswiki::cfg{SuperAdminGroup} ]
 
490
            );
 
491
        }
 
492
    }
 
493
    else {
 
494
 
 
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!
 
498
    }
 
499
 
 
500
    # Collect all messages into one string
 
501
    my $message = '';
 
502
    my $good    = 1;
 
503
    foreach my $userName (@userNames) {
 
504
        $good = $good
 
505
          && _resetUsersPassword( $session, $userName, $introduction,
 
506
            \$message );
 
507
    }
 
508
 
 
509
    my $action = '';
 
510
 
 
511
    # Redirect to a page that tells what happened
 
512
    if ($good) {
 
513
        unless ($isBulk) {
 
514
 
 
515
            # one user; refine the change password link to include their
 
516
            # username (can't use logged in user - by definition this won't
 
517
            # be them!)
 
518
            $action = '?username=' . $userNames[0];
 
519
        }
 
520
 
 
521
        throw Foswiki::OopsException(
 
522
            'attention',
 
523
            status => 200,
 
524
            topic  => $Foswiki::cfg{HomeTopicName},
 
525
            def    => 'reset_ok',
 
526
            params => [$message]
 
527
        );
 
528
    }
 
529
    else {
 
530
        throw Foswiki::OopsException(
 
531
            'attention',
 
532
            topic  => $Foswiki::cfg{HomeTopicName},
 
533
            def    => 'reset_bad',
 
534
            params => [$message]
 
535
        );
 
536
    }
 
537
}
 
538
 
 
539
# return status
 
540
sub _resetUsersPassword {
 
541
    my ( $session, $login, $introduction, $pMess ) = @_;
 
542
 
 
543
    my $users = $session->{users};
 
544
 
 
545
    unless ($login) {
 
546
        $$pMess .= $session->inlineAlert( 'alertsnohtml', 'bad_user', '' );
 
547
        return 0;
 
548
    }
 
549
 
 
550
    my $user    = $users->getCanonicalUserID($login);
 
551
    my $message = '';
 
552
    unless ( $user && $users->userExists($user) ) {
 
553
 
 
554
        # Not an error.
 
555
        $$pMess .=
 
556
          $session->inlineAlert( 'alertsnohtml', 'missing_user', $login );
 
557
        return 0;
 
558
    }
 
559
 
 
560
    require Foswiki::Users;
 
561
    my $password = Foswiki::Users::randomPassword();
 
562
 
 
563
    unless ( $users->setPassword( $user, $password, 1 ) ) {
 
564
        $$pMess .= $session->inlineAlert( 'alertsnohtml', 'reset_bad', $user );
 
565
        return 0;
 
566
    }
 
567
 
 
568
    # Now that we have successfully reset the password we log the event
 
569
    $session->logEvent('resetpasswd', $login);
 
570
 
 
571
    # absolute URL context for email generation
 
572
    $session->enterContext('absolute_urls');
 
573
 
 
574
    my @em   = $users->getEmails($user);
 
575
    my $sent = 0;
 
576
    if ( !scalar(@em) ) {
 
577
        $$pMess .=
 
578
          $session->inlineAlert( 'alertsnohtml', 'no_email_for', $user );
 
579
    }
 
580
    else {
 
581
        my $ln = $users->getLoginName($user);
 
582
        my $wn = $users->getWikiName($user);
 
583
        foreach my $email (@em) {
 
584
            my $err = _sendEmail(
 
585
                $session,
 
586
                'mailresetpassword',
 
587
                {
 
588
                    webName      => $Foswiki::cfg{UsersWebName},
 
589
                    LoginName    => $ln,
 
590
                    Name         => Foswiki::spaceOutWikiWord($wn),
 
591
                    WikiName     => $wn,
 
592
                    Email        => $email,
 
593
                    PasswordA    => $password,
 
594
                    Introduction => $introduction,
 
595
                }
 
596
            );
 
597
 
 
598
            if ($err) {
 
599
                $$pMess .=
 
600
                  $session->inlineAlert(
 
601
                      'alertsnohtml', 'generic', $err );
 
602
            }
 
603
            else {
 
604
                $sent = 1;
 
605
            }
 
606
        }
 
607
    }
 
608
 
 
609
    $session->leaveContext('absolute_urls');
 
610
 
 
611
    if ($sent) {
 
612
        $$pMess .= $session->inlineAlert(
 
613
            'alertsnohtml', 'new_sys_pass',
 
614
            $users->getLoginName($user),
 
615
            $users->getWikiName($user)
 
616
        );
 
617
    }
 
618
 
 
619
    return $sent;
 
620
}
 
621
 
 
622
=begin TML
 
623
 
 
624
---++ StaticMethod changePassword( $session )
 
625
 
 
626
Change the user's password and/or email. Details of the user and password
 
627
are passed in CGI parameters.
 
628
 
 
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
 
633
   5 'oopschangepasswd'
 
634
 
 
635
The NoPasswdUser case is not handled.
 
636
 
 
637
An admin user can change other user's passwords.
 
638
 
 
639
=cut
 
640
 
 
641
sub changePassword {
 
642
    my $session = shift;
 
643
 
 
644
    my $topic       = $session->{topicName};
 
645
    my $webName     = $session->{webName};
 
646
    my $query       = $session->{request};
 
647
    my $requestUser = $session->{user};
 
648
 
 
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');
 
655
 
 
656
    # check if required fields are filled in
 
657
    unless ($login) {
 
658
        throw Foswiki::OopsException(
 
659
            'attention',
 
660
            web    => $webName,
 
661
            topic  => $topic,
 
662
            def    => 'missing_fields',
 
663
            params => ['username']
 
664
        );
 
665
    }
 
666
 
 
667
    my $users = $session->{users};
 
668
 
 
669
    unless ($login) {
 
670
        throw Foswiki::OopsException(
 
671
            'attention',
 
672
            web    => $webName,
 
673
            topic  => $topic,
 
674
            def    => 'not_a_user',
 
675
            params => [$login]
 
676
        );
 
677
    }
 
678
 
 
679
    my $changePass = 0;
 
680
    if ( defined $passwordA || defined $passwordB ) {
 
681
        unless ( defined $passwordA ) {
 
682
            throw Foswiki::OopsException(
 
683
                'attention',
 
684
                web    => $webName,
 
685
                topic  => $topic,
 
686
                def    => 'missing_fields',
 
687
                params => ['password']
 
688
            );
 
689
        }
 
690
 
 
691
        # check if passwords are identical
 
692
        if ( $passwordA ne $passwordB ) {
 
693
            throw Foswiki::OopsException(
 
694
                'attention',
 
695
                web   => $webName,
 
696
                topic => $topic,
 
697
                def   => 'password_mismatch'
 
698
            );
 
699
        }
 
700
        $changePass = 1;
 
701
    }
 
702
 
 
703
    # check if required fields are filled in
 
704
    unless ( defined $oldpassword || $users->isAdmin($requestUser) ) {
 
705
        throw Foswiki::OopsException(
 
706
            'attention',
 
707
            web    => $webName,
 
708
            topic  => $topic,
 
709
            def    => 'missing_fields',
 
710
            params => ['oldpassword']
 
711
        );
 
712
    }
 
713
 
 
714
    unless ( $users->isAdmin($requestUser)
 
715
        || $users->checkPassword( $login, $oldpassword ) )
 
716
    {
 
717
        throw Foswiki::OopsException(
 
718
            'attention',
 
719
            web   => $webName,
 
720
            topic => $topic,
 
721
            def   => 'wrong_password'
 
722
        );
 
723
    }
 
724
 
 
725
    my $cUID = $users->getCanonicalUserID($login);
 
726
    if ( defined $email ) {
 
727
        my $return = $users->setEmails( $cUID, split( /\s+/, $email ) );
 
728
    }
 
729
 
 
730
    # OK - password may be changed
 
731
    if ($changePass) {
 
732
        if ( length($passwordA) < $Foswiki::cfg{MinPasswordLength} ) {
 
733
            throw Foswiki::OopsException(
 
734
                'attention',
 
735
                web    => $webName,
 
736
                topic  => $topic,
 
737
                def    => 'bad_password',
 
738
                params => [ $Foswiki::cfg{MinPasswordLength} ]
 
739
            );
 
740
        }
 
741
 
 
742
        unless ( $users->setPassword( $cUID, $passwordA, $oldpassword ) ) {
 
743
            throw Foswiki::OopsException(
 
744
                'attention',
 
745
                web   => $webName,
 
746
                topic => $topic,
 
747
                def   => 'password_not_changed'
 
748
            );
 
749
        }
 
750
        else {
 
751
            $session->logEvent('changepasswd', $login );
 
752
        }
 
753
 
 
754
        # OK - password changed
 
755
        throw Foswiki::OopsException(
 
756
            'attention',
 
757
            status => 200,
 
758
            web   => $webName,
 
759
            topic => $topic,
 
760
            def   => 'password_changed'
 
761
        );
 
762
    }
 
763
 
 
764
    # must be just email
 
765
    throw Foswiki::OopsException(
 
766
        'attention',
 
767
        status => 200,
 
768
        web    => $webName,
 
769
        topic  => $topic,
 
770
        def    => 'email_changed',
 
771
        params => [$email]
 
772
    );
 
773
}
 
774
 
 
775
=begin TML
 
776
 
 
777
---++ StaticMethod verifyEmailAddress($session)
 
778
 
 
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
 
784
 
 
785
=cut
 
786
 
 
787
sub verifyEmailAddress {
 
788
    my ($session) = @_;
 
789
 
 
790
    my $code = $session->{request}->param('code');
 
791
    unless ($code) {
 
792
        throw Error::Simple('verifyEmailAddress: no verification code!');
 
793
    }
 
794
    my $data = _loadPendingRegistration( $session, $code );
 
795
 
 
796
    if ( !exists $data->{Email} ) {
 
797
        throw Error::Simple('verifyEmailAddress: no email address!');
 
798
    }
 
799
 
 
800
    my $topic = $session->{topicName};
 
801
    my $web   = $session->{webName};
 
802
 
 
803
}
 
804
 
 
805
=begin TML
 
806
 
 
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.
 
811
 
 
812
=cut
 
813
 
 
814
sub deleteUser {
 
815
    my $session = shift;
 
816
 
 
817
    my $webName = $session->{webName};
 
818
    my $topic   = $session->{topicName};
 
819
    my $query   = $session->{request};
 
820
    my $cUID    = $session->{user};
 
821
 
 
822
    my $password = $query->param('password');
 
823
 
 
824
    # check if user entry exists
 
825
    my $users = $session->{users};
 
826
    if ( !$users->userExists($cUID) ) {
 
827
        throw Foswiki::OopsException(
 
828
            'attention',
 
829
            web    => $webName,
 
830
            topic  => $topic,
 
831
            def    => 'not_a_user',
 
832
            params => [ $session->{users}->getWikiName($cUID) ]
 
833
        );
 
834
    }
 
835
 
 
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() ) {
 
842
        my $list = '';
 
843
        while ( $git->hasNext() ) {
 
844
            $list .= ' ' . $git->next();
 
845
        }
 
846
        throw Foswiki::OopsException(
 
847
            'attention',
 
848
            web    => $webName,
 
849
            topic  => $topic,
 
850
            def    => 'in_a_group',
 
851
            params => [ $session->{users}->getWikiName($cUID), $list ]
 
852
        );
 
853
    }
 
854
 
 
855
    unless (
 
856
        $users->checkPassword(
 
857
            $session->{users}->getLoginName($cUID), $password
 
858
        )
 
859
      )
 
860
    {
 
861
        throw Foswiki::OopsException(
 
862
            'attention',
 
863
            web   => $webName,
 
864
            topic => $topic,
 
865
            def   => 'wrong_password'
 
866
        );
 
867
    }
 
868
 
 
869
    $users->removeUser($cUID);
 
870
 
 
871
    throw Foswiki::OopsException(
 
872
        'attention', status => 200,
 
873
        def    => 'remove_user_done',
 
874
        web    => $webName,
 
875
        topic  => $topic,
 
876
        params => [ $users->getWikiName($cUID) ]
 
877
    );
 
878
}
 
879
 
 
880
# Complete a registration
 
881
sub complete {
 
882
    my ($session) = @_;
 
883
 
 
884
    my $topic = $session->{topicName};
 
885
    my $web   = $session->{webName};
 
886
    my $query = $session->{request};
 
887
    my $code  = $query->param('code');
 
888
 
 
889
    my $data;
 
890
    if ( $Foswiki::cfg{Register}{NeedVerification} ) {
 
891
        $data = _loadPendingRegistration( $session, $code );
 
892
        _clearPendingRegistrationsForUser($code);
 
893
    }
 
894
    else {
 
895
        $data = _getDataFromQuery( $query, $query->param() );
 
896
        $data->{webName} = $web;
 
897
    }
 
898
 
 
899
    $data->{WikiName} = Foswiki::Sandbox::untaint(
 
900
        $data->{WikiName}, \&Foswiki::Sandbox::validateTopicName);
 
901
    throw Error::Simple('bad WikiName after reload') unless $data->{WikiName};
 
902
 
 
903
    if ( !exists $data->{LoginName} ) {
 
904
        if ( $Foswiki::cfg{Register}{AllowLoginName} ) {
 
905
 
 
906
            # This should have been populated
 
907
            throw Error::Simple('no LoginName after reload');
 
908
        }
 
909
        $data->{LoginName} ||= $data->{WikiName};
 
910
    }
 
911
 
 
912
    my $users = $session->{users};
 
913
    try {
 
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} });
 
921
        }
 
922
        
 
923
        my $cUID = $users->addUser(
 
924
            $data->{LoginName}, $data->{WikiName},
 
925
            $data->{Password},  $data->{Email}
 
926
        );
 
927
        my $log = _createUserTopic( $session, $data );
 
928
        $users->setEmails( $cUID, $data->{Email} );
 
929
    }
 
930
    catch Error::Simple with {
 
931
        my $e = shift;
 
932
 
 
933
        # Log the error
 
934
        $session->logger->log('warning', 'Registration failed: ' . $e->stringify() );
 
935
        throw Foswiki::OopsException(
 
936
            'attention',
 
937
            web    => $data->{webName},
 
938
            topic  => $topic,
 
939
            def    => 'problem_adding',
 
940
            params => [ $data->{WikiName}, $e->stringify() ]
 
941
        );
 
942
    };
 
943
 
 
944
    # Plugin to do some other post processing of the user.
 
945
    # for legacy, (callback to set cookies - now should use LoginHandler)
 
946
    $session->{plugins}
 
947
      ->dispatch( 'registrationHandler', $data->{WebName}, $data->{WikiName},
 
948
        $data->{LoginName}, $data );
 
949
 
 
950
    my $status;
 
951
    my $safe2login = 1;
 
952
 
 
953
    if ( $Foswiki::cfg{EnableEmail} ) {
 
954
 
 
955
        # inform user and admin about the registration.
 
956
        $status = _emailRegistrationConfirmations( $session, $data );
 
957
 
 
958
        # write log entry
 
959
        if ( $Foswiki::cfg{Log}{register} ) {
 
960
            $session->logEvent('register',
 
961
                $Foswiki::cfg{UsersWebName} . '.' . $data->{WikiName},
 
962
                $data->{Email}, $data->{WikiName} );
 
963
        }
 
964
 
 
965
        if ($status) {
 
966
            $status = $session->i18n->maketext(
 
967
                'Warning: Could not send confirmation email')
 
968
              . "\n\n$status";
 
969
            $safe2login = 0;
 
970
        }
 
971
        else {
 
972
            $status = $session->i18n->maketext(
 
973
                'A confirmation e-mail has been sent to [_1]',
 
974
                $data->{Email} );
 
975
        }
 
976
    }
 
977
    else {
 
978
        $status = $session->i18n->maketext(
 
979
'Warning: Could not send confirmation email, email has been disabled'
 
980
        );
 
981
    }
 
982
 
 
983
    # Only change the session's identity _if_ the registration was done by
 
984
    # WikiGuest, and an email was correctly sent.
 
985
    if (   $safe2login
 
986
        && $session->{user} eq
 
987
        $session->{users}->getCanonicalUserID( $Foswiki::cfg{DefaultUserLogin} ) )
 
988
    {
 
989
 
 
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} );
 
995
    }
 
996
 
 
997
    # and finally display thank you page
 
998
    throw Foswiki::OopsException(
 
999
        'attention',
 
1000
        status => 200,
 
1001
        web    => $Foswiki::cfg{UsersWebName},
 
1002
        topic  => $data->{WikiName},
 
1003
        def    => 'thanks',
 
1004
        params => [ $status, $data->{WikiName} ]
 
1005
    );
 
1006
}
 
1007
 
 
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
 
1011
#
 
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 ) ) {
 
1020
 
 
1021
        # Use the local customised version
 
1022
        ( $meta, $text ) =
 
1023
          $store->readTopic( undef, $Foswiki::cfg{UsersWebName}, $template );
 
1024
    }
 
1025
    else {
 
1026
 
 
1027
        # Use the default read-only version
 
1028
        ( $meta, $text ) =
 
1029
          $store->readTopic( undef, $Foswiki::cfg{SystemWebName}, $template );
 
1030
    }
 
1031
 
 
1032
    my $log =
 
1033
        $b1
 
1034
      . ' Writing topic '
 
1035
      . $Foswiki::cfg{UsersWebName} . '.'
 
1036
      . $row->{WikiName} . "\n"
 
1037
      . "$b1 !RegistrationHandler:\n"
 
1038
      . _writeRegistrationDetailsToTopic( $session, $row, $meta, $text );
 
1039
    return $log;
 
1040
}
 
1041
 
 
1042
# Writes the registration details passed as a hash to either BulletFields
 
1043
# or FormFields on the user's topic.
 
1044
#
 
1045
sub _writeRegistrationDetailsToTopic {
 
1046
    my ( $session, $data, $meta, $text ) = @_;
 
1047
 
 
1048
    ASSERT( $data->{WikiName} ) if DEBUG;
 
1049
 
 
1050
    # TODO - there should be some way of overwriting meta without
 
1051
    # blatting the content.
 
1052
 
 
1053
    my ( $before, $repeat, $after ) = split( /%SPLIT%/, $text, 3 );
 
1054
    $before = '' unless defined($before);
 
1055
    $after  = '' unless defined($after);
 
1056
 
 
1057
    my $log;
 
1058
    my $addText;
 
1059
    my $form = $meta->get('FORM');
 
1060
    if ($form) {
 
1061
        ( $meta, $addText ) =
 
1062
          _populateUserTopicForm( $session, $form->{name}, $meta, $data );
 
1063
        $log = "$b2 Using Form Fields\n";
 
1064
    }
 
1065
    else {
 
1066
        $addText = _getRegFormAsTopicContent($data);
 
1067
        $log     = "$b2 Using Bullet Fields\n";
 
1068
    }
 
1069
    $text = $before . $addText . $after;
 
1070
 
 
1071
    my $user = $data->{WikiName};
 
1072
    $text =
 
1073
      $session->expandVariablesOnTopicCreation( $text, $user,
 
1074
        $Foswiki::cfg{UsersWebName}, $user );
 
1075
 
 
1076
    $meta->put( 'TOPICPARENT', { 'name' => $Foswiki::cfg{UsersTopicName} } );
 
1077
 
 
1078
    $session->{store}->saveTopic(
 
1079
        $session->{users}->getCanonicalUserID($Foswiki::cfg{Register}{RegistrationAgentWikiName}),
 
1080
        $Foswiki::cfg{UsersWebName},
 
1081
        $user, $text, $meta
 
1082
    );
 
1083
    return $log;
 
1084
}
 
1085
 
 
1086
# Puts form fields into the topic form
 
1087
sub _populateUserTopicForm {
 
1088
    my ( $session, $formName, $meta, $data ) = @_;
 
1089
 
 
1090
    my %inform;
 
1091
    require Foswiki::Form;
 
1092
 
 
1093
    my $form =
 
1094
      new Foswiki::Form( $session, $Foswiki::cfg{UsersWebName}, $formName );
 
1095
 
 
1096
    return ( $meta, '' ) unless $form;
 
1097
 
 
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;
 
1106
            last;
 
1107
        }
 
1108
    }
 
1109
    my $leftoverText = '';
 
1110
    foreach my $fd ( @{ $data->{form} } ) {
 
1111
        unless ( $inform{ $fd->{name} } || $SKIPKEYS{ $fd->{name} } ) {
 
1112
            $leftoverText .= "   * $fd->{name}: $fd->{value}\n";
 
1113
        }
 
1114
    }
 
1115
    return ( $meta, $leftoverText );
 
1116
}
 
1117
 
 
1118
# Registers a user using the old bullet field code
 
1119
sub _getRegFormAsTopicContent {
 
1120
    my $data = shift;
 
1121
    my $text;
 
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";
 
1129
    }
 
1130
    return $text;
 
1131
}
 
1132
 
 
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 ) = @_;
 
1138
 
 
1139
    my $skin = $session->getSkin();
 
1140
    my $template = $session->templates->readTemplate( 'registernotify', $skin );
 
1141
    my $email =
 
1142
      _buildConfirmationEmail( $session, $data, $template,
 
1143
        $Foswiki::cfg{Register}{HidePasswd} );
 
1144
 
 
1145
    my $warnings = $session->net->sendEmail($email);
 
1146
 
 
1147
    if ($warnings) {
 
1148
 
 
1149
        # Email address doesn't work, likely fraudulent registration
 
1150
        try {
 
1151
            my $users = $session->{users};
 
1152
            my $cUID  = $users->getCanonicalUserID( $data->{LoginName} );
 
1153
 
 
1154
            if ( $session->{users}->removeUser($cUID) ) {
 
1155
                $template =
 
1156
                  $session->templates->readTemplate( 'registerfailedremoved',
 
1157
                    $skin );
 
1158
            }
 
1159
            else {
 
1160
                $template =
 
1161
                  $session->templates->readTemplate( 'registerfailedunremoved',
 
1162
                    $skin );
 
1163
            }
 
1164
        }
 
1165
        catch Error::Simple with {
 
1166
 
 
1167
            # Most Mapping Managers don't support removeUser, unfortunately
 
1168
            $template =
 
1169
              $session->templates->readTemplate( 'registerfailednoremove',
 
1170
                $skin );
 
1171
        };
 
1172
    }
 
1173
    else {
 
1174
        $template =
 
1175
          $session->templates->readTemplate( 'registernotifyadmin', $skin );
 
1176
    }
 
1177
 
 
1178
    $email = _buildConfirmationEmail( $session, $data, $template, 1 );
 
1179
 
 
1180
    my $err = $session->net->sendEmail($email);
 
1181
    if ($err) {
 
1182
 
 
1183
        # don't tell the user about this one
 
1184
        $session->logger->log('warning', 'Could not confirm registration: ' . $err );
 
1185
    }
 
1186
 
 
1187
    return $warnings;
 
1188
}
 
1189
 
 
1190
#The template dictates the to: field
 
1191
sub _buildConfirmationEmail {
 
1192
    my ( $session, $data, $templateText, $hidePassword ) = @_;
 
1193
 
 
1194
    $data->{Name} ||= $data->{WikiName};
 
1195
    $data->{LoginName} = '' unless defined $data->{LoginName};
 
1196
 
 
1197
    $templateText =~ s/%FIRSTLASTNAME%/$data->{Name}/go;
 
1198
    $templateText =~ s/%WIKINAME%/$data->{WikiName}/go;
 
1199
    $templateText =~ s/%EMAILADDRESS%/$data->{Email}/go;
 
1200
 
 
1201
    $templateText =
 
1202
      $session->handleCommonTags( $templateText, $Foswiki::cfg{UsersWebName},
 
1203
        $data->{WikiName} );
 
1204
 
 
1205
    #add LoginName to make it clear to new users
 
1206
    my $loginName = $b1 . ' LoginName: ' . $data->{LoginName} . "\n";
 
1207
 
 
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) ) {
 
1216
            $value = '*******';
 
1217
        }
 
1218
        if ( ( $name ne 'Confirm' ) and 
 
1219
            ( $name ne 'LoginName' ) ) {        #skip LoginName - we've put it on top.
 
1220
            $before .= $b1 . ' ' . $name . ': ' . $value . "\n";
 
1221
        }
 
1222
    }
 
1223
    $templateText = $before . ( $after || '' );
 
1224
    $templateText =~ s/( ?) *<\/?(nop|noautolink)\/?>\n?/$1/gois;
 
1225
 
 
1226
    # remove <nop> and <noautolink> tags
 
1227
 
 
1228
    return $templateText;
 
1229
}
 
1230
 
 
1231
# Throws an Oops exception if there is a problem.
 
1232
sub _validateRegistration {
 
1233
    my ( $session, $data, $requireForm ) = @_;
 
1234
 
 
1235
    if ( !defined( $data->{LoginName} )
 
1236
        && $Foswiki::cfg{Register}{AllowLoginName} )
 
1237
    {
 
1238
 
 
1239
        # Login name is required, barf
 
1240
        throw Foswiki::OopsException(
 
1241
            'attention',
 
1242
            web    => $data->{webName},
 
1243
            topic  => $session->{topicName},
 
1244
            def    => 'bad_loginname',
 
1245
            params => ['undefined']
 
1246
        );
 
1247
    }
 
1248
    elsif ( !defined( $data->{LoginName} ) ) {
 
1249
 
 
1250
        # Login name is optional, default to the wikiname
 
1251
        $data->{LoginName} = $data->{WikiName};
 
1252
    }
 
1253
 
 
1254
    # Check if login name matches expectations
 
1255
    unless ( $session->{users}->{loginManager}->isValidLoginName(
 
1256
        $data->{LoginName} )) {
 
1257
        throw Foswiki::OopsException(
 
1258
            'attention',
 
1259
            web    => $data->{webName},
 
1260
            topic  => $session->{topicName},
 
1261
            def    => 'bad_loginname',
 
1262
            params => [ $data->{LoginName} ]
 
1263
           );
 
1264
    }
 
1265
 
 
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);
 
1282
 
 
1283
    my $store = $session->{store};
 
1284
    if (
 
1285
        $user
 
1286
        &&
 
1287
 
 
1288
        #in the pwd system
 
1289
        # OR already logged in (shortcircuit to reduce perf impact)
 
1290
        # returns undef if passwordmgr=none
 
1291
        ( ( $users->userExists($user) ) )
 
1292
        &&
 
1293
 
 
1294
#user has an entry in the mapping system (if AllowLoginName == off, then entry is automatic)
 
1295
        (
 
1296
            ( !$Foswiki::cfg{Register}{AllowLoginName} )
 
1297
            || $store->topicExists( $Foswiki::cfg{UsersWebName},
 
1298
                $wikiname )    #mapping from new login exists
 
1299
        )
 
1300
      )
 
1301
    {
 
1302
        throw Foswiki::OopsException(
 
1303
            'attention',
 
1304
            web    => $data->{webName},
 
1305
            topic  => $session->{topicName},
 
1306
            def    => 'already_exists',
 
1307
            params => [ $data->{LoginName} ]
 
1308
        );
 
1309
    }
 
1310
 
 
1311
    #new user's topic already exists
 
1312
    if ( $store->topicExists( $Foswiki::cfg{UsersWebName}, $data->{WikiName} ) ) {
 
1313
        throw Foswiki::OopsException(
 
1314
            'attention',
 
1315
            web    => $data->{webName},
 
1316
            topic  => $session->{topicName},
 
1317
            def    => 'already_exists',
 
1318
            params => [ $data->{WikiName} ]
 
1319
        );
 
1320
    }
 
1321
 
 
1322
    # Check if WikiName is a WikiName
 
1323
    if ( !Foswiki::isValidWikiWord( $data->{WikiName} ) ) {
 
1324
        throw Foswiki::OopsException(
 
1325
            'attention',
 
1326
            web    => $data->{webName},
 
1327
            topic  => $session->{topicName},
 
1328
            def    => 'bad_wikiname',
 
1329
            params => [ $data->{WikiName} ]
 
1330
        );
 
1331
    }
 
1332
 
 
1333
    if ( exists $data->{Password} ) {
 
1334
 
 
1335
        # check password length
 
1336
        my $doCheckPasswordLength =
 
1337
          (      $Foswiki::cfg{PasswordManager} ne 'none'
 
1338
              && $Foswiki::cfg{MinPasswordLength} );
 
1339
 
 
1340
        if ( $doCheckPasswordLength
 
1341
            && length( $data->{Password} ) < $Foswiki::cfg{MinPasswordLength} )
 
1342
        {
 
1343
            throw Foswiki::OopsException(
 
1344
                'attention',
 
1345
                web    => $data->{webName},
 
1346
                topic  => $session->{topicName},
 
1347
                def    => 'bad_password',
 
1348
                params => [ $Foswiki::cfg{MinPasswordLength} ]
 
1349
            );
 
1350
        }
 
1351
 
 
1352
        # check if passwords are identical
 
1353
        if ( $data->{Password} ne $data->{Confirm} ) {
 
1354
            throw Foswiki::OopsException(
 
1355
                'attention',
 
1356
                web   => $data->{webName},
 
1357
                topic => $session->{topicName},
 
1358
                def   => 'password_mismatch'
 
1359
            );
 
1360
        }
 
1361
    }
 
1362
 
 
1363
    # check valid email address
 
1364
    if ( $data->{Email} !~ $Foswiki::regex{emailAddrRegex} ) {
 
1365
        throw Foswiki::OopsException(
 
1366
            'attention',
 
1367
            web    => $data->{webName},
 
1368
            topic  => $session->{topicName},
 
1369
            def    => 'bad_email',
 
1370
            params => [ $data->{Email} ]
 
1371
        );
 
1372
    }
 
1373
 
 
1374
    return unless $requireForm;
 
1375
 
 
1376
    # check if required fields are filled in
 
1377
    unless ( $data->{form} && ( $#{ $data->{form} } > 1 ) ) {
 
1378
        throw Foswiki::OopsException(
 
1379
            'attention',
 
1380
            web    => $data->{webName},
 
1381
            topic  => $session->{topicName},
 
1382
            def    => 'missing_fields',
 
1383
            params => ['form']
 
1384
        );
 
1385
    }
 
1386
    my @missing = ();
 
1387
    foreach my $fd ( @{ $data->{form} } ) {
 
1388
        if ( ( $fd->{required} ) && ( !$fd->{value} ) ) {
 
1389
            push( @missing, $fd->{name} );
 
1390
        }
 
1391
    }
 
1392
 
 
1393
    if ( scalar(@missing) ) {
 
1394
        throw Foswiki::OopsException(
 
1395
            'attention',
 
1396
            web    => $data->{webName},
 
1397
            topic  => $session->{topicName},
 
1398
            def    => 'missing_fields',
 
1399
            params => [ join( ', ', @missing ) ]
 
1400
        );
 
1401
    }
 
1402
}
 
1403
 
 
1404
# sends $p->{template} to $p->{Email} with a bunch of substitutions.
 
1405
sub _sendEmail {
 
1406
    my ( $session, $template, $p ) = @_;
 
1407
 
 
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},
 
1419
        $p->{WikiName} );
 
1420
 
 
1421
    return $session->net->sendEmail($text);
 
1422
}
 
1423
 
 
1424
sub _codeFile {
 
1425
    my ($code) = @_;
 
1426
    ASSERT($code) if DEBUG;
 
1427
    throw Error::Simple("bad code") unless $code =~ /^(\w+)\.(\d+)$/;
 
1428
    return "$Foswiki::cfg{WorkingDir}/registration_approvals/$1.$2";
 
1429
}
 
1430
 
 
1431
sub _codeWikiName {
 
1432
    my ($code) = @_;
 
1433
    ASSERT($code) if DEBUG;
 
1434
    $code =~ s/\.\d+$//;
 
1435
    return $code;
 
1436
}
 
1437
 
 
1438
sub _clearPendingRegistrationsForUser {
 
1439
    my $code = shift;
 
1440
    my $file = _codeFile($code);
 
1441
 
 
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) );
 
1447
    }
 
1448
}
 
1449
 
 
1450
use vars qw( $data $form );
 
1451
 
 
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 ) = @_;
 
1461
 
 
1462
    ASSERT($code) if DEBUG;
 
1463
 
 
1464
    my $file;
 
1465
    try {
 
1466
        $file = _codeFile($code);
 
1467
    }
 
1468
    catch Error::Simple with {
 
1469
        throw Foswiki::OopsException(
 
1470
            'attention',
 
1471
            def    => 'bad_ver_code',
 
1472
            params => [ $code, 'Invalid code' ],
 
1473
        );
 
1474
    };
 
1475
 
 
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] ) )
 
1481
        {
 
1482
            throw Foswiki::OopsException(
 
1483
                'attention',
 
1484
                def    => 'duplicate_activation',
 
1485
                params => [$wikiName],
 
1486
            );
 
1487
        }
 
1488
        throw Foswiki::OopsException(
 
1489
            'attention',
 
1490
            def    => 'bad_ver_code',
 
1491
            params => [ $code, 'Code is not recognised' ],
 
1492
        );
 
1493
    }
 
1494
 
 
1495
    do $file;
 
1496
    $data->{form} = $form if $form;
 
1497
    throw Foswiki::OopsException(
 
1498
        'attention',
 
1499
        def    => 'bad_ver_code',
 
1500
        params => [ $code, 'Bad activation code' ]
 
1501
    ) if $!;
 
1502
    throw Foswiki::OopsException(
 
1503
        'attention',
 
1504
        def    => 'bad_ver_code',
 
1505
        params => [ $code, 'Invalid activation code ' ]
 
1506
    ) unless $data->{VerificationCode} eq $code;
 
1507
 
 
1508
    return $data;
 
1509
}
 
1510
 
 
1511
sub _getDataFromQuery {
 
1512
    my $query = shift;
 
1513
 
 
1514
    # get all parameters from the form
 
1515
    my $data = {};
 
1516
    foreach ( $query->param() ) {
 
1517
        if (/^(Twk([0-9])(.*))/) {
 
1518
            my @values = $query->param( $1 ) || ();
 
1519
            my $required = $2;
 
1520
            my $name   = $3;
 
1521
            # deal with multivalue fields like checkboxen
 
1522
            my $value = join( ',', @values );
 
1523
 
 
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} },
 
1533
                  {
 
1534
                      required => $required,
 
1535
                      name => $name,
 
1536
                      value => $value,
 
1537
                  } );
 
1538
        }
 
1539
    }
 
1540
 
 
1541
    if (   !$data->{Name}
 
1542
        && defined $data->{FirstName}
 
1543
        && defined $data->{LastName} )
 
1544
    {
 
1545
        $data->{Name} = $data->{FirstName} . ' ' . $data->{LastName};
 
1546
    }
 
1547
    return $data;
 
1548
}
 
1549
 
 
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
 
1554
sub _deleteKey {
 
1555
    my ( $row, $key ) = @_;
 
1556
    my @formArray = @{ $row->{form} };
 
1557
 
 
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 );
 
1564
            last;
 
1565
        }
 
1566
    }
 
1567
}
 
1568
 
 
1569
1;
 
1570
__DATA__
 
1571
# Module of Foswiki - The Free and Open Source Wiki, http://foswiki.org/
 
1572
#
 
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.
 
1576
#
 
1577
# Additional copyrights apply to some or all of the code in this
 
1578
# file as follows:
 
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
 
1587
#
 
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.
 
1593
#
 
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.
 
1597
#
 
1598
# As per the GPL, removal of this notice is prohibited.