#!/usr/bin/perl # # DW::Controller::Rename # # This controller is for renames # # Authors: # Afuna # # Copyright (c) 2010-2018 by Dreamwidth Studios, LLC. # # This program is free software; you may redistribute it and/or modify it under # the same terms as Perl itself. For a copy of the license, please reference # 'perldoc perlartistic' or 'perldoc perlgpl'. # package DW::Controller::Rename; use strict; use warnings; use DW::Controller; use DW::Routing; use DW::Template; use DW::Controller::Admin; use DW::FormErrors; use DW::RenameToken; use DW::Shop; DW::Routing->register_string( "/rename/swap", \&swap_handler, app => 1 ); # token is now passed as a get argument DW::Routing->register_string( "/rename/index", \&rename_handler, app => 1 ); DW::Routing->register_string( "/admin/rename/index", \&rename_admin_handler, app => 1 ); DW::Routing->register_string( "/admin/rename/edit", \&rename_admin_edit_handler, app => 1 ); DW::Routing->register_string( "/admin/rename/new", \&siteadmin_rename_handler, app => 1 ); DW::Controller::Admin->register_admin_page( '/', path => '/admin/rename/', ml_scope => '/admin/rename.tt', privs => [ 'siteadmin:rename', 'payments' ] ); sub rename_handler { my ($opts) = @_; my $r = DW::Request->get; my ( $ok, $rv ) = controller(); return $rv unless $ok; my $remote = LJ::get_remote(); return error_ml('rename.error.invalidaccounttype') unless $remote->is_personal; my $vars = {}; my $post_args = DW::Request->get->post_args; my $get_args = DW::Request->get->get_args; my $given_token = $get_args->{giventoken}; my $token = DW::RenameToken->new( token => $given_token ); $get_args->{type} ||= "P"; $get_args->{type} = "P" unless $get_args->{type} =~ m/^(P|C)$/; if ( $r->method eq "POST" ) { # this is kind of ugly. $post_ok is a hashref of template args # if it's a success, and false if it failed my $errors = DW::FormErrors->new; my $post_ok = handle_post( $token, $post_args, errors => $errors ); return success_ml( "/rename.tt.success", $post_ok ) if $post_ok; $vars->{errors} = $errors; } $vars->{invalidtoken} = $given_token if $given_token && !$token; my $rename_to_errors = DW::FormErrors->new; if ( $get_args->{checkuser} ) { $vars->{checkusername} = { user => $get_args->{checkuser}, status => $remote->can_rename_to( $get_args->{checkuser}, errors => $rename_to_errors ) ? "available" : "unavailable", errors => $rename_to_errors }; } if ($token) { if ( $token->applied || $token->revoked ) { $vars->{usedtoken} = $token->token; } else { $vars->{token} = $token; # not using the regular authas logic because we want to exclude the current username my $authas = LJ::make_authas_select( $remote, { selectonly => 1, type => $get_args->{type}, authas => $post_args->{authas} || $get_args->{authas}, } ); $authas .= $remote->user if $get_args->{type} eq "P"; my @rel_types = $get_args->{type} eq "P" ? qw( trusted_by watched_by trusted watched communities ) : (); $vars->{rel_types} = \@rel_types; # initialize the form based on previous posts (in case of error) or with some default values $vars->{formdata} = { authas => $authas, journaltype => $get_args->{type}, journalname => $get_args->{type} eq "P" ? $remote->user : "communityname", journalurl => $get_args->{type} eq "P" ? $remote->journal_base : LJ::journal_base( "communityname", vhost => "community" ), pageurl => "/rename?giventoken=" . $token->token, token => $token->token, touser => $post_args->{touser} || $get_args->{to} || "", redirect => $post_args->{redirect} || "disconnect", rel_options => %$post_args ? { map { $_ => 1 } $post_args->get_all("rel_options") } : { map { $_ => 1 } @rel_types }, others => %$post_args ? { map { $_ => 1 } $post_args->get_all("others") } : { email => 0 }, }; } } if ( !$token || ( $token && $token->applied ) || ( $token && $token->revoked ) ) { # grab a list of tokens they can use in case they didn't provide a usable token # assume we always have a remote because our controller is registered as requiring a remote (default behavior) $vars->{unused_tokens} = DW::RenameToken->by_owner_unused( userid => $remote->userid ); } return DW::Template->render_template( 'rename.tt', $vars ); } sub handle_post { my ( $token, $post_args, %opts ) = @_; my $errors = $opts{errors} || DW::FormErrors->new; unless ( LJ::check_form_auth( $post_args->{lj_form_auth} ) ) { $errors->add( '', '/rename.tt.error.invalidform' ); return 0; } # the journal we are going to rename: yourself or a community you maintain my $journal = LJ::get_authas_user( $post_args->{authas} ); $errors->add( 'authas', '/rename.tt.error.nojournal' ) unless $journal; my $fromusername = $journal ? $journal->user : ""; my $tousername = $post_args->{touser}; my $redirect_journal = $post_args->{redirect} && $post_args->{redirect} eq "disconnect" ? 0 : 1; $errors->add( 'redirect', '/rename.tt.error.noredirectopt' ) unless $post_args->{redirect}; # since you can't recover deleted relationships, but you can delete the relationships later if something was missed # negate the form submission so we're explicitly stating which rels we want to delete, rather than deleting everything not listed my %keep_rel = map { $_ => 1 } $post_args->get_all("rel_options"); my %del_rel = map { +"del_$_" => !$keep_rel{$_} } qw( trusted_by watched_by trusted watched communities ); my %other_opts = map { $_ => 1 } $post_args->get_all("others"); if ( $other_opts{email} ) { if ( $post_args->{redirect} ne "forward" ) { $errors->add( 'redirect', '/rename.tt.error.emailnotforward', { emaildomain => "\@$LJ::USER_DOMAIN" } ); $other_opts{email} = 0; } unless ( $journal->can_have_email_alias ) { $errors->add( 'others_email', '/rename.tt.error.emailnoalias' ); $other_opts{email} = 0; } } # try the rename and see if there are any errors $journal->rename( $tousername, user => LJ::get_remote(), token => $token, redirect => $redirect_journal, redirect_email => $other_opts{email}, %del_rel, errors => $errors ); return { from => $fromusername, to => $journal->user } unless $errors->exist; # the list of errors should be present in the caller return 0; } sub swap_handler { my $r = DW::Request->get; my ( $ok, $rv ) = controller( authas => 1 ); return $rv unless $ok; my $remote = $rv->{remote}; return error_ml('rename.error.invalidaccounttype') unless $remote->is_personal; my $vars = {}; my $post_args = DW::Request->get->post_args; if ( $r->method eq "POST" ) { # this is kind of ugly. $post_ok is a hashref of template args # if it's a success, and false if it failed my $errors = DW::FormErrors->new; my $post_ok = handle_swap_post( $post_args, user => $remote, errors => $errors ); return success_ml( "/rename/swap.tt.success", $post_ok ) if $post_ok; $vars->{errors} = $errors; } my $authas = LJ::make_authas_select( $remote, { selectonly => 1, authas => $post_args->{authas}, } ); $vars->{authas} = $authas; $vars->{formdata} = $post_args; return DW::Template->render_template( 'rename/swap.tt', $vars ); } sub handle_swap_post { my ( $post_args, %opts ) = @_; my $errors = $opts{errors} || DW::FormErrors->new; $errors->add( '', '/rename.tt.error.invalidform' ) unless LJ::check_form_auth( $post_args->{lj_form_auth} ); my $journal = LJ::get_authas_user( $post_args->{authas} ); $errors->add( 'authas', '/rename/swap.tt.error.nojournal' ) unless $journal; my $swapjournal = LJ::load_user( $post_args->{swapjournal} ); $errors->add( 'swapjournal', '/rename/swap.tt.error.invalidswapjournal' ) unless $swapjournal; my $remote = $opts{user}; my $get_unused_tokens = sub { @{ DW::RenameToken->by_owner_unused( userid => $_[0] ) || [] } }; my @check_users = ( $journal, $swapjournal ); unshift @check_users, $remote if $remote; my %check_uids = map { $_->id => 1 } @check_users; # remove duplicates my @unused_tokens = grep { defined } map { $get_unused_tokens->($_) } keys %check_uids; $errors->add( '', '/rename/swap.tt.numtokens.toofew', { aopts => "href='$LJ::SHOPROOT/renames?for=self'" } ) unless @unused_tokens > 1; return 0 if $errors->exist; ( $journal, $swapjournal ) = ( $swapjournal, $journal ) if $journal->is_community && $swapjournal->is_personal; # let's do this my $swap_errors = $opts{errors} || DW::FormErrors->new; $journal->swap_usernames( $swapjournal, user => $opts{user}, tokens => \@unused_tokens, errors => $swap_errors ); return { journal => $journal->ljuser_display, swapjournal => $swapjournal->ljuser_display } unless $swap_errors->exist; # the list of errors should be present in the caller return 0; } sub rename_admin_handler { my ( $ok, $rv ) = controller( privcheck => [ 'siteadmin:rename', 'payments' ] ); return $rv unless $ok; my $r = DW::Request->get; my $post_args = $r->post_args; my $get_args = $r->get_args; # just get the username, and not a user object, because username may no longer be valid for a user my $user = $post_args->{user} || $get_args->{user}; my @renames; if ($user) { my @rename_tokens = sort { $a->rendate <=> $b->rendate } @{ DW::RenameToken->by_username( user => $user ) || [] }; foreach my $token (@rename_tokens) { push @renames, { token => $token->token, from => $token->fromuser, to => $token->touser, # FIXME: these should probably just be in DW::RenameToken instead owner => LJ::load_userid( $token->ownerid ), target => LJ::load_userid( $token->renuserid ), date => LJ::mysql_time( $token->rendate ), }; } } my $vars = { %$rv, # lookup a list of renames involving a username user => $user, renames => $user ? \@renames : undef, }; return DW::Template->render_template( "admin/rename.tt", $vars ); } sub rename_admin_edit_handler { my ( $ok, $rv ) = controller( privcheck => [ 'siteadmin:rename', 'payments' ] ); return $rv unless $ok; my $r = DW::Request->get; my $post_args = $r->post_args; my $get_args = $r->get_args; my $token = DW::RenameToken->new( token => $get_args->{token} ); return error_ml('rename.error.notoken') unless defined $token; my $u = LJ::load_userid( $token->renuserid ); my @rel_types = qw( trusted_by watched_by trusted watched communities ); my $form = { from => $token->fromuser, to => $token->touser, byuser => LJ::load_userid( $token->ownerid ), user => $u, journaltype => $u ? $u->journaltype : "P", }; # load up the old values my $token_details = $token->details; if ($token_details) { $form->{redirect} = $token_details->{redirect}->{username} ? "forward" : "disconnect"; $form->{rel_options} = { map { $_ => !$token_details->{del}->{$_} } @rel_types }; $form->{others}->{email} = $token_details->{redirect}->{email}; } my $vars = { %$rv, formdata => $form, rel_types => \@rel_types, token => $token, nodetails => $token_details ? 0 : 1, }; if ( $r->did_post ) { my $errors = DW::FormErrors->new; my $post_ok = handle_admin_post( $token, $post_args, journal => $u, from_user => $token->fromuser, to_user => $token->touser, errors => $errors, ); return DW::Template->render_template( 'success.tt', { message => "Successfully changed settings." } ) if $post_ok; $vars->{errors} = $errors; } return DW::Template->render_template( "admin/rename_edit.tt", $vars ); } sub handle_admin_post { my ( $token, $post_args, %opts ) = @_; my $errors = $opts{errors} || DW::FormErrors->new; unless ( LJ::check_form_auth( $post_args->{lj_form_auth} ) ) { $errors->add( '', '/rename.tt.error.invalidform' ); return 0; } my %rename_opts = ( user => LJ::get_remote(), from => $opts{from_user}, to => $opts{to_user} ); if ( $post_args->{override_redirect} ) { if ( LJ::isu( $opts{journal} ) && $opts{from_user} && $opts{to_user} ) { my $redirect_journal = $post_args->{redirect} && $post_args->{redirect} eq "disconnect" ? 0 : 1; $rename_opts{break_redirect}->{username} = !$redirect_journal; } else { $errors->add_string( '', "Cannot do redirect; invalid journal, or no username provided to rename from/to." ); } } if ( $post_args->{override_relationships} ) { # since you can't recover deleted relationships, but you can delete the relationships later if something was missed # negate the form submission so we're explicitly stating which rels we want to delete, rather than deleting everything not listed my %keep_rel = map { $_ => 1 } $post_args->get_all("rel_options"); my %del_rel = map { +"del_$_" => !$keep_rel{$_} } qw( trusted_by watched_by trusted watched communities ); $rename_opts{del} = \%del_rel; } if ( $post_args->{override_others} ) { my %other_opts = map { $_ => 1 } $post_args->get_all("others"); # force email to false if we can't support forwarding for this user if ( $other_opts{email} ) { if ( $post_args->{redirect} ne "forward" ) { $errors->add( 'redirect', '/rename.tt.error.emailnotforward', { emaildomain => "\@$LJ::USER_DOMAIN" } ); $other_opts{email} = 0; } unless ( $opts{journal}->can_have_email_alias ) { $errors->add( 'others_email', '/rename.tt.error.emailnoalias' ); $other_opts{email} = 0; } } $rename_opts{break_redirect}->{email} = !$other_opts{email}; } $opts{journal}->apply_rename_opts(%rename_opts); return $errors->exist ? 0 : 1; } sub siteadmin_rename_handler { my ( $ok, $rv ) = controller( privcheck => [ 'siteadmin:rename', 'payments' ] ); return $rv unless $ok; my $r = DW::Request->get; my $post_args = DW::Request->get->post_args; my $vars = {}; if ( $r->method eq "POST" ) { my $errors = DW::FormErrors->new; my $post_ok = handle_siteadmin_rename_post( $post_args, errors => $errors ); return DW::Template->render_template( 'success.tt', { message => "Successfully changed settings." } ) if $post_ok; $vars->{errors} = $errors; # also prefill form with previously submitted data $vars->{prev_user} = $post_args->{user}; $vars->{prev_touser} = $post_args->{touser}; } return DW::Template->render_template( "admin/rename_new.tt", $vars ); } sub handle_siteadmin_rename_post { my ( $post_args, %opts ) = @_; my $errors = $opts{errors} || DW::FormErrors->new; unless ( LJ::check_form_auth( $post_args->{lj_form_auth} ) ) { $errors->add( '', '/rename.tt.error.invalidform' ); return 0; } my $from_user = LJ::load_user( $post_args->{user} ); my $to_user = $post_args->{touser}; $errors->add( 'user', '/rename.tt.error.nojournal' ) unless $from_user; $from_user->rename( $to_user, token => DW::RenameToken->create_token( systemtoken => 1 ), user => LJ::get_remote(), force => 1, errors => $errors, form_from => 'user', ) if defined $from_user; return $errors->exist ? 0 : 1; } 1;