File Coverage

blib/lib/Dancer2/Plugin/Auth/Extensible/Provider/DBIC.pm
Criterion Covered Total %
statement 169 174 97.1
branch 82 100 82.0
condition 21 33 63.6
subroutine 21 21 100.0
pod 0 10 0.0
total 293 338 86.6


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