diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index a14a3e3c..79e7a0a8 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -69,6 +69,7 @@ jobs: docker exec -t libki-test-server prove /app/t/stdout.t docker exec -t -e TEST_POD=1 libki-test-server prove /app/t/02pod.t docker exec -t -e TEST_POD=1 libki-test-server prove /app/t/03podcoverage.t + docker exec -t prove t/controller_API-Client-v1_0.t publish-docker-io-debian: name: Push Debian image (docker.io) diff --git a/CHANGELOG.md b/CHANGELOG.md index 42bc0811..9987884a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,10 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [4.9.0] +### Added +- Add passwordless mode support to Print Manager authentication api + ## [4.7.9] ### Added - Add passwordless mode support to Print Manager authentication api diff --git a/lib/Libki.pm b/lib/Libki.pm index 81445167..79838e89 100644 --- a/lib/Libki.pm +++ b/lib/Libki.pm @@ -13,7 +13,7 @@ use DateTime::Format::MySQL; use DateTime; use File::Slurp; -our $VERSION = '4.7.9'; +our $VERSION = '4.9.0'; # Set flags and add plugins for the application. # diff --git a/lib/Libki/Controller/API/Client/v1_0.pm b/lib/Libki/Controller/API/Client/v1_0.pm index da8f7912..0d990086 100644 --- a/lib/Libki/Controller/API/Client/v1_0.pm +++ b/lib/Libki/Controller/API/Client/v1_0.pm @@ -9,6 +9,7 @@ use Libki::SIP qw( authenticate_via_sip ); use Libki::LDAP qw( authenticate_via_ldap ); use Libki::Hours qw( minutes_until_closing ); use Libki::Utils::Printing qw( create_print_job_and_file ); +use Libki::Utils::User qw( create_guest ); use DateTime::Format::MySQL; use DateTime; @@ -84,18 +85,19 @@ sub index : Path : Args(0) { } $client->get_from_storage; + my $client_status = $client->status // q{}; - if ($client->status eq "unlock") { + if ($client_status eq "unlock") { $c->stash( unlock => 1, minutes => $client->session->minutes, username => $client->session->user->username, ); - } elsif ($client->status eq "shutdown" || $client->status eq "suspend" || $client->status eq "restart") { + } elsif ($client_status eq "shutdown" || $client_status eq "suspend" || $client_status eq "restart") { $c->stash( - $client->status => 1, + $client_status => 1, ); - } elsif ($client->status eq "wakeup") { + } elsif ($client_status eq "wakeup") { my $host = $c->setting('WOLHost') || '255.255.255.255'; my $port = $c->setting('WOLPort') || 9; my @mac_addresses = split(/[\r\n]+/, $c->setting('ClientMACAddresses')); @@ -139,6 +141,8 @@ sub index : Path : Args(0) { ClientBehavior => $c->stash->{Settings}->{ClientBehavior}, ReservationShowUsername => $c->stash->{Settings}->{ReservationShowUsername}, + EnableGuestSelfRegistration => $c->stash->{Settings}->{EnableGuestSelfRegistration}, + EnableClientSessionLocking => $c->stash->{Settings}->{EnableClientSessionLocking}, EnableClientPasswordlessMode => $c->stash->{Settings}->{EnableClientPasswordlessMode}, @@ -199,10 +203,32 @@ sub index : Path : Args(0) { my $client_type = $c->request->params->{'type'}; my $units; - my $user = $c->model('DB::User') - ->single( { instance => $instance, username => $username } ); + my $user + = $username + ? $c->model('DB::User')->single( { instance => $instance, username => $username } ) + : undef; if ( $action eq 'login' ) { + my $create_guest = $c->request->params->{'createGuest'}; + if ($create_guest) { + if ( $c->setting('EnableGuestSelfRegistration') ) { + ( $user, $password ) = Libki::Utils::User::create_guest($c); + $username = $user->username; + + $c->stash( + username => $username, + password => $password, + ); + } + else { + delete( $c->stash->{Settings} ); + $c->stash( authenticated => 0, error => "GUEST_SELF_REG_NOT_ENABLED" ); + $c->res->status(501); + $c->forward( $c->view('JSON') ); + return; + } + } + $log->debug( __PACKAGE__ . " - username: $username, client_name: $client_name" ); @@ -488,31 +514,35 @@ sub index : Path : Args(0) { } elsif ( $action eq 'logout' ) { my $session = $user->session; - my $session_id = $session->session_id; - my $location = $session->client->location; - my $type = $session->client->type; - my $success = $user->session->delete(); - $success &&= 1; - $c->stash( logged_out => $success ); + if ($session) { + my $session_id = $session->session_id; + my $location = $session->client->location; + my $type = $session->client->type; - $c->model('DB::Statistic')->create( - { - instance => $instance, - username => $username, - client_name => $client_name, - client_location => $client_location, - client_type => $client_type, - action => 'LOGOUT', - created_on => $now, - session_id => $session_id, - info => to_json( - { - client_version => $version - } - ), - } - ); + my $success = $session->delete() ? 1 : 0; + $c->stash( logged_out => $success ); + + $c->model('DB::Statistic')->create( + { + instance => $instance, + username => $username, + client_name => $client_name, + client_location => $client_location, + client_type => $client_type, + action => 'LOGOUT', + created_on => $now, + session_id => $session_id, + info => to_json( + { + client_version => $version + } + ), + } + ); + } else { + $c->stash( logged_out => 0 ); + } } } diff --git a/lib/Libki/Controller/Administration/API/User.pm b/lib/Libki/Controller/Administration/API/User.pm index 4e79b7cd..b0086a6f 100644 --- a/lib/Libki/Controller/Administration/API/User.pm +++ b/lib/Libki/Controller/Administration/API/User.pm @@ -1,12 +1,12 @@ package Libki::Controller::Administration::API::User; use Moose; -use String::Random qw(random_string); use namespace::autoclean; use POSIX; use Libki::Utils::Printing; +use Libki::Utils::User; use JSON qw(to_json); @@ -32,7 +32,7 @@ sub get : Local : Args(1) { my ( $self, $c, $id ) = @_; my $instance = $c->instance; - my $user = $c->model('DB::User')->find({ instance => $instance, id => $id }); + my $user = $c->model('DB::User')->find( { instance => $instance, id => $id } ); my $roles = $user->roles; my @roles; @@ -42,18 +42,20 @@ sub get : Local : Args(1) { $c->stash( { - id => $user->id, - username => $user->username, - firstname => $user->firstname, - lastname => $user->lastname, - category => $user->category, - minutes => $user->minutes($c), - status => $user->status, - notes => $user->notes, - funds => $user->funds, - is_troublemaker => $user->is_troublemaker, - troublemaker_until => defined($user->troublemaker_until) ? $user->troublemaker_until->strftime('%Y-%m-%d 23:59') : undef, - roles => \@roles, + id => $user->id, + username => $user->username, + firstname => $user->firstname, + lastname => $user->lastname, + category => $user->category, + minutes => $user->minutes($c), + status => $user->status, + notes => $user->notes, + funds => $user->funds, + is_troublemaker => $user->is_troublemaker, + troublemaker_until => defined( $user->troublemaker_until ) + ? $user->troublemaker_until->strftime('%Y-%m-%d 23:59') + : undef, + roles => \@roles, } ); @@ -79,33 +81,24 @@ sub create : Local : Args(0) { my $success = 0; - my $now = $c->now(); - my $user = $c->model('DB::User')->create( + my $now = $c->now(); + my $user = Libki::Utils::User::create_or_update_user( + $c, { - instance => $instance, - username => $username, - firstname => $firstname, - lastname => $lastname, - category => $category, - password => $password, - status => 'enabled', - created_on => $now, - updated_on => $now, - creation_source => 'local', + instance => $instance, + username => $username, + firstname => $firstname, + lastname => $lastname, + category => $category, + password => $password, + status => 'enabled', + created_on => $now, + updated_on => $now, + creation_source => 'local', + minutes => $minutes, } ); - if (defined $minutes) { - $c->model('DB::Allotment')->update_or_create( - { - instance => $instance, - user_id => $user->id, - location => '', - minutes => $minutes, - } - ); - } - $success = 1 if ($user); $c->stash( 'success' => $success ); @@ -123,43 +116,11 @@ sub create_guest : Local : Args(0) { my $category = $params->{category}; - my $current_guest_number_setting = $c->model('DB::Setting')->find_or_new({ instance => $instance, name => 'CurrentGuestNumber' }); - my $current_guest_number = $current_guest_number_setting->value ? $current_guest_number_setting->value + 1 : 1; - $current_guest_number_setting->set_column( 'value', $current_guest_number ); - $current_guest_number_setting->update_or_insert(); - - my $prefix_setting = $c->setting('GuestPassPrefix'); - my $prefix = $prefix_setting || 'guest'; - - my $username = $prefix . $current_guest_number; - my $password = - random_string("nnnn"); #TODO: Make the pattern a system setting - - my $minutes_allotment = $c->setting('DefaultGuestTimeAllowance'); - $minutes_allotment = 0 unless $minutes_allotment > 0; - - my $success = 0; - - my $now = $c->now(); - my $user = $c->model('DB::User')->create( - { - instance => $instance, - username => $username, - password => $password, - status => 'enabled', - is_guest => 'Yes', - created_on => $now, - updated_on => $now, - category => $category, - creation_source => 'local', - } - ); - - $success = 1 if ($user); + my ( $user, $password, $minutes_allotment ) = Libki::Utils::User::create_guest( $c, $category ); $c->stash( - 'success' => $success, - 'username' => $username, + 'success' => $user ? 1 : 0, + 'username' => $user ? $user->username : q{}, 'password' => $password, 'category' => $category, 'minutes' => $minutes_allotment, @@ -175,58 +136,47 @@ sub batch_create_guest : Local : Args(0) { my ( $self, $c ) = @_; my $instance = $c->instance; - my $params = $c->request->params; - my $category = $params->{category}; + my $params = $c->request->params; + my $category = $params->{category}; my $prefix_setting = $c->setting('GuestPassPrefix'); - my $prefix = $prefix_setting || 'guest'; + my $prefix = $prefix_setting || 'guest'; my $success = 0; my $batch_guest_pass_custom_css = $c->setting('BatchGuestPassCustomCSS'); - my $batch_guest_pass_template = $c->setting('BatchGuestPassTemplate'); + my $batch_guest_pass_template = $c->setting('BatchGuestPassTemplate'); - my $guest_count = $c->setting('GuestBatchCount') || 10; + my $guest_count = $c->setting('GuestBatchCount') || 10; my $batch_guest_pass_username_label = $c->setting('BatchGuestPassUsernameLabel'); my $batch_guest_pass_password_label = $c->setting('BatchGuestPassPasswordLabel'); my $minutes_allotment = $c->setting('DefaultGuestTimeAllowance'); $minutes_allotment = 0 unless ( $minutes_allotment > 0 ); - my $current_guest_number_setting = $c->model('DB::Setting')->find_or_new({ instance => $instance, name => 'CurrentGuestNumber' }); - my $current_guest_number = $current_guest_number_setting->value ? $current_guest_number_setting->value + 1 : 1; + my $current_guest_number_setting = $c->model('DB::Setting') + ->find_or_new( { instance => $instance, name => 'CurrentGuestNumber' } ); + my $current_guest_number + = $current_guest_number_setting->value ? $current_guest_number_setting->value + 1 : 1; my $now = $c->now(); my $file_contents .= ""; my @guests; - for ( my $i = 0 ; $i < $guest_count ; $i++ ) { + for ( my $i = 0; $i < $guest_count; $i++ ) { - $current_guest_number = $current_guest_number + 1; - my $username = $prefix . $current_guest_number; - my $password = - random_string("nnnn"); #TODO: Make the pattern a system setting - - my $user = $c->model('DB::User')->create( - { - instance => $instance, - username => $username, - password => $password, - status => 'enabled', - is_guest => 'Yes', - created_on => $now, - updated_on => $now, - category => $category, - creation_source => 'local', - } - ); + my ( $user, $password, $minutes_allotment ) + = Libki::Utils::User::create_guest( $c, $category ); + my $username = $user->username; $file_contents .= "\n
"; $file_contents .= "\n"; - $file_contents .= "$batch_guest_pass_username_label$username"; + $file_contents + .= "$batch_guest_pass_username_label$username"; $file_contents .= ""; $file_contents .= "\n\n"; - $file_contents .= "$batch_guest_pass_password_label$password\n\n"; + $file_contents + .= "$batch_guest_pass_password_label$password\n\n"; $file_contents .= ""; $file_contents .= "
"; $file_contents .= ""; @@ -236,12 +186,9 @@ sub batch_create_guest : Local : Args(0) { $success = $success + 1 if ($user); } - $current_guest_number_setting->set_column( 'value', $current_guest_number ); - $current_guest_number_setting->update_or_insert(); - - if ( $batch_guest_pass_template ) { + if ($batch_guest_pass_template) { $file_contents = q{}; - my $tt = Template->new() || die $Template::ERROR; + my $tt = Template->new() || die $Template::ERROR; my $vars = { batch_guest_pass_custom_css => $batch_guest_pass_custom_css, batch_guest_pass_username_label => $batch_guest_pass_username_label, @@ -285,12 +232,12 @@ sub update : Local : Args(0) { # For some reason the list of checkboxes are created # as a list within a list if multiple are checked - @roles = @{$roles[0]} if ref( $roles[0] ) eq 'ARRAY'; + @roles = @{ $roles[0] } if ref( $roles[0] ) eq 'ARRAY'; $minutes = undef if $minutes eq q{}; - $minutes = 0 if defined($minutes) && $minutes < 0; + $minutes = 0 if defined($minutes) && $minutes < 0; - my $user = $c->model('DB::User')->find({ instance => $instance, id => $id }); + my $user = $c->model('DB::User')->find( { instance => $instance, id => $id } ); my $now = $c->now(); @@ -309,7 +256,7 @@ sub update : Local : Args(0) { } ); - if (defined $minutes) { + if ( defined $minutes ) { $c->model('DB::Allotment')->update_or_create( { instance => $instance, @@ -318,25 +265,26 @@ sub update : Local : Args(0) { minutes => $minutes, } ); - } else { - $c->model('DB::Allotment')->search( { instance => $instance, user_id => $user->id } )->delete; + } + else { + $c->model('DB::Allotment')->search( { instance => $instance, user_id => $user->id } ) + ->delete; } if ( $c->check_user_roles(qw/superadmin/) ) { # Update the user's roles my @libki_roles = $c->model('DB::Role')->search(); - foreach my $lr ( @libki_roles ) { + foreach my $lr (@libki_roles) { my $role = $lr->role; - if ( grep { /$role/ } @roles ) { + if ( grep {/$role/} @roles ) { ## Add the role if it doesn't exists - $c->model('DB::UserRole') - ->find_or_create( { user_id => $id, role_id => $lr->id } ); + $c->model('DB::UserRole')->find_or_create( { user_id => $id, role_id => $lr->id } ); } else { ## Delete the role if it does already exist - $c->model('DB::UserRole') - ->search( { user_id => $id, role_id => $lr->id } )->delete(); + $c->model('DB::UserRole')->search( { user_id => $id, role_id => $lr->id } ) + ->delete(); } } } @@ -370,7 +318,7 @@ sub delete : Local : Args(1) { $msg = q{ADMIN_CANNOT_DELETE_SUPERADMIN}; } elsif ( $i_am_superadmin || ( $i_am_admin && !$user_is_superadmin ) ) { - $c->schema->txn_do( + $c->model('DB')->txn_do( sub { if ( $user->delete() ) { $success = 1; @@ -414,8 +362,8 @@ sub is_username_unique : Local : Args(1) { my ( $self, $c, $username ) = @_; my $instance = $c->instance; - my $count = - $c->model('DB::User')->search( { instance => $instance, username => $username } )->count(); + my $count = $c->model('DB::User')->search( { instance => $instance, username => $username } ) + ->count(); my $is_unique = ($count) ? 0 : 1; @@ -436,21 +384,21 @@ sub toggle_troublemaker : Local : Args(3) { my $success = 0; - my $user = $c->model('DB::User')->find({ instance => $instance, id => $id }); + my $user = $c->model('DB::User')->find( { instance => $instance, id => $id } ); my $is_troublemaker = ( $user->is_troublemaker eq 'Yes' ) ? 'No' : 'Yes'; my $now = $c->now(); - $user->set_column( 'is_troublemaker', $is_troublemaker ); - $user->set_column( 'updated_on', $now ); + $user->set_column( 'is_troublemaker', $is_troublemaker ); + $user->set_column( 'updated_on', $now ); $user->set_column( 'troublemaker_until', undef ); - if ( $until != 0 && $is_troublemaker eq 'Yes'){ + if ( $until != 0 && $is_troublemaker eq 'Yes' ) { my $troublemaker_until = $now->clone; $troublemaker_until->add( days => $until ); $user->set_column( 'troublemaker_until', $troublemaker_until ); - $user->set_column( 'notes', $notes eq '' ? undef : $notes ); + $user->set_column( 'notes', $notes eq '' ? undef : $notes ); } if ( $user->update() ) { @@ -474,7 +422,7 @@ sub change_password : Local : Args(0) { my $id = $c->request->params->{'id'}; my $password = $c->request->params->{'password'}; - my $user = $c->model('DB::User')->find({ instance => $instance, id => $id }); + my $user = $c->model('DB::User')->find( { instance => $instance, id => $id } ); my $now = $c->now(); @@ -487,7 +435,7 @@ sub change_password : Local : Args(0) { if ( $i_am_admin || $i_am_superadmin ) { if ( $i_am_superadmin || ( $i_am_admin && !$user_is_superadmin ) ) { - $user->set_column( 'password', $password ); + $user->set_column( 'password', $password ); $user->set_column( 'updated_on', $now ); if ( $user->update() ) { diff --git a/lib/Libki/SIP.pm b/lib/Libki/SIP.pm index cc673375..7717d2e6 100644 --- a/lib/Libki/SIP.pm +++ b/lib/Libki/SIP.pm @@ -191,7 +191,7 @@ sub authenticate_via_sip { ); unless ($is_admin) { - $c->txn_do( + $c->model('DB')->txn_do( sub { $user->delete; diff --git a/lib/Libki/Schema/DB/Result/User.pm b/lib/Libki/Schema/DB/Result/User.pm index 5cf4a34b..d7a22369 100644 --- a/lib/Libki/Schema/DB/Result/User.pm +++ b/lib/Libki/Schema/DB/Result/User.pm @@ -392,9 +392,9 @@ __PACKAGE__->add_columns( ); =head2 has_role - + Check if a user has the specified role - + =cut use Perl6::Junction qw/any/; diff --git a/lib/Libki/Utils/User.pm b/lib/Libki/Utils/User.pm new file mode 100644 index 00000000..426d6150 --- /dev/null +++ b/lib/Libki/Utils/User.pm @@ -0,0 +1,176 @@ +package Libki::Utils::User; + +use Modern::Perl; + +use Carp; + +use String::Random qw(random_string); + +=head2 create_or_update_user + +Create a user with the given criterea. +If the username exists, that user will be updated instead. + +=cut + +sub create_or_update_user { + my ( $c, $params ) = @_; + + my $instance = $params->{instance} || $c->instance; + my $username = $params->{username}; + my $password = $params->{password}; + my $firstname = $params->{firstname}; + my $lastname = $params->{lastname}; + my $category = $params->{category}; + my $creation_source = $params->{creation_source} || 'local'; + my $status = $params->{status} || 'enabled'; + my $minutes = $params->{minutes} || undef; + my $is_admin = $params->{admin}; + my $is_superadmin = $params->{superadmin}; + + confess "No username passed" unless $username; + confess "No password passed" unless $password; + + my $schema = $c->schema; + my $user_rs = $schema->resultset('User'); + + my $now = $c->now(); + + my $default_time_allowance_setting = $schema->resultset('Setting') + ->find( { instance => $instance, name => 'DefaultTimeAllowance' } ); + my $default_time_allowance + = $default_time_allowance_setting ? $default_time_allowance_setting->value : 0; + + my $user; + $schema->txn_do( + sub { + $user + = $user_rs->search( { instance => $instance, username => $username } )->next(); + + if ($user) { + $user->set_column( 'password', $password ); + $user->update( + { + password => $password, + updated_on => $now, + } + ); + } + else { + $user = $user_rs->create( + { + instance => $instance, + username => $username, + password => $password, + category => $category, + firstname => $firstname, + lastname => $lastname, + status => 'enabled', + creation_source => 'local', + is_troublemaker => 'No', + created_on => $now, + updated_on => $now, + } + ); + + } + + if ( defined $minutes ) { + $c->model('DB::Allotment')->update_or_create( + { + instance => $user->instance, + user_id => $user->id, + location => '', + minutes => $minutes, + } + ); + } + + if ($is_superadmin) { + my $role = $schema->resultset('Role')->search( { role => 'superadmin' } )->single(); + + $schema->resultset('UserRole')->update_or_create( + { + role_id => $role->id, + user_id => $user->id, + } + ); + } + + if ( $is_admin || $is_superadmin ) { + my $role = $schema->resultset('Role')->search( { role => 'admin' } )->single(); + + $schema->resultset('UserRole')->update_or_create( + { + role_id => $role->id, + user_id => $user->id, + } + ); + } + } + ); + + return $user; +} + +=head2 create_guest + +Create a guest account with the given category. +If $client is passed, minutes will be set for that client type. + +=cut + +sub create_guest { + my ( $c, $category, $client ) = @_; + + my $instance = $c->instance; + + my $now = $c->now(); + + my ( $user, $password, $minutes_allotment ); + $c->model('DB')->txn_do( + sub { + + my $current_guest_number_setting = $c->model('DB::Setting') + ->find_or_new( { instance => $instance, name => 'CurrentGuestNumber' } ); + + my $current_guest_number + = $current_guest_number_setting->value + ? $current_guest_number_setting->value + 1 + : 1; + $current_guest_number_setting->set_column( 'value', $current_guest_number ); + $current_guest_number_setting->update_or_insert(); + + my $prefix_setting = $c->setting('GuestPassPrefix'); + my $prefix = $prefix_setting || 'guest'; + + my $username = $prefix . $current_guest_number; + $password = random_string("nnnn"); #TODO: Make the pattern a system setting + + $user = $c->model('DB::User')->create( + { + instance => $instance, + username => $username, + password => $password, + status => 'enabled', + is_guest => 'Yes', + created_on => $now, + updated_on => $now, + category => $category, + creation_source => 'local', + } + ); + + $minutes_allotment = $c->setting('DefaultGuestTimeAllowance'); + $minutes_allotment = 0 unless $minutes_allotment > 0; + + # If created from the client login as guest button, we can give a more accurate number + $minutes_allotment = $user->minutes( $c, $client ) if $client; + + } + ); + + return ( $user, $password, $minutes_allotment ); +} + +1; diff --git a/root/dynamic/templates/administration/settings/index.tt b/root/dynamic/templates/administration/settings/index.tt index 411a64c8..af91f21d 100644 --- a/root/dynamic/templates/administration/settings/index.tt +++ b/root/dynamic/templates/administration/settings/index.tt @@ -799,6 +799,17 @@
[% c.loc("Guest passes") %] +
+ +
+ + + + + [% c.loc("If enabled, clients will have a 'Log in as guest' button that will create a guest account and log them into it automatically.") %] +
+
+
diff --git a/script/administration/create_user.pl b/script/administration/create_user.pl index 656f2497..7126f530 100755 --- a/script/administration/create_user.pl +++ b/script/administration/create_user.pl @@ -5,13 +5,14 @@ use Getopt::Long::Descriptive; use Libki; +use Libki::Utils::User; my ( $opt, $usage ) = describe_options( '%c %o', - [ 'instance|i=s', "the instance for the user to exist on", { default => q{} } ], - [ 'username|u=s', "the username for this user, required", { required => 1 } ], + [ 'instance|i=s', "the instance for the user to exist on", { default => q{} } ], + [ 'username|u=s', "the username for this user, required", { required => 1 } ], [ 'password|p=s', "the password for this user" ], - [ 'minutes|m=s', "number of minutes for this user" ], + [ 'minutes|m=i', "number of minutes for this user" ], [ 'admin|a', "makes the user an admin" ], [ 'superadmin|s', "makes the user a superadmin" ], [], @@ -20,64 +21,28 @@ print( $usage->text ), exit unless ( $opt->username ); -my $c = Libki->new(); -my $schema = $c->schema; -my $user_rs = $schema->resultset('User'); - -my $user = $user_rs->search( { instance => $opt->instance, username => $opt->username } )->next(); - -if ($user) { - $user->set_column( 'password', $opt->password ); - $user->update(); -} -else { - my $default_time_allowance_setting = $schema->resultset('Setting')->find({ instance => $opt->instance, name => 'DefaultTimeAllowance' }); - my $default_time_allowance = $default_time_allowance_setting ? $default_time_allowance_setting->value : 0; - - $user = $user_rs->create( - { - instance => $opt->instance, - username => $opt->username, - password => $opt->password, - status => 'enabled', - is_troublemaker => 'No', - } - ); - - if (defined $opt->minutes) { - $c->model('DB::Allotment')->update_or_create( - { - instance => $user->instance, - user_id => $user->id, - location => '', - minutes => $opt->minutes, - } - ); +my $c = Libki->new(); +my $user = Libki::Utils::User::create_or_update_user( + $c, + { + instance => $opt->instance, + username => $opt->username, + password => $opt->password, + minutes => $opt->minutes, + admin => $opt->admin, + superadmin => $opt->superadmin, } -} - -if ( $opt->superadmin ) { - my $role = - $schema->resultset('Role')->search( { role => 'superadmin' } )->single(); - - $schema->resultset('UserRole')->update_or_create( - { - role_id => $role->id, - user_id => $user->id, - } - ); -} - -if ( $opt->admin || $opt->superadmin ) { - my $role = - $schema->resultset('Role')->search( { role => 'admin' } )->single(); +); - $schema->resultset('UserRole')->update_or_create( - { - role_id => $role->id, - user_id => $user->id, - } - ); +say "User created" if $user; +say "User not created" unless $user; +if ( $user && $opt->verbose ) { + say "User Id: " . $user->id; + say "Username: " . $user->username; + say "Instance: " . $user->instance; + say "Minutes: " . $user->minutes($c); + say "Is Admin: " . ( $user->has_role(q{admin}) ? 'Yes' : 'No' ); + say "Is Admin: " . ( $user->has_role(q{superadmin}) ? 'Yes' : 'No' ); } =head1 AUTHOR diff --git a/t/controller_API-Client-v1_0.t b/t/controller_API-Client-v1_0.t index 8d6af77e..8835e6fd 100644 --- a/t/controller_API-Client-v1_0.t +++ b/t/controller_API-Client-v1_0.t @@ -1,10 +1,137 @@ -use strict; -use warnings; +use Modern::Perl; use Test::More; +use JSON; +use Data::Dumper; use Catalyst::Test 'Libki'; use Libki::Controller::API::Client::v1_0; +use Libki::Utils::User; -ok( request('/api/client/v1_0')->is_success, 'Request should succeed' ); -done_testing(); +my $c = Libki->new(); + +my $base = '/api/client/v1_0'; +my $base_params + = '?node=testClient&location=testLocation&type=testType&ipaddress=123.123.123&macaddress=00-B0-D0-63-C2-26&hostname=testHostnamev'; + +# Test response for no action FIXME: Maybe we should return a 4xx here? +my $res = request($base); +ok( $res->is_success, 'Request should succeed' ); +is( $res->status_line, '200 OK', 'Status line is 200 OK' ); +is( $res->decoded_content, '{}', 'Content is empty JSON' ); + +# Test basic client registration +$res = request( $base . $base_params . '&node_name=TestClient&action=register_node' ); +ok( $res->is_success, 'Client registration succeeded' ); +my $json = decode_json( $res->decoded_content ); +is( $json->{status}, 'online', 'Registered client status is "online"' ); + +my $user1 + = Libki::Utils::User::create_or_update_user( $c, { username => "test1", password => "test1" } ); +isa_ok( $user1, "Libki::Model::DB::User", "Got a user" ); + +my $user2 + = Libki::Utils::User::create_or_update_user( $c, { username => "test2", password => "test2" } ); +isa_ok( $user2, "Libki::Model::DB::User", "Got another user" ); + +subtest 'Client login' => sub { + + # Test bad logins + ## Bad username + $res + = request( $base + . $base_params + . '&username=AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA&password=badpassword&action=login' ); + ok( $res->is_success, 'Client login response succeeded' ); + $json = decode_json( $res->decoded_content ); + is( $json->{error}, 'BAD_LOGIN', 'User was not authenticated' ); + + ### No username + $res = request( $base . $base_params . '&username=&password=badpassword&action=login' ); + ok( $res->is_success, 'Client login response succeeded' ); + $json = decode_json( $res->decoded_content ); + is( $json->{error}, 'BAD_LOGIN', 'User was not authenticated' ); + + ### Bad password + $res = request( $base . $base_params . '&username=test1&password=badpassword&action=login' ); + ok( $res->is_success, 'Client login response succeeded' ); + $json = decode_json( $res->decoded_content ); + is( $json->{error}, 'BAD_LOGIN', 'User was not authenticated' ); + + ### No password + $res = request( $base . $base_params . '&username=test1&password=&action=login' ); + ok( $res->is_success, 'Client login response succeeded' ); + $json = decode_json( $res->decoded_content ); + is( $json->{error}, 'BAD_LOGIN', 'User was not authenticated' ); + + ## Test good credentials + $res = request( $base . $base_params . '&username=test1&password=test1&action=login' ); + ok( $res->is_success, 'Client login response succeeded' ); + $json = decode_json( $res->decoded_content ); + is( $json->{authenticated}, '1', 'User was authenticated' ); + + ## Test duplicate login, should succeed + $res = request( $base . $base_params . '&username=test1&password=test1&action=login' ); + ok( $res->is_success, 'Client login response succeeded' ); + $json = decode_json( $res->decoded_content ); + is( $json->{authenticated}, '1', 'User was authenticated' ); + + ## Test login with another user while original user is logged in + $res = request( $base . $base_params . '&username=test2&password=test2&action=login' ); + ok( $res->is_success, 'Client login response succeeded' ); + $json = decode_json( $res->decoded_content ); + is( $json->{authenticated}, '1', 'User was authenticated' ); + + #TODO: Check the sessions table to ensure old session was removed and new session was created + + # Test logout + ## Test logout when logged in + $res = request( $base . $base_params . '&username=test2&password=test2&action=logout' ); + ok( $res->is_success, 'Client logout response succeeded' ); + $json = decode_json( $res->decoded_content ); + is( $json->{logged_out}, '1', 'User was logged out' ); + + #TODO: Check the sessions table to ensure session was removed + + ## Test logout when not logged in + $res = request( $base . $base_params . '&username=test2&password=test2&action=logout' ); + ok( $res->is_success, 'Client logout response succeeded' ); + $json = decode_json( $res->decoded_content ); + is( $json->{logged_out}, '0', 'Logged out' ); +}; + +subtest 'Client guest login' => sub { + my $setting = $c->model( 'DB::Setting' )->find( { instance => $c->instance, name => 'EnableGuestSelfRegistration' } ); + my $original_value = $setting->value; + $setting->update({ value => 1 }); + + # Do guest login + $res = request( $base . $base_params . '&createGuest=1&action=login' ); + ok( $res->is_success, 'Client login response succeeded' ); + $json = decode_json( $res->decoded_content ); + is( $json->{authenticated}, '1', 'User was authenticated' ); + ok( $json->{username}, 'Got guest username' ); + ok( $json->{password}, 'Got guest password' ); + my $username1 = $json->{username}; + + # Do second guest login + $res = request( $base . $base_params . '&createGuest=1&action=login' ); + ok( $res->is_success, 'Client login response succeeded' ); + $json = decode_json( $res->decoded_content ); + is( $json->{authenticated}, '1', 'User was authenticated' ); + ok( $json->{username}, 'Got guest username' ); + ok( $json->{password}, 'Got guest password' ); + my $username2 = $json->{username}; + + isnt( $username1, $username2, "Got different guest users for each login" ); + + $setting->update({ value => 0 }); + $res = request( $base . $base_params . '&createGuest=1&action=login' ); + ok( !$res->is_success, 'Client login response failed' ); + $json = decode_json( $res->decoded_content ); + is( $json->{authenticated}, '0', 'User was not authenticated' ); + is( $json->{error}, 'GUEST_SELF_REG_NOT_ENABLED', "Error indicates guest self registration is not enabled" ); + my $username1 = $json->{username}; +}; + +done_testing()