view release on metacpan or search on metacpan
0.74 2014-11-22
- Fixed path to reset.min.js from Semantic-UI.
- Fixed regression with I18n and Mojolicious 5.62.
- Code optimisations.
0.73 2014-11-17
- Upgraded to Mojolicious::Plugin::SemanticUI 0.08.
- Works with Mojolicious 5.62.
0.72 2014-11-15
- Emphasized the passwordless login feature in Ado::Plugin::Auth.
- overeall improvement of Ado::Plugin::Auth documentation.
- Added Mojolicious::Plugin::OAuth2 as a recommended dependency.
It will be required only if some of the supported providers is enabled.
- Implemented experimental authentication via Google.
- Added hook "after_user_add" emited by Ado::Plugin::Auth.
- Added German language (Joachim Astel). Thanks!
- Improved Ado::Manual::Plugins a lot.
0.71 2014-11-01
- Fixed bug in Ado::Plugin::home_dir(). Wrong assumption regarding %INC keys.
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
etc/ado-sqlite-schema.sql view on Meta::CPAN
disabled INT(1) NOT NULL DEFAULT 1
);
-- 'This table stores the users'
DROP TABLE IF EXISTS users;
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
-- 'Primary group for this user'
group_id INTEGER REFERENCES groups(id),
login_name varchar(100) UNIQUE,
-- 'Mojo::Util::sha1_hex($login_name.$login_password)'
login_password varchar(40) NOT NULL,
first_name varchar(100) NOT NULL DEFAULT '',
last_name varchar(100) NOT NULL DEFAULT '',
email varchar(255) NOT NULL UNIQUE,
description varchar(255) DEFAULT NULL,
-- 'id of who created this user.'
created_by INTEGER REFERENCES users(id),
-- 'Who modified this user the last time?'
changed_by INTEGER REFERENCES users(id),
-- 'last modification time'
-- 'All dates are stored as seconds since the epoch(1970) in GMT. In Perl we use gmtime as object from Time::Piece'
lib/Ado/Command/adduser.pm view on Meta::CPAN
has args => sub {
my $t = time;
{ changed_by => 1,
created_by => 1,
disabled => 1,
#TODO: add funcionality for notifying users on account expiration
#TODO: document this
stop_date => $t + ONE_YEAR, #account expires after one year
start_date => $t,
login_password => rand($t) . $$ . {} . $t,
};
};
sub init {
my ($self, @args) = @_;
$self->SUPER::init();
unless (@args) { Carp::croak($self->usage); }
my $args = $self->args;
my $ret = GetOptionsFromArray(
\@args,
'u|login_name=s' => \$args->{login_name},
'p|login_password=s' => \$args->{login_password},
'e|email=s' => \$args->{email},
'g|ingroup=s' => \$args->{ingroup},
'd|disabled:i' => \$args->{disabled},
'f|first_name=s' => \$args->{first_name},
'l|last_name=s' => \$args->{last_name},
'start_date=s' => sub {
$args->{start_date} =
$_[1] ? Time::Piece->strptime('%Y-%m-%d', $_[1])->epoch : time;
},
);
# Assume an UTF-8 terminal. TODO: make this more clever
utf8::decode($args->{login_name})
if ($args->{login_name} && !utf8::is_utf8($args->{login_name}));
utf8::decode($args->{first_name})
if ($args->{first_name} && !utf8::is_utf8($args->{first_name}));
utf8::decode($args->{last_name})
if ($args->{last_name} && !utf8::is_utf8($args->{last_name}));
$args->{login_password} = Mojo::Util::sha1_hex($args->{login_name} . $args->{login_password});
unless ($args->{ingroup}) {
say($self->usage)
unless ($args->{first_name}
and $args->{last_name}
and $args->{login_name}
and $args->{email});
}
$self->app->log->debug('$self->args: ' . $self->app->dumper($self->args));
return $ret;
}
lib/Ado/Command/adduser.pm view on Meta::CPAN
USAGE
# On the command line
# Minimal required options to add a user
ado adduser --login_name USERNAME --email user\@example.com \
--first_name John --last_name Smith
# Add a user to an additional group
ado adduser --login_name USERNAME --ingroup GROUPNAME
# Change password / disable a user
ado adduser --login_name USERNAME --login_password N3W$36RE7P1$5W
# Disable a user
ado adduser --login_name USERNAME --disabled
# Programatically
use Ado::Command::adduser;
Ado::Command::adduser->run('--login_name'=>'test1',...);
=head1 DESCRIPTION
lib/Ado/Command/adduser.pm view on Meta::CPAN
my $usage = $a->usage;
$a = $a->usage('Foo!');
Usage information for this command, used for the help screen.
=head1 OPTIONS
On the commandline C<ado adduser> accepts the following options:
'u|login_name=s' #username (mandatory)
'p|login_password=s' #the user password (optional, random is generated)
'e|email=s' #user email (mandatory)
'g|ingroup=s' #existing users can be added to other groups too
'd|disabled:i' #is user disabled? (1 by default)
'f|first_name=s' #user's first name (mandatory)
'l|last_name=s' #user's last name (mandatory)
'start_date=s' #format: %Y-%m-%d (optional, today by default)
=head1 METHODS
L<Ado::Command::adduser> inherits all methods from
lib/Ado/Command/generate/crud.pm view on Meta::CPAN
\@args,
'C|controller_namespace=s' => \$args->{controller_namespace},
#'d|dsn=s' => \$args->{dsn},
'L|lib=s' => \$args->{lib},
'M|model_namespace=s' => \$args->{model_namespace},
#'N|no_dsc_code' => \$args->{no_dsc_code},
'O|overwrite' => \$args->{overwrite},
#'P|password=s' => \$args->{password},
'T|templates_root=s' => \$args->{templates_root},
't|tables=s@' => \$args->{tables},
'H|home_dir=s' => \$args->{home_dir},
#'U|user=s' => \$args->{user},
);
@{$args->{tables}} = split(/\,/, join(',', @{$args->{tables}}));
Carp::croak $self->usage unless scalar @{$args->{tables}};
my $app = $self->app;
lib/Ado/Control.pm view on Meta::CPAN
data => 'validate_input'
}
)
: (output => $v->output)
)
};
}
sub user {
my ($c, $user) = @_;
state $delete_fields = [qw(login_password created_by changed_by disabled start_date email)];
if ($user) {
# Remove as much as possible user data.
delete @{$user->data}{@$delete_fields};
$c->{user} = $user;
return $c;
}
elsif ($c->{user}) {
return $c->{user};
}
lib/Ado/Control.pm view on Meta::CPAN
#400 Bad Request
return $c->render(
status => 400,
json => $result->{json}
) if $result->{errors};
=head2 user
Returns the current user. This is the user C<guest> for not authenticated
users. Note that this instance is not meant for manipulation and some fields
are not available for security reasons. The fields are: C<login_password
created_by changed_by disabled start_date>. TODO: move as much as possible
checks and fields retrieval in SQL, not in Perl.
$c->user(Ado::Model::Users->by_login_name($login_name));
my $names = $c->user->name;
=head1 SEE ALSO
lib/Ado/I18n/bg.pm view on Meta::CPAN
package Ado::I18n::bg;
use Mojo::Base 'Ado::I18n';
use I18N::LangTags::List;
our %Lexicon = ( ##no critic(ProhibitPackageVars)
hello => 'ÐдÑаÑÑи, [_1]!',
Login => 'ÐпиÑване',
Logout => 'ÐзÑ
од',
Help => 'ÐомоÑ',
login_name => 'ÐоÑÑебиÑел',
login_password => 'ÐаÑола',
login_field_error =>
'ÐÐ¾Ð»Ñ Ð²ÑведеÑе валидна ÑÑойноÑÑ Ð·Ð° полеÑо "[_1]"!',
first_name => 'Ðме',
last_name => 'ФамилиÑ',
email => 'Ð-поÑа',
title => 'Ðаглавие/Ðме',
tags => 'ÐÑикеÑи',
time_created => 'Created on',
sorting => 'ÐодÑедба',
data_type => 'Тип',
lib/Ado/I18n/de.pm view on Meta::CPAN
package Ado::I18n::de;
use Mojo::Base 'Ado::I18n';
use I18N::LangTags::List;
our %Lexicon = ( ##no critic(ProhibitPackageVars)
hello => 'Hallo [_1],',
Logout => 'Ausloggen',
login_name => 'Benutzer',
login_password => 'Passwort',
login_field_error => 'Bitte gültigen Wert in Feld "[_1]" eintragen!',
first_name => 'Vorname',
last_name => 'Nachname',
title => 'Titel/Name',
tags => 'Tags',
time_created => 'Angelegt am',
tstamp => 'Verändert am',
body => 'Inhalt (body)',
invisible => 'Versteckt',
language => 'Sprache',
lib/Ado/I18n/en.pm view on Meta::CPAN
package Ado::I18n::en;
use Mojo::Base 'Ado::I18n';
use I18N::LangTags::List;
our %Lexicon = ( ##no critic(ProhibitPackageVars)
hello => 'Hello [_1],',
Logout => 'Sign out',
login_name => 'User',
login_password => 'Password',
login_field_error => 'Please enter a valid value for the field "[_1]"!',
first_name => 'First Name',
last_name => 'Last Name',
title => 'Title/Name',
tags => 'Tags',
time_created => 'Created on',
tstamp => 'Changed on',
body => 'Content (body)',
invisible => 'Invisible',
language => 'Language',
lib/Ado/Model/Users.pm view on Meta::CPAN
use parent qw(Ado::Model);
use Carp;
use Email::Address;
sub is_base_class { return 0 }
my $CLASS = __PACKAGE__;
my $TABLE_NAME = 'users';
sub TABLE { return $TABLE_NAME }
sub PRIMARY_KEY { return 'id' }
my $COLUMNS = [
'id', 'group_id', 'login_name', 'login_password',
'first_name', 'last_name', 'email', 'description',
'created_by', 'changed_by', 'tstamp', 'reg_date',
'disabled', 'start_date', 'stop_date'
];
sub COLUMNS { return $COLUMNS }
my $ALIASES = {};
sub ALIASES { return $ALIASES }
my $CHECKS = {
lib/Ado/Model/Users.pm view on Meta::CPAN
'required' => 1,
'defined' => 1,
'allow' => qr/(?^x:^\d{1,1}$)/,
'default' => '1'
},
'tstamp' => {
'required' => 1,
'defined' => 1,
'allow' => qr/(?^x:^\d{1,10}$)/
},
'login_password' => {
'required' => 1,
'defined' => 1,
#result of Mojo::Util::sha1_hex($login_name.$login_password)
'allow' => qr/^[A-Fa-f0-9]{40}$/x
},
'stop_date' => {'allow' => qr/(?^x:^-?\d{1,}$)/},
'description' => {
'allow' => qr/(?^x:^.{0,255}$)/,
'default' => ''
},
'last_name' => {'allow' => qr/(?^x:^.{1,100}$)/},
'email' => {
'required' => 1,
lib/Ado/Model/Users.pm view on Meta::CPAN
disabled => 0,
description => 'Primary group for user ' . $args->{login_name},
created_by => $args->{created_by} || 1,
);
#Let us create the user now...
$user = $class->create(
first_name => $args->{first_name},
last_name => $args->{last_name},
login_name => $args->{login_name},
login_password => $args->{login_password},
email => $args->{email},
disabled => $args->{disabled},
tstamp => time,
reg_date => time,
created_by => $args->{created_by},
changed_by => $args->{changed_by},
stop_date => $args->{stop_date},
start_date => $args->{start_date},
description => $args->{description},
group_id => $group->id,
lib/Ado/Model/Users.pm view on Meta::CPAN
=head1 COLUMNS
Each column from table C<users> has an accessor method in this class.
=head2 id
=head2 group_id
=head2 login_name
=head2 login_password
=head2 first_name
=head2 last_name
=head2 email
=head2 description
=head2 created_by
lib/Ado/Model/Users.pm view on Meta::CPAN
=head2 add
Given enough parameters creates a new user object and inserts it into the
table C<users>. Creates a primary group for the user with the same group
C<name>. Throws an exception if any of the above fails. Returns (the
eventually newly created) user object.
my $user = Ado::Model::Users->add(
login_name => $login_name,
login_password => Mojo::Util::sha1_hex($login_name.$login_password)
);
=head2 add_to_group
Adds a user with C<login_name> to a group.
Creates the group if it does not already exists.
Returns the group.
$ingroup = $user->add_to_group(ingroup=>'admin');
lib/Ado/Plugin/Auth.pm view on Meta::CPAN
#2. find the user and do logical checks
my $login_name = $val->param('login_name');
my $user = Ado::Model::Users->by_login_name($login_name);
if ((not $user->id) or $user->disabled) {
delete $c->session->{csrf_token};
$c->stash(error_login_name => "No such user '$login_name'!");
return '';
}
#3. really authnticate the user
my $checksum = Mojo::Util::sha1_hex($c->session->{csrf_token} . $user->login_password);
if ($checksum eq $val->param('digest')) {
$c->session(login_name => $user->login_name);
$c->user($user);
$c->app->log->info('$user ' . $user->login_name . ' logged in!');
delete $c->session->{csrf_token};
return 1;
}
$c->debug('We should not be here! - wrong password') if $Ado::Control::DEV_MODE;
delete $c->session->{csrf_token};
return '';
}
#used as helper within login()
# this method is called as return_url after the user
# agrees or denies access for the application
sub _login_google {
my ($c) = @_;
state $app = $c->app;
lib/Ado/Plugin/Auth.pm view on Meta::CPAN
$args{last_name} = $ui->{last_name};
}
#Add another elsif to map different %args to $ui from a new provider
else {
Carp::croak('Unknown provider info_url:' . $provider->{info_url});
}
$args{email} = $ui->{email};
$args{login_name} = $ui->{email};
$args{login_name} =~ s/[\@\.]+//g;
$args{login_password} =
Mojo::Util::sha1_hex($args{login_name} . Ado::Sessions->generate_id());
$args{description} = "Registered via $provider->{info_url}!";
$args{created_by} = $args{changed_by} = 1;
$args{start_date} = $args{disabled} = $args{stop_date} = 0;
return %args;
}
1;
lib/Ado/Plugin/Auth.pm view on Meta::CPAN
=head2 login_ado
Finds and logs in a user locally. Returns true on success, false otherwise.
=head2 login_google
Called via C</login/google>. Finds an existing user and logs it in via Google.
Creates a new user if it does not exist and logs it in via Google. The new
user can login via any supported OAuth2 provider as long as it has the same
email. The user can not login using Ado local authentication because he does
not know his password, which is randomly generated. Returns true on success,
false otherwise.
=head2 login_facebook
Called via C</login/facebook>. Finds an existing user and logs it in via
Facebook. Creates a new user if it does not exist and logs it in via Facebook.
The new user can login via any supported Oauth2 provider as long as it has the
same email. The user can not login using Ado local authentication because he
does not know his password, which is randomly generated. Returns true on
success, false otherwise.
=head1 HOOKS
Ado::Plugin::Auth emits the following hooks.
=head2 after_login
In your plugin you can define some functionality to be executed right after a
user has logged in. For example add some links to the adobar template,
public/js/auth.js view on Meta::CPAN
/* auth.js */
/**
* Generates digest value and adds it to digest field in the login form.
* Removes the password field. It is not sent over HTTP.
* See https://developer.mozilla.org/en-US/docs/Security/InsecurePasswords
*/
function generate_digest () {
var digest = $('#login_form [name="digest"]');
var login_name = $('#login_form [name="login_name"]');
var login_password = $('#login_form [name="login_password"]');
var csrf_token = $('#login_form [name="csrf_token"]');
login_password_sha1 = CryptoJS.SHA1(login_name.val() + login_password.val());
//set digest
digest.val(CryptoJS.SHA1(csrf_token.val() + login_password_sha1));
login_password.remove();
}
jQuery( document ).ready(function( $ ) {
$( "#login_form" ).submit(function( event ) {
generate_digest();
return true;
});
});
t/command/adduser.t view on Meta::CPAN
#TODO: Add user friendly error messages when creating a user.
# and find why sometime with invalid arguments, user gets created
}
#Going deeper
subtest 'Ado::Command::adduser/direct_usage' => \&direct_usage;
sub direct_usage {
isa_ok(my $command = $class->new(), $class);
like((eval { $command->init() }, $@), qr/^\s*?USAGE/, 'init croaks "USAGE..."');
ok($command->init(%$opt, '--login_password' => '--------'), "\$command->init");
is($command->args->{login_password}, Mojo::Util::sha1_hex('test3--------'), 'login_password');
is($command->args->{first_name}, $opt->{'--f'}, 'first_name');
is($command->args->{last_name}, $opt->{'--l'}, 'last_name');
is($command->args->{login_name}, $opt->{'--login_name'}, 'login_name');
is($command->args->{email}, $opt->{'--email'}, 'email');
is($command->args->{ingroup}, $opt->{'-g'}, 'ingroup');
my $out = <<'OO';
User 'test3' was created with primary group 'test3'.
User 'test3' was added to group 'guest'.
OO
stdout_is(sub { $command->adduser() }, $out, 'adduser');
my $user = Ado::Model::Users->by_login_name($opt->{'--login_name'});
$uid = $user->id;
is($user->login_password, Mojo::Util::sha1_hex('test3--------'), '$user->login_password');
is($user->email, $opt->{'--email'}, '$user->email');
my $user_by_email = Ado::Model::Users->by_email($user->email);
is($user_by_email->id, $uid, 'by_email returns the same user as by_login_name');
} #end direct_usage
$app->dbix->query('DELETE FROM user_group WHERE user_id=?', $uid);
$app->dbix->query('DELETE FROM users WHERE id=?', $uid);
$app->dbix->query('DELETE FROM groups WHERE name=?', $opt->{'--login_name'});
done_testing();
t/command/adoplugin-01.t view on Meta::CPAN
# Login after following the Location header (redirect)
$t->get_ok($login_url);
#get the csrf fields
my $form = $t->tx->res->dom->at('#login_form');
my $csrf_token = $form->at('[name="csrf_token"]')->{value};
my $form_hash = {
_method => 'login/ado',
login_name => 'test1',
login_password => '',
csrf_token => $csrf_token,
digest => Mojo::Util::sha1_hex($csrf_token . Mojo::Util::sha1_hex('test1test1')),
};
$t->post_ok($login_url => {} => form => $form_hash)->status_is(302)
->header_is('Location' => '/testatii/create.html', 'redirected back to /testatii/create');
my $create_url = $t->tx->res->headers->header('Location');
#now we can create a new resouce after authenticating
$t->post_ok(
$create_url => {Accept => 'text/html'} => form => {
t/command/crud.t view on Meta::CPAN
);
is_deeply(
$c->args,
{ controller_namespace => $ado->routes->namespaces->[0],
#dsn => undef,
lib => catdir($tempdir, 'lib'),
model_namespace => 'Ado::Model',
#no_dsc_code => undef,
#password => undef,
overwrite => undef,
templates_root => catdir($tempdir, 'site_templates'),
tables => ['testatii'],
home_dir => $tempdir,
#user => undef,
},
'args are ok'
);
is(ref($c->routes), 'ARRAY', '$c->routes ISA ARRAY');
t/plugin/auth-01.t view on Meta::CPAN
->text_like('#authbar .modal form#login_form .ui.header:nth-child(1)' => qr'Sign in')
#user is Guest
->text_is('article.ui.main.container h1' => 'Hello Guest,');
#same form is at /login/:auth_method
my $test_auth_url = $t->ua->server->url->path('/test/authenticateduser');
$t->get_ok('/login/ado', {Referer => $test_auth_url})->status_is(200)
->element_exists('section.ui.login_form form#login_form')
->text_like('form#login_form .ui.header:nth-child(1)' => qr'Sign in')
->element_exists('#login_name')->element_exists('#login_password');
#try unexisting login method
my $help_url = $t->ua->server->url->path('/help');
$t->get_ok('/login/alabala', {Referer => $help_url})->status_is(401)
->element_exists_not('input[name="_method"][checked="checked"]');
$t->post_ok('/login/alabala', {Referer => $help_url})->status_is(401)
->text_like('.ui.error.message' => qr/of the supported login methods/);
#try condition (redirects to /login url)
my $login_url =
t/plugin/auth-01.t view on Meta::CPAN
#login after following the Location header (redirect)
$t->get_ok($login_url);
#get the csrf fields
my $form = $t->tx->res->dom->at('#login_form');
my $csrf_token = $form->at('[name="csrf_token"]')->{value};
my $form_hash = {
_method => 'login/ado',
login_name => 'test1',
login_password => '',
csrf_token => $csrf_token,
digest => Mojo::Util::sha1_hex($csrf_token . Mojo::Util::sha1_hex('test1' . 'wrong_pass')),
};
$t->post_ok($login_url => {} => form => $form_hash)->status_is(401);
#try with wrong (unchanged) csrf token
$t->post_ok($login_url => {DNT => 1} => form => $form_hash)->status_is(403, 'Wrong csrf_token')
; #403 Forbidden
#try with no user passed
t/plugin/auth-02.t view on Meta::CPAN
'/login' => {} => form => {
_method => 'login/ado',
csrf_token => $csrf_token,
}
)->status_is(401 => 'user submitted empty form 401 ok');
my $login_url =
$t->get_ok('/test/authenticateduser')->status_is(302)->header_like('Location' => qr|/login$|)
->tx->res->headers->header('Location');
$t->get_ok('/login/ado');
#try again with the right password this time
my $form = $t->tx->res->dom->at('#login_form');
my $new_csrf_token = $form->at('[name="csrf_token"]')->{value};
ok($new_csrf_token ne $csrf_token, '$new_csrf_token is different');
$t->post_ok(
$login_url => {},
form => {
_method => 'login/ado',
login_name => 'test1',
login_password => '',
csrf_token => $new_csrf_token,
digest => Mojo::Util::sha1_hex($new_csrf_token . Mojo::Util::sha1_hex('test1' . 'test1')),
}
#redirect back to the $c->session('over_route')
)->status_is(302)->header_is('Location' => '/test/authenticateduser');
# after authentication
$t->get_ok('/test/authenticateduser')->status_is(200)
templates/partials/login_form.html.ep view on Meta::CPAN
<i class="user icon"></i>
<div class="ui corner label"><i class="icon asterisk"></i></div>
% if(stash->{error_login_name}) {
<div id="error_login_name" class="ui error message" style="display:block">
<%= stash->{error_login_name} %>
</div>
% }
</div>
</div>
<div class="field">
<label for="login_password"><%=l('login_password')%></label>
<div class="ui left labeled icon input">
<input type="password" name="login_password" id="login_password" required="" />
<i class="lock icon"></i>
<div class="ui corner label"><i class="icon asterisk"></i></div>
</div>
</div>
%= csrf_field
%= hidden_field 'digest'
<div class="ui center">
<button class="ui small green submit button"
type="submit"><%=l('Login')%></button>
</div>