Use Data::Entropy for random numbers
[plack-middleware-auth-complex.git] / lib / Plack / Middleware / Auth / Complex.pm
index 4436e5a81321c59b827140f55e22f2efe84ac677..59d9bc3b8df46972f6fb2d59c5afc081e09fc880 100644 (file)
@@ -4,15 +4,17 @@ use 5.014000;
 use strict;
 use warnings;
 
-our $VERSION = '0.001';
+our $VERSION = '0.002';
 
 use parent qw/Plack::Middleware/;
 use re '/s';
 
 use Authen::Passphrase;
 use Authen::Passphrase::BlowfishCrypt;
-use Bytes::Random::Secure qw/random_bytes/;
-use Carp qw/croak/;
+use Data::Entropy qw/entropy_source/;
+use Data::Entropy::Source;
+use Data::Entropy::RawSource::Local;
+use Carp qw/carp croak/;
 use DBI;
 use Digest::SHA qw/hmac_sha1_base64 sha256/;
 use Email::Simple;
@@ -21,6 +23,18 @@ use MIME::Base64 qw/decode_base64/;
 use Plack::Request;
 use Tie::Hash::Expire;
 
+sub make_entropy_source {
+       if (-e '/dev/urandom') {
+               Data::Entropy::Source->new(
+                       Data::Entropy::RawSource::Local->new('/dev/urandom'),
+                       'sysread'
+               )
+       } else {
+               carp "/dev/urandom not found, using insecure random source\n";
+               entropy_source
+       }
+}
+
 sub default_opts {(
        dbi_connect       => ['dbi:Pg:', '', ''],
        select_user       => 'SELECT passphrase, email FROM users WHERE id = ?',
@@ -32,6 +46,7 @@ sub default_opts {(
        cache_max_age     => 5 * 60,
        token_max_age     => 60 * 60,
        username_regex    => qr/^\w{2,20}$/as,
+       invalid_username  => 'Invalid username',
        register_url      => '/action/register',
        passwd_url        => '/action/passwd',
        request_reset_url => '/action/request-reset',
@@ -42,8 +57,8 @@ sub new {
        my ($class, $opts) = @_;
        my %self = $class->default_opts;
        %self = (%self, %$opts);
+       $self{entropy_source} //= make_entropy_source;
        my $self = bless \%self, $class;
-       $self->init;
        $self
 }
 
@@ -100,7 +115,7 @@ sub set_passphrase {
 
 sub make_reset_hmac {
        my ($self, $username, @data) = @_;
-       $self->{hmackey} //= random_bytes 512; # uncoverable condition false
+       $self->{hmackey} //= $self->{entropy_source}->get_bits(8 * 512); # uncoverable condition false
        my $user = $self->get_user($username);
        my $message = join ' ', $username, $user->{passphrase}, @data;
        hmac_sha1_base64 $message, $self->{hmackey};
@@ -177,7 +192,7 @@ sub call_register {
                return $self->bad_request("Missing parameter $_") unless $parms{$_};
        }
 
-       return $self->bad_request('Username must match ' . $self->{username_regex}) unless $parms{username} =~ $self->{username_regex};
+       return $self->bad_request($self->{invalid_username}) unless $parms{username} =~ $self->{username_regex};
        return $self->bad_request('Username already in use') if $self->get_user($parms{username});
        return $self->bad_request('The two passwords do not match') unless $parms{password} eq $parms{confirm_password};
 
@@ -232,6 +247,12 @@ sub call_reset {
 
 sub call {
        my ($self, $env) = @_;
+
+       unless ($self->{init_done}) {
+               $self->init;
+               $self->{init_done} = 1;
+       }
+
        my $auth = $env->{HTTP_AUTHORIZATION};
        if ($auth && $auth =~ /^Basic (.*)$/i) {
                my ($user, $pass) = split /:/, decode_base64($1), 2;
@@ -326,6 +347,14 @@ This URL performs a password reset.
 Arrayref of arguments to pass to DBI->connect. Defaults to
 C<['dbi:Pg', '', '']>.
 
+=item entropy_source
+
+C<Data::Entropy::Source> object to get random numbers from. By default
+uses F</dev/urandom> via C<Data::Entropy::RawSource::Local> if
+possible, or the default entropy source otherwise. A warning is
+printed if the default entropy source is used, to supress it set this
+argument to the default entropy source.
+
 =item post_connect_cb
 
 Callback (coderef) that is called just after connecting to the
@@ -386,6 +415,11 @@ Password reset token validity, in seconds. Defaults to 1 hour.
 Regular expression that matches valid usernames. Defaults to
 C<qr/^\w{2,20}$/as>.
 
+=item invalid_username
+
+Error message returned when the username does not match
+username_regex. Defaults to C<'Invalid username'>
+
 =item register_url
 
 URL for registering. Defaults to C<'/action/register'>.
@@ -420,7 +454,7 @@ Creates a new AuthComplex object.
 
 =item B<init>
 
-Called at the end of the constructor. The default implementation
+Called when the first request is received. The default implementation
 connects to the database, calls C<post_connect_cb> and prepares the
 SQL statements.
 
@@ -486,19 +520,19 @@ Returns a 401 Authorization required response.
 
 =item B<call_register>(I<$req>)
 
-Handles the C</register> endpoint. I<$req> is a Plack::Request object.
+Handles the C</action/register> endpoint. I<$req> is a Plack::Request object.
 
 =item B<call_passwd>(I<$req>)
 
-Handles the C</passwd> endpoint. I<$req> is a Plack::Request object.
+Handles the C</action/passwd> endpoint. I<$req> is a Plack::Request object.
 
 =item B<call_request_reset>(I<$req>)
 
-Handles the C</request-reset> endpoint. I<$req> is a Plack::Request object.
+Handles the C</action/request-reset> endpoint. I<$req> is a Plack::Request object.
 
 =item B<call_reset>(I<$req>)
 
-Handles the C</reset> endpoint. I<$req> is a Plack::Request object.
+Handles the C</action/reset> endpoint. I<$req> is a Plack::Request object.
 
 =back
 
@@ -508,7 +542,7 @@ Marius Gavrilescu, E<lt>marius@ieval.roE<gt>
 
 =head1 COPYRIGHT AND LICENSE
 
-Copyright (C) 2015 by Marius Gavrilescu
+Copyright (C) 2015-2017 by Marius Gavrilescu
 
 This library is free software; you can redistribute it and/or modify
 it under the same terms as Perl itself, either Perl version 5.20.1 or,
This page took 0.01348 seconds and 4 git commands to generate.