/[suikacvs]/test/suika-accounts/edit.cgi
Suika

Contents of /test/suika-accounts/edit.cgi

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1.1 - (show annotations) (download)
Wed Oct 29 14:43:28 2008 UTC (15 years, 6 months ago) by wakaba
Branch: MAIN
New

1 #!/usr/bin/perl
2 use strict;
3
4 use lib qw[/home/httpd/html/www/markup/html/whatpm
5 /home/wakaba/work/manakai2/lib];
6
7 use CGI::Carp qw[fatalsToBrowser];
8 require Message::CGI::Carp;
9
10 require 'users.pl';
11
12 require Message::CGI::HTTP;
13 require Encode;
14 my $cgi = Message::CGI::HTTP->new;
15 $cgi->{decoder}->{'#default'} = sub {
16 return Encode::decode ('utf-8', $_[1]);
17 };
18
19 require Message::DOM::DOMImplementation;
20 my $dom = Message::DOM::DOMImplementation->new;
21
22 my $path = $cgi->path_info;
23 $path = '' unless defined $path;
24
25 my @path = split m#/#, percent_decode ($path), -1;
26 shift @path;
27
28 if (@path == 3 and
29 $path[0] eq 'users' and
30 $path[1] =~ /\A[0-9a-z-]+\z/) {
31 my $user_id = $path[1];
32 check_access_right (allowed_users => {$user_id => 1},
33 allowed_groups => {'admin-users' => 1});
34
35 if ($path[2] eq '') {
36 my $user_prop = get_user_prop ($user_id);
37 if ($user_prop) {
38 binmode STDOUT, ':encoding(utf-8)';
39
40 my $e_user_id = htescape ($user_id);
41
42 print qq[Content-Type: text/html; charset=utf-8
43
44 <!DOCTYPE HTML>
45 <html lang=en>
46 <title>User $e_user_id</title>
47 <link rel=stylesheet href="/www/style/html/xhtml">
48 <h1>User $e_user_id</h1>
49 ];
50
51 my @joined;
52 my @requested;
53 my @invited;
54 my @can_join;
55 my @can_request;
56 for my $group_id (get_all_groups ()) {
57 my $gs = $user_prop->{'group.' . $group_id};
58 if ($gs->{member}) {
59 push @joined, $group_id;
60 } elsif ($gs->{no_approval}) {
61 push @requested, $group_id;
62 } elsif ($gs->{invited}) {
63 push @invited, $group_id;
64 } else {
65 my $group_prop = get_group_prop ($group_id);
66 if ($group_prop->{join_condition}->{invitation}) {
67 #
68 } elsif ($group_prop->{join_condition}->{approval}) {
69 push @can_request, $group_id;
70 } else {
71 push @can_join, $group_id;
72 }
73 }
74 }
75
76 print qq[<section id=groups><h2>Groups</h2>];
77
78 if (@joined) {
79 print_list_section
80 (id => 'groups-joined',
81 title => 'Groups you have joined',
82 items => \@joined,
83 print_item => sub {
84 my $group_id = shift;
85 print q[<form action="group.] . htescape ($group_id);
86 print q[" accept-charset=utf-8 method=post>];
87 print q[<a href="../../groups/].htescape ($group_id) . '/';
88 print q[">] . htescape ($group_id), q[</a> ];
89 print q[<input type=hidden name=action value=leave>];
90 print q[<input type=submit value="Leave this group"></form>];
91 });
92 }
93
94 if (@requested) {
95 print_list_section
96 (id => 'groups-requested',
97 title => 'Groups you have requested to join but not approved yet',
98 items => \@requested,
99 print_item => sub {
100 my $group_id = shift;
101 print q[<form action="group.] . htescape ($group_id);
102 print q[" accept-charset=utf-8 method=post>];
103 print q[<a href="../../groups/].htescape ($group_id) . '/';
104 print q[">] . htescape ($group_id), q[</a> ];
105 print q[<input type=hidden name=action value=leave>];
106 print q[<input type=submit value="Cancel the request"></form>];
107 });
108 }
109
110 if (@invited) {
111 print_list_section
112 (id => 'groups-invited',
113 title => 'Groups you have been invited but not joined yet, or you have left',
114 items => \@invited,
115 print_item => sub {
116 my $group_id = shift;
117 print q[<form action="group.] . htescape ($group_id);
118 print q[" accept-charset=utf-8 method=post>];
119 print q[<a href="../../groups/].htescape ($group_id) . '/';
120 print q[">] . htescape ($group_id), q[</a> ];
121 print q[<input type=hidden name=action value=join>];
122 print q[<input type=submit value="Join this group"></form>];
123 });
124 }
125
126 if (@can_join) {
127 print_list_section
128 (id => 'groups-can-join',
129 title => 'Groups you can join now (without approval)',
130 items => \@can_join,
131 print_item => sub {
132 my $group_id = shift;
133 print q[<form action="group.] . htescape ($group_id);
134 print q[" accept-charset=utf-8 method=post>];
135 print q[<a href="../../groups/].htescape ($group_id) . '/';
136 print q[">] . htescape ($group_id), q[</a>];
137 print q[<input type=hidden name=action value=join>];
138 print q[<input type=submit value="Join this group"></form>];
139 });
140 }
141
142 if (@can_request) {
143 print_list_section
144 (id => 'groups-can-request',
145 title => 'Groups you can request to join (approval required to join)',
146 items => \@can_request,
147 print_item => sub {
148 my $group_id = shift;
149 print q[<form action="group.] . htescape ($group_id);
150 print q[" accept-charset=utf-8 method=post>];
151 print q[<a href="../../groups/].htescape ($group_id) . '/';
152 print q[">] . htescape ($group_id), q[</a> ];
153 print q[<input type=hidden name=action value=join>];
154 print q[<input type=submit value="Join this group"></form>];
155 });
156 }
157
158 print q[</section>];
159
160 print qq[<section id=password>
161 <h2>Password</h2>
162
163 <form action=password method=post accept-charset=utf-8>
164
165 <p>You can change the password.
166
167 <p><strong>New password</strong>: <input type=password name=user-pass
168 size=10 required pattern=".{4,}" title="Type 4 characters at minimum">
169
170 <p><strong>New password</strong> (type again): <input type=password
171 name=user-pass2 size=10 required pattern=".{4,}">
172
173 <p><input type=submit value=Change>
174
175 </form>
176 </section>
177
178 <section id=disable-account><h2>Disable account</h2>
179
180 <form action=disabled method=post accept-charset=utf-8>
181
182 <p><label><input type=checkbox name=action value=enable
183 @{[$user_prop->{disabled} ? '' : 'checked']}> Enable this
184 account.</label>
185
186 <p><strong>Caution!</strong> Once you disable your own account, you
187 cannot enable your account by yourself.
188
189 <p><input type=submit value=Change>
190
191 </form>
192
193 </section>];
194
195 exit;
196 }
197 } elsif ($path[2] =~ /\Agroup\.([0-9a-z-]+)\z/) {
198 my $group_id = $1;
199 if ($cgi->request_method eq 'POST') {
200 lock_start ();
201 binmode STDOUT, ':encoding(utf-8)';
202
203 my $user_prop = get_user_prop ($user_id);
204 my $group_prop = get_group_prop ($group_id);
205
206 if ($user_prop and $group_prop) {
207 my $gs = ($user_prop->{'group.' . $group_id} ||= {});
208
209 my $action = $cgi->get_parameter ('action');
210 my $status;
211 if ($action eq 'join') {
212 if ($gs->{member}) {
213 $status = q[You are a member];
214 #
215 } elsif ($gs->{no_approval}) {
216 $status = q[You are waiting for an approval];
217 #
218 } elsif ($gs->{invited}) {
219 $gs->{member} = 1;
220 $status = q[Registered];
221 #
222 } else {
223 if ($group_prop->{join_condition}->{invitation}) {
224 print_error (403, 'You are not invited to this group');
225 exit;
226 } elsif ($group_prop->{join_condition}->{approval}) {
227 $gs->{no_approval} = 1;
228 $status = q[Request submitted];
229 #
230 } else {
231 $gs->{member} = 1;
232 $status = q[Registered];
233 #
234 }
235 }
236 } elsif ($action eq 'leave') {
237 if ($gs->{member}) {
238 delete $gs->{member};
239 $gs->{invited} = 1;
240 $status = 'Unregistered';
241 #
242 } elsif ($gs->{no_approval}) {
243 delete $gs->{no_approval};
244 delete $gs->{invited};
245 $status = 'Request canceled';
246 #
247 } else {
248 $status = 'You are not a member';
249 #
250 }
251 } else {
252 print_error (400, 'Bad action parameter');
253 exit;
254 }
255
256 set_user_prop ($user_id, $user_prop);
257 regenerate_htpasswd_and_htgroup ();
258 commit ();
259
260 print qq[Status: 204 $status\n\n];
261 exit;
262 }
263 }
264 } elsif ($path[2] eq 'password') {
265 if ($cgi->request_method eq 'POST') {
266 lock_start ();
267 binmode STDOUT, ':encoding(utf-8)';
268
269 my $user_prop = get_user_prop ($user_id);
270
271 if ($user_prop) {
272 $user_prop->{pass_crypted} = check_password ($cgi);
273
274 set_user_prop ($user_id, $user_prop);
275 regenerate_htpasswd_and_htgroup ();
276 commit ();
277
278 ## Browsers do not support 205.
279 #print qq[Status: 205 Password changed\n\n];
280 print qq[Status: 204 Password changed\n\n];
281 exit;
282 }
283 }
284 } elsif ($path[2] eq 'disabled') {
285 if ($cgi->request_method eq 'POST') {
286 lock_start ();
287 binmode STDOUT, ':encoding(utf-8)';
288
289 my $user_prop = get_user_prop ($user_id);
290
291 if ($user_prop) {
292 my $action = $cgi->get_parameter ('action');
293 if (defined $action and $action eq 'enable') {
294 delete $user_prop->{disabled};
295 } else {
296 $user_prop->{disabled} = 1;
297 }
298
299 set_user_prop ($user_id, $user_prop);
300 regenerate_htpasswd_and_htgroup ();
301 commit ();
302
303 print "Status: 204 Property updated\n\n";
304 exit;
305 }
306 }
307 }
308 } elsif (@path == 3 and
309 $path[0] eq 'groups' and
310 $path[1] =~ /\A[0-9a-z-]+\z/) {
311 my $group_id = $path[1];
312 my $ac = check_access_right (allowed_groups => {'admin-groups' => 1},
313 group_context => $group_id);
314
315 if ($path[2] eq '') {
316 my $group_prop = get_group_prop ($group_id);
317 if ($group_prop) {
318 binmode STDOUT, ':encoding(utf-8)';
319
320 my $e_group_id = htescape ($group_id);
321
322 print qq[Content-Type: text/html; charset=utf-8
323
324 <!DOCTYPE HTML>
325 <html lang=en>
326 <title>Group $e_group_id</title>
327 <link rel=stylesheet href="/www/style/html/xhtml">
328 <h1>Group $e_group_id</h1>
329
330 <section id=members><h2>Members</h2>];
331
332 if ($ac->{read_group_member_list}) {
333 my @members;
334 my @apps;
335 my @invited;
336 for my $user_id (get_all_users ()) {
337 my $user_prop = get_user_prop ($user_id);
338 my $gs = $user_prop->{'group.' . $group_id};
339 if ($gs->{member}) {
340 push @members, $user_id;
341 } elsif ($gs->{no_approval}) {
342 push @apps, $user_id;
343 } elsif ($gs->{invited}) {
344 push @invited, $user_id;
345 }
346 }
347
348 if (@members) {
349 print_list_section
350 (id => 'formal-members',
351 title => 'Formal members',
352 items => \@members,
353 print_item => sub {
354 my $user_id = shift;
355 print q[<form action="user.] . htescape ($user_id);
356 print q[" accept-charset=utf-8 method=post>];
357 print qq[<a href="../../users/@{[htescape ($user_id)]}/">];
358 print '' . htescape ($user_id) . q[</a> ];
359 print q[<input type=hidden name=action value=unapprove>];
360 print q[<input type=submit value="Kick"></form>];
361 });
362 }
363
364 if (@apps) {
365 print_list_section
366 (id => 'non-approved-users',
367 title => 'Users who are waiting for the approval to join',
368 items => \@apps,
369 print_item => sub {
370 my $user_id = shift;
371 print q[<form action="user.] . htescape ($user_id);
372 print q[" accept-charset=utf-8 method=post>];
373 print qq[<a href="../../users/@{[htescape ($user_id)]}/">];
374 print '' . htescape ($user_id) . q[</a> ];
375 print q[<input type=hidden name=action value=approve>];
376 print q[<input type=submit value=Approve></form>];
377 });
378 }
379
380 if (@invited) {
381 print_list_section
382 (id => 'invited-users',
383 title => 'Users who are invited but not joined or are leaved',
384 items => \@invited,
385 print_item => sub {
386 my $user_id = shift;
387 print q[<form action="user.] . htescape ($user_id);
388 print q[" accept-charset=utf-8 method=post>];
389 print qq[<a href="../../users/@{[htescape ($user_id)]}/">];
390 print '' . htescape ($user_id), q[</a> ];
391 print q[<input type=hidden name=action value=unapprove>];
392 print q[<input type=submit value="Cancel invitation"></form>];
393 });
394 }
395 }
396
397 my $join_condition = $group_prop->{join_condition};
398 my $disabled = $ac->{write} ? '' : 'disabled';
399 print qq[<section id=member-approval>
400 <h3>Member approval policy</h3>
401
402 <form action=join-condition method=post accept-charset=utf-8>
403
404 <p><label><input type=radio name=condition value=invitation $disabled
405 @{[$join_condition->{invitation} ? 'checked' : '']}> A user who is
406 invited by an administrator of the group can join the group.</label>
407
408 <p><label><input type=radio name=condition value=approval $disabled
409 @{[(not $join_condition->{invitation} and $join_condition->{approval})
410 ? 'checked' : '']}> A user who is invited or approved by an
411 administrator of the group can join the group.</label>
412
413 <p><label><input type=radio name=condition value=anyone $disabled
414 @{[(not $join_condition->{invitation} and not
415 $join_condition->{approval}) ? 'checked' : '']}> Any user can join
416 the group.</label>
417
418 @{[$disabled ? '' : '<p><input type=submit value=Change>']}
419
420 </form>
421
422 </section>];
423
424 if ($ac->{write}) {
425 print q[<section id=member-invitation>
426 <h3>Invite a user</h3>
427
428 <form action=invite-user accept-charset=utf-8 method=post>
429
430 <p><strong>User id</strong>: <input type=text name=user-id
431 maxlength=20 size=10 required pattern="[0-9a-z-]{4,20}">
432
433 <p><input type=submit value=Invite>
434
435 </form>
436
437 </section>];
438 }
439
440 print q[</section>];
441
442 exit;
443 }
444 } elsif ($path[2] eq 'join-condition') {
445 forbidden () unless $ac->{write};
446
447 if ($cgi->request_method eq 'POST') {
448 lock_start ();
449 my $group_prop = get_group_prop ($group_id);
450 if ($group_prop) {
451 binmode STDOUT, ':encoding(utf-8)';
452
453 my $new_condition = $cgi->get_parameter ('condition');
454 if ($new_condition eq 'invitation') {
455 $group_prop->{join_condition}->{invitation} = 1;
456 $group_prop->{join_condition}->{approval} = 1;
457 } elsif ($new_condition eq 'approval') {
458 $group_prop->{join_condition}->{approval} = 1;
459 delete $group_prop->{join_condition}->{invitation};
460 } else {
461 delete $group_prop->{join_condition}->{invitation};
462 delete $group_prop->{join_condition}->{approval};
463 }
464
465 set_group_prop ($group_id, $group_prop);
466 commit ();
467
468 print "Status: 204 join-condition property updated\n\n";
469 exit;
470 }
471 }
472 } elsif ($path[2] =~ /\Auser\.([0-9a-z-]+)\z/ or
473 $path[2] eq 'invite-user') {
474 my $user_id = $1 // $cgi->get_parameter ('user-id') // '';
475 if ($user_id =~ /\A[0-9a-z-]+\z/ and
476 $cgi->request_method eq 'POST') {
477 forbidden () unless $ac->{write};
478
479 lock_start ();
480 my $group_prop = get_group_prop ($group_id);
481 my $user_prop = get_user_prop ($user_id);
482 if ($group_prop and $user_prop) {
483 binmode STDOUT, ':encoding(utf-8)';
484
485 my $gs = ($user_prop->{'group.' . $group_id} ||= {});
486
487 my $action = $cgi->get_parameter ('action');
488 $action = 'approve' if $path[2] eq 'invite-user';
489 my $status;
490 if ($action eq 'approve') {
491 if ($gs->{member}) {
492 $status = 'He is a member';
493 #
494 } elsif ($gs->{no_approval}) {
495 $gs->{member} = 1;
496 delete $gs->{no_approval};
497 $status = 'Registered';
498 #
499 } elsif ($gs->{invited}) {
500 $status = 'He has been invited';
501 #
502 } else {
503 $gs->{invited} = 1;
504 $status = 'Invited';
505 #
506 }
507 } elsif ($action eq 'unapprove') {
508 if ($gs->{member}) {
509 delete $gs->{member};
510 delete $gs->{invited};
511 $status = 'Unregistered';
512 #
513 } elsif ($gs->{invited}) {
514 delete $gs->{invited};
515 $status = 'Invitation canceled';
516 #
517 } else {
518 $status = 'Not a member';
519 #
520 }
521 } else {
522 print_error (400, 'Bad action parameter');
523 exit;
524 }
525
526 set_user_prop ($user_id, $user_prop);
527 regenerate_htpasswd_and_htgroup ();
528 commit ();
529
530 print "Status: 204 $status\n\n";
531 exit;
532 }
533 }
534 }
535 } elsif (@path == 1 and $path[0] eq 'new-group') {
536 check_access_right (allowed_groups => {'admin-groups' => 1});
537
538 if ($cgi->request_method eq 'POST') {
539 lock_start ();
540 binmode STDOUT, ':encoding(utf-8)';
541
542 my $group_id = $cgi->get_parameter ('group-id');
543
544 if ($group_id !~ /\A[0-9a-z-]{4,20}\z/) {
545 print_error (400, qq[Group id "$group_id" is invalid; use characters [0-9a-z-]{4,20}]);
546 exit;
547 }
548
549 if (get_group_prop ($group_id)) {
550 print_error (400, qq[Group id "$group_id" is already used]);
551 exit;
552 }
553
554 my $group_prop = {id => $group_id};
555 set_group_prop ($group_id, $group_prop);
556
557 commit ();
558
559 my $group_url = get_absolute_url ('groups/' . $group_id . '/');
560
561 print qq[Status: 201 Group registered
562 Location: $group_url
563 Content-Type: text/html; charset=utf-8
564
565 <!DOCTYPE HTML>
566 <html lang=en>
567 <title>Group "@{[htescape ($group_id)]}" registered</title>
568 <link rel=stylesheet href="/www/style/html/xhtml">
569 <h1>Group "@{[htescape ($group_id)]}" registered</h1>
570 <p>The new group is created successfully.
571 <p>See <a href="@{[htescape ($group_url)]}">the group information page</a>.];
572 exit;
573 } else {
574 binmode STDOUT, ":encoding(utf-8)";
575 print qq[Content-Type: text/html; charset=utf-8
576
577 <!DOCTYPE HTML>
578 <html lang=en>
579 <title>Create a new group</title>
580 <link rel=stylesheet href="/www/style/html/xhtml">
581 <h1>Create a new group</h1>
582
583 <form action=new-group accept-charset=utf-8 method=post>
584
585 <p><strong>Group id</strong>: <input type=text name=group-id
586 maxlength=20 size=10 required pattern="[0-9a-z-]{4,20}"
587 title="Use a string of characters 'a'..'z', '0'..'9', and '-' with length 4..10 (inclusive)">
588
589 <p><input type=submit value=Create>
590
591 </form>];
592 exit;
593 }
594 } elsif (@path == 1 and $path[0] eq '') {
595 my $user_id = $cgi->remote_user;
596 forbidden () if not defined $user_id or $user_id !~ /\A[0-9a-z-]+\z/;
597
598 my $user_url = get_absolute_url ('users/' . $user_id . '/');
599
600 print qq[Status: 303 See Other
601 Location: $user_url
602 Content-Type: text/html; charset=us-ascii
603
604 See <a href="@{[htescape ($user_url)]}">your user page</a>.];
605 exit;
606 } elsif (@path == 0) {
607 my $root_url = get_absolute_url ('edit/');
608
609 print qq[Status: 301 Moved permanently
610 Location: $root_url
611 Content-Type: text/html; charset=us-ascii
612
613 See <a href="@{[htescape ($root_url)]}">other page</a>.];
614 exit;
615 }
616
617 print_error (404, 'Not found');
618 exit;
619
620 sub print_list_section (%) {
621 my %opt = @_;
622 $opt{level} ||= 3;
623
624 print q[<section id="] . htescape ($opt{id});
625 print q["><h] . $opt{level} . q[>] . htescape ($opt{title});
626 print q[</h] . $opt{level} . q[><ul>];
627 for my $item (sort {$a cmp $b} @{$opt{items}}) {
628 print q[<li>];
629 $opt{print_item}->($item);
630 }
631 print q[</ul></section>];
632 } # print_list_section
633
634 sub check_access_right (%) {
635 my %opt = @_;
636
637 my $user_id = $cgi->remote_user;
638 forbidden () if not defined $user_id or $user_id !~ /\A[0-9a-z-]+\z/;
639
640 my $user_prop = get_user_prop ($user_id);
641 forbidden () unless $user_prop;
642
643 if ($opt{allowed_users}->{$user_id}) {
644 return {
645 write => 1,
646 #read_group_member_list => 0,
647 };
648 }
649
650 my $ac = {};
651 my $return_ac;
652 for my $group_id (keys %{$opt{allowed_groups} or {}}) {
653 my $group_prop = get_group_prop ($group_id);
654 next unless $group_prop;
655
656 my $gs = $user_prop->{'group.' . $group_id};
657 if ($gs->{member}) {
658 return {write => 1, read_group_member_list => 1};
659 }
660 }
661
662 if (defined $opt{group_context}) {
663 my $group_prop = get_group_prop ($opt{group_context});
664 if ($group_prop) {
665 my $gs = $user_prop->{'group.' . $opt{group_context}};
666 if ($gs->{member}) {
667 $return_ac = 1;
668 } elsif ($gs->{invited}) {
669 $return_ac = 1;
670 } elsif ($group_prop->{join_condition}->{acception}) {
671 $return_ac = 1;
672 } elsif (not $group_prop->{join_condition}->{invitation}) {
673 $return_ac = 1;
674 }
675 }
676 }
677
678 return $ac if $return_ac;
679
680 forbidden ();
681 } # check_access_right
682
683 sub forbidden () {
684 my $user = $cgi->remote_user;
685 if (defined $user) {
686 print_error (403, q[Forbidden (you've logged in as ] . $user . ')');
687 } else {
688 print_error (403, 'Forbidden');
689 }
690 exit;
691 } # forbidden
692
693 sub percent_decode ($) {
694 return $dom->create_uri_reference ($_[0])
695 ->get_iri_reference
696 ->uri_reference;
697 } # percent_decode
698
699 sub get_absolute_url ($) {
700 return $dom->create_uri_reference ($_[0])
701 ->get_absolute_reference ($cgi->request_uri)
702 ->get_iri_reference
703 ->uri_reference;
704 } # get_absolute_url

admin@suikawiki.org
ViewVC Help
Powered by ViewVC 1.1.24