File Coverage

blib/lib/Dancer2/Plugin/Auth/Extensible/Provider/DBIC.pm
Criterion Covered Total %
statement 158 171 92.4
branch 70 94 74.4
condition 21 33 63.6
subroutine 20 21 95.2
pod 0 10 0.0
total 269 329 81.7


line stmt bran cond sub pod time code
1             package Dancer2::Plugin::Auth::Extensible::Provider::DBIC;
2              
3 2     2   1828283 use Carp;
  2         6  
  2         179  
4 2     2   14 use Dancer2::Core::Types qw/Bool Int Str/;
  2         3  
  2         118  
5 2     2   11 use DateTime;
  2         8  
  2         90  
6 2     2   1172 use DBIx::Class::ResultClass::HashRefInflator;
  2         693  
  2         75  
7 2     2   13 use Scalar::Util qw(blessed);
  2         4  
  2         117  
8 2     2   964 use String::CamelCase qw(camelize);
  2         1111  
  2         129  
9              
10 2     2   13 use Moo;
  2         2  
  2         17  
11             with "Dancer2::Plugin::Auth::Extensible::Role::Provider";
12 2     2   661 use namespace::clean;
  2         3  
  2         34  
13              
14             our $VERSION = '0.622';
15              
16             =head1 NAME
17              
18             Dancer2::Plugin::Auth::Extensible::Provider::DBIC - authenticate via the
19             L<Dancer2::Plugin::DBIC> plugin
20              
21              
22             =head1 DESCRIPTION
23              
24             This class is an authentication provider designed to authenticate users against
25             a database, using L<Dancer2::Plugin::DBIC> to access a database.
26              
27             See L<Dancer2::Plugin::DBIC> for how to configure a database connection
28             appropriately; see the L</CONFIGURATION> section below for how to configure this
29             authentication provider with database details.
30              
31             See L<Dancer2::Plugin::Auth::Extensible> for details on how to use the
32             authentication framework.
33              
34              
35             =head1 CONFIGURATION
36              
37             This provider tries to use sensible defaults, in the same manner as
38             L<Dancer2::Plugin::Auth::Extensible::Provider::Database>, so you may not need
39             to provide much configuration if your database tables look similar to those.
40              
41             The most basic configuration, assuming defaults for all options, and defining a
42             single authentication realm named 'users':
43              
44             plugins:
45             Auth::Extensible:
46             realms:
47             users:
48             provider: 'DBIC'
49              
50             You would still need to have provided suitable database connection details to
51             L<Dancer2::Plugin::DBIC>, of course; see the docs for that plugin for full
52             details, but it could be as simple as, e.g.:
53              
54             plugins:
55             Auth::Extensible:
56             realms:
57             users:
58             provider: 'DBIC'
59             users_resultset: 'User'
60             roles_resultset: Role
61             user_roles_resultset: UserRole
62             DBIC:
63             default:
64             dsn: dbi:mysql:database=mydb;host=localhost
65             schema_class: MyApp::Schema
66             user: user
67             pass: secret
68              
69             A full example showing all options:
70              
71             plugins:
72             Auth::Extensible:
73             realms:
74             users:
75             provider: 'DBIC'
76              
77             # Should get_user_details return an inflated DBIC row
78             # object? Defaults to false which will return a hashref
79             # inflated using DBIx::Class::ResultClass::HashRefInflator
80             # instead. This also affects what `logged_in_user` returns.
81             user_as_object: 1
82              
83             # Optionally specify the sources of the data if not the
84             # defaults (as shown). See notes below for how these
85             # generate the resultset names. If you use standard DBIC
86             # resultset names, then these and the column names are the
87             # only settings you might need. The relationships between
88             # these resultsets is automatically introspected by
89             # inspection of the schema.
90             users_source: 'user'
91             roles_source: 'role'
92             user_roles_source: 'user_role'
93              
94             # optionally set the column names
95             users_username_column: username
96             users_password_column: password
97             roles_role_column: role
98              
99             # This plugin supports the DPAE record_lastlogin functionality.
100             # Optionally set the column name:
101             users_lastlogin_column: lastlogin
102              
103             # Optionally set columns for user_password functionality in
104             # Dancer2::Plugin::Auth::Extensible
105             users_pwresetcode_column: pw_reset_code
106             users_pwchanged_column: # Time of reset column. No default.
107              
108             # Days after which passwords expire. See logged_in_user_password_expired
109             # functionality in Dancer2::Plugin::Auth::Extensible
110             password_expiry_days: # No default
111              
112             # Optionally set the name of the DBIC schema
113             schema_name: myschema
114              
115             # Optionally set additional conditions when searching for the
116             # user in the database. These are the same format as required
117             # by DBIC, and are passed directly to the DBIC resultset search
118             user_valid_conditions:
119             deleted: 0
120             account_request:
121             "<": 1
122              
123             # Optionally specify a key for the user's roles to be returned in.
124             # Roles will be returned as role_name => 1 hashref pairs
125             roles_key: roles
126              
127             # Optionally specify the algorithm when encrypting new passwords
128             encryption_algorithm: SHA-512
129              
130             # If you don't use standard DBIC resultset names, you might
131             # need to configure these instead:
132             users_resultset: User
133             roles_resultset: Role
134             user_roles_resultset: UserRole
135              
136             # Optional: To validate passwords using a method called
137             # 'check_password' in users_resultset result class
138             # which takes the password to check as a single argument:
139             users_password_check: check_password
140              
141             # Deprecated settings. The following settings were renamed for clarity
142             # to the *_source settings
143             users_table:
144             roles_table:
145             user_roles_table:
146              
147              
148             =over
149              
150             =cut
151              
152             sub deprecated_setting {
153 0     0 0 0 my ( $setting, $replacement ) = @_;
154 0         0 carp __PACKAGE__, " config setting \"$setting\" is deprecated.",
155             " Use \"$replacement\" instead.";
156             }
157              
158             sub BUILDARGS {
159 6     6 0 6933 my $class = shift;
160 6 50       63 my %args = ref( $_[0] ) eq 'HASH' ? %{ $_[0] } : @_;
  0         0  
161              
162 6         25 my $app = $args{plugin}->app;
163              
164             # backwards compat
165              
166 6 50       18 if ( $args{users_table} ) {
167 0         0 deprecated_setting( 'users_table', 'users_source' );
168             $args{users_source} = delete $args{users_table}
169 0 0       0 if !$args{users_source};
170             }
171              
172 6 50       15 if ( $args{roles_table} ) {
173 0         0 deprecated_setting( 'roles_table', 'roles_source' );
174             $args{roles_source} = delete $args{roles_table}
175 0 0       0 if !$args{roles_source};
176             }
177              
178 6 50       15 if ( $args{user_roles_table} ) {
179 0         0 deprecated_setting( 'user_roles_table', 'user_roles_source' );
180             $args{user_roles_source} = delete $args{user_roles_table}
181 0 0       0 if !$args{user_roles_source};
182             }
183              
184 6         139 return \%args;
185             }
186              
187             =item user_as_object
188              
189             Defaults to false.
190              
191             By default a row object is returned as a simple hash reference using
192             L<DBIx::Class::ResultClass::HashRefInflator>. Setting this to true
193             causes normal row objects to be returned instead.
194              
195             =item user_source
196              
197             Specifies the source name that contains the users. This will be camelized to generate
198             the resultset name. The relationship to user_roles_source will be introspected from
199             the schema.
200              
201             =item role_source
202              
203             Specifies the source name that contains the roles. This will be camelized to generate
204             the resultset name. The relationship to user_roles_source will be introspected from
205             the schema.
206              
207             =item user_roles_source
208              
209             Specifies the source name that contains the user_roles joining table. This will be
210             camelized to generate the resultset name. The relationship to the user and role
211             source will be introspected from the schema.
212              
213             =item users_username_column
214              
215             Specifies the column name of the username column in the users table
216              
217             =item users_password_column
218              
219             Specifies the column name of the password column in the users table
220              
221             =item roles_role_column
222              
223             Specifies the column name of the role name column in the roles table
224              
225             =item schema_name
226              
227             Specfies the name of the L<Dancer2::Plugin::DBIC> schema to use. If not
228             specified, will default in the same manner as the DBIC plugin.
229              
230             =item user_valid_conditions
231              
232             Specifies additional search parameters when looking up a user in the users table.
233             For example, you might want to exclude any account this is flagged as deleted
234             or disabled.
235              
236             The value of this parameter will be passed directly to DBIC as a search condition.
237             It is therefore possible to nest parameters and use different operators for the
238             condition. See the example config above for an example.
239              
240             =item roles_key
241              
242             Specifies a key for the returned user hash to also return the user's roles in.
243             The value of this key will contain a hash ref, which will contain each
244             permission with a value of 1. In your code you might then have:
245              
246             my $user = logged_in_user;
247             return foo_bar($user);
248              
249             sub foo_bar
250             { my $user = shift;
251             if ($user->{roles}->{beer_drinker}) {
252             ...
253             }
254             }
255              
256             This isn't intended to replace the L<Dancer2::Plugin::Auth::Extensible/user_has_role>
257             keyword. Instead it is intended to make it easier to access a user's roles if the
258             user hash is being passed around (without requiring access to the user_has_role
259             keyword in other modules).
260              
261             =item users_resultset
262              
263             =item roles_resultset
264              
265             =item user_roles_resultset
266              
267             These configuration values are provided for fine-grain tuning of your DBIC
268             resultset names. If you use standard DBIC naming practices, you will not need
269             to configure these, and they will be generated internally automatically.
270              
271             =back
272              
273             =head1 SUGGESTED SCHEMA
274              
275             If you use a schema similar to the examples provided here, you should need minimal
276             configuration to get this authentication provider to work for you. The examples
277             given here should be MySQL-compatible; minimal changes should be required to use
278             them with other database engines.
279              
280             =head2 user Table
281              
282             You'll need a table to store user accounts in, of course. A suggestion is something
283             like:
284              
285             CREATE TABLE user (
286             id int(11) NOT NULL AUTO_INCREMENT,
287             username varchar(32) NOT NULL,
288             password varchar(40) DEFAULT NULL,
289             name varchar(128) DEFAULT NULL,
290             email varchar(255) DEFAULT NULL,
291             deleted tinyint(1) NOT NULL DEFAULT '0',
292             lastlogin datetime DEFAULT NULL,
293             pw_changed datetime DEFAULT NULL,
294             pw_reset_code varchar(255) DEFAULT NULL,
295             PRIMARY KEY (id)
296             );
297              
298             All columns from the users table will be returned by the C<logged_in_user> keyword
299             for your convenience.
300              
301             =head2 role Table
302              
303             You'll need a table to store a list of available groups in.
304              
305             CREATE TABLE role (
306             id int(11) NOT NULL AUTO_INCREMENT,
307             role varchar(32) NOT NULL,
308             PRIMARY KEY (id)
309             );
310              
311             =head2 user_role Table
312              
313             Also requred is a table mapping the users to the roles.
314              
315             CREATE TABLE user_role (
316             user_id int(11) NOT NULL,
317             role_id int(11) NOT NULL,
318             PRIMARY KEY (user_id, role_id),
319             FOREIGN KEY (user_id) REFERENCES user(id),
320             FOREIGN KEY (role_id) REFERENCES role(id)
321             );
322              
323             =head1 SEE ALSO
324              
325             L<Dancer2::Plugin::Auth::Extensible>
326              
327             L<Dancer2::Plugin::DBIC>
328              
329             L<Dancer2::Plugin::Auth::Extensible::Provider::Database>
330              
331             =head1 AUTHORS
332              
333             Andrew Beverley C<< <a.beverley@ctrlo.com> >>
334              
335             Rewrite for Plugin2:
336              
337             Peter Mottram, C<< <peter@sysnix.com> >>
338              
339             =head1 CONTRIBUTORS
340              
341             Ben Kaufman (whosgonna)
342              
343             =head1 LICENSE AND COPYRIGHT
344              
345             Copyright 2015-2016 Andrew Beverley
346              
347             This program is free software; you can redistribute it and/or modify it
348             under the terms of either: the GNU General Public License as published
349             by the Free Software Foundation; or the Artistic License.
350              
351             See http://dev.perl.org/licenses/ for more information.
352              
353             =cut
354              
355             has user_as_object => (
356             is => 'ro',
357             isa => Bool,
358             default => 0,
359             );
360              
361             has dancer2_plugin_dbic => (
362             is => 'ro',
363             lazy => 1,
364             default => sub { $_[0]->plugin->app->with_plugin('Dancer2::Plugin::DBIC') },
365             handles => { dbic_schema => 'schema' },
366             init_arg => undef,
367             );
368              
369             has schema_name => ( is => 'ro', );
370              
371             has schema => (
372             is => 'ro',
373             lazy => 1,
374             default => sub {
375             my $self = shift;
376             $self->schema_name
377             ? $self->dbic_schema( $self->schema_name )
378             : $self->dbic_schema;
379             },
380             );
381              
382             has password_expiry_days => (
383             is => 'ro',
384             isa => Int,
385             );
386              
387             has roles_key => (
388             is => 'ro',
389             );
390              
391             has roles_resultset => (
392             is => 'ro',
393             lazy => 1,
394             default => sub { camelize( $_[0]->roles_source ) },
395             );
396              
397             has roles_role_column => (
398             is => 'ro',
399             default => 'role',
400             );
401              
402             has roles_source => (
403             is => 'ro',
404             default => 'role',
405             );
406              
407             has users_resultset => (
408             is => 'ro',
409             lazy => 1,
410             default => sub { camelize( $_[0]->users_source ) },
411             );
412              
413             has users_source => (
414             is => 'ro',
415             default => 'user',
416             );
417              
418             has users_lastlogin_column => (
419             is => 'ro',
420             default => 'lastlogin',
421             );
422              
423             has users_password_column => (
424             is => 'ro',
425             default => 'password',
426             );
427              
428             has users_pwchanged_column => (
429             is => 'ro',
430             );
431              
432             has users_pwresetcode_column => (
433             is => 'ro',
434             default => 'pw_reset_code',
435             );
436              
437             has users_password_check => (
438             is => 'ro',
439             );
440              
441             has users_username_column => (
442             is => 'ro',
443             default => 'username',
444             );
445              
446             has user_user_roles_relationship => (
447             is => 'ro',
448             lazy => 1,
449             default => sub { $_[0]->_build_user_roles_relationship('user') },
450             );
451              
452             has user_roles_resultset => (
453             is => 'ro',
454             lazy => 1,
455             default => sub { camelize( $_[0]->user_roles_source ) },
456             );
457              
458             has user_roles_source => (
459             is => 'ro',
460             default => 'user_roles',
461             );
462              
463             has user_valid_conditions => (
464             is => 'ro',
465             default => sub { {} },
466             );
467              
468             has role_user_roles_relationship => (
469             is => 'ro',
470             lazy => 1,
471             default => sub { $_[0]->_build_user_roles_relationship('role') },
472             );
473              
474             has user_roles_result_class => (
475             is => 'ro',
476             lazy => 1,
477             default => sub {
478             my $self = shift;
479             # undef if roles are disabled
480             return undef if $self->plugin->disable_roles;
481             return $self->schema->resultset( $self->user_roles_resultset )
482             ->result_source->result_class;
483             },
484             );
485              
486             sub _build_user_roles_relationship {
487 10     10   20 my ( $self, $name ) = @_;
488              
489 10 50       194 return undef if $self->plugin->disable_roles;
490              
491             # Introspect result sources to find relationships
492              
493 10         237 my $user_roles_class =
494             $self->schema->resultset( $self->user_roles_resultset )
495             ->result_source->result_class;
496              
497 10         3084 my $resultset_name = "${name}s_resultset";
498              
499 10         389 my $result_source =
500             $self->schema->resultset( $self->$resultset_name )->result_source;
501              
502 10         2818 foreach my $relname ( $result_source->relationships ) {
503 10         263 my $info = $result_source->relationship_info($relname);
504 10         41 my %cond = %{ $info->{cond} };
  10         46  
505 10 50 33     156 if ( $info->{class} eq $user_roles_class
      33        
      33        
506             && $info->{attrs}->{accessor} eq 'multi'
507             && $info->{attrs}->{join_type} eq 'LEFT'
508             && scalar keys %cond == 1 )
509             {
510 10         72 return $relname;
511             }
512             }
513             }
514              
515             has role_relationship => (
516             is => 'ro',
517             lazy => 1,
518             default => sub { $_[0]->_build_relationship('role') },
519             );
520              
521             has user_relationship => (
522             is => 'ro',
523             lazy => 1,
524             default => sub { $_[0]->_build_relationship('user') },
525             );
526              
527             sub _build_relationship {
528 9     9   35 my ( $self, $name ) = @_;
529              
530 9 50       175 return undef if $self->plugin->disable_roles;
531              
532             # Introspect result sources to find relationships
533              
534 9         1538 my $user_roles_class =
535             $self->schema->resultset( $self->user_roles_resultset )
536             ->result_source->result_class;
537              
538 9         2931 my $resultset_name = "${name}s_resultset";
539              
540 9         379 my $result_source =
541             $self->schema->resultset( $self->$resultset_name )->result_source;
542              
543 9         2449 my $user_roles_relationship = "${name}_user_roles_relationship";
544              
545             my ($relationship) = keys %{
546 9         199 $result_source->reverse_relationship_info(
  9         192  
547             $self->$user_roles_relationship
548             )
549             };
550              
551 9         3936 return $relationship;
552             }
553              
554             # Returns a DBIC rset for the user
555             sub _user_rset {
556 811     811   1865 my ($self, $column, $value, $options) = @_;
557 811         2463 my $username_column = $self->users_username_column;
558 811         1924 my $user_valid_conditions = $self->user_valid_conditions;
559              
560 811 50       2516 my $search_column = $column eq 'username'
    100          
561             ? $username_column
562             : $column eq 'pw_reset_code'
563             ? $self->users_pwresetcode_column
564             : $column;
565              
566             # Search based on standard username search, plus any additional
567             # conditions in ignore_user
568 811         4213 my $search = { %$user_valid_conditions, 'me.' . $search_column => $value };
569              
570             # Look up the user
571 811         19610 $self->schema->resultset($self->users_resultset)->search($search, $options);
572             }
573              
574             sub authenticate_user {
575 202     202 0 5143535 my ($self, $username, $password, %options) = @_;
576 202 100 100     2420 croak "username and password must be defined"
577             unless defined $username && defined $password;
578              
579 196         690 my ( $user ) = $self->_user_rset( 'username', $username )->all;
580 196 100       572535 return unless $user;
581              
582 76 50       5321 if ( my $password_check = $self->users_password_check ) {
583             # check password via result class method
584 0         0 return $user->$password_check($password);
585             }
586              
587             # OK, we found a user, let match_password (from our base class) take care of
588             # working out if the password is correct
589 76         316 my $password_column = $self->users_password_column;
590              
591 76 100       2700 if ( my $match =
592             $self->match_password( $password, $user->$password_column ) )
593             {
594 60 100       10669 if ( $options{lastlogin} ) {
595 56 100       1624 if ( my $lastlogin = $user->lastlogin ) {
596 36 50       683 if ( ref($lastlogin) eq '' ) {
597             # not inflated to DateTime
598 36         773 my $db_parser = $self->schema->storage->datetime_parser;
599 36         2426 $lastlogin = $db_parser->parse_datetime($lastlogin);
600             }
601             # Stash in session as epoch since we don't want to have to mess
602             # with with stringified data or perhaps session engine barfing
603             # when trying to serialize DateTime object.
604             $self->plugin->app->session->write(
605 36         26920 $options{lastlogin} => $lastlogin->epoch );
606             }
607 56         29994 $self->set_user_details( $username,
608             $self->users_lastlogin_column => DateTime->now, );
609             }
610 60         4204 return $match;
611             }
612 16         2812 return; # Make sure we return nothing
613             }
614              
615             sub set_user_password {
616 26     26 0 19641 my ( $self, $username, $password ) = @_;
617 26 100 100     900 croak "username and password must be defined"
618             unless defined $username && defined $password;
619              
620 20         132 my $encrypted = $self->encrypt_password($password);
621 20         4292 my $password_column = $self->users_password_column;
622 20         72 my %update = ( $password_column => $encrypted );
623 20 100       114 if ( my $pwchanged = $self->users_pwchanged_column ) {
624 18         224 $update{$pwchanged} = DateTime->now;
625             }
626 20         6591 $self->set_user_details( $username, %update );
627             }
628              
629             # Return details about the user. The user's row in the users table will be
630             # fetched and all columns returned as a hashref.
631             sub get_user_details {
632 274     274 0 2050256 my ($self, $username) = @_;
633 274 100       1508 croak "username must be defined"
634             unless defined $username;
635              
636             # Look up the user
637 272         1055 my $users_rs = $self->_user_rset(username => $username);
638              
639             # Inflate to a hashref, otherwise it's returned as a DBIC rset
640 272 100       146321 $users_rs->result_class('DBIx::Class::ResultClass::HashRefInflator')
641             unless $self->user_as_object;
642              
643 272         13044 my ($user) = $users_rs->all;
644            
645 272 100       593763 if (!$user) {
646 26         307 $self->plugin->app->log( 'debug', "No such user $username" );
647 26         20651 return;
648             }
649              
650 246 100       1497 if ( !$self->user_as_object ) {
651 123 50       696 if ( my $roles_key = $self->roles_key ) {
652 123         208 my @roles = @{ $self->get_user_roles($username) };
  123         522  
653 123         7977 my %roles = map { $_ => 1 } @roles;
  108         470  
654 123         760 $user->{$roles_key} = \%roles;
655             }
656             }
657 246         1160 return $user;
658             }
659              
660             # Find a user based on a password reset code
661             sub get_user_by_code {
662 44     44 0 506334 my ($self, $code) = @_;
663 44 100 66     946 croak "code needs to be specified"
664             unless $code && $code ne '';
665              
666 40         197 my ($user) = $self->_user_rset( pw_reset_code => $code )->all;
667 40 100       124207 return unless $user;
668              
669 8         478 my $username_column = $self->users_username_column;
670 8         309 return $user->$username_column;
671             }
672              
673             sub create_user {
674 32     32 0 389592 my ($self, %user) = @_;
675 32         144 my $username_column = $self->users_username_column;
676 32         75 my $username = delete $user{username}; # Prevent attempt to update wrong key
677 32 100 100     849 croak "Username not supplied in args"
678             unless defined $username && $username ne '';
679              
680 28         777 $self->schema->resultset($self->users_resultset)->create({
681             $username_column => $username
682             });
683 22         44949 $self->set_user_details($username, %user);
684             }
685              
686             # Update a user. Username is provided in the update details
687             sub set_user_details {
688 140     140 0 415224 my ($self, $username, %update) = @_;
689              
690 140 100       1220 croak "Username to update needs to be specified"
691             unless $username;
692              
693             # Look up the user
694 136         589 my ($user) = $self->_user_rset(username => $username)->all;
695 136 100       368342 $user or return;
696              
697             # Are we expecting a user_roles key?
698 126 50       7835 if ( my $roles_key = $self->roles_key ) {
699 126 100       589 if ( my $new_roles = delete $update{$roles_key} ) {
700              
701 4         17 my $roles_role_column = $self->roles_role_column;
702 4         59 my $users_username_column = $self->users_username_column;
703              
704 4         115 my @all_roles =
705             $self->schema->resultset( $self->roles_resultset )->all;
706             my %existing_roles =
707 4         6901 map { $_ => 1 } @{ $self->get_user_roles($username) };
  0         0  
  4         183  
708              
709 4         116 foreach my $role (@all_roles) {
710 12         266 my $role_name = $role->$roles_role_column;
711              
712 12 100 66     323 if ( $new_roles->{$role_name}
    50 33        
713             && !$existing_roles{$role_name} )
714             {
715             # Needs to be added
716             $self->schema->resultset( $self->user_roles_resultset )
717             ->create(
718             {
719             $self->user_relationship => {
720             $users_username_column => $username,
721 4         80 %{ $self->user_valid_conditions }
  4         109  
722             },
723             $self->role_relationship => {
724             $roles_role_column => $role_name
725             },
726             }
727             );
728             }
729             elsif ( !$new_roles->{$role_name}
730             && $existing_roles{$role_name} )
731             {
732             # Needs to be removed
733 0         0 $self->schema->resultset( $self->user_roles_resultset )
734             ->search(
735             {
736             $self->user_relationship
737             . ".$users_username_column" => $username,
738             $self->role_relationship
739             . ".$roles_role_column" => $role_name,
740             },
741             {
742             join => [
743             $self->user_relationship,
744             $self->role_relationship
745             ],
746             }
747             )->delete;
748             }
749             }
750             }
751             }
752              
753             # Move password reset code between keys if required
754 126 50       55313 if (my $users_pwresetcode_column = $self->users_pwresetcode_column) {
755 126 100       588 if (exists $update{pw_reset_code}) {
756 10         32 my $pw_reset_code = delete $update{pw_reset_code};
757 10         37 $update{$users_pwresetcode_column} = $pw_reset_code;
758             }
759             }
760 126         1211 $user->update({%update});
761             # Update $username if it was submitted in update
762 126 50       120753 $username = $update{username} if $update{username};
763 126         571 return $self->get_user_details($username);
764             }
765              
766             sub get_user_roles {
767 169     169 0 93638 my ($self, $username) = @_;
768 169 100       937 croak "username must be defined"
769             unless defined $username;
770              
771 167         4668 my $role_relationship = $self->role_relationship;
772 167         4944 my $user_user_roles_relationship = $self->user_user_roles_relationship;
773 167         1763 my $roles_role_column = $self->roles_role_column;
774              
775 167         683 my $options =
776             { prefetch => { $user_user_roles_relationship => $role_relationship } };
777              
778 167         607 my ($user) = $self->_user_rset(username => $username, $options)->all;
779              
780 167 100       2506827 if (!$user) {
781 4         558 $self->plugin->app->log( 'debug',
782             "No such user $username when looking for roles" );
783 4         2984 return;
784             }
785              
786 163         17208 my @roles;
787 163         4932 foreach my $ur ($user->$user_user_roles_relationship)
788             {
789 164         26750 my $role = $ur->$role_relationship->$roles_role_column;
790 164         9783 push @roles, $role;
791             }
792              
793 163         18166 \@roles;
794             }
795              
796             sub password_expired {
797 12     12 0 11907 my ($self, $user) = @_;
798 12 100 66     569 croak "user must be specified"
      66        
799             unless defined $user
800             && ( ref($user) eq 'HASH'
801             || ( blessed($user) && $user->isa('DBIx::Class::Row') ) );
802              
803 10 50       64 my $expiry = $self->password_expiry_days or return 0; # No expiry set
804              
805 10 50       48 if (my $pwchanged = $self->users_pwchanged_column) {
806             my $last_changed =
807 10 100       186 $self->user_as_object ? $user->$pwchanged : $user->{$pwchanged};
808              
809             # If not changed then report expired
810 10 100       99 return 1 unless $last_changed;
811              
812 8 50       36 if ( ref($last_changed) ne 'DateTime' ) {
813             # not inflated to DateTime by schema so do it now
814 8         166 my $db_parser = $self->schema->storage->datetime_parser;
815 8         69805 $last_changed = $db_parser->parse_datetime($last_changed);
816             }
817 8         5050 my $duration = $last_changed->delta_days(DateTime->now);
818 8 100       2119 $duration->in_units('days') > $expiry ? 1 : 0;
819             } else {
820 0           croak "users_pwchanged_column not configured";
821             }
822             }
823              
824             1;