# Copyright © 2010 Julian Blake Kongslie # Licensed under the BSD 3-clause license. use strict; use warnings; package Piny::User; use Moose; use Moose::Util::TypeConstraints; use MooseX::StrictConstructor; use Piny::Config; use Piny::Email; use Piny::Group; # Types subtype 'Username' => as 'Str' => where { $_ =~ /^(?!(git|iki)-)[a-zA-Z][a-zA-Z0-9_.-]{0,30}$/ } => message { if ( /^((?:git|iki)-|[^a-zA-Z])/ ) { "Usernames are not allowed to begin with $1" } elsif ( /([^a-zA-Z0-9_.-])/ ) { "Usernames are not allowed to contain $1" } elsif ( /[a-zA-Z0-9_.-]{32,}/ ) { "Usernames are not allowed to be more than 31 bytes" } else { "Invalid username" } } ; # Attributes has 'uid' => ( is => 'ro' , isa => 'Int' , lazy_build => 1 ); has 'name' => ( is => 'ro' , isa => 'Username' , lazy_build => 1 ); has 'pwent' => ( is => 'ro' , isa => 'ArrayRef' , lazy_build => 1 , init_arg => undef ); has 'password_hash' => ( is => 'ro' , isa => 'Str' , lazy_build => 1 , init_arg => undef ); has 'home' => ( is => 'ro' , isa => 'Path' , lazy_build => 1 , init_arg => undef ); has 'config' => ( is => 'ro' , isa => 'Piny::Config' , lazy_build => 1 , init_arg => undef ); has 'email' => ( is => 'ro' , isa => 'Piny::Email' , lazy_build => 1 , init_arg => undef ); has 'groups' => ( is => 'ro' , isa => 'ArrayRef[Piny::Group]' , lazy_build => 1 , init_arg => undef ); # Public methods sub add_group { my ( $s, @groups ) = @_; foreach my $group ( @groups ) { $group->add_member( $s ); }; }; sub remove_group { my ( $s, @groups ) = @_; foreach my $group ( @groups ) { $group->remove_member( $s ); }; }; sub has_group { my ( $s, $group ) = @_; foreach my $owngroup ( @{$s->groups( )} ) { return 1 if $owngroup->gid( ) == $group->gid( ); }; return; }; # Class methods sub all_users { my ( $class ) = @_; my @ret; endpwent( ); while ( my @info = getpwent( ) ) { eval { my $user = $class->new( uid => $info[2] ); # Some forced early evaluation, so error checking happens now. $user->name( ); $user->email( ); push( @ret, $user ); }; }; endpwent( ); return @ret; }; # Builder methods # If constructed with just one argument, then # * If that argument is numeric, treat it as a UID. # * Otherwise, treat it as a username. around BUILDARGS => sub { my ( $orig, $class ) = ( shift, shift ); if ( @_ == 1 && ! ref $_[0] ) { if ( $_[0] =~ m/^\d+$/ ) { return $class->$orig( uid => $_[0] ); } else { return $class->$orig( name => $_[0] ); }; } else { return $class->$orig( @_ ); }; }; sub BUILD { my ( $s ) = @_; if ( not ( $s->has_uid( ) or $s->has_name( ) ) ) { die "You must provide either UID or name!"; }; if ( $s->has_uid( ) and $s->has_name( ) ) { die "You must not provide both UID and name!"; }; }; sub _build_uid { my ( $s ) = @_; return $s->pwent( )->[2]; }; sub _build_name { my ( $s ) = @_; return $s->pwent( )->[0]; }; sub _build_pwent { my ( $s ) = @_; if ( $s->has_uid( ) ) { my @res = getpwuid( $s->uid( ) ); die "getpwuid( " . $s->uid( ) . " ) failed: $!" unless @res; return \@res; } elsif ( $s->has_name( ) ) { my @res = getpwnam( $s->name( ) ); die "getpwnam( " . $s->name( ) . " ) failed: $!" unless @res; return \@res; } else { die "Not enough information provided to lookup user!"; }; }; sub _build_password_hash { my ( $s ) = @_; return $s->pwent( )->[1]; }; sub _build_home { my ( $s ) = @_; return $s->pwent( )->[7]; }; sub _build_config { my ( $s ) = @_; return Piny::Config->new( confpath => $s->home . "/.gitconfig" ); }; sub _build_email { my ( $s ) = @_; if ( not defined $s->config->user_email ) { die "You must provide a user.email attribute in your .gitconfig!\nPlease run pinyconfig --user user.email your\@email.com"; }; return Piny::Email->new( address => $s->config->user_email ); }; sub _build_groups { my ( $s ) = @_; my @res; my @ent; endgrent( ); while ( @ent = getgrent( ) ) { next if ( $ent[3] eq "" ); foreach my $member ( split( / /, $ent[3] ) ) { if ( $member eq $s->name( ) ) { push @res, Piny::Group->new( gid => $ent[2] ); last; }; }; }; endgrent( ); return \@res; }; # Moose boilerplate __PACKAGE__->meta->make_immutable; 1;