File Coverage

blib/lib/Dancer2/Plugin/Auth/Extensible/Provider/DBIxClass.pm
Criterion Covered Total %
statement 173 178 97.1
branch 83 102 81.3
condition 22 33 66.6
subroutine 22 22 100.0
pod 0 10 0.0
total 300 345 86.9


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