Index: otrs/Kernel/Config/Defaults.pm diff -u otrs/Kernel/Config/Defaults.pm:1.31 otrs/Kernel/Config/Defaults.pm:1.33 --- otrs/Kernel/Config/Defaults.pm:1.31 Mon Dec 13 14:25:36 2004 +++ otrs/Kernel/Config/Defaults.pm Mon Apr 4 01:08:33 2005 @@ -2,7 +2,7 @@ # Kernel/Config/Defaults.pm - Default Config file for OTRS kernel # Copyright (C) 2001-2004 Martin Edenhofer # -- -# $Id: Defaults.pm,v 1.31 2004/12/13 19:25:36 malberts Exp $ +# $Id: Defaults.pm,v 1.33 2005/04/04 05:08:33 jason Exp $ # -- # This software comes with ABSOLUTELY NO WARRANTY. For details, see # the enclosed file COPYING for license information (GPL). If you @@ -20,7 +20,7 @@ use strict; use vars qw(@ISA $VERSION); -$VERSION = '$Revision: 1.31 $'; +$VERSION = '$Revision: 1.33 $'; $VERSION =~ s/^\$.*:\W(.*)\W.+?$/$1/; # -- @@ -316,6 +316,16 @@ Lastname => 'sn', Email => 'mail', }; + + # --------------------------------------------------- # + # Password Change Options # + # --------------------------------------------------- # + $Self->{PasswordClassEnforce} = 1; + $Self->{PasswordClassMinimum} = 3; # 4 classes max + $Self->{PasswordLengthMin} = 6; + $Self->{PasswordLengthMax} = 12; + $Self->{PasswordHistoryEnforce} = 1; + $Self->{PasswordHistoryMinimum} = 5; # Number of password changes to review # --------------------------------------------------- # # default agent settings # Index: otrs/Kernel/Modules/AdminCustomerUser.pm diff -u otrs/Kernel/Modules/AdminCustomerUser.pm:1.4 otrs/Kernel/Modules/AdminCustomerUser.pm:1.6 --- otrs/Kernel/Modules/AdminCustomerUser.pm:1.4 Tue Dec 21 22:46:08 2004 +++ otrs/Kernel/Modules/AdminCustomerUser.pm Tue Mar 22 13:15:12 2005 @@ -2,7 +2,7 @@ # Kernel/Modules/AdminCustomerUser.pm - to add/update/delete customer user and preferences # Copyright (C) 2001-2004 Martin Edenhofer # -- -# $Id: AdminCustomerUser.pm,v 1.4 2004/12/22 03:46:08 jason Exp $ +# $Id: AdminCustomerUser.pm,v 1.6 2005/03/22 18:15:12 jason Exp $ # -- # This software comes with ABSOLUTELY NO WARRANTY. For details, see # the enclosed file COPYING for license information (GPL). If you @@ -13,9 +13,10 @@ use strict; use Kernel::System::CustomerUser; +use Kernel::System::PasswordValidate; use vars qw($VERSION); -$VERSION = '$Revision: 1.4 $ '; +$VERSION = '$Revision: 1.6 $ '; $VERSION =~ s/^\$.*:\W(.*)\W.+?$/$1/; # -- @@ -145,66 +146,82 @@ $GetParam{UserCustomerID} = $Self->{ParamObject}->GetParam(Param => 'UserEmail') || ''; } $GetParam{ID} = $Self->{ParamObject}->GetParam(Param => 'ID') || ''; - # update user - if ($Self->{CustomerUserObject}->CustomerUserUpdate(%GetParam, UserID => $Self->{UserID})) { - # update preferences - foreach my $Pref (sort keys %{$Self->{ConfigObject}->Get('CustomerPreferencesView')}) { - foreach my $Group (@{$Self->{ConfigObject}->Get('CustomerPreferencesView')->{$Pref}}) { - my $PrefKey = $Self->{ConfigObject}->{CustomerPreferencesGroups}->{$Group}->{PrefKey} || ''; - my $Type = $Self->{ConfigObject}->{CustomerPreferencesGroups}->{$Group}->{Type} || ''; - if ($Type eq 'Generic' && $PrefKey) { - if (!$Self->{CustomerUserObject}->SetPreferences( - UserID => $GetParam{ID}, - Key => $PrefKey, - Value => $Self->{ParamObject}->GetParam(Param => "GenericTopic::$PrefKey"), - )) { - my $Output = $NavBar.$Self->{LayoutObject}->Error(); - $Output .= $Self->{LayoutObject}->Footer(); - return $Output; + # enforce password guidelines + my $password_obj = Kernel::System::PasswordValidate->new( + password => $GetParam{UserPassword}, + min_char_classes => $Self->{ConfigObject}->Get('PasswordClassMinimum'), + min_length => $Self->{ConfigObject}->Get('PasswordLengthMin'), + max_length => $Self->{ConfigObject}->Get('PasswordLengthMax'), + ); + my $error = $password_obj->check(); + if (($Self->{ConfigObject}->Get('PasswordClassEnforce')) && ($error != 1) && (length($GetParam{UserPassword}) > 0)) { + $Self->{LogObject}->Log(Priority => 'error', Message => $error); + my $NavBar = $Self->{LayoutObject}->Header(Area => 'Admin', Title => 'Customer User'); + my $Output = $NavBar.$Self->{LayoutObject}->Error(); + $Output .= $Self->{LayoutObject}->Footer(); + return $Output; + } else { + # update user + if ($Self->{CustomerUserObject}->CustomerUserUpdate(%GetParam, UserID => $Self->{UserID})) { + # update preferences + foreach my $Pref (sort keys %{$Self->{ConfigObject}->Get('CustomerPreferencesView')}) { + foreach my $Group (@{$Self->{ConfigObject}->Get('CustomerPreferencesView')->{$Pref}}) { + my $PrefKey = $Self->{ConfigObject}->{CustomerPreferencesGroups}->{$Group}->{PrefKey} || ''; + my $Type = $Self->{ConfigObject}->{CustomerPreferencesGroups}->{$Group}->{Type} || ''; + if ($Type eq 'Generic' && $PrefKey) { + if (!$Self->{CustomerUserObject}->SetPreferences( + UserID => $GetParam{ID}, + Key => $PrefKey, + Value => $Self->{ParamObject}->GetParam(Param => "GenericTopic::$PrefKey"), + )) { + my $Output = $NavBar.$Self->{LayoutObject}->Error(); + $Output .= $Self->{LayoutObject}->Footer(); + return $Output; + } } - } - if ($Type eq 'Upload' && $PrefKey) { - my %UploadStuff = $Self->{ParamObject}->GetUploadAll( - Param => "GenericTopic::$PrefKey", - Source => 'String', - ); - if ($UploadStuff{Content}) { - $Self->{CustomerUserObject}->SetPreferences( - UserID => $GetParam{ID}, - Key => $PrefKey, - Value => $UploadStuff{Content}, - ); - $Self->{CustomerUserObject}->SetPreferences( - UserID => $GetParam{ID}, - Key => $PrefKey."::Filename", - Value => $UploadStuff{Filename}, - ); - $Self->{CustomerUserObject}->SetPreferences( - UserID => $GetParam{ID}, - Key => $PrefKey."::ContentType", - Value => $UploadStuff{ContentType}, - ); + if ($Type eq 'Upload' && $PrefKey) { + my %UploadStuff = $Self->{ParamObject}->GetUploadAll( + Param => "GenericTopic::$PrefKey", + Source => 'String', + ); + if ($UploadStuff{Content}) { + $Self->{CustomerUserObject}->SetPreferences( + UserID => $GetParam{ID}, + Key => $PrefKey, + Value => $UploadStuff{Content}, + ); + $Self->{CustomerUserObject}->SetPreferences( + UserID => $GetParam{ID}, + Key => $PrefKey."::Filename", + Value => $UploadStuff{Filename}, + ); + $Self->{CustomerUserObject}->SetPreferences( + UserID => $GetParam{ID}, + Key => $PrefKey."::ContentType", + Value => $UploadStuff{ContentType}, + ); + } } + } } - } + # redirect + if (my $ZoomTicketReturnID = $Self->{ParamObject}->GetParam(Param => 'ZoomTicketReturnID')) { + return $Self->{LayoutObject}->Redirect( + OP => "Action=AgentZoom&TicketID=$ZoomTicketReturnID", + ); + } + else { + return $Self->{LayoutObject}->Redirect( + OP => "Action=AdminCustomerUser&Nav=$Nav&Search=$Search", + ); + } } - # redirect - if (my $ZoomTicketReturnID = $Self->{ParamObject}->GetParam(Param => 'ZoomTicketReturnID')) { - return $Self->{LayoutObject}->Redirect( - OP => "Action=AgentZoom&TicketID=$ZoomTicketReturnID", - ); - } - else { - return $Self->{LayoutObject}->Redirect( - OP => "Action=AdminCustomerUser&Nav=$Nav&Search=$Search", - ); - } - } - else { - my $Output = $NavBar.$Self->{LayoutObject}->Error(); - $Output .= $Self->{LayoutObject}->Footer(); - return $Output; - } + else { + my $Output = $NavBar.$Self->{LayoutObject}->Error(); + $Output .= $Self->{LayoutObject}->Footer(); + return $Output; + } + } # new closing TEST } # search elsif ($Self->{Subaction} eq 'Search') { Index: otrs/Kernel/Modules/AgentPreferences.pm diff -u otrs/Kernel/Modules/AgentPreferences.pm:1.1.1.1 otrs/Kernel/Modules/AgentPreferences.pm:1.3 --- otrs/Kernel/Modules/AgentPreferences.pm:1.1.1.1 Sat Nov 13 22:14:15 2004 +++ otrs/Kernel/Modules/AgentPreferences.pm Mon Apr 4 01:09:29 2005 @@ -2,7 +2,7 @@ # Kernel/Modules/AgentPreferences.pm - provides agent preferences # Copyright (C) 2001-2004 Martin Edenhofer # -- -# $Id: AgentPreferences.pm,v 1.1.1.1 2004/11/14 03:14:15 jason Exp $ +# $Id: AgentPreferences.pm,v 1.3 2005/04/04 05:09:29 jason Exp $ # -- # This software comes with ABSOLUTELY NO WARRANTY. For details, see # the enclosed file COPYING for license information (GPL). If you @@ -12,9 +12,12 @@ package Kernel::Modules::AgentPreferences; use strict; +use Kernel::System::PasswordValidate; +use Kernel::System::PasswordHistory; +use Data::Dumper; use vars qw($VERSION); -$VERSION = '$Revision: 1.1.1.1 $'; +$VERSION = '$Revision: 1.3 $'; $VERSION =~ s/^\$.*:\W(.*)\W.+?$/$1/; # -- @@ -102,11 +105,52 @@ my $Pw1 = $Self->{ParamObject}->GetParam(Param => 'NewPw1') || ''; if ($Pw eq $Pw1 && $Pw) { + my $password_class_obj = Kernel::System::PasswordValidate->new( + password => $Pw, + min_char_classes => $Self->{ConfigObject}->Get('PasswordClassMinimum'), + min_length => $Self->{ConfigObject}->Get('PasswordLengthMin'), + max_length => $Self->{ConfigObject}->Get('PasswordLengthMax'), + ); + my $password_class_error = $password_class_obj->check(); + my $password_history_obj = Kernel::System::PasswordHistory->new( + dbh => $Self->{DBObject}, + table => 'system_user_password_history', + password => crypt($Pw, $Self->{UserLogin}), + user => $Self->{UserLogin}, + history_num => $Self->{ConfigObject}->Get('PasswordHistoryMinimum'), + ); + my $password_history_error = $password_history_obj->check(); + print STDERR "Kernel::System::CustomerPreferences::UpdatePw\n"; + print STDERR "Password class error = $password_class_error\n"; + print STDERR "Password history error = $password_history_error\n"; + if ($Self->{ConfigObject}->Get('DemoSystem')) { $Output .= $Self->{LayoutObject}->Redirect( OP => "Action=AgentPreferences&What=1", ); } + elsif (($Self->{ConfigObject}->Get('PasswordClassEnforce')) && ($password_class_error ne 1)) { + $Output .= $Self->{LayoutObject}->Header(Area => 'Agent', Title => 'Preferences'); + my %LockedData = $Self->{TicketObject}->GetLockedCount(UserID => $Self->{UserID}); + $Output .= $Self->{LayoutObject}->NavigationBar(LockData => \%LockedData); + $Output .= $Self->{LayoutObject}->Error( + Message => "$password_class_error Please try again!", + Comment => "$password_class_error Please try again!", + ); + $Output .= $Self->Form(); + $Output .= $Self->{LayoutObject}->Footer(); + } + elsif (($Self->{ConfigObject}->Get('PasswordHistoryEnforce')) && ($password_history_error ne 1)) { + $Output .= $Self->{LayoutObject}->Header(Area => 'Agent', Title => 'Preferences'); + my %LockedData = $Self->{TicketObject}->GetLockedCount(UserID => $Self->{UserID}); + $Output .= $Self->{LayoutObject}->NavigationBar(LockData => \%LockedData); + $Output .= $Self->{LayoutObject}->Error( + Message => "$password_history_error Please try again!", + Comment => "$password_history_error Please try again!", + ); + $Output .= $Self->Form(); + $Output .= $Self->{LayoutObject}->Footer(); + } elsif ($Self->{UserObject}->SetPassword(UserLogin => $Self->{UserLogin}, PW => $Pw)) { $Output .= $Self->{LayoutObject}->Redirect( OP => "Action=AgentPreferences&What=1", @@ -127,7 +171,7 @@ $Output .= $Self->{LayoutObject}->Header(Area => 'Agent', Title => 'Preferences'); my %LockedData = $Self->{TicketObject}->GetLockedCount(UserID => $Self->{UserID}); $Output .= $Self->{LayoutObject}->NavigationBar(LockData => \%LockedData); - $Output .= $Self->{LayoutObject}->Notify(Info => 'Passwords dosn\'t match! Please try it again!'); + $Output .= $Self->{LayoutObject}->Notify(Info => 'Passwords don\'t match! Please try again!'); $Output .= $Self->Form(); $Output .= $Self->{LayoutObject}->Footer(); } Index: otrs/Kernel/Modules/CustomerPreferences.pm diff -u otrs/Kernel/Modules/CustomerPreferences.pm:1.1.1.1 otrs/Kernel/Modules/CustomerPreferences.pm:1.4 --- otrs/Kernel/Modules/CustomerPreferences.pm:1.1.1.1 Sat Nov 13 22:14:15 2004 +++ otrs/Kernel/Modules/CustomerPreferences.pm Mon Apr 4 01:09:29 2005 @@ -2,7 +2,7 @@ # Kernel/Modules/CustomerPreferences.pm - provides agent preferences # Copyright (C) 2001-2004 Martin Edenhofer # -- -# $Id: CustomerPreferences.pm,v 1.1.1.1 2004/11/14 03:14:15 jason Exp $ +# $Id: CustomerPreferences.pm,v 1.4 2005/04/04 05:09:29 jason Exp $ # -- # This software comes with ABSOLUTELY NO WARRANTY. For details, see # the enclosed file COPYING for license information (GPL). If you @@ -12,9 +12,11 @@ package Kernel::Modules::CustomerPreferences; use strict; +use Kernel::System::PasswordValidate; +use Kernel::System::PasswordHistory; use vars qw($VERSION); -$VERSION = '$Revision: 1.1.1.1 $'; +$VERSION = '$Revision: 1.4 $'; $VERSION =~ s/^\$.*:\W(.*)\W.+?$/$1/; # -- @@ -98,16 +100,52 @@ my $UserID = $Self->{UserID}; if ($Pw eq $Pw1 && $Pw) { - $Self->{UserObject}->SetPassword(UserLogin => $Self->{UserLogin}, PW => $Pw); - $Output .= $Self->{LayoutObject}->Redirect( - OP => "Action=CustomerPreferences", + my $password_class_obj = Kernel::System::PasswordValidate->new( + password => $Pw, + min_char_classes => $Self->{ConfigObject}->Get('PasswordClassMinimum'), + min_length => $Self->{ConfigObject}->Get('PasswordLengthMin'), + max_length => $Self->{ConfigObject}->Get('PasswordLengthMax'), + ); + my $password_class_error = $password_class_obj->check(); + my $password_history_obj = Kernel::System::PasswordHistory->new( + dbh => $Self->{DBObject}, + table => 'customer_user_password_history', + password => $Pw, + user => $Self->{UserLogin}, + history_num => $Self->{ConfigObject}->Get('PasswordHistoryMinimum'), ); + my $password_history_error = $password_history_obj->check(); + if (($Self->{ConfigObject}->Get('PasswordClassEnforce')) && ($password_class_error ne 1)) { + $Output .= $Self->{LayoutObject}->CustomerHeader(); + $Output .= $Self->{LayoutObject}->CustomerWarning( + Message => "$password_class_error Please try again!", + Comment => "$password_class_error Please try again!", + ); + $Output .= $Self->{LayoutObject}->CustomerFooter(); + } + elsif (($Self->{ConfigObject}->Get('PasswordHistoryEnforce')) && ($password_history_error ne 1)) { + $Output .= $Self->{LayoutObject}->Header(Area => 'Agent', Title => 'Preferences'); + my %LockedData = $Self->{TicketObject}->GetLockedCount(UserID => $Self->{UserID}); + $Output .= $Self->{LayoutObject}->NavigationBar(LockData => \%LockedData); + $Output .= $Self->{LayoutObject}->Error( + Message => "$password_history_error Please try again!", + Comment => "$password_history_error Please try again!", + ); + $Output .= $Self->Form(); + $Output .= $Self->{LayoutObject}->Footer(); + } + else { + $Self->{UserObject}->SetPassword(UserLogin => $Self->{UserLogin}, PW => $Pw); + $Output .= $Self->{LayoutObject}->Redirect( + OP => "Action=CustomerPreferences", + ); + } } else { $Output .= $Self->{LayoutObject}->CustomerHeader(); $Output .= $Self->{LayoutObject}->CustomerWarning( - Message => 'Passwords dosn\'t match! Please try it again!', - Comment => 'Passwords dosn\'t match! Please try it again!', + Message => "Passwords don't match! Please try again!", + Comment => "Passwords don't match! Please try again!", ); $Output .= $Self->{LayoutObject}->CustomerFooter(); } Index: otrs/Kernel/Output/HTML/Admin.pm diff -u otrs/Kernel/Output/HTML/Admin.pm:1.1.1.1 otrs/Kernel/Output/HTML/Admin.pm:1.2 --- otrs/Kernel/Output/HTML/Admin.pm:1.1.1.1 Sat Nov 13 22:14:15 2004 +++ otrs/Kernel/Output/HTML/Admin.pm Mon Mar 21 15:46:58 2005 @@ -2,7 +2,7 @@ # Kernel/Output/HTML/Admin.pm - provides generic admin HTML output # Copyright (C) 2001-2004 Martin Edenhofer # -- -# $Id: Admin.pm,v 1.1.1.1 2004/11/14 03:14:15 jason Exp $ +# $Id: Admin.pm,v 1.2 2005/03/21 20:46:58 jason Exp $ # -- # This software comes with ABSOLUTELY NO WARRANTY. For details, see # the enclosed file COPYING for license information (GPL). If you @@ -14,7 +14,7 @@ use strict; use vars qw($VERSION); -$VERSION = '$Revision: 1.1.1.1 $'; +$VERSION = '$Revision: 1.2 $'; $VERSION =~ s/^\$.*:\W(.*)\W.+?$/$1/; # -- @@ -75,6 +75,9 @@ if ($Entry->[0] =~ /^ValidID/i) { $Param{Value} = $Param{'ValidOption'}; } + elsif ($Entry->[0] =~ /^UserPasswor/i) { + $Param{Value} = "[0]\" value=\"\" size=\"35\" maxlength=\"50\" $Param{ReadOnlyType}>"; + } else { my $Value = $Self->{LayoutObject}->Ascii2Html( Text => $Param{$Entry->[0]} || '', Index: otrs/Kernel/System/CustomerUser.pm diff -u otrs/Kernel/System/CustomerUser.pm:1.1.1.1 otrs/Kernel/System/CustomerUser.pm:1.6 --- otrs/Kernel/System/CustomerUser.pm:1.1.1.1 Sat Nov 13 22:14:15 2004 +++ otrs/Kernel/System/CustomerUser.pm Tue Mar 22 13:18:11 2005 @@ -2,7 +2,7 @@ # Kernel/System/CustomerUser.pm - some customer user functions # Copyright (C) 2001-2004 Martin Edenhofer # -- -# $Id: CustomerUser.pm,v 1.1.1.1 2004/11/14 03:14:15 jason Exp $ +# $Id: CustomerUser.pm,v 1.6 2005/03/22 18:18:11 jason Exp $ # -- # This software comes with ABSOLUTELY NO WARRANTY. For details, see # the enclosed file COPYING for license information (GPL). If you @@ -14,7 +14,7 @@ use strict; use vars qw(@ISA $VERSION); -$VERSION = '$Revision: 1.1.1.1 $'; +$VERSION = '$Revision: 1.6 $'; $VERSION =~ s/^\$.*:\W(.*)\W.+?$/$1/; # -- Index: otrs/Kernel/System/PasswordHistory.pm diff -u /dev/null otrs/Kernel/System/PasswordHistory.pm:1.2 --- /dev/null Tue Apr 5 17:53:44 2005 +++ otrs/Kernel/System/PasswordHistory.pm Mon Apr 4 01:09:42 2005 @@ -0,0 +1,85 @@ +# -- +# Kernel/System/PasswordHistory.pm - password enforcement +# Copyright (C) 2001-2005 Jason Dixon +# -- +# $Id: PasswordHistory.pm,v 1.2 2005/04/04 05:09:42 jason Exp $ +# -- +# This software comes with ABSOLUTELY NO WARRANTY. For details, see +# the enclosed file COPYING for license information (GPL). If you +# did not receive this file, see http://www.gnu.org/licenses/gpl.txt. +# -- + +package Kernel::System::PasswordHistory; + +use strict; + +use vars qw(@ISA $VERSION); +$VERSION = '$Revision: 1.2 $'; +$VERSION =~ s/^\$.*:\W(.*)\W.+?$/$1/; + +=head1 NAME + +Kernel::System::PasswordHistory - Password history check + +=head1 SYNOPSIS + +An object-oriented interface to compare a given password against a database of previous passwords. + +=head1 PUBLIC INTERFACE + +=over 4 + +=cut + +=item new() + +Create a password history object + + use Kernel::System::PasswordHistory; + + my $obj = Kernel::System::PasswordHistory->new( + dbh => $dbh, # database handle object + table => $db_table # password history table + user => 'jason', # user login + password => 'crypt_pw', # crypted password + history_num => 5, # number of entries to review + ); + +=cut + +my $HISTORY_NUM = 5; + +sub new { + my $class = shift; + bless { @_ }, $class; +} + +=item check() + +Reviews the user's password history going back history_num timestamps to check for uniqueness. Any return value other than "1" should be considered an error message. + + my $error = $obj->check; + +=cut + +sub check { + my $self = shift; + my $dbh = $self->{dbh}; + my $query = "SELECT pw FROM $self->{table} WHERE login = \'" . $self->{user} . "\'" . + " ORDER BY timestamp desc LIMIT " . + ($self->{history_num} || $HISTORY_NUM); + $dbh->Prepare(SQL => $query); + my @results; + while (my @result = $dbh->FetchrowArray()) { + push(@results, $result[0]); + } + if (grep { $_ eq $self->{password} } @results) { + my $err_msg = "Password has already been used within the last " . + ($self->{history_num} || $HISTORY_NUM) . " instances."; + return $err_msg; + } else { + return 1; + } +} + +1; Index: otrs/Kernel/System/PasswordValidate.pm diff -u /dev/null otrs/Kernel/System/PasswordValidate.pm:1.1 --- /dev/null Tue Apr 5 17:53:44 2005 +++ otrs/Kernel/System/PasswordValidate.pm Mon Apr 4 02:02:39 2005 @@ -0,0 +1,99 @@ +# -- +# Kernel/System/PasswordValidate.pm - password enforcement +# Copyright (C) 2001-2005 Jason Dixon +# -- +# $Id: PasswordValidate.pm,v 1.1 2005/04/04 06:02:39 jason Exp $ +# -- +# This software comes with ABSOLUTELY NO WARRANTY. For details, see +# the enclosed file COPYING for license information (GPL). If you +# did not receive this file, see http://www.gnu.org/licenses/gpl.txt. +# -- + +package Kernel::System::PasswordValidate; + +use strict; + +use vars qw(@ISA $VERSION); +$VERSION = '$Revision: 1.1 $'; +$VERSION =~ s/^\$.*:\W(.*)\W.+?$/$1/; + +=head1 NAME + +Kernel::System::PasswordValidate - Validate password based on character classes + +=head1 SYNOPSIS + +An object-oriented interface to validate password strings. Used to validate against minimum- and maximum-length values and a number of character class requirements (upper- and lower-case, numbers and symbols). + +=head1 PUBLIC INTERFACE + +=over 4 + +=cut + +=item new() + +Create a password object + + use Kernel::System::PasswordValidate; + + my $obj = Kernel::System::PasswordValidate->new( + password => 'changeme', + min_char_classes => 2, + min_length => 6, + max_length => 12, + ); + +=cut + +my $MIN_LENGTH = 6; +my $MAX_LENGTH = 12; +my $MIN_CHAR_CLASSES = 3; + +sub new { + my $class = shift; + bless { @_ }, $class; +} + +=item check() + +Validates the password object based on the parameters passed to the new() method. Any return value other than "1" should be considered an error message. + + my $error = $obj->check; + +=cut + +sub check { + my $self = shift; + my $matching_classes = 0; + # check for minimum length + unless (length($self->{'password'}) >= ($self->{'min_length'} || $MIN_LENGTH)) + { + my $error = "Password must be minimum " . ($self->{min_length} || $MIN_LENGTH) . " characters.\n"; + return $error; + } + # check for maximum length + unless (length($self->{'password'}) <= ($self->{'max_length'} || $MAX_LENGTH)) + { + my $error = "Password must be maximum " . ($self->{max_length} || $MAX_LENGTH) . " characters.\n"; + return $error; + } + # check for uppercase + $self->{'password'} =~ /[A-Z]/ && $matching_classes++; + # check for lowercase + $self->{'password'} =~ /[a-z]/ && $matching_classes++; + # check for numbers + $self->{'password'} =~ /[0-9]/ && $matching_classes++; + # check for symbols + $self->{'password'} =~ /[\W]/ && $matching_classes++; + + unless ($matching_classes >= ($self->{'$min_char_classes'} || $MIN_CHAR_CLASSES)) + { + my $error = "Password does not meet number of character class requirements.\n"; + return $error; + } + + return 1; +} + +1; Index: otrs/Kernel/System/User.pm diff -u otrs/Kernel/System/User.pm:1.1.1.1 otrs/Kernel/System/User.pm:1.2 --- otrs/Kernel/System/User.pm:1.1.1.1 Sat Nov 13 22:14:15 2004 +++ otrs/Kernel/System/User.pm Mon Apr 4 01:09:42 2005 @@ -2,7 +2,7 @@ # Kernel/System/User.pm - some user functions # Copyright (C) 2001-2004 Martin Edenhofer # -- -# $Id: User.pm,v 1.1.1.1 2004/11/14 03:14:15 jason Exp $ +# $Id: User.pm,v 1.2 2005/04/04 05:09:42 jason Exp $ # -- # This software comes with ABSOLUTELY NO WARRANTY. For details, see # the enclosed file COPYING for license information (GPL). If you @@ -15,7 +15,7 @@ use Kernel::System::CheckItem; use vars qw(@ISA $VERSION); -$VERSION = '$Revision: 1.1.1.1 $'; +$VERSION = '$Revision: 1.2 $'; $VERSION =~ s/^\$.*:\W(.*)\W.+?$/$1/; # -- @@ -283,14 +283,6 @@ close (IO); chomp $CryptedPw; } - # check pw - if ($CryptedPw eq $User{UserPw}) { - $Self->{LogObject}->Log( - Priority => 'notice', - Message => "Not possible to use the same password again!", - ); - return; - } # db quote foreach (keys %Param) { $Param{$_} = $Self->{DBObject}->Quote($Param{$_}); @@ -303,6 +295,11 @@ " $Self->{UserTableUserPW} = '$NewPw' ". " WHERE ". " $Self->{UserTableUser} = '$Param{UserLogin}'", + # add pw to history + ) && $Self->{DBObject}->Do( + SQL => "INSERT INTO system_user_password_history (login, pw, timestamp) VALUES ('" . + $Self->{DBObject}->Quote($Param{UserLogin}) . "', '" . + $Self->{DBObject}->Quote($CryptedPw) . "', NOW())", )) { # log notice $Self->{LogObject}->Log( Index: otrs/Kernel/System/CustomerUser/DB.pm diff -u otrs/Kernel/System/CustomerUser/DB.pm:1.2 otrs/Kernel/System/CustomerUser/DB.pm:1.6 --- otrs/Kernel/System/CustomerUser/DB.pm:1.2 Tue Dec 21 22:46:16 2004 +++ otrs/Kernel/System/CustomerUser/DB.pm Mon Apr 4 01:09:48 2005 @@ -2,7 +2,7 @@ # Kernel/System/CustomerUser/DB.pm - some customer user functions # Copyright (C) 2001-2004 Martin Edenhofer # -- -# $Id: DB.pm,v 1.2 2004/12/22 03:46:16 jason Exp $ +# $Id: DB.pm,v 1.6 2005/04/04 05:09:48 jason Exp $ # -- # This software comes with ABSOLUTELY NO WARRANTY. For details, see # the enclosed file COPYING for license information (GPL). If you @@ -15,7 +15,7 @@ use Kernel::System::CheckItem; use vars qw(@ISA $VERSION); -$VERSION = '$Revision: 1.2 $'; +$VERSION = '$Revision: 1.6 $'; $VERSION =~ s/^\$.*:\W(.*)\W.+?$/$1/; # -- @@ -389,6 +389,7 @@ } # -- sub CustomerUserUpdate { + my $Self = shift; my %Param = @_; # check ro/rw @@ -399,8 +400,11 @@ # check needed stuff foreach my $Entry (@{$Self->{CustomerUserMap}->{Map}}) { if (!$Param{$Entry->[0]} && $Entry->[4]) { - $Self->{LogObject}->Log(Priority => 'error', Message => "Need $Entry->[0]!"); - return; + # password should not be required field for AdminCustomerUser + if ($Entry->[0] ne 'UserPassword') { + $Self->{LogObject}->Log(Priority => 'error', Message => "Need $Entry->[0]!"); + return; + } } } # check email address @@ -436,7 +440,8 @@ ); # check pw my $GetPw = $UserData{UserPassword} || ''; - if ($GetPw ne $Param{UserPassword}) { + # only update if password is different AND has been submitted in form + if (($GetPw ne $Param{UserPassword}) && (length($Param{UserPassword}) > 0)) { $Self->SetPassword(UserLogin => $Param{UserLogin}, PW => $Param{UserPassword}); } return 1; @@ -498,7 +503,12 @@ " $Param{PasswordCol} = '".$Self->{DBObject}->Quote($CryptedPw)."' ". " WHERE ". " $Param{LoginCol} = '".$Self->{DBObject}->Quote($Param{UserLogin})."'", - )) { + # add pw to history + ) && $Self->{DBObject}->Do( + SQL => "INSERT INTO customer_user_password_history (login, pw, timestamp) VALUES ('" . + $Self->{DBObject}->Quote($Param{UserLogin}) . "', '" . + $Self->{DBObject}->Quote($CryptedPw) . "', NOW())", + )) { # log notice $Self->{LogObject}->Log( Priority => 'notice', Index: otrs/bin/cgi-bin/customer.pl diff -u otrs/bin/cgi-bin/customer.pl:1.1.1.1 otrs/bin/cgi-bin/customer.pl:1.3 --- otrs/bin/cgi-bin/customer.pl:1.1.1.1 Sat Nov 13 22:14:16 2004 +++ otrs/bin/cgi-bin/customer.pl Tue Mar 22 13:18:16 2005 @@ -3,7 +3,7 @@ # customer.pl - the global CGI handle file (incl. auth) for OTRS # Copyright (C) 2001-2004 Martin Edenhofer # -- -# $Id: customer.pl,v 1.1.1.1 2004/11/14 03:14:16 jason Exp $ +# $Id: customer.pl,v 1.3 2005/03/22 18:18:16 jason Exp $ # -- # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -28,7 +28,7 @@ use strict; use vars qw($VERSION @INC); -$VERSION = '$Revision: 1.1.1.1 $'; +$VERSION = '$Revision: 1.3 $'; $VERSION =~ s/^\$.*:\W(.*)\W.+?$/$1/; # --