File Coverage

blib/lib/Mojolicious/Plugin/ContextAuth/DB/User.pm
Criterion Covered Total %
statement 294 294 100.0
branch 90 90 100.0
condition 21 21 100.0
subroutine 41 41 100.0
pod 11 11 100.0
total 457 457 100.0


line stmt bran cond sub pod time code
1             package Mojolicious::Plugin::ContextAuth::DB::User;
2              
3             # ABSTRACT: User object for the ContextAuth database
4              
5 51     51   366 use Mojo::Base -base, -signatures;
  51         115  
  51         423  
6              
7 51     51   11461 use Crypt::Eksblowfish::Bcrypt ();
  51         118  
  51         729  
8 51     51   259 use Crypt::URandom ();
  51         105  
  51         819  
9 51     51   24255 use Data::UUID;
  51         31669  
  51         3365  
10 51     51   388 use List::Util qw(any);
  51         103  
  51         3329  
11 51     51   27496 use Try::Tiny;
  51         68681  
  51         2972  
12              
13 51     51   396 use feature 'postderef';
  51         121  
  51         1803  
14 51     51   285 no warnings 'experimental::postderef';
  51         170  
  51         163274  
15              
16             has [qw'dbh username user_password user_id error'];
17              
18 65     65 1 5582 sub load ($self, $id) {
  64         186  
  64         222  
  64         172  
19 64         411 $self->error('');
20            
21 64 100       701 if ( !$id ) {
22 1         9 $self->error( "Need id" );
23 1         14 return;
24             }
25              
26 63         294 my $result = $self->dbh->db->select(
27             corbac_users => [qw/user_id username user_password/], {
28             user_id => $id,
29             }
30             );
31              
32 63         56859 my $data = $result->hash;
33 63         3912 $result->finish;
34              
35 63 100       916 return if !$result->rows;
36              
37 53         800 my $user = __PACKAGE__->new(
38             dbh => $self->dbh,
39             $data->%*,
40             user_id => $id,
41             );
42              
43 53         1522 return $user;
44             }
45              
46 40     40 1 8256 sub add ($self, %params) {
  40         117  
  40         168  
  40         90  
47 40         245 $self->error('');
48              
49 40 100   77   668 if ( any{ !$params{$_} }qw/username user_password/ ) {
  77         306  
50 5         24 $self->error('Need username and user_password');
51 5         34 return;
52             }
53              
54 35         196 my $cost = 12;
55 35         407 my $settings = '$2a' . sprintf '$%02i', $cost;
56              
57 35         201 _crypt( \%params );
58              
59 35 100 100     15263309 if ( length $params{username} > 255 || length $params{username} < 3 ) {
60 2         22 $self->error( 'Invalid parameter' );
61 2         38 return;
62             }
63              
64 33         13629 $params{user_id} = Data::UUID->new->create_str;
65              
66 33         70284 my $error;
67             try {
68 33     33   3854 $self->dbh->db->insert( corbac_users => \%params);
69             }
70             catch {
71 1     1   2131 $self->error( 'Invalid parameter' );
72 1         11 $error = $_;
73 33         946 };
74              
75 33 100       304951 return if $error;
76              
77 32         406 my $user = $self->load( $params{user_id} );
78 32         2394 return $user;
79             }
80              
81 10     10 1 6446 sub delete ($self, $id = $self->user_id) {
  10         23  
  10         28  
  10         37  
82 10         40 $self->error('');
83            
84 10 100       76 if ( !$id ) {
85 2         10 $self->error( "Need user id" );
86 2         23 return;
87             }
88              
89 8 100       27 if ( ref $id ) {
90 1         5 $self->error( "Invalid user id" );
91 1         9 return;
92             }
93            
94 7         15 my $error;
95             my $result;
96              
97             try {
98 7     7   335 my $tx = $self->dbh->db->begin;
99              
100 7         886 $self->dbh->db->delete(
101             corbac_user_sessions => { user_id => $id },
102             );
103              
104 6         3422 $self->dbh->db->delete(
105             corbac_user_context_roles => { user_id => $id }
106             );
107              
108 6         3071 $result = $self->dbh->db->delete(
109             corbac_users => {
110             user_id => $id,
111             }
112             );
113              
114 6         3239 $tx->commit;
115             }
116             catch {
117 1     1   1180 $self->error( "Cannot delete user: " . $_ );
118 1         10 $error = 1;
119 7         56 };
120              
121 7 100       669 return if $error;
122 6         26 return $result->rows;
123             }
124              
125 11     11 1 9469 sub update ($self, @params) {
  11         28  
  11         35  
  11         22  
126 11         52 $self->error('');
127            
128 11 100       120 my $id = @params % 2 ? shift @params : $self->user_id;
129 11         70 my %to_update = @params;
130              
131 11 100 100     106 if ( length $to_update{username} > 255 || length $to_update{username} < 3 ) {
132 4         19 $self->error( 'Invalid parameter' );
133 4         35 return;
134             }
135              
136 7         19 delete $to_update{user_id};
137              
138 7 100       40 _crypt( \%to_update ) if exists $to_update{user_password};
139              
140 7         2219418 my $result;
141             my $error;
142             try {
143 7     7   660 $result = $self->dbh->db->update(
144             corbac_users => \%to_update,
145             { user_id => $id }
146             );
147             }
148             catch {
149 1     1   1376 $self->error( 'Invalid parameter' );
150 1         11 $error = $_;
151 7         133 };
152              
153 7 100       7500 return if $error;
154              
155 6 100       39 if ( !$result->rows ) {
156 1         21 $self->error( 'No user updated' );
157 1         11 return;
158             }
159              
160 5         89 return $self->load( $id );
161             }
162              
163 40     40   116 sub _crypt ( $params ) {
  40         98  
  40         82  
164 40         106 my $cost = 12;
165 40         185 my $settings = '$2a' . sprintf '$%02i', $cost;
166              
167             $params->{user_password} = Crypt::Eksblowfish::Bcrypt::bcrypt(
168             $params->{user_password},
169 40         348 $settings . '$' . Crypt::Eksblowfish::Bcrypt::en_base64(Crypt::URandom::urandom(16)),
170             );
171             }
172              
173 3     3 1 15 sub add_session ( $self, $session_id ) {
  3         6  
  3         8  
  3         7  
174 3         23 $self->error('');
175            
176 3         73 $self->dbh->db->insert( corbac_user_sessions => {
177             user_id => $self->user_id,
178             session_id => $session_id,
179             session_started => time,
180             });
181              
182 3         2409 return 1;
183             }
184              
185 5     5 1 6372 sub search ($self, %params) {
  5         10  
  5         27  
  5         11  
186 5         20 $self->error('');
187              
188 5         32 my $error;
189             my @user_ids;
190              
191             try {
192 5     5   219 my $result = $self->dbh->db->select(
193             corbac_users => ['user_id'] => \%params,
194             );
195              
196 4         2963 while ( my $next = $result->hash ) {
197 4         163 push @user_ids, $next->{user_id};
198             }
199             }
200             catch {
201 1     1   1217 $self->error('Cannot search for users');
202 1         10 $error = $_;
203 5         39 };
204              
205 5 100       368 return if $error;
206 4         19 return @user_ids;
207             }
208              
209 17     17 1 15476 sub set_context_roles ( $self, %params ) {
  17         38  
  17         62  
  17         28  
210 17         68 $self->error('');
211            
212 17   100     189 my $user_id = $params{user_id} // $self->user_id;
213 17 100       121 if ( !$user_id ) {
214 1         13 $self->error("Need user id");
215 1         10 return;
216             }
217              
218 16 100       48 if ( ref $user_id ) {
219 2         13 $self->error("Invalid user id");
220 2         20 return;
221             }
222              
223 14 100   27   91 if ( any{ !$params{$_} }qw/context_id roles/ ) {
  27         74  
224 2         8 $self->error("need context_id and roles");
225 2         19 return;
226             }
227              
228 12         51 my $context_id = $params{context_id};
229              
230 12 100       36 if ( ref $context_id ) {
231 1         11 $self->error("Invalid context id");
232 1         10 return;
233             }
234              
235 11         24 my $count = 0;
236 11         20 my $error;
237             try {
238 11     11   858 my $tx = $self->dbh->db->begin;
239              
240 11         1353 $self->dbh->db->delete(
241             corbac_user_context_roles => {
242             user_id => $user_id,
243             context_id => $context_id,
244             }
245             );
246              
247 10 100       6617 $count = -1 if !$params{roles}->@*;
248              
249 9         385 for my $role_id ( $params{roles}->@* ) {
250 9         91 my $result = $self->dbh->db->insert(
251             corbac_user_context_roles => {
252             user_id => $user_id,
253             context_id => $context_id,
254             role_id => $role_id,
255             }
256             );
257              
258 9         5906 $count += $result->rows;
259             }
260              
261 9         494 $tx->commit;
262             }
263             catch {
264 2     2   1209 $self->error( "Transaction error: $_" );
265 2         19 $error = $_;
266 11         96 };
267              
268 11 100       1131 return if $error;
269 9         62 return $count;
270             }
271              
272 11     11 1 5493 sub has_role ( $self, %params ) {
  11         20  
  11         26  
  11         15  
273 11         31 $self->error('');
274            
275 11   100     74 my $user_id = $params{user_id} // $self->user_id;
276 11 100       49 if ( !$user_id ) {
277 1         5 $self->error("Need user id");
278 1         8 return;
279             }
280              
281 10 100       22 if ( ref $user_id ) {
282 1         11 $self->error("Invalid user id");
283 1         7 return;
284             }
285              
286 9         13 my $context_id = $params{context_id};
287              
288 9 100       17 if ( !$context_id ) {
289 1         9 $self->error('Need context_id');
290 1         8 return;
291             }
292              
293 8 100       18 if ( ref $context_id ) {
294 2         6 $self->error("Invalid context id");
295 2         13 return;
296             }
297              
298 6         7 my @binds;
299 6         11 my $join = '';
300 6 100       12 if ( $params{role} ) {
301 4         15 $join = 'JOIN corbac_roles r ON r.role_id = ucr.role_id AND r.role_name = ?';
302 4         7 push @binds, $params{role};
303             }
304              
305 6         9 my $where = '';
306 6 100       13 if ( $params{role_id} ) {
307 2         3 $where = ' ucr.role_id = ? AND ';
308 2         5 push @binds, $params{role_id};
309             }
310              
311 6         8 my $error;
312             my $has_role;
313              
314             try {
315 6     6   237 my $select = sprintf q~
316             SELECT ucr.role_id
317             FROM corbac_user_context_roles ucr
318             %s
319             WHERE
320             %s
321             ucr.context_id = ?
322             AND ucr.user_id = ?
323             ~, $join, $where;
324              
325 6         15 my $result = $self->dbh->db->query(
326             $select,
327             @binds,
328             $context_id,
329             $user_id,
330             );
331              
332 5         1194 my $hash = $result->hash;
333              
334 5 100       126 return if !$hash;
335              
336 2         8 $has_role = 1;
337             }
338             catch {
339 1     1   642 $self->error('Cannot determine if user has role: ' . $_);
340 1         7 $error = $_;
341 6         38 };
342              
343 6 100       309 return if $error;
344 5 100       24 return if !$has_role;
345 2         12 return 1;
346             }
347              
348 14     14 1 7498 sub context_roles ($self, %params) {
  14         30  
  14         38  
  14         23  
349 14         49 $self->error('');
350            
351 14   100     130 my $user_id = $params{user_id} // $self->user_id;
352 14 100       82 if ( !$user_id ) {
353 1         4 $self->error("Need user id");
354 1         14 return;
355             }
356              
357 13 100       42 if ( ref $user_id ) {
358 1         20 $self->error("Invalid user id");
359 1         8 return;
360             }
361              
362 12         27 my $context_id = $params{context_id};
363 12 100       38 if ( !$context_id ) {
364 1         5 $self->error('Need context_id');
365 1         8 return;
366             }
367              
368 11 100       30 if ( ref $context_id ) {
369 1         4 $self->error("Invalid context id");
370 1         9 return;
371             }
372              
373 10         23 my $error;
374             my @roles;
375              
376             try {
377 10     10   438 my $select = q~
378             SELECT ucr.role_id
379             FROM corbac_user_context_roles ucr
380             WHERE ucr.context_id = ?
381             AND ucr.user_id = ?
382             ~;
383              
384 10         40 my $result = $self->dbh->db->query(
385             $select,
386             $context_id,
387             $user_id,
388             );
389              
390 9         2470 while ( my $next = $result->hash ) {
391 5         195 push @roles, $next->{role_id};
392             }
393             }
394             catch {
395 1     1   683 $self->error('Cannot get context_roles: ' . $_);
396 1         9 $error = $_;
397 10         126 };
398              
399 10 100       834 return if $error;
400 9         51 return sort @roles;
401             }
402              
403 9     9 1 4990 sub has_permission ($self, %params ){
  9         19  
  9         24  
  9         17  
404 9         32 $self->error('');
405            
406 9   100     75 my $user_id = $params{user_id} // $self->user_id;
407 9 100       42 if ( !$user_id ) {
408 1         12 $self->error("Need user id");
409 1         9 return;
410             }
411              
412 8 100       23 if ( ref $user_id ) {
413 1         11 $self->error("Invalid user id");
414 1         13 return;
415             }
416              
417 7 100   13   45 if ( any{ !$params{$_} }qw/context_id permission_id/ ) {
  13         78  
418 2         6 $self->error('Need context_id and permission_id');
419 2         26 return;
420             }
421              
422 5         24 my $context_id = $params{context_id};
423              
424 5 100       16 if ( ref $context_id ) {
425 1         11 $self->error("Invalid context id");
426 1         10 return;
427             }
428              
429 4         7 my $permission_id = $params{permission_id};
430              
431 4 100       11 if ( ref $permission_id ) {
432 1         5 $self->error("Invalid permission id");
433 1         11 return;
434             }
435              
436 3         7 my $error;
437             my $has_permission;
438              
439             try {
440 3     3   147 my $select = q~
441             SELECT ucr.user_id
442             FROM corbac_user_context_roles ucr
443             INNER JOIN corbac_roles r
444             ON ucr.role_id = r.role_id
445             AND ucr.context_id = r.context_id
446             INNER JOIN corbac_role_permissions rp
447             ON r.role_id = rp.role_id
448             WHERE ucr.context_id = ?
449             AND rp.permission_id = ?
450             AND ucr.user_id = ?
451             ~;
452              
453              
454 3         13 my $result = $self->dbh->db->query(
455             $select,
456             $context_id,
457             $permission_id,
458             $user_id
459             );
460              
461 2         675 my $hash = $result->hash;
462              
463 2 100       69 return if !$hash;
464              
465 1         6 $has_permission = 1;
466             }
467             catch {
468 1     1   772 $self->error('Cannot determine if user has permission: ' . $_);
469 1         9 $error = $_;
470 3         41 };
471              
472 3 100       157 return if $error;
473 2 100       13 return if !$has_permission;
474 1         7 return 1;
475             }
476              
477 6     6 1 3878 sub contexts ($self, %params) {
  6         13  
  6         14  
  6         10  
478 6         22 $self->error('');
479            
480 6   100     56 my $user_id = $params{user_id} // $self->user_id;
481 6 100       31 if ( !$user_id ) {
482 1         7 $self->error("Need user id");
483 1         9 return;
484             }
485              
486 5 100       13 if ( ref $user_id ) {
487 1         5 $self->error("Invalid user id");
488 1         9 return;
489             }
490              
491 4         10 my $error;
492             my @contexts;
493              
494             try {
495 4     4   179 my $select = q~
496             SELECT ucr.context_id
497             FROM corbac_user_context_roles ucr
498             WHERE ucr.user_id = ?
499             ~;
500              
501 4         27 my $result = $self->dbh->db->query(
502             $select,
503             $user_id,
504             );
505              
506 3         1191 while ( my $next = $result->hash ) {
507 3         109 push @contexts, $next->{context_id};
508             }
509             }
510             catch {
511 1     1   700 $self->error('Cannot get contexts: ' . $_);
512 1         9 $error = $_;
513 4         32 };
514              
515 4 100       281 return if $error;
516 3         21 return sort @contexts;
517             }
518              
519             1;
520              
521             =pod
522              
523             =encoding UTF-8
524              
525             =head1 NAME
526              
527             Mojolicious::Plugin::ContextAuth::DB::User - User object for the ContextAuth database
528              
529             =head1 VERSION
530              
531             version 0.01
532              
533             =head1 SYNOPSIS
534              
535             my $db = Mojolicious::Plugin::ContextAuth::DB->new(
536             dsn => 'sqlite:' . $file,
537             );
538              
539             my $user = Mojolicious::Plugin::ContextAuth::DB::User->new(
540             dbh => $db->dbh,
541             );
542              
543             my $new_user = $user->add(
544             username => 'test',
545             user_password => 'hallo',
546             );
547              
548             my $updated_user = $new_user->update(
549             username => 'ernie',
550             user_password => 'bert',
551             );
552              
553             # create user object with data for user id 1
554             my $found_user = $user->load( 1 );
555              
556             # delete user
557             $new_user->delete;
558              
559             # check if user is allowed to update the title of the project in "Project X"
560             my $can_update = $found_user->has_permission(
561             context_id => 123,
562             permission_id => 33,
563             );
564              
565             # check if user has role Y in "Project X"
566             my $has_role = $found_user->has_role(
567             context_id => 123,
568             role_id => 15,
569             );
570              
571             =head1 ATTRIBUTES
572              
573             =over 4
574              
575             =item * dbh
576              
577             =item * username
578              
579             =item * user_password
580              
581             =item * user_id
582              
583             =item * error
584              
585             =back
586              
587             =head1 METHODS
588              
589             =head2 load
590              
591             # create user object with data for user id 1
592             my $found_user = $user->load( 1 );
593              
594             =head2 has_permission
595              
596             # check if user is allowed to update the title of the project in "Project X"
597             my $can_update = $found_user->has_permission(
598             context_id => 123,
599             permission_id => 33,
600             );
601              
602             =head2 has_role
603              
604             # check if user has role Y in "Project X"
605             my $has_role = $found_user->has_role(
606             context_id => 123,
607             role_id => 15,
608             );
609              
610             =head2 add
611              
612             my $new_user = $user->add(
613             username => 'test',
614             user_password => 'hallo',
615             );
616              
617             =head2 update
618              
619             my $updated_user = $new_user->update(
620             username => 'ernie',
621             user_password => 'bert',
622             );
623              
624             =head2 delete
625              
626             $user->delete;
627              
628             =head2 add_session
629              
630             # add new session for the user
631             my $success = $found_user->add_session(
632             $session_id
633             );
634              
635             =head2 set_context_roles
636              
637             # set roles the user has in the given context
638             my $success = $found_user->set_context_roles(
639             context_id => 123,
640             roles => [ # list of role_ids
641             15,
642             22,
643             33,
644             ],
645             );
646              
647             =head2 context_roles
648              
649             my @roles = $user->context_roles(
650             context_id => 123,
651             );
652              
653             Returns a list of role ids.
654              
655             =head2 contexts
656              
657             my @context_ids = $user->context_roles();
658              
659             Returns a list of context ids.
660              
661             =head2 has_permission
662              
663             my $has_permission = $user->has_permission(
664             context_id => 123,
665             role_id => 456, # or role => 'role_name'
666             );
667              
668             =head2 has_role
669              
670             my @roles = $user->has_role(
671             context_id => 123,
672             role_id => 456, # or role => 'role_name'
673             );
674              
675             =head2 search
676              
677             =head1 AUTHOR
678              
679             Renee Baecker
680              
681             =head1 COPYRIGHT AND LICENSE
682              
683             This software is Copyright (c) 2020 by Renee Baecker.
684              
685             This is free software, licensed under:
686              
687             The Artistic License 2.0 (GPL Compatible)
688              
689             =cut
690              
691             __END__