1 package FS::access_user;
4 use base qw( FS::m2m_Common FS::option_Common );
5 use vars qw( $DEBUG $me $conf $htpasswd_file );
8 use FS::Record qw( qsearch qsearchs dbh );
9 use FS::access_user_pref;
10 use FS::access_usergroup;
15 $me = '[FS::access_user]';
17 #kludge htpasswd for now (i hope this bootstraps okay)
18 FS::UID->install_callback( sub {
20 $htpasswd_file = $conf->base_dir. '/htpasswd';
25 FS::access_user - Object methods for access_user records
31 $record = new FS::access_user \%hash;
32 $record = new FS::access_user { 'column' => 'value' };
34 $error = $record->insert;
36 $error = $new_record->replace($old_record);
38 $error = $record->delete;
40 $error = $record->check;
44 An FS::access_user object represents an internal access user. FS::access_user
45 inherits from FS::Record. The following fields are currently supported:
49 =item usernum - primary key
59 =item disabled - empty or 'Y'
69 Creates a new internal access user. To add the user to the database, see L<"insert">.
71 Note that this stores the hash reference, not a distinct copy of the hash it
72 points to. You can ask the object for a copy with the I<hash> method.
76 # the new method can be inherited from FS::Record, if a table method is defined
78 sub table { 'access_user'; }
80 sub _option_table { 'access_user_pref'; }
81 sub _option_namecol { 'prefname'; }
82 sub _option_valuecol { 'prefvalue'; }
86 Adds this record to the database. If there is an error, returns the error,
87 otherwise returns false.
94 my $error = $self->check;
95 return $error if $error;
97 local $SIG{HUP} = 'IGNORE';
98 local $SIG{INT} = 'IGNORE';
99 local $SIG{QUIT} = 'IGNORE';
100 local $SIG{TERM} = 'IGNORE';
101 local $SIG{TSTP} = 'IGNORE';
102 local $SIG{PIPE} = 'IGNORE';
104 my $oldAutoCommit = $FS::UID::AutoCommit;
105 local $FS::UID::AutoCommit = 0;
108 $error = $self->htpasswd_kludge();
110 $dbh->rollback or die $dbh->errstr if $oldAutoCommit;
114 $error = $self->SUPER::insert(@_);
117 $dbh->rollback or die $dbh->errstr if $oldAutoCommit;
119 #make sure it isn't a dup username? or you could nuke people's passwords
120 #blah. really just should do our own login w/cookies
121 #and auth out of the db in the first place
122 #my $hterror = $self->htpasswd_kludge('-D');
123 #$error .= " - additionally received error cleaning up htpasswd file: $hterror"
127 $dbh->commit or die $dbh->errstr if $oldAutoCommit;
133 sub htpasswd_kludge {
136 return '' if $self->is_system_user;
138 unshift @_, '-c' unless -e $htpasswd_file;
140 system('htpasswd', '-b', @_,
149 return 'htpasswd exited unsucessfully';
155 Delete this record from the database.
162 local $SIG{HUP} = 'IGNORE';
163 local $SIG{INT} = 'IGNORE';
164 local $SIG{QUIT} = 'IGNORE';
165 local $SIG{TERM} = 'IGNORE';
166 local $SIG{TSTP} = 'IGNORE';
167 local $SIG{PIPE} = 'IGNORE';
169 my $oldAutoCommit = $FS::UID::AutoCommit;
170 local $FS::UID::AutoCommit = 0;
174 $self->SUPER::delete(@_)
175 || $self->htpasswd_kludge('-D')
179 $dbh->rollback or die $dbh->errstr if $oldAutoCommit;
182 $dbh->commit or die $dbh->errstr if $oldAutoCommit;
188 =item replace OLD_RECORD
190 Replaces the OLD_RECORD with this one in the database. If there is an error,
191 returns the error, otherwise returns false.
198 my $old = ( ref($_[0]) eq ref($new) )
202 local $SIG{HUP} = 'IGNORE';
203 local $SIG{INT} = 'IGNORE';
204 local $SIG{QUIT} = 'IGNORE';
205 local $SIG{TERM} = 'IGNORE';
206 local $SIG{TSTP} = 'IGNORE';
207 local $SIG{PIPE} = 'IGNORE';
209 my $oldAutoCommit = $FS::UID::AutoCommit;
210 local $FS::UID::AutoCommit = 0;
213 if ( $new->_password ne $old->_password ) {
214 my $error = $new->htpasswd_kludge();
216 $dbh->rollback or die $dbh->errstr if $oldAutoCommit;
219 } elsif ( $old->disabled && !$new->disabled
220 && $new->_password =~ /changeme/i ) {
221 return "Must change password when enabling this account";
224 my $error = $new->SUPER::replace($old, @_);
227 $dbh->rollback or die $dbh->errstr if $oldAutoCommit;
230 $dbh->commit or die $dbh->errstr if $oldAutoCommit;
238 Checks all fields to make sure this is a valid internal access user. If there is
239 an error, returns the error, otherwise returns false. Called by the insert
244 # the check method should currently be supplied - FS::Record contains some
245 # data checking routines
251 $self->ut_numbern('usernum')
252 || $self->ut_alpha_lower('username')
253 || $self->ut_text('_password')
254 || $self->ut_text('last')
255 || $self->ut_text('first')
256 || $self->ut_foreign_keyn('user_custnum', 'cust_main', 'custnum')
257 || $self->ut_enum('disabled', [ '', 'Y' ] )
259 return $error if $error;
266 Returns a name string for this user: "Last, First".
272 return $self->username
273 if $self->get('last') eq 'Lastname' && $self->first eq 'Firstname';
274 return $self->get('last'). ', '. $self->first;
279 Returns the FS::cust_main object (see L<FS::cust_main>), if any, for this
286 qsearchs( 'cust_main', { 'custnum' => $self->user_custnum } );
289 =item access_usergroup
291 Returns links to the the groups this user is a part of, as FS::access_usergroup
292 objects (see L<FS::access_usergroup>).
296 sub access_usergroup {
298 qsearch( 'access_usergroup', { 'usernum' => $self->usernum } );
309 #=item access_groupnames
313 #sub access_groupnames {
319 Returns a list of agentnums this user can view (via group membership).
325 my $sth = dbh->prepare(
326 "SELECT DISTINCT agentnum FROM access_usergroup
327 JOIN access_groupagent USING ( groupnum )
329 ) or die dbh->errstr;
330 $sth->execute($self->usernum) or die $sth->errstr;
331 map { $_->[0] } @{ $sth->fetchall_arrayref };
336 Returns a hashref of agentnums this user can view.
342 scalar( { map { $_ => 1 } $self->agentnums } );
345 =item agentnums_sql [ HASHREF | OPTION => VALUE ... ]
347 Returns an sql fragement to select only agentnums this user can view.
349 Options are passed as a hashref or a list. Available options are:
355 The frament will also allow the selection of null agentnums.
359 The fragment will also allow the selection of null agentnums if the current
360 user has the provided access right
364 Optional table name in which agentnum is being checked. Sometimes required to
365 resolve 'column reference "agentnum" is ambiguous' errors.
369 All agents will be viewable if the current user has the provided access right.
370 Defaults to 'View customers of all agents'.
378 my %opt = ref($_[0]) ? %{$_[0]} : @_;
380 my $agentnum = $opt{'table'} ? $opt{'table'}.'.agentnum' : 'agentnum';
384 my $viewall_right = $opt{'viewall_right'} || 'View customers of all agents';
385 if ( $self->access_right($viewall_right) ) {
386 push @or, "$agentnum IS NOT NULL";
388 push @or, "$agentnum IN (". join(',', $self->agentnums). ')';
391 push @or, "$agentnum IS NULL"
393 || ( $opt{'null_right'} && $self->access_right($opt{'null_right'}) );
395 return ' 1 = 0 ' unless scalar(@or);
396 '( '. join( ' OR ', @or ). ' )';
402 Returns true if the user can view the specified agent.
407 my( $self, $agentnum ) = @_;
408 my $sth = dbh->prepare(
409 "SELECT COUNT(*) FROM access_usergroup
410 JOIN access_groupagent USING ( groupnum )
411 WHERE usernum = ? AND agentnum = ?"
412 ) or die dbh->errstr;
413 $sth->execute($self->usernum, $agentnum) or die $sth->errstr;
414 $sth->fetchrow_arrayref->[0];
417 =item agents [ HASHREF | OPTION => VALUE ... ]
419 Returns the list of agents this user can view (via group membership), as
420 FS::agent objects. Accepts the same options as the agentnums_sql method.
428 'hashref' => { disabled=>'' },
429 'extra_sql' => ' AND '. $self->agentnums_sql(@_),
433 =item access_right RIGHTNAME | LISTREF
435 Given a right name or a list reference of right names, returns true if this
436 user has this right, or, for a list, one of the rights (currently via group
437 membership, eventually also via user overrides).
442 my( $self, $rightname ) = @_;
444 $rightname = [ $rightname ] unless ref($rightname);
446 warn "$me access_right called on ". join(', ', @$rightname). "\n"
449 #some caching of ACL requests for low-hanging fruit perf improvement
450 #since we get a new $CurrentUser object each page view there shouldn't be any
451 #issues with stickiness
452 if ( $self->{_ACLcache} ) {
454 unless ( grep !exists($self->{_ACLcache}{$_}), @$rightname ) {
455 warn "$me ACL cache hit for ". join(', ', @$rightname). "\n"
457 return grep $self->{_ACLcache}{$_}, @$rightname
460 warn "$me ACL cache miss for ". join(', ', @$rightname). "\n"
465 warn "initializing ACL cache\n"
467 $self->{_ACLcache} = {};
471 my $has_right = ' rightname IN ('. join(',', map '?', @$rightname ). ') ';
473 my $sth = dbh->prepare("
474 SELECT groupnum FROM access_usergroup
475 LEFT JOIN access_group USING ( groupnum )
476 LEFT JOIN access_right
477 ON ( access_group.groupnum = access_right.rightobjnum )
479 AND righttype = 'FS::access_group'
482 ") or die dbh->errstr;
483 $sth->execute($self->usernum, @$rightname) or die $sth->errstr;
484 my $row = $sth->fetchrow_arrayref;
486 my $return = $row ? $row->[0] : '';
488 #just caching the single-rightname hits should be enough of a win for now
489 if ( scalar(@$rightname) == 1 ) {
490 $self->{_ACLcache}{${$rightname}[0]} = $return;
497 =item default_customer_view
499 Returns the default customer view for this user, from the
500 "default_customer_view" user preference, the "cust_main-default_view" config,
501 or the hardcoded default, "basics" (formerly "jumbo" prior to 3.0).
505 sub default_customer_view {
508 $self->option('default_customer_view')
509 || $conf->config('cust_main-default_view')
510 || 'basics'; #s/jumbo/basics/ starting with 3.0
514 =item spreadsheet_format [ OVERRIDE ]
516 Returns a hashref of this user's Excel spreadsheet download settings:
517 'extension' (xls or xlsx), 'class' (Spreadsheet::WriteExcel or
518 Excel::Writer::XLSX), and 'mime_type'. If OVERRIDE is 'XLS' or 'XLSX',
519 use that instead of the user's setting.
523 # is there a better place to put this?
527 class => 'Spreadsheet::WriteExcel',
528 mime_type => 'application/vnd.ms-excel',
531 extension => '.xlsx',
532 class => 'Excel::Writer::XLSX',
533 mime_type => # it's on wikipedia, it must be true
534 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
538 sub spreadsheet_format {
540 my $override = shift;
543 || $self->option('spreadsheet_format')
544 || $conf->config('spreadsheet_format')
552 Returns true if this user has the name of a known system account. These
553 users will not appear in the htpasswd file and can't have passwords set.
559 return grep { $_ eq $self->username } ( qw(
575 L<FS::Record>, schema.html from the base documentation.