package FS::password_history; use base qw( FS::Record ); use strict; use FS::Record qw( qsearch qsearchs ); use Authen::Passphrase; # the only bit of autogenerated magic in here our @foreign_keys; FS::UID->install_callback(sub { @foreign_keys = grep /__/, __PACKAGE__->dbdef_table->columns; }); =head1 NAME FS::password_history - Object methods for password_history records =head1 SYNOPSIS use FS::password_history; $record = new FS::password_history \%hash; $record = new FS::password_history { 'column' => 'value' }; $error = $record->insert; $error = $new_record->replace($old_record); $error = $record->delete; $error = $record->check; =head1 DESCRIPTION An FS::password_history object represents a current or past password used by a login account, employee, or other account managed within Freeside. FS::password_history inherits from FS::Record. The following fields are currently supported: =over 4 =item passwordnum - primary key =item _password - the encrypted password, as an RFC2307-style string ("{CRYPT}$2a$08$..." or "{MD5}1ab201f..." or similar). This is a serialized L object. =item created - the date the password was set to this value. The record with the most recent created time is the current password. =back Plus one of the following foreign keys: =over 4 =item svc_acct__svcnum =item svc_dsl__svcnum =item svc_alarm__svcnum =item agent__agentnum =item contact__contactnum =item access_user__usernum =back =head1 METHODS =over 4 =item new HASHREF Creates a new password history record. To add the record to the database, see L<"insert">. =cut sub table { 'password_history'; } =item insert =item delete =item replace OLD_RECORD =item check Checks all fields to make sure this is a valid password history record. If there is an error, returns the error, otherwise returns false. Called by the insert and replace methods. =cut sub check { my $self = shift; my $error = $self->ut_numbern('passwordnum') || $self->ut_anything('_password') || $self->ut_numbern('create') || $self->ut_numbern('create') ; return $error if $error; # FKs are mutually exclusive my $fk_in_use; foreach my $fk ( @foreign_keys ) { if ( $self->get($fk) ) { $self->ut_numbern($fk); return "multiple records linked to this password_history" if $fk_in_use; $fk_in_use = $fk; } } $self->SUPER::check; } =item linked_acct Returns the object that's using this password. =cut sub linked_acct { my $self = shift; foreach my $fk ( @foreign_keys ) { if ( my $val = $self->get($fk) ) { my ($table, $key) = split(/__/, $fk); return qsearchs($table, { $key => $val }); } } } =item password_equals PASSWORD Returns true if PASSWORD (plaintext) is the same as the one stored in the history record, false if not. =cut sub password_equals { my ($self, $check_password) = @_; # _password here is always LDAP-style. try { my $auth = Authen::Passphrase->from_rfc2307($self->_password); return $auth->match($check_password); } catch { # if there's somehow bad data in the _password field, then it doesn't # match anything. much better than having it match _everything_. warn "password_history #" . $self->passwordnum . ": $_"; return ''; } } sub _upgrade_schema { my $class = shift; # if the table doesn't exist yet then nothing needs to happen here my $dbdef_table = $class->dbdef_table or return; # clean up history records where linked_acct has gone away my @where; for my $fk ( grep /__/, $dbdef_table->columns ) { my ($table, $key) = split(/__/, $fk); push @where, " ( $fk IS NOT NULL AND NOT EXISTS(SELECT 1 FROM $table WHERE $table.$key = $fk) )"; } return '' unless @where; my @recs = qsearch({ 'table' => 'password_history', 'extra_sql' => ' WHERE ' . join(' AND ', @where), }); my $error; if (@recs) { warn "Removing unattached password_history records (".scalar(@recs).").\n"; foreach my $password_history (@recs) { $error = $password_history->delete; die $error if $error; } } ''; } =back =head1 BUGS =head1 SEE ALSO L =cut 1;