Date: Wed, 21 May 2014 15:17:38 +0000 (UTC) From: Dag-Erling Smørgrav <des@FreeBSD.org> To: src-committers@freebsd.org, svn-src-user@freebsd.org Subject: svn commit: r266499 - in user/des/fbce/lib/FBCE: Schema/Result Script Message-ID: <201405211517.s4LFHcjx059991@svn.freebsd.org>
next in thread | raw e-mail | index | archive | help
Author: des Date: Wed May 21 15:17:38 2014 New Revision: 266499 URL: http://svnweb.freebsd.org/changeset/base/266499 Log: Backport some of the improved user handling from the FBP code. Modified: user/des/fbce/lib/FBCE/Schema/Result/Person.pm user/des/fbce/lib/FBCE/Script/User.pm Modified: user/des/fbce/lib/FBCE/Schema/Result/Person.pm ============================================================================== --- user/des/fbce/lib/FBCE/Schema/Result/Person.pm Wed May 21 15:17:22 2014 (r266498) +++ user/des/fbce/lib/FBCE/Schema/Result/Person.pm Wed May 21 15:17:38 2014 (r266499) @@ -210,7 +210,6 @@ __PACKAGE__->has_many( # DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:40qaS/evx1U+HUHTXygyFQ use Crypt::SaltedHash; -use Digest::MD5 qw(md5_hex); # # Change the password. @@ -218,7 +217,13 @@ use Digest::MD5 qw(md5_hex); sub set_password($$) { my ($self, $password) = @_; - my $csh = new Crypt::SaltedHash(algorithm => 'SHA-1'); + if ($password !~ m/^[[:print:]]{8,}$/a || $password !~ m/[0-9]/a || + $password !~ m/[A-Z]/a || $password !~ m/[a-z]/a) { + die("Your password must be at least 8 characters long and contain" . + " at least one upper-case letter, one lower-case letter and" . + " one digit.\n"); + } + my $csh = new Crypt::SaltedHash(algorithm => 'SHA-256'); $csh->add($password); $self->set_column(password => $csh->generate()); $self->update() Modified: user/des/fbce/lib/FBCE/Script/User.pm ============================================================================== --- user/des/fbce/lib/FBCE/Script/User.pm Wed May 21 15:17:22 2014 (r266498) +++ user/des/fbce/lib/FBCE/Script/User.pm Wed May 21 15:17:38 2014 (r266499) @@ -1,15 +1,12 @@ +use utf8; package FBCE::Script::User; use Moose; -use MooseX::Types::Common::Numeric qw/PositiveInt/; -use MooseX::Types::Moose qw/Str Bool Int/; +use MooseX::Types::Moose qw/Bool Str/; use FBCE; use Archive::Tar; -use LWP::UserAgent; use namespace::autoclean; -use Data::Dumper; - with 'Catalyst::ScriptRole'; has debug => ( @@ -28,81 +25,113 @@ has dryrun => ( documentation => q{Dry run}, ); -# XXX should be traits -our %lwp_options = ( - timeout => 10, - env_proxy => 1, - keep_alive => 1, +has tarball => ( + traits => [qw(Getopt)], + cmd_aliases => 't', + isa => Str, + is => 'ro', + documentation => q{Name of password tarball}, + default => 'fbce-passwords.tgz', ); -# Survey URLs for various repos -sub survey_url($) { "http://people.freebsd.org/~peter/$_[0].total.txt" } - -# Name of password tarball -our $pwtar = 'fbce-passwords.tgz'; +has file => ( + traits => [qw(Getopt)], + cmd_aliases => 'f', + isa => Str, + is => 'ro', + documentation => q{Name of password file}, + default => 'fbce-password', +); # -# Download and parse Peter Wemm's survey for a specific repo +# Read a list of users. # -sub retrieve_commit_data($$) { - my ($self, $repo) = @_; +sub _read_users($@) { + my ($self, @argv) = @_; - # create new user agent unless one already exists - $self->{user_agent} //= LWP::UserAgent->new(%lwp_options); - my $url = survey_url($repo); - my $req = HTTP::Request->new(GET => $url); - warn("Retrieving $url...\n") - if $self->debug; - my $res = $self->{user_agent}->request($req); - if (!$res->is_success()) { - die("$url: " . $res->status_line() . "\n"); - } - my $survey = $res->decoded_content(); - foreach (split('\n', $survey)) { - # - # Each line looks like this: - # - # 20120430 ok 84 95 des - # - # The first column is the date of the last commit. The second - # column is "ok" if this committer has a commit bit in this - # repo, "doc" or "visitor" if they have a commit bit in a - # different repo or "-" if they are retired. The third and - # fourth columns are not relevant to us. The fifth is the - # freefall login. - # - next unless m/^(\d\d\d\d)(\d\d)(\d\d)\s+ - (?:\w+)\s+ - (?:\d+)\s+ - (?:\d+)\s+ - (\w+)\s*$/x && - $1 > 0 && $2 > 0 && $3 > 0; - my $date = DateTime->new(year => $1, month => $2, day => $3, - time_zone => 'UTC'); - my $login = $4; - if (defined($self->{committers}->{$login}) && - DateTime->compare($date, $self->{committers}->{$login}) < 0) { -# warn(sprintf("skipping %s: %s < %s\n", $login, $date->ymd(), -# $self->{committers}->{$login}->ymd())) -# if $self->debug; + my %users; + @ARGV = @argv; + while (<>) { + chomp(); + if (m/^\s*(\w+)\s*$/) { + # login + $users{$1} = $1; + } elsif (m/^\s*(\w+)\s+(\S.*\S)\s*$/) { + # login gecos + $users{$1} = $2; + } elsif (m/^(\w+)(?::[^:]*){3}:([^:,]*)(?:,[^:]*)?(?::[^:]*){2}$/) { + # v7 passwd file + $users{$1} = $2 || $1; + } elsif (m/^(\w+)(?::[^:]*){6}:([^:,]*)(?:,[^:]*)?(?::[^:]*){2}$/) { + # BSD passwd file + $users{$1} = $2 || $1; } else { -# warn(sprintf("adding %s: %s (%s)\n", $login, $date->ymd(), $repo)) -# if $self->debug; - $self->{committers}->{$login} = $date; + # ignore } } + return \%users; +} + +# +# Activate or deactivate named users +# +sub _set_active($$@) { + my ($self, $active, @users) = @_; + + my $persons = FBCE->model('FBCE::Person'); + my $schema = $persons->result_source()->schema(); + $schema->txn_do(sub { + foreach my $login (@users) { + my $person = $persons->find({ login => $login }); + if ($person) { + warn("marking $login " . + ($active ? "active" : "inactive") . "\n") + if $self->debug; + $person->update({ active => $active }); + } else { + warn("No such user: $login\n"); + } + } + $schema->txn_rollback() + if $self->dryrun; + }); +} + +# +# Mark named users as incumbent or not +# +sub _set_incumbent($$@) { + my ($self, $incumbent, @users) = @_; + + my $persons = FBCE->model('FBCE::Person'); + my $schema = $persons->result_source()->schema(); + $schema->txn_do(sub { + foreach my $login (@users) { + my $person = $persons->find({ login => $login }); + if ($person) { + warn("marking $login " . + ($incumbent ? "incumbent" : "inincumbent") . "\n") + if $self->debug; + $person->update({ incumbent => $incumbent }); + } else { + warn("No such user: $login\n"); + } + } + $schema->txn_rollback() + if $self->dryrun; + }); } # # List existing users # -sub cmd_list(@) { +sub cmd_list($@) { my ($self, @argv) = @_; die("too many arguments") if @argv; my $persons = FBCE->model('FBCE::Person')-> - search({}, { order_by => 'login' }); + search(undef, { order_by => 'login' }); printf("%-16s%-8s%-8s%s\n", 'login', 'active', @@ -120,17 +149,16 @@ sub cmd_list(@) { # # Mark all users inactive # -sub cmd_smash(@) { +sub cmd_smash($@) { my ($self, @argv) = @_; die("too many arguments") if @argv; - my $persons = FBCE->model('FBCE::Person')->search(); + my $persons = FBCE->model('FBCE::Person'); my $schema = $persons->result_source()->schema(); $schema->txn_do(sub { - $persons->reset(); - while (my $person = $persons->next) { - $person->update({ active => 0 }); + foreach my $person ($persons->all) { + $person->update({ active => 0, incumbent => 0 }); } $schema->txn_rollback() if $self->dryrun; @@ -138,47 +166,53 @@ sub cmd_smash(@) { } # -# Pull the list of active committers; create users for committers that -# don't already have one, and set the active bit. +# Activate named users # -sub cmd_pull(@) { +sub cmd_activate(@) { my ($self, @argv) = @_; - die("too many arguments") - if @argv; + my $users = $self->_read_users(@argv); + $self->_set_active(1, keys %$users); +} - # retrieve cutoff date - my $cutoff_date = FBCE->model('Rules')->cutoff_date; - warn(sprintf("Setting cutoff date to %sT%sZ\n", - $cutoff_date->ymd(), $cutoff_date->hms())) - if $self->debug; +# +# Deactivate named users +# +sub cmd_deactivate(@) { + my ($self, @argv) = @_; - # pull "last commit" data for src, ports and doc / www repos - foreach my $repo (qw(src ports docwww)) { - $self->retrieve_commit_data($repo); - } + my $users = $self->_read_users(@argv); + $self->_set_active(0, keys %$users); +} + +# +# Mark the specified user(s) as incumbent +# +sub cmd_incumbent(@) { + my ($self, @argv) = @_; + + my $users = $self->_read_users(@argv); + $self->_set_incumbent(1, keys %$users); +} + +# +# Read a list of users from a file and create corresponding database +# records. This will not touch existing users. +# +sub cmd_import(@) { + my ($self, @argv) = @_; - # insert it into the database + my $users = $self->_read_users(@argv); my $persons = FBCE->model('FBCE::Person'); my $schema = $persons->result_source()->schema(); $schema->txn_do(sub { - while (my ($login, $last_commit) = each(%{$self->{committers}})) { + while (my ($login, $gecos) = each(%$users)) { my $person = $persons->find_or_new({ login => $login }); - my $active = - DateTime->compare($last_commit, $cutoff_date) >= 0 ? 1 : 0; - if ($person->in_storage()) { - if ($active != $person->active) { - warn(sprintf("updating %s: %s -> %s\n", - $person->login, - $person->active ? 'active' : 'inactive', - $active ? 'active' : 'inactive')) - if $self->debug; - $person->update({ active => $active }); - } - } else { - $person->set_column(active => $active); - $person->insert(); - } + next if $person->in_storage; + warn("importing user $login\n") + if $self->debug; + $person->set_columns({ realname => $gecos }); + $person->update_or_insert(); } $schema->txn_rollback() if $self->dryrun; @@ -186,58 +220,29 @@ sub cmd_pull(@) { } # -# Set each user's realname column based on their gecos +# Read a list of users from a file and set their names accordingly. +# Users that are listed in the file but not in the database will be +# ignored. # -sub cmd_gecos(@) { - my ($self, $pwfn, @argv) = @_; - - my %gecos; - - die("too many arguments") - if @argv; - - # read passwd file - $pwfn //= "/etc/passwd"; - open(my $pwfh, '<', $pwfn) - or die("$pwfn: $!\n"); - warn("reading names from $pwfn\n") - if $self->debug; - while (<$pwfh>) { - chomp($_); - my @pwent = split(':', $_); - next unless @pwent == 7; - next unless $pwent[4] =~ m/^([^,]+)/; - $gecos{$pwent[0]} = $1; - } - close($pwfh); +sub cmd_gecos($@) { + my ($self, @argv) = @_; - # update the database - my $persons = FBCE->model('FBCE::Person')-> - search({}, { order_by => 'login' }); + my $users = $self->_read_users(@argv); + my $persons = FBCE->model('FBCE::Person'); my $schema = $persons->result_source()->schema(); - my $n; $schema->txn_do(sub { - warn("setting names in the database\n") - if $self->debug; - $n = 0; - $persons->reset(); - while (my $person = $persons->next) { - my $login = $person->login; - my $gecos = $gecos{$login}; - next unless $gecos; - next if $person->realname; + while (my ($login, $gecos) = each(%$users)) { + my $person = $persons->find({ login => $login }) + or next; $person->update({ realname => $gecos }); - ++$n; } - warn("$n record(s) updated\n") - if $self->debug; $schema->txn_rollback() if $self->dryrun; }); } # -# Use sysutils/pwgen2 to generate random passwords +# Use sysutils/pwgen to generate random passwords # sub pwgen($$;$) { my ($self, $n, $len) = @_; @@ -254,8 +259,8 @@ sub pwgen($$;$) { } elsif ($pid == 0) { # child process - run pwgen # ugh hardcoded... - exec('/usr/local/bin/pwgen', '-can', $len, $n); - die("child: exec(): $!\n"); + exec('/usr/local/bin/pwgen', '-can', $len, $n); + die("child: exec(): $!\n"); } # read output from child @@ -268,18 +273,18 @@ sub pwgen($$;$) { # check exit status if (waitpid($pid, 0) != $pid) { - if ($? & 0xff) { - die(sprintf("pwgen caught signal %d\n", $? & 0x7f)); - } elsif ($? >> 8) { - die(sprintf("pwgen exited with code %d\n", $? >> 8)); - } else { - die("waitpid(): $!\n"); - } + if ($? & 0xff) { + die(sprintf("pwgen caught signal %d\n", $? & 0x7f)); + } elsif ($? >> 8) { + die(sprintf("pwgen exited with code %d\n", $? >> 8)); + } else { + die("waitpid(): $!\n"); + } } close($pipe); # sanity check and we're done - die(sprintf("expected %d passwords, got %d\n", $n, @passwords)) + die(sprintf("expected %d passwords, got %d\n", $n, int(@passwords))) unless @passwords == $n; warn("got $n passwords as expected\n") if $self->debug; @@ -287,19 +292,22 @@ sub pwgen($$;$) { } # -# Generate passwords for all users. Use with caution! +# Generate passwords users that don't already have one. Use with +# caution! # -sub cmd_pwgen(@) { +sub cmd_pwgen($@) { my ($self, @argv) = @_; die("too many arguments") if @argv; - # please don't overwrite an existing password tarball... - die("$pwtar exists, delete or move and try again\n") - if -e $pwtar; + # Please don't overwrite an existing password tarball! + my $tarball = $self->tarball; + die("$tarball exists, delete or move and try again\n") + if -e $tarball; + my $pwfile = $self->file; - # generate enough passwords for everybody + # Generate enough passwords for everybody my $persons = FBCE->model('FBCE::Person')-> search({ password => '*' }, { order_by => 'login' }); my $n = $persons->count(); @@ -313,19 +321,17 @@ sub cmd_pwgen(@) { $schema->txn_do(sub { warn("setting the passwords in the database\n") if $self->debug; - $persons->reset(); - while (my $person = $persons->next) { + foreach my $person ($persons->all) { my ($login, $password) = ($person->login, shift(@passwords)); - # printf("%s\t%s\n", $person->login, $password); warn("setting password for $login\n") if $self->debug; $person->set_password($password); - $tar->add_data("$login/election-password", "$password\n", + $tar->add_data("$login/$pwfile", "$password\n", { uname => $login, gname => $login, mode => 0400 }); } warn("writing the tar file\n") if $self->debug; - $tar->write($pwtar, COMPRESS_GZIP) + $tar->write($tarball, COMPRESS_GZIP) or die($tar->error()); $schema->txn_rollback() if $self->dryrun; @@ -336,16 +342,22 @@ sub run($) { my ($self) = @_; local $ENV{CATALYST_DEBUG} = 1 - if $self->debug; + if $self->debug; my $command = shift(@{$self->extra_argv}) or die("command required\n"); if ($command eq 'list') { $self->cmd_list(@{$self->extra_argv}); + } elsif ($command eq 'import') { + $self->cmd_import(@{$self->extra_argv}); } elsif ($command eq 'smash') { $self->cmd_smash(@{$self->extra_argv}); - } elsif ($command eq 'pull') { - $self->cmd_pull(@{$self->extra_argv}); + } elsif ($command eq 'activate') { + $self->cmd_activate(@{$self->extra_argv}); + } elsif ($command eq 'deactivate') { + $self->cmd_deactivate(@{$self->extra_argv}); + } elsif ($command eq 'incumbent') { + $self->cmd_incumbent(@{$self->extra_argv}); } elsif ($command eq 'gecos') { $self->cmd_gecos(@{$self->extra_argv}); } elsif ($command eq 'pwgen') {
Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?201405211517.s4LFHcjx059991>