testing
[freeside.git] / FS / FS / password_history.pm
1 package FS::password_history;
2 use base qw( FS::Record );
3
4 use strict;
5 use FS::Record qw( qsearch qsearchs );
6 use Authen::Passphrase;
7
8 # the only bit of autogenerated magic in here
9 our @foreign_keys;
10 FS::UID->install_callback(sub {
11     @foreign_keys = grep /__/, __PACKAGE__->dbdef_table->columns;
12 });
13
14 =head1 NAME
15
16 FS::password_history - Object methods for password_history records
17
18 =head1 SYNOPSIS
19
20   use FS::password_history;
21
22   $record = new FS::password_history \%hash;
23   $record = new FS::password_history { 'column' => 'value' };
24
25   $error = $record->insert;
26
27   $error = $new_record->replace($old_record);
28
29   $error = $record->delete;
30
31   $error = $record->check;
32
33 =head1 DESCRIPTION
34
35 An FS::password_history object represents a current or past password used
36 by a login account, employee, or other account managed within Freeside.  
37 FS::password_history inherits from FS::Record.  The following fields are
38 currently supported:
39
40 =over 4
41
42 =item passwordnum - primary key
43
44 =item _password - the encrypted password, as an RFC2307-style string
45 ("{CRYPT}$2a$08$..." or "{MD5}1ab201f..." or similar). This is a serialized
46 L<Authen::Passphrase> object.
47
48 =item created - the date the password was set to this value. The record with
49 the most recent created time is the current password.
50
51 =back
52
53 Plus one of the following foreign keys:
54
55 =over 4
56
57 =item svc_acct__svcnum
58
59 =item svc_dsl__svcnum
60
61 =item svc_alarm__svcnum
62
63 =item agent__agentnum
64
65 =item contact__contactnum
66
67 =item access_user__usernum
68
69 =back
70
71 =head1 METHODS
72
73 =over 4
74
75 =item new HASHREF
76
77 Creates a new password history record.  To add the record to the database,
78 see L<"insert">.
79
80 =cut
81
82 sub table { 'password_history'; }
83
84 =item insert
85
86 =item delete
87
88 =item replace OLD_RECORD
89
90 =item check
91
92 Checks all fields to make sure this is a valid password history record.  If
93 there is an error, returns the error, otherwise returns false.  Called by the
94 insert and replace methods.
95
96 =cut
97
98 sub check {
99   my $self = shift;
100
101   my $error = 
102     $self->ut_numbern('passwordnum')
103     || $self->ut_anything('_password')
104     || $self->ut_numbern('create')
105     || $self->ut_numbern('create')
106   ;
107   return $error if $error;
108
109   # FKs are mutually exclusive
110   my $fk_in_use;
111   foreach my $fk ( @foreign_keys ) {
112     if ( $self->get($fk) ) {
113       $self->ut_numbern($fk);
114       return "multiple records linked to this password_history" if $fk_in_use;
115       $fk_in_use = $fk;
116     }
117   }
118
119   $self->SUPER::check;
120 }
121
122 =item linked_acct
123
124 Returns the object that's using this password.
125
126 =cut
127
128 sub linked_acct {
129   my $self = shift;
130
131   foreach my $fk ( @foreign_keys ) {
132     if ( my $val = $self->get($fk) ) {
133       my ($table, $key) = split(/__/, $fk);
134       return qsearchs($table, { $key => $val });
135     }
136   }
137 }
138
139 =item password_equals PASSWORD
140
141 Returns true if PASSWORD (plaintext) is the same as the one stored in the 
142 history record, false if not.
143
144 =cut
145
146 sub password_equals {
147
148   my ($self, $check_password) = @_;
149
150   # _password here is always LDAP-style.
151   try {
152     my $auth = Authen::Passphrase->from_rfc2307($self->_password);
153     return $auth->match($check_password);
154   } catch {
155     # if there's somehow bad data in the _password field, then it doesn't
156     # match anything. much better than having it match _everything_.
157     warn "password_history #" . $self->passwordnum . ": $_";
158     return '';
159   }
160
161 }
162
163 sub _upgrade_schema {
164   # clean up history records where linked_acct has gone away
165   my @where;
166   for my $fk ( grep /__/, __PACKAGE__->dbdef_table->columns ) {
167     my ($table, $key) = split(/__/, $fk);
168     push @where, "
169       ( $fk IS NOT NULL AND NOT EXISTS(SELECT 1 FROM $table WHERE $table.$key = $fk) )";
170   }
171   my @recs = qsearch({
172       'table'     => 'password_history',
173       'extra_sql' => ' WHERE ' . join(' AND ', @where),
174   });
175   my $error;
176   if (@recs) {
177     warn "Removing unattached password_history records (".scalar(@recs).").\n";
178     foreach my $password_history (@recs) {
179       $error = $password_history->delete;
180       die $error if $error;
181     }
182   }
183   '';
184 }
185
186 =back
187
188 =head1 BUGS
189
190 =head1 SEE ALSO
191
192 L<FS::Record>
193
194 =cut
195
196 1;
197