aboutsummaryrefslogtreecommitdiffstats
path: root/lib/ManaTools/Shared/Users.pm
diff options
context:
space:
mode:
Diffstat (limited to 'lib/ManaTools/Shared/Users.pm')
-rw-r--r--lib/ManaTools/Shared/Users.pm1612
1 files changed, 1612 insertions, 0 deletions
diff --git a/lib/ManaTools/Shared/Users.pm b/lib/ManaTools/Shared/Users.pm
new file mode 100644
index 0000000..83c6061
--- /dev/null
+++ b/lib/ManaTools/Shared/Users.pm
@@ -0,0 +1,1612 @@
+# vim: set et ts=4 sw=4:
+package ManaTools::Shared::Users;
+#============================================================= -*-perl-*-
+
+=head1 NAME
+
+ManaTools::Shared::Users - backend to manage users
+
+=head1 SYNOPSIS
+
+ my $userBackEnd = ManaTools::Shared::Users->new();
+ my $userInfo = $userManager->getUserInfo('username');
+
+=head1 DESCRIPTION
+
+This module gives a low level access to the system user management it uses libUSER module.
+
+
+=head1 SUPPORT
+
+You can find documentation for this module with the perldoc command:
+
+perldoc ManaTools::Shared::Users
+
+=head1 SEE ALSO
+
+libUSER
+
+=head1 AUTHOR
+
+Angelo Naselli <anaselli@linux.it>
+
+=head1 COPYRIGHT and LICENSE
+
+Copyright (C) 2014-2015, Angelo Naselli.
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License version 2, as
+published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+
+=head1 METHODS
+
+=cut
+
+use Moose;
+use diagnostics;
+
+use Config::Auto;
+use Data::Password::Meter;
+use IO::All;
+use File::Basename;
+use File::Copy;
+use File::Remove 'remove';
+
+## USER is from userdrake
+use USER;
+use English;
+use POSIX qw/ceil/;
+
+use ManaTools::Shared::Locales;
+use ManaTools::Shared;
+
+
+#=============================================================
+
+=head2 new - optional parameters
+
+=head3 face_dir
+
+ optional parameter to set the system face icon directory,
+ default value is /usr/share/mga/faces/
+
+=cut
+
+#=============================================================
+
+has 'face_dir' => (
+ is => 'rw',
+ isa => 'Str',
+ default => "/usr/share/mga/faces/",
+);
+
+#=============================================================
+
+=head2 new - optional parameters
+
+=head3 user_face_dir
+
+ optional parameter to set the user face icon directory,
+ default value is /usr/share/mga/faces/
+
+=cut
+
+#=============================================================
+has 'user_face_dir' => (
+ is => 'rw',
+ isa => 'Str',
+ default => "/usr/share/faces/",
+);
+
+
+has 'loc' => (
+ is => 'rw',
+ init_arg => undef,
+ builder => '_localeInitialize'
+);
+
+
+sub _localeInitialize {
+ my $self = shift();
+
+ # TODO fix domain binding for translation
+ $self->loc(ManaTools::Shared::Locales->new(domain_name => 'userdrake') );
+ # TODO if we want to give the opportunity to test locally add dir_name => 'path'
+}
+
+## Used by USER (for getting values? TODO need explanations, where?)
+has 'USER_GetValue' => (
+ default => -65533,
+ is => 'ro',
+ isa => 'Int',
+ init_arg => undef,
+);
+
+## Used by USER (for getting values? TODO need explanations, where?)
+has 'ctx' => (
+ is => 'ro',
+ init_arg => undef,
+ builder => '_USERInitialize',
+);
+
+sub _USERInitialize {
+ my $self = shift;
+
+ # $EUID: effective user identifier
+ if ($EUID == 0) {
+ return USER::ADMIN->new;
+ }
+
+ return undef;
+}
+
+## min (custom) UID was 500 now is 1000, let's change in a single point
+has 'min_UID' => (
+ default => 1000,
+ is => 'ro',
+ isa => 'Int',
+ init_arg => undef,
+);
+
+## min (custom) GID was 500 now should be 1000 as for users
+has 'min_GID' => (
+ default => 1000,
+ is => 'ro',
+ isa => 'Int',
+ init_arg => undef,
+);
+
+#=============================================================
+
+=head2 BUILD
+
+=head3 INPUT
+
+ $self: this object
+
+=head3 DESCRIPTION
+
+ The BUILD method is called after a Moose object is created,
+ Into this method new optional parameters are tested once,
+ instead of into any other methods.
+
+=cut
+
+#=============================================================
+sub BUILD {
+ my $self = shift;
+
+ die "Missing face directory" if (! -d $self->face_dir);
+ die "Missing user face directory" if (! -d $self->user_face_dir);
+
+ $self->face_dir($self->face_dir . "/") if (substr($self->face_dir, -1) ne "/");
+ $self->user_face_dir($self->user_face_dir . "/") if (substr($self->user_face_dir, -1) ne "/");
+
+}
+
+
+=head2 facedir
+
+=head3 OUTPUT
+
+ path to directory containing face icon
+
+=head3 DESCRIPTION
+
+ Return the directory containing face icons.
+
+=cut
+
+#=============================================================
+
+sub facedir {
+ my $self = shift;
+
+ return $self->face_dir;
+}
+
+
+#=============================================================
+
+=head2 userfacedir
+
+=head3 OUTPUT
+
+ path to directory containing user face icons
+
+=head3 DESCRIPTION
+
+ Return the directory containing user face icons.
+
+=cut
+
+#=============================================================
+
+sub userfacedir {
+ my $self = shift;
+
+ return $self->user_face_dir;
+}
+
+
+#=============================================================
+
+=head2 face2png
+
+=head3 INPUT
+
+ $face: face icon name (usually username)
+
+=head3 OUTPUT
+
+ pathname to $face named icon with png extension
+
+=head3 DESCRIPTION
+
+ This method returns the face icon pathname related to username
+
+=cut
+
+#=============================================================
+
+sub face2png {
+ my ($self, $face) = @_;
+
+ return $self->face_dir . $face . ".png" if $face;
+}
+
+#=============================================================
+
+=head2 facenames
+
+
+=head3 OUTPUT
+
+ $namelist: ARRAY reference containing the face name list
+
+=head3 DESCRIPTION
+
+ Retrieves the list of icon name from facesdir()
+
+=cut
+
+#=============================================================
+
+sub facenames {
+ my $self = shift;
+
+ my $dir = $self->face_dir;
+ my @files = io->dir($dir)->all_files;
+ my @l = grep { /^[A-Z]/ } @files;
+ my @namelist = map { my $f = fileparse($_->filename, qr/\Q.png\E/) } (@l ? @l : @files);
+
+ return \@namelist;
+}
+
+#=============================================================
+
+=head2 addKdmIcon
+
+=head3 INPUT
+
+ $user: username to add
+ $icon: chosen icon for username $user
+
+
+=head3 DESCRIPTION
+
+ Add a $user named icon to $self->user_face_dir. It just copies
+ $icon to $self->user_face_dir, naming it as $user
+
+=cut
+
+#=============================================================
+
+sub addKdmIcon {
+ my ($self, $user, $icon) = @_;
+
+ if ($icon && $user) {
+ my $icon_name = $self->face_dir . $icon . ".png";
+ my $dest = $self->user_face_dir . $user . ".png";
+
+ eval { copy($icon_name, $dest) } ;
+ }
+}
+
+#=============================================================
+
+=head2 removeKdmIcon
+
+=head3 INPUT
+
+ $user: username icon to remove
+
+=head3 DESCRIPTION
+
+ Remove a $user named icon from $self->user_face_dir
+
+=cut
+
+#=============================================================
+sub removeKdmIcon {
+ my ($self, $user) = @_;
+
+ if ($user) {
+ my $icon_name = $self->user_face_dir . $user . ".png";
+ eval { remove($icon_name) } ;
+ }
+}
+
+
+#=============================================================
+
+=head2 _valid
+
+=head3 INPUT
+
+ $name: User or Group name
+ $name_length: Max length of $name (default 32)
+
+=head3 OUTPUT
+
+ 1, locale "Ok" if valid
+ 0, and explanation string if not valid:
+ - Name field is empty please provide a name
+ - The name must contain only lower cased latin letters, numbers, '.', '-' and '_'
+ - Name is too long
+
+=head3 DESCRIPTION
+
+ this internal method return if a name is compliant to
+ a group or user name.
+
+=cut
+
+#=============================================================
+
+sub _valid {
+ my ($self, $name, $name_length) = @_;
+
+ return (0, $self->loc->N("Name field is empty please provide a name")) if (!$name );
+
+ $name_length = 32 if !$name_length;
+
+ $name =~ /^[a-z]+?[a-z0-9_\-\.]*?$/ or do {
+ return (0, $self->loc->N("The name must start with a letter and contain only lower cased latin letters, numbers, '.', '-' and '_'"));
+ };
+
+ return (0, $self->loc->N("Name is too long. Maximum length is %d", $name_length)) if (! (length($name) <= $name_length));
+
+ return (1, $self->loc->N("Ok"));
+}
+
+#=============================================================
+
+=head2 valid_username
+
+=head3 INPUT
+
+ $username: user name to check
+
+=head3 OUTPUT
+
+ 1 if valid, 0 if not (see _valid)
+
+=head3 DESCRIPTION
+
+ Checks the valididty of the string $username
+
+=cut
+
+#=============================================================
+
+sub valid_username {
+ my ($self, $username) = @_;
+
+ return $self->_valid($username, 32);
+}
+
+#=============================================================
+
+=head2 valid_groupname
+
+=head3 INPUT
+
+ $groupname: user name to check
+
+=head3 OUTPUT
+
+ 1 if valid, 0 if not (see _valid)
+
+=head3 DESCRIPTION
+
+ Checks the valididty of the string $groupname
+
+=cut
+
+#=============================================================
+sub valid_groupname {
+ my ($self, $groupname) = @_;
+
+ return $self->_valid($groupname, 16);
+}
+
+
+#=============================================================
+
+=head2 updateOrDelUsersInGroup
+
+=head3 INPUT
+
+ $name: username
+
+=head3 DESCRIPTION
+
+ Fixes user deletion into groups.
+
+=cut
+
+#=============================================================
+sub updateOrDelUserInGroup {
+ my ($self, $name) = @_;
+ my $groups = $self->ctx->GroupsEnumerateFull;
+ foreach my $g (@$groups) {
+ my $members = $g->MemberName(1, 0);
+ if (ManaTools::Shared::inArray($name, $members)) {
+ eval { $g->MemberName($name, 2) };
+ eval { $self->ctx->GroupModify($g) };
+ }
+ }
+}
+
+
+#=============================================================
+
+=head2 getGoups
+
+=head3 OUTPUT
+
+ $groups: ARRAY reference containing all the groups
+
+=head3 DESCRIPTION
+
+ This method return the configured groups
+
+=cut
+
+#=============================================================
+sub getGoups {
+ my $self = shift;
+
+ return $self->ctx->GroupsEnumerate;
+}
+
+
+#=============================================================
+
+=head2 groupNameExists
+
+=head3 INPUT
+
+ $groupname: the name of the group to check
+
+=head3 OUTPUT
+
+ if group exists
+
+=head3 DESCRIPTION
+
+ This method return if a given group exists
+
+=cut
+
+#=============================================================
+sub groupNameExists {
+ my ($self, $groupname) = @_;
+
+ return 0 if (!defined($groupname));
+
+ return (defined($self->ctx->LookupGroupByName($groupname)));
+}
+
+#=============================================================
+
+=head2 groupIDExists
+
+=head3 INPUT
+
+ $group: the id of the group to check
+
+=head3 OUTPUT
+
+ if group exists
+
+=head3 DESCRIPTION
+
+ This method return if a given group exists
+
+=cut
+
+#=============================================================
+sub groupIDExists {
+ my ($self, $group) = @_;
+
+ return 0 if (!defined($group));
+
+ return (defined($self->ctx->LookupGroupById($group)));
+}
+
+
+#=============================================================
+
+=head2 groupID
+
+=head3 INPUT
+
+ $groupname: group name
+
+=head3 OUTPUT
+
+ groupid or undef
+
+=head3 DESCRIPTION
+
+ This method returns the group id for the group name
+
+=cut
+
+#=============================================================
+sub groupID {
+ my ($self, $groupname) = @_;
+
+ my $gr = $self->ctx->LookupGroupByName($groupname);
+ return $gr->Gid($self->USER_GetValue) if ($gr);
+
+ return undef;
+}
+
+#=============================================================
+
+=head2 groupName
+
+=head3 INPUT
+
+ $gid group identifier
+
+=head3 OUTPUT
+
+ group name or undef
+
+=head3 DESCRIPTION
+
+ This method returns the group name for the given group
+ identifier
+
+=cut
+
+#=============================================================
+sub groupName {
+ my ($self, $gid) = @_;
+
+ my $gr = $self->ctx->LookupGroupById($gid);
+ return $gr->GroupName($self->USER_GetValue) if ($gr);
+
+ return undef;
+}
+
+
+#=============================================================
+
+=head2 addGroup
+
+=head3 INPUT
+
+ $params: HASH reference containing:
+ groupname => name of teh group to be added
+ gid => group id of the group to be added
+ is_system => is a system group?
+
+=head3 OUTPUT
+
+ $gid the actual group id
+
+=head3 DESCRIPTION
+
+ This method add a group to system
+
+=cut
+
+#=============================================================
+sub addGroup {
+ my ($self, $params) = @_;
+
+ my $is_system = defined($params->{is_system}) ?
+ $params->{is_system} :
+ 0;
+
+ return -1 if !defined($params->{groupname});
+
+ my $groupEnt = $self->ctx->InitGroup($params->{groupname}, $is_system);
+
+ return -1 if !defined($groupEnt);
+
+ $groupEnt->Gid($params->{gid}) if defined($params->{gid});
+
+ $self->ctx->GroupAdd($groupEnt);
+
+ return $groupEnt->Gid($self->USER_GetValue);
+}
+
+#=============================================================
+
+=head2 groupMembers
+
+=head3 INPUT
+
+ $groupname: The group name
+
+=head3 OUTPUT
+
+ $members: ARRAY reference containing all the user belonging
+ to the given $groupname
+
+=head3 DESCRIPTION
+
+ This method gets the group name and returns the users
+ belonging to it
+
+=cut
+
+#=============================================================
+sub groupMembers {
+ my ($self, $groupname) = @_;
+
+ return $groupname if !defined($groupname);
+
+ my $members = $self->ctx->EnumerateUsersByGroup($groupname);
+
+ return $members;
+}
+
+
+#=============================================================
+
+=head2 isPrimaryGroup
+
+=head3 INPUT
+
+ $groupname: the name of the group
+
+=head3 OUTPUT
+
+ $username: undef if it is primary group or the username for
+ which the group is the primary one.
+
+=head3 DESCRIPTION
+
+ This methods check if the given group name is primary group
+ for any users belonging to the group
+
+=cut
+
+#=============================================================
+sub isPrimaryGroup {
+ my ($self, $groupname) = @_;
+
+ return $groupname if !defined($groupname);
+
+ my $groupEnt = $self->ctx->LookupGroupByName($groupname);
+ my $members = $self->ctx->EnumerateUsersByGroup($groupname);
+ foreach my $username (@$members) {
+ my $userEnt = $self->ctx->LookupUserByName($username);
+ if ($userEnt && $userEnt->Gid($self->USER_GetValue) == $groupEnt->Gid($self->USER_GetValue)) {
+ return $username;
+ }
+ }
+ return undef;
+}
+
+
+#=============================================================
+
+=head2 deleteGroup
+
+=head3 INPUT
+
+ $groupname: in_par_description
+
+=head3 OUTPUT
+
+ 0: if error occurred
+ 1: if removed
+
+=head3 DESCRIPTION
+
+ This method remove the group from the system
+
+=cut
+
+#=============================================================
+sub deleteGroup {
+ my ($self, $groupname) = @_;
+
+ return 0 if !defined($groupname);
+
+ my $groupEnt = $self->ctx->LookupGroupByName($groupname);
+ eval { $self->ctx->GroupDel($groupEnt) };
+ return 0 if $@;
+
+ return 1;
+}
+
+
+
+#=============================================================
+
+=head2 modifyGroup
+
+=head3 INPUT
+
+ $groupInfo: HASH reference containing:
+ old_groupname => old name of the group (if renaming)
+ groupname => group name
+ members => users belonging to the group
+
+=head3 OUTPUT
+
+ $retval => HASH reference
+ status => 1 (ok) 0 (error)
+ error => error message if status is 0
+
+=head3 DESCRIPTION
+
+ This method modifies the group groupname
+
+=cut
+
+#=============================================================
+sub modifyGroup {
+ my ($self, $groupInfo) = @_;
+
+ die "group name is mandatory" if !defined($groupInfo->{groupname});
+
+ my $groupEnt = defined($groupInfo->{old_groupname}) ?
+ $self->ctx->LookupGroupByName($groupInfo->{old_groupname}) :
+ $self->ctx->LookupGroupByName($groupInfo->{groupname});
+
+ my $orig_groupname = $groupInfo->{groupname};
+ if (defined($groupInfo->{old_groupname}) &&
+ $groupInfo->{old_groupname} ne $groupInfo->{groupname}) {
+ $groupEnt->GroupName($groupInfo->{groupname});
+ $orig_groupname = $groupInfo->{old_groupname};
+ }
+
+ my $members = $groupInfo->{members};
+ my $gid = $groupEnt->Gid($self->USER_GetValue);
+ my $users = $self->getUsers();
+ my @susers = sort(@{$users});
+
+ foreach my $user (@susers) {
+ my $uEnt = $self->ctx->LookupGroupByName($user);
+ if ($uEnt) {
+ my $ugid = $uEnt->Gid($self->USER_GetValue);
+ my $m = $self->ctx->EnumerateUsersByGroup($orig_groupname);
+ if (MDK::Common::DataStructure::member($user, @{$members})) {
+ if (!ManaTools::Shared::inArray($user, $m)) {
+ if ($ugid != $gid) {
+ eval { $groupEnt->MemberName($user, 1) };
+ }
+ }
+ }
+ else {
+ if (ManaTools::Shared::inArray($user, $m)) {
+ if ($ugid == $gid) {
+ return {
+ status => 0,
+ error =>$self->loc->N("You cannot remove user <%s> from their primary group", $user)
+ };
+ }
+ else {
+ eval { $groupEnt->MemberName($user, 2) };
+ }
+ }
+ }
+ }
+ }
+
+ $self->ctx->GroupModify($groupEnt);
+
+ return {status => 1,};
+}
+
+#=============================================================
+
+=head2 getGroupsInfo
+
+ $options: HASH reference containing
+ groupname_filter => groupname search string
+ filter_system => hides system groups
+
+=head3 OUTPUT
+
+ $groupsInfo: HASH reference containing
+ groupname-1 => {
+ gid => group identifier
+ members => ARRAY of username
+ }
+ groupname-2 => {
+ ...
+ }
+
+=head3 DESCRIPTION
+
+ This method get group information (all groups or the
+ filtered ones)
+
+=cut
+
+#=============================================================
+sub getGroupsInfo {
+ my ($self, $options) = @_;
+
+ my $groupsInfo = {};
+ return $groupsInfo if !defined $self->ctx;
+
+ my $strfilt = $options->{groupname_filter} if exists($options->{groupname_filter});
+ my $filtergroups = $options->{filter_system} if exists($options->{filter_system});
+
+ my $groups = $self->ctx->GroupsEnumerateFull;
+
+ my @GroupReal;
+ LOOP: foreach my $g (@{$groups}) {
+ my $gid = $g->Gid($self->USER_GetValue);
+ next LOOP if $filtergroups && $gid <= 499 || $gid == 65534;
+ if ($filtergroups && $gid > 499 && $gid < $self->min_GID) {
+ my $groupname = $g->GroupName($self->USER_GetValue);
+ my $l = $self->ctx->LookupUserByName($groupname);
+ if (!defined($l)) {
+ my $members = $self->ctx->EnumerateUsersByGroup($groupname);
+ next LOOP if !scalar(@{$members});
+ foreach my $username (@$members) {
+ my $userEnt = $self->ctx->LookupUserByName($username);
+ next LOOP if $userEnt->HomeDir($self->USER_GetValue) =~ /^\/($|var\/|run\/)/ || $userEnt->LoginShell($self->USER_GetValue) =~ /(nologin|false)$/;
+ }
+ }
+ else {
+ next LOOP if $l->HomeDir($self->USER_GetValue) =~ /^\/($|var\/|run\/)/ || $l->LoginShell($self->USER_GetValue) =~ /(nologin|false)$/;
+ }
+ }
+ push @GroupReal, $g if $g->GroupName($self->USER_GetValue) =~ /^\Q$strfilt/;
+ }
+
+ foreach my $g (@GroupReal) {
+ my $groupname = $g->GroupName($self->USER_GetValue);
+ my $u_b_g = $self->ctx->EnumerateUsersByGroup($groupname);
+ my $group_id = $g->Gid($self->USER_GetValue);
+
+ $groupsInfo->{"$groupname"} = {
+ gid => $group_id,
+ members => $u_b_g,
+ };
+ }
+
+ return $groupsInfo;
+}
+
+#=============================================================
+
+=head2 getUsers
+
+=head3 OUTPUT
+
+ $users: ARRAY reference containing all the users
+
+=head3 DESCRIPTION
+
+ This method return the configured users
+
+=cut
+
+#=============================================================
+sub getUsers {
+ my $self = shift;
+
+ return $self->ctx->UsersEnumerate;
+}
+
+#=============================================================
+
+=head2 getUserInfo
+
+=head3 INPUT
+
+ $username: user name
+
+=head3 OUTPUT
+
+ $userInfo: HASH reference containing
+ {
+ uid => user identifier
+ gid => group identifier
+ fullname => user full name
+ home => home directory
+ shell => user shell
+ expire => shadow expire time
+ locked => is locked?
+ exp_min => shadow Min
+ exp_max => shadow Max
+ exp_warn => shadow Warn
+ exp_inact => shadow Inact
+ last_change => Shadow last change
+ members => groups the user belongs to
+ }
+
+=head3 DESCRIPTION
+
+ This method get all the information for the given user
+
+=cut
+
+#=============================================================
+sub getUserInfo {
+ my ($self, $username) = @_;
+
+ my $userInfo = {};
+ return $userInfo if !defined $self->ctx;
+
+ my $userEnt = $self->ctx->LookupUserByName($username);
+ return $userInfo if !defined($userEnt);
+
+ my $fullname = $userEnt->Gecos($self->USER_GetValue);
+ utf8::decode($fullname);
+ $userInfo->{fullname} = $fullname;
+ $userInfo->{shell} = $userEnt->LoginShell($self->USER_GetValue);
+ $userInfo->{home} = $userEnt->HomeDir($self->USER_GetValue);
+ $userInfo->{uid} = $userEnt->Uid($self->USER_GetValue);
+ $userInfo->{gid} = $userEnt->Gid($self->USER_GetValue);
+ $userInfo->{expire} = $userEnt->ShadowExpire($self->USER_GetValue);
+ $userInfo->{locked} = $self->ctx->IsLocked($userEnt);
+
+ $userInfo->{exp_min} = $userEnt->ShadowMin($self->USER_GetValue);
+ $userInfo->{exp_max} = $userEnt->ShadowMax($self->USER_GetValue);
+ $userInfo->{exp_warn} = $userEnt->ShadowWarn($self->USER_GetValue);
+ $userInfo->{exp_inact} = $userEnt->ShadowInact($self->USER_GetValue);
+ $userInfo->{last_change} = $userEnt->ShadowLastChange($self->USER_GetValue);
+ $userInfo->{members} = $self->ctx->EnumerateGroupsByUser($username);
+
+ return $userInfo;
+}
+
+#=============================================================
+
+=head2 getUsersInfo
+
+=head3 INPUT
+
+ $options: HASH reference containing
+ username_filter => username search string
+ filter_system => hides system users
+
+=head3 OUTPUT
+
+ $usersInfo: HASH reference containing
+ username-1 => {
+ uid => user identifier
+ group => primary group name
+ gid => group identifier
+ fullname => user full name
+ home => home directory
+ shell => user shell
+ status => login status (locked, expired, etc)
+ }
+ username-2 => {
+ ...
+ }
+
+=head3 DESCRIPTION
+
+ This method get user information (all users or filtered ones)
+
+=cut
+
+#=============================================================
+sub getUsersInfo {
+ my ($self, $options) = @_;
+
+ my $usersInfo = {};
+ return $usersInfo if !defined $self->ctx;
+
+ my $strfilt = $options->{username_filter} if exists($options->{username_filter});
+ my $filterusers = $options->{filter_system} if exists($options->{filter_system});
+
+ my ($users, $group, $groupnm, $expr);
+ $users = $self->ctx->UsersEnumerateFull;
+
+ my @UserReal;
+ LOOP: foreach my $l (@{$users}) {
+ my $uid = $l->Uid($self->USER_GetValue);
+ next LOOP if $filterusers && $uid <= 499 || $uid == 65534;
+ next LOOP if $filterusers && $uid > 499 && $uid < $self->min_UID &&
+ ($l->HomeDir($self->USER_GetValue) =~ /^\/($|var\/|run\/)/ || $l->LoginShell($self->USER_GetValue) =~ /(nologin|false)$/);
+ push @UserReal, $l if $l->UserName($self->USER_GetValue) =~ /^\Q$strfilt/;
+ }
+ my $i;
+ my $itemColl = new yui::YItemCollection;
+ foreach my $l (@UserReal) {
+ $i++;
+ my $uid = $l->Uid($self->USER_GetValue);
+ if (!defined $uid) {
+ warn "bogus user at line $i\n";
+ next;
+ }
+ my $gid = $l->Gid($self->USER_GetValue);
+ $group = $self->ctx->LookupGroupById($gid);
+ $groupnm = '';
+ $expr = $self->computeLockExpire($l);
+ $group and $groupnm = $group->GroupName($self->USER_GetValue);
+ my $fulln = $l->Gecos($self->USER_GetValue);
+ utf8::decode($fulln);
+ my $username = $l->UserName($self->USER_GetValue);
+ my $shell = $l->LoginShell($self->USER_GetValue);
+ my $homedir = $l->HomeDir($self->USER_GetValue);
+ $usersInfo->{"$username"} = {
+ uid => $uid,
+ group => $groupnm,
+ gid => $gid,
+ fullname => $fulln,
+ home => $homedir,
+ status => $expr,
+ shell => $shell,
+ };
+ }
+
+ return $usersInfo;
+}
+
+#=============================================================
+
+=head2 getUserHome
+
+=head3 INPUT
+
+ $username: given user name
+
+=head3 OUTPUT
+
+ $homedir: user home directory
+
+=head3 DESCRIPTION
+
+ This method return the home directory belonging to the given
+ username
+
+=cut
+
+#=============================================================
+sub getUserHome {
+ my ($self, $username) = @_;
+
+ return $username if !defined($username);
+
+ my $userEnt = $self->ctx->LookupUserByName($username);
+ my $homedir = $userEnt->HomeDir($self->USER_GetValue);
+
+ return $homedir;
+}
+
+#=============================================================
+
+=head2 userNameExists
+
+=head3 INPUT
+
+ $username: the name of the user to check
+
+=head3 OUTPUT
+
+ if user exists
+
+=head3 DESCRIPTION
+
+ This method return if a given user exists
+
+=cut
+
+#=============================================================
+sub userNameExists {
+ my ($self, $username) = @_;
+
+ return 0 if (!defined($username));
+
+ return (defined($self->ctx->LookupUserByName($username)));
+}
+
+#=============================================================
+
+=head2 computeLockExpire
+
+=head3 INPUT
+
+ $l: login user info
+
+=head3 OUTPUT
+
+ $status: Locked, Expired, or empty string
+
+=head3 DESCRIPTION
+
+ This method returns if the login is Locked, Expired or ok.
+ Note this function is meant for internal use only
+
+=cut
+
+#=============================================================
+sub computeLockExpire {
+ my ( $self, $l ) = @_;
+ my $ep = $l->ShadowExpire($self->USER_GetValue);
+ my $tm = ceil(time()/(24*60*60));
+ $ep = -1 if int($tm) <= $ep;
+ my $status = $self->ctx->IsLocked($l) ? $self->loc->N("Locked") : ($ep != -1 ? $self->loc->N("Expired") : '');
+ return $status;
+}
+
+#=============================================================
+
+=head2 addUser
+
+=head3 INPUT
+
+ $params: HASH reference containing:
+ username => name of teh user to be added
+ uid => user id of the username to be added
+ is_system => is a system user?
+ homedir => user home directory
+ donotcreatehome => do not create the home directory
+ shell => user shall
+ fullname => user full name
+ gid => group id for the user
+ shadowMin => min time password validity
+ shadowMax => max time password validity
+ shadowInact =>
+ shadowWarn =>
+ password => user password
+
+=head3 OUTPUT
+
+ 0 if errors 1 if ok
+
+=head3 DESCRIPTION
+
+ This method add a user to system
+
+=cut
+
+#=============================================================
+sub addUser {
+ my ($self, $params) = @_;
+
+ return 0 if !defined($params->{username});
+
+ my $is_system = defined($params->{is_system}) ?
+ $params->{is_system} :
+ 0;
+
+ my $userEnt = $self->ctx->InitUser($params->{username}, $is_system);
+ return 0 if !defined($userEnt);
+
+
+ $userEnt->HomeDir($params->{homedir}) if defined($params->{homedir});
+ $userEnt->Uid($params->{uid}) if defined($params->{uid});
+ $userEnt->Gecos($params->{fullname}) if defined($params->{fullname});
+ $userEnt->LoginShell($params->{shell}) if defined($params->{shell});
+ $userEnt->Gid($params->{gid}) if defined ($params->{gid});
+ my $shd = defined ($params->{shadowMin}) ? $params->{shadowMin} : -1;
+ $userEnt->ShadowMin($shd);
+ $shd = defined ($params->{shadowMax}) ? $params->{shadowMax} : 99999;
+ $userEnt->ShadowMax($shd);
+ $shd = defined ($params->{shadowWarn}) ? $params->{shadowWarn} : -1;
+ $userEnt->ShadowWarn($shd);
+ $shd = defined ($params->{shadowInact}) ? $params->{shadowInact} : -1;
+ $userEnt->ShadowInact($shd);
+ $self->ctx->UserAdd($userEnt, $is_system, $params->{donotcreatehome});
+ $self->ctx->UserSetPass($userEnt, $params->{password});
+
+ return 1;
+}
+
+
+#=============================================================
+
+=head2 modifyUser
+
+=head3 INPUT
+
+ $userInfo: HASH reference containing:
+ old_username => old name of the user (if renaming)
+ username => user name
+ fullname => full name of teh user
+ password => password
+ homedir => home directory
+ shell => user shell
+ members => groups the user belongs to
+ gid => primary group identifier
+ lockuser => lock user
+ acc_expires => account expire time - containing:
+ exp_y => year
+ exp_m => month
+ exp_d => day
+ password_expires => password expire time - containing:
+ exp_min => min
+ exp_max => max
+ exp_warn => when warn
+ exp_inact => when inactive
+
+=head3 DESCRIPTION
+
+ This method modifies the group groupname
+
+=cut
+
+#=============================================================
+sub modifyUser {
+ my ($self, $userInfo) = @_;
+
+ die "user name is mandatory" if !defined($userInfo->{username});
+ die "primary group identifier is mandatory" if !defined($userInfo->{gid});
+ die "a valid group identifier is mandatory" if $userInfo->{gid} < 0;
+
+ if (defined($userInfo->{acc_expires})) {
+ die "expiring year is mandatory" if !defined($userInfo->{acc_expires}->{exp_y});
+ die "expiring month is mandatory" if !defined($userInfo->{acc_expires}->{exp_m});
+ die "expiring day is mandatory" if !defined($userInfo->{acc_expires}->{exp_d});
+ }
+ if (defined($userInfo->{password_expires})) {
+ die "password expiring min is mandatory" if !($userInfo->{password_expires}->{exp_min});
+ die "password expiring max is mandatory" if !($userInfo->{password_expires}->{exp_max});
+ die "password expiring warn is mandatory" if !($userInfo->{password_expires}->{exp_warn});
+ die "password expiring inactive is mandatory" if !($userInfo->{password_expires}->{exp_inact});
+ }
+ my $userEnt = defined($userInfo->{old_username}) ?
+ $self->ctx->LookupUserByName($userInfo->{old_username}) :
+ $self->ctx->LookupUserByName($userInfo->{username});
+
+ my $orig_username = $userInfo->{username};
+ if (defined($userInfo->{old_username}) &&
+ $userInfo->{old_username} ne $userInfo->{username}) {
+ $userEnt->UserName($userInfo->{username});
+ $orig_username = $userInfo->{old_username};
+ }
+
+ # $userEnt->UserName($userInfo->{username});
+ $userEnt->Gecos($userInfo->{fullname}) if defined($userInfo->{fullname});
+ $userEnt->HomeDir($userInfo->{homedir}) if defined($userInfo->{homedir});
+ $userEnt->LoginShell($userInfo->{shell}) if defined($userInfo->{shell});
+
+
+ my $username = $userEnt->UserName($self->USER_GetValue);
+ my $grps = $self->getGoups();
+ my @sgroups = sort @{$grps};
+
+ my $members = $userInfo->{members};
+ foreach my $group (@sgroups) {
+ my $gEnt = $self->ctx->LookupGroupByName($group);
+ my $ugid = $gEnt->Gid($self->USER_GetValue);
+ my $m = $gEnt->MemberName(1,0);
+ if (MDK::Common::DataStructure::member($group, @$members)) {
+ if (!ManaTools::Shared::inArray($username, $m) && $userInfo->{gid} != $ugid) {
+ eval { $gEnt->MemberName($username, 1) };
+ $self->ctx->GroupModify($gEnt);
+ }
+ }
+ else {
+ if (ManaTools::Shared::inArray($username, $m)) {
+ eval { $gEnt->MemberName($username, 2) };
+ $self->ctx->GroupModify($gEnt);
+ }
+ }
+ }
+
+ $userEnt->Gid($userInfo->{gid}) if defined($userInfo->{gid});
+
+ if (defined($userInfo->{acc_expires})) {
+ my $yr = $userInfo->{acc_expires}->{exp_y};
+ my $mo = $userInfo->{acc_expires}->{exp_m};
+ my $dy = $userInfo->{acc_expires}->{exp_d};
+ my $Exp = _ConvTime($dy, $mo, $yr);
+ $userEnt->ShadowExpire($Exp);
+ }
+ else {
+ $userEnt->ShadowExpire(ceil(-1))
+ }
+ if (defined($userInfo->{password_expires})) {
+ my $allowed = $userInfo->{password_expires}->{exp_min};
+ my $required = $userInfo->{password_expires}->{exp_max};
+ my $warning = $userInfo->{password_expires}->{exp_warn};
+ my $inactive = $userInfo->{password_expires}->{exp_inact};
+ $userEnt->ShadowMin($allowed);
+ $userEnt->ShadowMax($required);
+ $userEnt->ShadowWarn($warning);
+ $userEnt->ShadowInact($inactive);
+ }
+ else {
+ $userEnt->ShadowMin(-1);
+ $userEnt->ShadowMax(99999);
+ $userEnt->ShadowWarn(-1);
+ $userEnt->ShadowInact(-1);
+ }
+
+ $self->ctx->UserSetPass($userEnt, $userInfo->{password}) if defined($userInfo->{password});
+ $self->ctx->UserModify($userEnt);
+
+ if ($userInfo->{lockuser}) {
+ !$self->ctx->IsLocked($userEnt) and $self->ctx->Lock($userEnt);
+ }
+ else {
+ $self->ctx->IsLocked($userEnt) and $self->ctx->UnLock($userEnt);
+ }
+
+ return 1;
+}
+
+
+#=============================================================
+
+=head2 deleteUser
+
+=head3 INPUT
+
+ $username: username to be deleted
+ $options: HASH reference containing
+ clean_home => if home has to be removed
+ clean_spool => if sppol has to be removed
+
+=head3 OUTPUT
+
+ error string or undef if no errors occurred
+
+=head3 DESCRIPTION
+
+ This method delete a user from the system.
+
+=cut
+
+#=============================================================
+sub deleteUser {
+ my ($self, $username, $options) = @_;
+
+ return $username if !defined($username);
+
+ my $userEnt = $self->ctx->LookupUserByName($username);
+
+ $self->ctx->UserDel($userEnt);
+ $self->updateOrDelUserInGroup($username);
+ #Let's check out the user's primary group
+ my $usergid = $userEnt->Gid($self->USER_GetValue);
+ my $groupEnt = $self->ctx->LookupGroupById($usergid);
+ if ($groupEnt) {
+ my $member = $groupEnt->MemberName(1, 0);
+ # TODO check if 499 is ok nowadays
+ if (scalar(@$member) == 0 && $groupEnt->Gid($self->USER_GetValue) > 499) {
+ $self->ctx->GroupDel($groupEnt);
+ }
+ }
+ if (defined($options)) {
+ ## testing jusr if exists also undef is allowed
+ ## as valid option
+ if (exists($options->{clean_home})) {
+ eval { $self->ctx->CleanHome($userEnt) };
+ return $@ if $@;
+ }
+ if (exists($options->{clean_spool})) {
+ eval { $self->ctx->CleanSpool($userEnt) };
+ return $@ if $@;
+ }
+ }
+ return undef;
+}
+
+#=============================================================
+
+=head2 getUserShells
+
+
+=head3 OUTPUT
+
+ GetUserShells: from libUSER
+
+=head3 DESCRIPTION
+
+ This method returns the available shell
+
+=cut
+
+#=============================================================
+
+sub getUserShells {
+ my $self = shift;
+
+ return $self->ctx->GetUserShells;
+}
+#=============================================================
+
+=head2 GetFaceIcon
+
+=head3 INPUT
+
+ $name: icon name for the given username
+ $next: if passed means getting next icon from the given $name
+
+=head3 OUTPUT
+
+ $user_icon: icon name
+
+=head3 DESCRIPTION
+
+ This method returns the icon for the given user ($name) or the
+ following one if $next is passed
+
+=cut
+
+#=============================================================
+sub GetFaceIcon {
+ my ($self, $name, $next) = @_;
+ my $icons = $self->facenames();
+ my $i;
+ my $current_icon;
+ # remove shortcut "&" from label
+ $name =~ s/&// if ($name);
+ my $user_icon = $self->user_face_dir . $name . ".png" if ($name);
+ if ($name) {
+ $user_icon = $self->face2png($name) unless(-e $user_icon);
+ }
+ if ($name && -e $user_icon) {
+ my $current_md5 = ManaTools::Shared::md5sum($user_icon);
+ my $found = 0;
+ for ($i = 0; $i < scalar(@$icons); $i++) {
+ if (ManaTools::Shared::md5sum($self->face2png($icons->[$i])) eq $current_md5) {
+ $found = 1;
+ last;
+ }
+ }
+ if ($found) { #- current icon found in @icons, select it
+ $current_icon = $icons->[$i];
+ } else { #- add and select current icon in @icons
+ push @$icons, $user_icon;
+ $current_icon = $user_icon;
+ $i = scalar(@$icons) - 1;
+ }
+ } else {
+ #- no icon yet, select a random one
+ $current_icon = $icons->[$i = rand(scalar(@$icons))];
+ }
+
+ if ($next) {
+ $current_icon = $icons->[$i = defined $icons->[$i+1] ? $i+1 : 0];
+ }
+ return $current_icon;
+}
+
+
+#=============================================================
+
+=head2 strongPassword
+
+=head3 INPUT
+
+ $passwd: password to be checked
+
+=head3 OUTPUT
+
+ 1: if password is strong
+ 0: if password is weak
+
+=head3 DESCRIPTION
+
+ Check for a strong password
+
+=cut
+
+#=============================================================
+sub strongPassword {
+ my ($self, $passwd, $threshold) = @_;
+
+ return 0 if !$passwd;
+
+ my $pwdm = $threshold ? Data::Password::Meter->new($threshold) : Data::Password::Meter->new();
+
+ # Check a password
+ return $pwdm->strong($passwd);
+}
+
+
+# TODO methods not tested in Users.t
+
+#=============================================================
+
+=head2 weakPasswordForSecurityLevel
+
+=head3 INPUT
+
+ $passwd: password to check
+
+=head3 OUTPUT
+
+ 1: if the password is too weak for security level
+
+=head3 DESCRIPTION
+
+ Check the security level set if /etc/security/msec/security.conf
+ exists and the level is not 'standard' and if the password
+ is not at least 6 characters return true
+
+=cut
+
+#=============================================================
+sub weakPasswordForSecurityLevel {
+ my ($self, $passwd) = @_;
+
+ my $sec_conf_file = "/etc/security/msec/security.conf";
+ if (-e $sec_conf_file) {
+ my $prefs = Config::Auto::parse($sec_conf_file);
+ my $level = $prefs->{BASE_LEVEL};
+ if ($level eq 'none' or $level eq 'standard') {
+ return 0;
+ }
+ elsif (length($passwd) < 6) {
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+
+#=============================================================
+
+=head2 Add2UsersGroup
+
+=head3 INPUT
+
+ $name: username
+
+=head3 OUTPUT
+
+ $gid: group id
+
+=head3 DESCRIPTION
+
+ Adds the given username $name to 'users' group
+
+=cut
+
+#=============================================================
+sub Add2UsersGroup {
+ my ($self, $name) = @_;
+
+ my $usersgroup = $self->ctx->LookupGroupByName('users');
+ $usersgroup->MemberName($name, 1);
+ return $usersgroup->Gid($self->USER_GetValue);
+}
+
+sub _ConvTime {
+ my ($day, $month, $year) = @_;
+ my ($tm, $days, $mon, $yr);
+ $mon = $month - 1; $yr = $year - 1900;
+ $tm = POSIX::mktime(0, 0, 0, $day, $mon, $yr);
+ $days = ceil($tm / (24 * 60 * 60));
+ return $days;
+}
+
+no Moose;
+__PACKAGE__->meta->make_immutable;
+
+1;