release 2.023000
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
2.023000 -
|
2.023000 - 2014-02-10
|
||||||
|
|
||||||
[NEW FEATURES]
|
[NEW FEATURES]
|
||||||
|
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ lib/App/Netdisco.pm
|
|||||||
lib/App/Netdisco/Core/Arpnip.pm
|
lib/App/Netdisco/Core/Arpnip.pm
|
||||||
lib/App/Netdisco/Core/Discover.pm
|
lib/App/Netdisco/Core/Discover.pm
|
||||||
lib/App/Netdisco/Core/Macsuck.pm
|
lib/App/Netdisco/Core/Macsuck.pm
|
||||||
|
lib/App/Netdisco/Core/Nbtstat.pm
|
||||||
lib/App/Netdisco/Daemon/DB.pm
|
lib/App/Netdisco/Daemon/DB.pm
|
||||||
lib/App/Netdisco/Daemon/DB/Result/Admin.pm
|
lib/App/Netdisco/Daemon/DB/Result/Admin.pm
|
||||||
lib/App/Netdisco/Daemon/Queue.pm
|
lib/App/Netdisco/Daemon/Queue.pm
|
||||||
@@ -37,6 +38,7 @@ lib/App/Netdisco/Daemon/Worker/Poller/Common.pm
|
|||||||
lib/App/Netdisco/Daemon/Worker/Poller/Device.pm
|
lib/App/Netdisco/Daemon/Worker/Poller/Device.pm
|
||||||
lib/App/Netdisco/Daemon/Worker/Poller/Expiry.pm
|
lib/App/Netdisco/Daemon/Worker/Poller/Expiry.pm
|
||||||
lib/App/Netdisco/Daemon/Worker/Poller/Macsuck.pm
|
lib/App/Netdisco/Daemon/Worker/Poller/Macsuck.pm
|
||||||
|
lib/App/Netdisco/Daemon/Worker/Poller/Nbtstat.pm
|
||||||
lib/App/Netdisco/Daemon/Worker/Scheduler.pm
|
lib/App/Netdisco/Daemon/Worker/Scheduler.pm
|
||||||
lib/App/Netdisco/DB.pm
|
lib/App/Netdisco/DB.pm
|
||||||
lib/App/Netdisco/DB/ExplicitLocking.pm
|
lib/App/Netdisco/DB/ExplicitLocking.pm
|
||||||
@@ -71,6 +73,7 @@ lib/App/Netdisco/DB/Result/Virtual/ActiveNode.pm
|
|||||||
lib/App/Netdisco/DB/Result/Virtual/ActiveNodeWithAge.pm
|
lib/App/Netdisco/DB/Result/Virtual/ActiveNodeWithAge.pm
|
||||||
lib/App/Netdisco/DB/Result/Virtual/ApRadioChannelPower.pm
|
lib/App/Netdisco/DB/Result/Virtual/ApRadioChannelPower.pm
|
||||||
lib/App/Netdisco/DB/Result/Virtual/CidrIps.pm
|
lib/App/Netdisco/DB/Result/Virtual/CidrIps.pm
|
||||||
|
lib/App/Netdisco/DB/Result/Virtual/DeviceDnsMismatch.pm
|
||||||
lib/App/Netdisco/DB/Result/Virtual/DeviceLinks.pm
|
lib/App/Netdisco/DB/Result/Virtual/DeviceLinks.pm
|
||||||
lib/App/Netdisco/DB/Result/Virtual/DuplexMismatch.pm
|
lib/App/Netdisco/DB/Result/Virtual/DuplexMismatch.pm
|
||||||
lib/App/Netdisco/DB/Result/Virtual/NodeWithAge.pm
|
lib/App/Netdisco/DB/Result/Virtual/NodeWithAge.pm
|
||||||
@@ -89,10 +92,10 @@ lib/App/Netdisco/DB/ResultSet/Device.pm
|
|||||||
lib/App/Netdisco/DB/ResultSet/DevicePort.pm
|
lib/App/Netdisco/DB/ResultSet/DevicePort.pm
|
||||||
lib/App/Netdisco/DB/ResultSet/Node.pm
|
lib/App/Netdisco/DB/ResultSet/Node.pm
|
||||||
lib/App/Netdisco/DB/ResultSet/NodeIp.pm
|
lib/App/Netdisco/DB/ResultSet/NodeIp.pm
|
||||||
|
lib/App/Netdisco/DB/ResultSet/NodeNbt.pm
|
||||||
lib/App/Netdisco/DB/ResultSet/NodeWireless.pm
|
lib/App/Netdisco/DB/ResultSet/NodeWireless.pm
|
||||||
lib/App/Netdisco/DB/ResultSet/Subnet.pm
|
lib/App/Netdisco/DB/ResultSet/Subnet.pm
|
||||||
lib/App/Netdisco/DB/schema_versions/App-Netdisco-DB-1-2-PostgreSQL.sql
|
lib/App/Netdisco/DB/schema_versions/App-Netdisco-DB-1-2-PostgreSQL.sql
|
||||||
lib/App/Netdisco/DB/schema_versions/App-Netdisco-DB-1-PostgreSQL.sql
|
|
||||||
lib/App/Netdisco/DB/schema_versions/App-Netdisco-DB-10-11-PostgreSQL.sql
|
lib/App/Netdisco/DB/schema_versions/App-Netdisco-DB-10-11-PostgreSQL.sql
|
||||||
lib/App/Netdisco/DB/schema_versions/App-Netdisco-DB-11-12-PostgreSQL.sql
|
lib/App/Netdisco/DB/schema_versions/App-Netdisco-DB-11-12-PostgreSQL.sql
|
||||||
lib/App/Netdisco/DB/schema_versions/App-Netdisco-DB-12-13-PostgreSQL.sql
|
lib/App/Netdisco/DB/schema_versions/App-Netdisco-DB-12-13-PostgreSQL.sql
|
||||||
@@ -100,13 +103,10 @@ lib/App/Netdisco/DB/schema_versions/App-Netdisco-DB-13-14-PostgreSQL.sql
|
|||||||
lib/App/Netdisco/DB/schema_versions/App-Netdisco-DB-14-15-PostgreSQL.sql
|
lib/App/Netdisco/DB/schema_versions/App-Netdisco-DB-14-15-PostgreSQL.sql
|
||||||
lib/App/Netdisco/DB/schema_versions/App-Netdisco-DB-15-16-PostgreSQL.sql
|
lib/App/Netdisco/DB/schema_versions/App-Netdisco-DB-15-16-PostgreSQL.sql
|
||||||
lib/App/Netdisco/DB/schema_versions/App-Netdisco-DB-16-17-PostgreSQL.sql
|
lib/App/Netdisco/DB/schema_versions/App-Netdisco-DB-16-17-PostgreSQL.sql
|
||||||
lib/App/Netdisco/DB/schema_versions/App-Netdisco-DB-16-PostgreSQL.sql
|
|
||||||
lib/App/Netdisco/DB/schema_versions/App-Netdisco-DB-17-18-PostgreSQL.sql
|
lib/App/Netdisco/DB/schema_versions/App-Netdisco-DB-17-18-PostgreSQL.sql
|
||||||
lib/App/Netdisco/DB/schema_versions/App-Netdisco-DB-17-PostgreSQL.sql
|
|
||||||
lib/App/Netdisco/DB/schema_versions/App-Netdisco-DB-18-19-PostgreSQL.sql
|
lib/App/Netdisco/DB/schema_versions/App-Netdisco-DB-18-19-PostgreSQL.sql
|
||||||
lib/App/Netdisco/DB/schema_versions/App-Netdisco-DB-19-20-PostgreSQL.sql
|
lib/App/Netdisco/DB/schema_versions/App-Netdisco-DB-19-20-PostgreSQL.sql
|
||||||
lib/App/Netdisco/DB/schema_versions/App-Netdisco-DB-2-3-PostgreSQL.sql
|
lib/App/Netdisco/DB/schema_versions/App-Netdisco-DB-2-3-PostgreSQL.sql
|
||||||
lib/App/Netdisco/DB/schema_versions/App-Netdisco-DB-2-PostgreSQL.sql
|
|
||||||
lib/App/Netdisco/DB/schema_versions/App-Netdisco-DB-20-21-PostgreSQL.sql
|
lib/App/Netdisco/DB/schema_versions/App-Netdisco-DB-20-21-PostgreSQL.sql
|
||||||
lib/App/Netdisco/DB/schema_versions/App-Netdisco-DB-21-22-PostgreSQL.sql
|
lib/App/Netdisco/DB/schema_versions/App-Netdisco-DB-21-22-PostgreSQL.sql
|
||||||
lib/App/Netdisco/DB/schema_versions/App-Netdisco-DB-22-23-PostgreSQL.sql
|
lib/App/Netdisco/DB/schema_versions/App-Netdisco-DB-22-23-PostgreSQL.sql
|
||||||
@@ -122,12 +122,15 @@ lib/App/Netdisco/DB/schema_versions/App-Netdisco-DB-30-31-PostgreSQL.sql
|
|||||||
lib/App/Netdisco/DB/schema_versions/App-Netdisco-DB-31-32-PostgreSQL.sql
|
lib/App/Netdisco/DB/schema_versions/App-Netdisco-DB-31-32-PostgreSQL.sql
|
||||||
lib/App/Netdisco/DB/schema_versions/App-Netdisco-DB-32-33-PostgreSQL.sql
|
lib/App/Netdisco/DB/schema_versions/App-Netdisco-DB-32-33-PostgreSQL.sql
|
||||||
lib/App/Netdisco/DB/schema_versions/App-Netdisco-DB-33-34-PostgreSQL.sql
|
lib/App/Netdisco/DB/schema_versions/App-Netdisco-DB-33-34-PostgreSQL.sql
|
||||||
|
lib/App/Netdisco/DB/schema_versions/App-Netdisco-DB-34-35-PostgreSQL.sql
|
||||||
|
lib/App/Netdisco/DB/schema_versions/App-Netdisco-DB-35-36-PostgreSQL.sql
|
||||||
|
lib/App/Netdisco/DB/schema_versions/App-Netdisco-DB-36-37-PostgreSQL.sql
|
||||||
|
lib/App/Netdisco/DB/schema_versions/App-Netdisco-DB-37-38-PostgreSQL.sql
|
||||||
lib/App/Netdisco/DB/schema_versions/App-Netdisco-DB-4-5-PostgreSQL.sql
|
lib/App/Netdisco/DB/schema_versions/App-Netdisco-DB-4-5-PostgreSQL.sql
|
||||||
lib/App/Netdisco/DB/schema_versions/App-Netdisco-DB-5-6-PostgreSQL.sql
|
lib/App/Netdisco/DB/schema_versions/App-Netdisco-DB-5-6-PostgreSQL.sql
|
||||||
lib/App/Netdisco/DB/schema_versions/App-Netdisco-DB-6-7-PostgreSQL.sql
|
lib/App/Netdisco/DB/schema_versions/App-Netdisco-DB-6-7-PostgreSQL.sql
|
||||||
lib/App/Netdisco/DB/schema_versions/App-Netdisco-DB-7-8-PostgreSQL.sql
|
lib/App/Netdisco/DB/schema_versions/App-Netdisco-DB-7-8-PostgreSQL.sql
|
||||||
lib/App/Netdisco/DB/schema_versions/App-Netdisco-DB-8-9-PostgreSQL.sql
|
lib/App/Netdisco/DB/schema_versions/App-Netdisco-DB-8-9-PostgreSQL.sql
|
||||||
lib/App/Netdisco/DB/schema_versions/App-Netdisco-DB-8-PostgreSQL.sql
|
|
||||||
lib/App/Netdisco/DB/schema_versions/App-Netdisco-DB-9-10-PostgreSQL.sql
|
lib/App/Netdisco/DB/schema_versions/App-Netdisco-DB-9-10-PostgreSQL.sql
|
||||||
lib/App/Netdisco/Manual/Configuration.pod
|
lib/App/Netdisco/Manual/Configuration.pod
|
||||||
lib/App/Netdisco/Manual/Deployment.pod
|
lib/App/Netdisco/Manual/Deployment.pod
|
||||||
@@ -136,6 +139,7 @@ lib/App/Netdisco/Manual/ReleaseNotes.pod
|
|||||||
lib/App/Netdisco/Manual/WritingPlugins.pod
|
lib/App/Netdisco/Manual/WritingPlugins.pod
|
||||||
lib/App/Netdisco/Util/Device.pm
|
lib/App/Netdisco/Util/Device.pm
|
||||||
lib/App/Netdisco/Util/DNS.pm
|
lib/App/Netdisco/Util/DNS.pm
|
||||||
|
lib/App/Netdisco/Util/Node.pm
|
||||||
lib/App/Netdisco/Util/Noop.pm
|
lib/App/Netdisco/Util/Noop.pm
|
||||||
lib/App/Netdisco/Util/Port.pm
|
lib/App/Netdisco/Util/Port.pm
|
||||||
lib/App/Netdisco/Util/PortMAC.pm
|
lib/App/Netdisco/Util/PortMAC.pm
|
||||||
@@ -170,10 +174,12 @@ lib/App/Netdisco/Web/Plugin/Report/ApClients.pm
|
|||||||
lib/App/Netdisco/Web/Plugin/Report/ApRadioChannelPower.pm
|
lib/App/Netdisco/Web/Plugin/Report/ApRadioChannelPower.pm
|
||||||
lib/App/Netdisco/Web/Plugin/Report/DeviceAddrNoDNS.pm
|
lib/App/Netdisco/Web/Plugin/Report/DeviceAddrNoDNS.pm
|
||||||
lib/App/Netdisco/Web/Plugin/Report/DeviceByLocation.pm
|
lib/App/Netdisco/Web/Plugin/Report/DeviceByLocation.pm
|
||||||
|
lib/App/Netdisco/Web/Plugin/Report/DeviceDnsMismatch.pm
|
||||||
lib/App/Netdisco/Web/Plugin/Report/DevicePoeStatus.pm
|
lib/App/Netdisco/Web/Plugin/Report/DevicePoeStatus.pm
|
||||||
lib/App/Netdisco/Web/Plugin/Report/DuplexMismatch.pm
|
lib/App/Netdisco/Web/Plugin/Report/DuplexMismatch.pm
|
||||||
lib/App/Netdisco/Web/Plugin/Report/HalfDuplex.pm
|
lib/App/Netdisco/Web/Plugin/Report/HalfDuplex.pm
|
||||||
lib/App/Netdisco/Web/Plugin/Report/IpInventory.pm
|
lib/App/Netdisco/Web/Plugin/Report/IpInventory.pm
|
||||||
|
lib/App/Netdisco/Web/Plugin/Report/Netbios.pm
|
||||||
lib/App/Netdisco/Web/Plugin/Report/NodeMultiIPs.pm
|
lib/App/Netdisco/Web/Plugin/Report/NodeMultiIPs.pm
|
||||||
lib/App/Netdisco/Web/Plugin/Report/PhonesDiscovered.pm
|
lib/App/Netdisco/Web/Plugin/Report/PhonesDiscovered.pm
|
||||||
lib/App/Netdisco/Web/Plugin/Report/PortAdminDown.pm
|
lib/App/Netdisco/Web/Plugin/Report/PortAdminDown.pm
|
||||||
@@ -241,6 +247,7 @@ share/public/images/tango_sweep.png
|
|||||||
share/public/images/vaga_copy.png
|
share/public/images/vaga_copy.png
|
||||||
share/public/javascripts/bootstrap.min.js
|
share/public/javascripts/bootstrap.min.js
|
||||||
share/public/javascripts/d3.min.js
|
share/public/javascripts/d3.min.js
|
||||||
|
share/public/javascripts/daterangepicker.js
|
||||||
share/public/javascripts/jquery-deserialize.js
|
share/public/javascripts/jquery-deserialize.js
|
||||||
share/public/javascripts/jquery-history.js
|
share/public/javascripts/jquery-history.js
|
||||||
share/public/javascripts/jquery-latest.min.js
|
share/public/javascripts/jquery-latest.min.js
|
||||||
@@ -248,6 +255,7 @@ share/public/javascripts/jquery-ui.custom.min.js
|
|||||||
share/public/javascripts/jquery.cookie.js
|
share/public/javascripts/jquery.cookie.js
|
||||||
share/public/javascripts/jquery.floatThead.js
|
share/public/javascripts/jquery.floatThead.js
|
||||||
share/public/javascripts/jquery.qtip.min.js
|
share/public/javascripts/jquery.qtip.min.js
|
||||||
|
share/public/javascripts/moment.min.js
|
||||||
share/public/javascripts/netdisco.js
|
share/public/javascripts/netdisco.js
|
||||||
share/public/javascripts/netdisco_portcontrol.js
|
share/public/javascripts/netdisco_portcontrol.js
|
||||||
share/public/javascripts/toastr.js
|
share/public/javascripts/toastr.js
|
||||||
@@ -281,6 +289,8 @@ share/views/ajax/report/deviceaddrnodns.tt
|
|||||||
share/views/ajax/report/deviceaddrnodns_csv.tt
|
share/views/ajax/report/deviceaddrnodns_csv.tt
|
||||||
share/views/ajax/report/devicebylocation.tt
|
share/views/ajax/report/devicebylocation.tt
|
||||||
share/views/ajax/report/devicebylocation_csv.tt
|
share/views/ajax/report/devicebylocation_csv.tt
|
||||||
|
share/views/ajax/report/devicednsmismatch.tt
|
||||||
|
share/views/ajax/report/devicednsmismatch_csv.tt
|
||||||
share/views/ajax/report/devicepoestatus.tt
|
share/views/ajax/report/devicepoestatus.tt
|
||||||
share/views/ajax/report/devicepoestatus_csv.tt
|
share/views/ajax/report/devicepoestatus_csv.tt
|
||||||
share/views/ajax/report/duplexmismatch.tt
|
share/views/ajax/report/duplexmismatch.tt
|
||||||
@@ -289,6 +299,7 @@ share/views/ajax/report/halfduplex.tt
|
|||||||
share/views/ajax/report/halfduplex_csv.tt
|
share/views/ajax/report/halfduplex_csv.tt
|
||||||
share/views/ajax/report/ipinventory.tt
|
share/views/ajax/report/ipinventory.tt
|
||||||
share/views/ajax/report/ipinventory_csv.tt
|
share/views/ajax/report/ipinventory_csv.tt
|
||||||
|
share/views/ajax/report/netbios.tt
|
||||||
share/views/ajax/report/nodemultiips.tt
|
share/views/ajax/report/nodemultiips.tt
|
||||||
share/views/ajax/report/nodemultiips_csv.tt
|
share/views/ajax/report/nodemultiips_csv.tt
|
||||||
share/views/ajax/report/phonesdiscovered.tt
|
share/views/ajax/report/phonesdiscovered.tt
|
||||||
@@ -321,9 +332,7 @@ share/views/inventory.tt
|
|||||||
share/views/js/admintask.js
|
share/views/js/admintask.js
|
||||||
share/views/js/bootstrap-tree.js
|
share/views/js/bootstrap-tree.js
|
||||||
share/views/js/common.js
|
share/views/js/common.js
|
||||||
share/views/js/daterangepicker.js
|
|
||||||
share/views/js/device.js
|
share/views/js/device.js
|
||||||
share/views/js/moment.min.js
|
|
||||||
share/views/js/report.js
|
share/views/js/report.js
|
||||||
share/views/js/search.js
|
share/views/js/search.js
|
||||||
share/views/layouts/main.tt
|
share/views/layouts/main.tt
|
||||||
@@ -334,6 +343,7 @@ share/views/sidebar/admintask/portlog.tt
|
|||||||
share/views/sidebar/device/netmap.tt
|
share/views/sidebar/device/netmap.tt
|
||||||
share/views/sidebar/device/ports.tt
|
share/views/sidebar/device/ports.tt
|
||||||
share/views/sidebar/report/ipinventory.tt
|
share/views/sidebar/report/ipinventory.tt
|
||||||
|
share/views/sidebar/report/netbios.tt
|
||||||
share/views/sidebar/report/subnets.tt
|
share/views/sidebar/report/subnets.tt
|
||||||
share/views/sidebar/search/device.tt
|
share/views/sidebar/search/device.tt
|
||||||
share/views/sidebar/search/node.tt
|
share/views/sidebar/search/node.tt
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ requires:
|
|||||||
Dancer: 1.3112
|
Dancer: 1.3112
|
||||||
Dancer::Plugin::Auth::Extensible: 0.3
|
Dancer::Plugin::Auth::Extensible: 0.3
|
||||||
Dancer::Plugin::DBIC: 0.1803
|
Dancer::Plugin::DBIC: 0.1803
|
||||||
Dancer::Plugin::Passphrase: 2
|
Dancer::Plugin::Passphrase: 2.0.1
|
||||||
File::ShareDir: 1.03
|
File::ShareDir: 1.03
|
||||||
Guard: 1.022
|
Guard: 1.022
|
||||||
HTML::Parser: 3.7
|
HTML::Parser: 3.7
|
||||||
@@ -53,7 +53,7 @@ requires:
|
|||||||
Plack: 1.0023
|
Plack: 1.0023
|
||||||
Plack::Middleware::Expires: 0.03
|
Plack::Middleware::Expires: 0.03
|
||||||
Role::Tiny: 1.002005
|
Role::Tiny: 1.002005
|
||||||
SNMP::Info: 3.11
|
SNMP::Info: 3.12
|
||||||
SQL::Translator: 0.11016
|
SQL::Translator: 0.11016
|
||||||
Socket6: 0.23
|
Socket6: 0.23
|
||||||
Starman: 0.4008
|
Starman: 0.4008
|
||||||
@@ -74,4 +74,4 @@ resources:
|
|||||||
homepage: http://netdisco.org/
|
homepage: http://netdisco.org/
|
||||||
license: http://opensource.org/licenses/bsd-license.php
|
license: http://opensource.org/licenses/bsd-license.php
|
||||||
repository: git://git.code.sf.net/p/netdisco/netdisco-ng
|
repository: git://git.code.sf.net/p/netdisco/netdisco-ng
|
||||||
version: 2.022000
|
version: 2.023000
|
||||||
|
|||||||
@@ -18,9 +18,7 @@ requires 'Daemon::Control' => 0.001000;
|
|||||||
requires 'Dancer' => 1.3112;
|
requires 'Dancer' => 1.3112;
|
||||||
requires 'Dancer::Plugin::DBIC' => 0.1803;
|
requires 'Dancer::Plugin::DBIC' => 0.1803;
|
||||||
requires 'Dancer::Plugin::Auth::Extensible' => 0.30;
|
requires 'Dancer::Plugin::Auth::Extensible' => 0.30;
|
||||||
requires 'Data::Entropy::Algorithms' => 0.007;
|
requires 'Dancer::Plugin::Passphrase' => '2.0.1';
|
||||||
requires 'Digest::Bcrypt' => 1.0.1;
|
|
||||||
requires 'Digest::SHA' => 5.86;
|
|
||||||
requires 'File::ShareDir' => 1.03;
|
requires 'File::ShareDir' => 1.03;
|
||||||
requires 'Guard' => 1.022;
|
requires 'Guard' => 1.022;
|
||||||
requires 'HTML::Parser' => 3.70;
|
requires 'HTML::Parser' => 3.70;
|
||||||
@@ -41,7 +39,7 @@ requires 'Plack::Middleware::Expires' => 0.03;
|
|||||||
requires 'Role::Tiny' => 1.002005;
|
requires 'Role::Tiny' => 1.002005;
|
||||||
requires 'Socket6' => 0.23;
|
requires 'Socket6' => 0.23;
|
||||||
requires 'Starman' => 0.4008;
|
requires 'Starman' => 0.4008;
|
||||||
requires 'SNMP::Info' => 3.11;
|
requires 'SNMP::Info' => 3.12;
|
||||||
requires 'SQL::Translator' => 0.11016;
|
requires 'SQL::Translator' => 0.11016;
|
||||||
requires 'Template' => 2.24;
|
requires 'Template' => 2.24;
|
||||||
requires 'Template::Plugin::CSV' => 0.04;
|
requires 'Template::Plugin::CSV' => 0.04;
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ BEGIN {
|
|||||||
use App::Netdisco;
|
use App::Netdisco;
|
||||||
use Dancer ':script';
|
use Dancer ':script';
|
||||||
use Dancer::Plugin::DBIC 'schema';
|
use Dancer::Plugin::DBIC 'schema';
|
||||||
use App::Netdisco::Web::Plugin::Passphrase;
|
use Dancer::Plugin::Passphrase;
|
||||||
|
|
||||||
info "App::Netdisco version $App::Netdisco::VERSION loaded.";
|
info "App::Netdisco version $App::Netdisco::VERSION loaded.";
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ use 5.010_000;
|
|||||||
use File::ShareDir 'dist_dir';
|
use File::ShareDir 'dist_dir';
|
||||||
use Path::Class;
|
use Path::Class;
|
||||||
|
|
||||||
our $VERSION = '2.022000';
|
our $VERSION = '2.023000';
|
||||||
|
|
||||||
BEGIN {
|
BEGIN {
|
||||||
if (not ($ENV{DANCER_APPDIR} || '')
|
if (not ($ENV{DANCER_APPDIR} || '')
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ use base 'Dancer::Plugin::Auth::Extensible::Provider::Base';
|
|||||||
|
|
||||||
use Dancer ':syntax';
|
use Dancer ':syntax';
|
||||||
use Dancer::Plugin::DBIC;
|
use Dancer::Plugin::DBIC;
|
||||||
use App::Netdisco::Web::Plugin::Passphrase;
|
use Dancer::Plugin::Passphrase;
|
||||||
use Digest::MD5;
|
use Digest::MD5;
|
||||||
|
|
||||||
sub authenticate_user {
|
sub authenticate_user {
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ use Dancer ':syntax';
|
|||||||
use Dancer::Plugin::Ajax;
|
use Dancer::Plugin::Ajax;
|
||||||
use Dancer::Plugin::DBIC;
|
use Dancer::Plugin::DBIC;
|
||||||
use Dancer::Plugin::Auth::Extensible;
|
use Dancer::Plugin::Auth::Extensible;
|
||||||
use App::Netdisco::Web::Plugin::Passphrase;
|
use Dancer::Plugin::Passphrase;
|
||||||
|
|
||||||
use App::Netdisco::Web::Plugin;
|
use App::Netdisco::Web::Plugin;
|
||||||
use Digest::MD5 ();
|
use Digest::MD5 ();
|
||||||
|
|||||||
@@ -1,753 +0,0 @@
|
|||||||
package App::Netdisco::Web::Plugin::Passphrase;
|
|
||||||
|
|
||||||
# ABSTRACT: Passphrases and Passwords as objects for Dancer
|
|
||||||
|
|
||||||
=head1 NAME
|
|
||||||
|
|
||||||
Dancer::Plugin::Passphrase - Passphrases and Passwords as objects for Dancer
|
|
||||||
|
|
||||||
=head1 SYNOPSIS
|
|
||||||
|
|
||||||
This plugin manages the hashing of passwords for Dancer apps, allowing
|
|
||||||
developers to follow cryptography best practices without having to
|
|
||||||
become a cryptography expert.
|
|
||||||
|
|
||||||
It uses the bcrypt algorithm as the default, while also supporting any
|
|
||||||
hashing function provided by L<Digest>
|
|
||||||
|
|
||||||
=head1 USAGE
|
|
||||||
|
|
||||||
package MyWebService;
|
|
||||||
use Dancer ':syntax';
|
|
||||||
use Dancer::Plugin::Passphrase;
|
|
||||||
|
|
||||||
post '/login' => sub {
|
|
||||||
my $phrase = passphrase( param('my password') )->generate;
|
|
||||||
|
|
||||||
# $phrase is now an object that contains RFC 2307 representation
|
|
||||||
# of the hashed passphrase, along with the salt, and other metadata
|
|
||||||
|
|
||||||
# You should store $phrase->rfc2307() for use later
|
|
||||||
};
|
|
||||||
|
|
||||||
get '/protected' => sub {
|
|
||||||
# Retrieve $stored_rfc_2307_string, like we created above.
|
|
||||||
# IT MUST be a valid RFC 2307 string
|
|
||||||
|
|
||||||
if ( passphrase( param('my password') )->matches( $stored_rfc_2307 ) ) {
|
|
||||||
# Passphrase matches!
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
get '/generate_new_password' => sub {
|
|
||||||
return passphrase->generate_random;
|
|
||||||
};
|
|
||||||
|
|
||||||
=cut
|
|
||||||
|
|
||||||
use strict;
|
|
||||||
use feature 'switch';
|
|
||||||
|
|
||||||
use Dancer::Plugin;
|
|
||||||
|
|
||||||
use Carp qw(carp croak);
|
|
||||||
use Data::Entropy::Algorithms qw(rand_bits rand_int);
|
|
||||||
use Digest;
|
|
||||||
use MIME::Base64 qw(decode_base64 encode_base64);
|
|
||||||
use Scalar::Util qw(blessed);
|
|
||||||
|
|
||||||
our $VERSION = '2.0.0';
|
|
||||||
|
|
||||||
# Auto stringifies and returns the RFC 2307 representation
|
|
||||||
# of the object unless we are calling a method on it
|
|
||||||
use overload (
|
|
||||||
'""' => sub {
|
|
||||||
if (blessed($_[0]) && $_[0]->isa('App::Netdisco::Web::Plugin::Passphrase')) {
|
|
||||||
$_[0]->rfc2307();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
fallback => 1,
|
|
||||||
);
|
|
||||||
|
|
||||||
register passphrase => \&passphrase;
|
|
||||||
|
|
||||||
|
|
||||||
=head1 KEYWORDS
|
|
||||||
|
|
||||||
=head2 passphrase
|
|
||||||
|
|
||||||
Given a plaintext password, it returns a Dancer::Plugin::Passphrase
|
|
||||||
object that you can generate a new hash from, or match against a stored hash.
|
|
||||||
|
|
||||||
=cut
|
|
||||||
|
|
||||||
sub passphrase {
|
|
||||||
# Dancer 2 keywords receive a reference to the DSL object as a first param.
|
|
||||||
# We don't need it, so get rid of it, and just get the plaintext
|
|
||||||
shift if blessed($_[0]) && $_[0]->isa('Dancer::Core::DSL');
|
|
||||||
|
|
||||||
my $plaintext = $_[0];
|
|
||||||
|
|
||||||
return bless {
|
|
||||||
plaintext => $plaintext
|
|
||||||
}, 'App::Netdisco::Web::Plugin::Passphrase';
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
=head1 MAIN METHODS
|
|
||||||
|
|
||||||
=head2 generate
|
|
||||||
|
|
||||||
Generates an RFC 2307 representation of the hashed passphrase
|
|
||||||
that is suitable for storage in a database.
|
|
||||||
|
|
||||||
my $pass = passphrase('my passphrase')->generate;
|
|
||||||
|
|
||||||
You should store C<$phrase->rfc_2307()> in your database. For convenience
|
|
||||||
the object will automagically return the RFC 2307 representation when no
|
|
||||||
method is called on it.
|
|
||||||
|
|
||||||
Accepts a hashref of options to specify what kind of hash should be
|
|
||||||
generated. All options settable in the config file are valid.
|
|
||||||
|
|
||||||
If you specify only the algorithm, the default settings for that algorithm will be used.
|
|
||||||
|
|
||||||
A cryptographically random salt is used if salt is not defined.
|
|
||||||
Only if you specify the empty string will an empty salt be used
|
|
||||||
This is not recommended, and should only be used to upgrade old insecure hashes
|
|
||||||
|
|
||||||
my $phrase = passphrase('my password')->generate({
|
|
||||||
algorithm => '', # What algorithm is used to generate the hash
|
|
||||||
cost => '', # Cost / Work Factor if using bcrypt
|
|
||||||
salt => '', # Manually specify salt if using a salted digest
|
|
||||||
});
|
|
||||||
|
|
||||||
=cut
|
|
||||||
|
|
||||||
sub generate {
|
|
||||||
my ($self, $options) = @_;
|
|
||||||
|
|
||||||
$self->_get_settings($options);
|
|
||||||
$self->_calculate_hash;
|
|
||||||
|
|
||||||
return $self;
|
|
||||||
}
|
|
||||||
|
|
||||||
sub generate_hash {
|
|
||||||
carp "generate_hash method is deprecated";
|
|
||||||
return shift->generate();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
=head2 matches
|
|
||||||
|
|
||||||
Matches a plaintext password against a stored hash.
|
|
||||||
Returns 1 if the hash of the password matches the stored hash.
|
|
||||||
Returns undef if they don't match or if there was an error
|
|
||||||
Fail-Secure, rather than Fail-Safe.
|
|
||||||
|
|
||||||
passphrase('my password')->matches($stored_rfc_2307_string);
|
|
||||||
|
|
||||||
$stored_rfc_2307_string B<MUST> be a valid RFC 2307 string,
|
|
||||||
as created by L<generate()|/"passphrase__generate">
|
|
||||||
|
|
||||||
An RFC 2307 string is made up of a scheme identifier, followed by a
|
|
||||||
base64 encoded string. The base64 encoded string should contain
|
|
||||||
the password hash and the salt concatenated together - in that order.
|
|
||||||
|
|
||||||
'{'.$scheme.'}'.encode_base64($hash . $salt, '');
|
|
||||||
|
|
||||||
Where C<$scheme> can be any of the following and their unsalted variants,
|
|
||||||
which have the leading S removed. CRYPT will be Bcrypt.
|
|
||||||
|
|
||||||
SMD5 SSHA SSHA224 SSHA256 SSHA384 SSHA512 CRYPT
|
|
||||||
|
|
||||||
A complete RFC2307 string looks like this:
|
|
||||||
|
|
||||||
{SSHA}K3LAbIjRL5CpLzOlm3/HzS3qt/hUaGVTYWx0
|
|
||||||
|
|
||||||
This is the format created by L<generate()|/"passphrase__generate">
|
|
||||||
|
|
||||||
=cut
|
|
||||||
|
|
||||||
sub matches {
|
|
||||||
my ($self, $stored_hash) = @_;
|
|
||||||
|
|
||||||
# Force auto stringification in case we were passed an object.
|
|
||||||
($stored_hash) = ($stored_hash =~ m/(.*)/s);
|
|
||||||
|
|
||||||
my $new_hash = $self->_extract_settings($stored_hash)->_calculate_hash->rfc2307;
|
|
||||||
|
|
||||||
return ($new_hash eq $stored_hash) ? 1 : undef;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
=head2 generate_random
|
|
||||||
|
|
||||||
Generates and returns any number of cryptographically random
|
|
||||||
characters from the url-safe base64 charater set.
|
|
||||||
|
|
||||||
my $rand_pass = passphrase->generate_random;
|
|
||||||
|
|
||||||
The passwords generated are suitable for use as
|
|
||||||
temporary passwords or one-time authentication tokens.
|
|
||||||
|
|
||||||
You can configure the length and the character set
|
|
||||||
used by passing a hashref of options.
|
|
||||||
|
|
||||||
my $rand_pass = passphrase->generate_random({
|
|
||||||
length => 32,
|
|
||||||
charset => ['a'..'z', 'A'..'Z'],
|
|
||||||
});
|
|
||||||
|
|
||||||
=cut
|
|
||||||
|
|
||||||
sub generate_random {
|
|
||||||
my ($self, $options) = @_;
|
|
||||||
|
|
||||||
# Default is 16 URL-safe base64 chars. Supported everywhere and a reasonable length
|
|
||||||
my $length = $options->{length} || 16;
|
|
||||||
my $charset = $options->{charset} || ['a'..'z', 'A'..'Z', '0'..'9', '-', '_'];
|
|
||||||
|
|
||||||
return join '', map { @$charset[rand_int scalar @$charset] } 1..$length;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
=head1 ADDITIONAL METHODS
|
|
||||||
|
|
||||||
The methods are only applicable once you have called C<generate>
|
|
||||||
|
|
||||||
passphrase( 'my password' )->generate->rfc2307; # CORRECT
|
|
||||||
|
|
||||||
passphrase( 'my password' )->rfc2307; # INCORRECT, Returns undef
|
|
||||||
|
|
||||||
|
|
||||||
=head2 rfc2307
|
|
||||||
|
|
||||||
Returns the rfc2307 representation from a C<Dancer::Plugin::Passphrase> object.
|
|
||||||
|
|
||||||
passphrase('my password')->generate->rfc2307;
|
|
||||||
|
|
||||||
=cut
|
|
||||||
|
|
||||||
sub rfc2307 {
|
|
||||||
return shift->{rfc2307} || undef;
|
|
||||||
}
|
|
||||||
|
|
||||||
sub as_rfc2307 {
|
|
||||||
carp "as_rfc2307 method is deprecated";
|
|
||||||
return shift->rfc2307();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
=head2 scheme
|
|
||||||
|
|
||||||
Returns the scheme name from a C<Dancer::Plugin::Passphrase> object.
|
|
||||||
|
|
||||||
This is the scheme name as used in the RFC 2307 representation
|
|
||||||
|
|
||||||
passphrase('my password')->generate->scheme;
|
|
||||||
|
|
||||||
The scheme name can be any of the following, and will always be capitalized
|
|
||||||
|
|
||||||
SMD5 SSHA SSHA224 SSHA256 SSHA384 SSHA512 CRYPT
|
|
||||||
MD5 SHA SHA224 SHA256 SHA384 SHA512
|
|
||||||
|
|
||||||
=cut
|
|
||||||
|
|
||||||
sub scheme {
|
|
||||||
return shift->{scheme} || undef;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
=head2 algorithm
|
|
||||||
|
|
||||||
Returns the algorithm name from a C<Dancer::Plugin::Passphrase> object.
|
|
||||||
|
|
||||||
The algorithm name can be anything that is accepted by C<Digest->new($alg)>
|
|
||||||
This includes any modules in the C<Digest::> Namespace
|
|
||||||
|
|
||||||
passphrase('my password')->generate->algorithm;
|
|
||||||
|
|
||||||
=cut
|
|
||||||
|
|
||||||
sub algorithm {
|
|
||||||
return shift->{algorithm} || undef;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
=head2 cost
|
|
||||||
|
|
||||||
Returns the bcrypt cost from a C<Dancer::Plugin::Passphrase> object.
|
|
||||||
Only works when using the bcrypt algorithm, returns undef for other algorithms
|
|
||||||
|
|
||||||
passphrase('my password')->generate->cost;
|
|
||||||
|
|
||||||
=cut
|
|
||||||
|
|
||||||
sub cost {
|
|
||||||
return shift->{cost} || undef;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
=head2 salt_raw
|
|
||||||
|
|
||||||
Returns the raw salt from a C<Dancer::Plugin::Passphrase> object.
|
|
||||||
|
|
||||||
passphrase('my password')->generate->salt_raw;
|
|
||||||
|
|
||||||
Can be defined, but false - The empty string is technically a valid salt.
|
|
||||||
|
|
||||||
Returns C<undef> if there is no salt.
|
|
||||||
|
|
||||||
=cut
|
|
||||||
|
|
||||||
sub salt_raw {
|
|
||||||
return shift->{salt} // undef;
|
|
||||||
}
|
|
||||||
|
|
||||||
sub raw_salt {
|
|
||||||
carp "raw_salt method is deprecated";
|
|
||||||
return shift->salt_raw();
|
|
||||||
}
|
|
||||||
|
|
||||||
=head2 hash_raw
|
|
||||||
|
|
||||||
Returns the raw hash from a C<Dancer::Plugin::Passphrase> object.
|
|
||||||
|
|
||||||
passphrase('my password')->generate->hash_raw;
|
|
||||||
|
|
||||||
=cut
|
|
||||||
|
|
||||||
sub hash_raw {
|
|
||||||
return shift->{hash} || undef;
|
|
||||||
}
|
|
||||||
|
|
||||||
sub raw_hash {
|
|
||||||
carp "raw_hash method is deprecated";
|
|
||||||
return shift->hash_raw();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
=head2 salt_hex
|
|
||||||
|
|
||||||
Returns the hex-encoded salt from a C<Dancer::Plugin::Passphrase> object.
|
|
||||||
|
|
||||||
Can be defined, but false - The empty string is technically a valid salt.
|
|
||||||
Returns C<undef> if there is no salt.
|
|
||||||
|
|
||||||
passphrase('my password')->generate->salt_hex;
|
|
||||||
|
|
||||||
=cut
|
|
||||||
|
|
||||||
sub salt_hex {
|
|
||||||
return unpack("H*", shift->{salt}) // undef;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
=head2 hash_hex
|
|
||||||
|
|
||||||
Returns the hex-encoded hash from a C<Dancer::Plugin::Passphrase> object.
|
|
||||||
|
|
||||||
passphrase('my password')->generate->hash_hex;
|
|
||||||
|
|
||||||
=cut
|
|
||||||
|
|
||||||
sub hash_hex {
|
|
||||||
return unpack("H*", shift->{hash}) || undef;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
=head2 salt_base64
|
|
||||||
|
|
||||||
Returns the base64 encoded salt from a C<Dancer::Plugin::Passphrase> object.
|
|
||||||
|
|
||||||
Can be defined, but false - The empty string is technically a valid salt.
|
|
||||||
Returns C<undef> if there is no salt.
|
|
||||||
|
|
||||||
passphrase('my password')->generate->salt_base64;
|
|
||||||
|
|
||||||
=cut
|
|
||||||
|
|
||||||
sub salt_base64 {
|
|
||||||
return encode_base64(shift->{salt}, '') // undef;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
=head2 hash_base64
|
|
||||||
|
|
||||||
Returns the base64 encoded hash from a C<Dancer::Plugin::Passphrase> object.
|
|
||||||
|
|
||||||
passphrase('my password')->generate->hash_base64;
|
|
||||||
|
|
||||||
=cut
|
|
||||||
|
|
||||||
sub hash_base64 {
|
|
||||||
return encode_base64(shift->{hash}, '') || undef;
|
|
||||||
}
|
|
||||||
|
|
||||||
=head2 plaintext
|
|
||||||
|
|
||||||
Returns the plaintext password as originally supplied to the L<passphrase> keyword.
|
|
||||||
|
|
||||||
passphrase('my password')->generate->plaintext;
|
|
||||||
|
|
||||||
=cut
|
|
||||||
|
|
||||||
sub plaintext {
|
|
||||||
return shift->{plaintext} || undef;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# Actual generation of the hash, using the provided settings
|
|
||||||
sub _calculate_hash {
|
|
||||||
my $self = shift;
|
|
||||||
|
|
||||||
my $hasher = Digest->new( $self->algorithm );
|
|
||||||
|
|
||||||
given ($self->algorithm) {
|
|
||||||
when ('Bcrypt') {
|
|
||||||
$hasher->add($self->{plaintext});
|
|
||||||
$hasher->salt($self->salt_raw);
|
|
||||||
$hasher->cost($self->cost);
|
|
||||||
|
|
||||||
$self->{hash} = $hasher->digest;
|
|
||||||
$self->{rfc2307}
|
|
||||||
= '{CRYPT}$'
|
|
||||||
. $self->{type} . '$'
|
|
||||||
. $self->cost . '$'
|
|
||||||
. _en_bcrypt_base64($self->salt_raw)
|
|
||||||
. _en_bcrypt_base64($self->hash_raw);
|
|
||||||
}
|
|
||||||
default {
|
|
||||||
$hasher->add($self->{plaintext});
|
|
||||||
$hasher->add($self->{salt});
|
|
||||||
|
|
||||||
$self->{hash} = $hasher->digest;
|
|
||||||
$self->{rfc2307}
|
|
||||||
= '{' . $self->{scheme} . '}'
|
|
||||||
. encode_base64($self->hash_raw . $self->salt_raw, '');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return $self;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
# Extracts the settings from an RFC 2307 string
|
|
||||||
sub _extract_settings {
|
|
||||||
my ($self, $rfc2307_string) = @_;
|
|
||||||
|
|
||||||
my ($scheme, $settings) = ($rfc2307_string =~ m/^{(\w+)}(.*)/s);
|
|
||||||
|
|
||||||
unless ($scheme && $settings) {
|
|
||||||
croak "An RFC 2307 compliant string must be passed to matches()";
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($scheme eq 'CRYPT'){
|
|
||||||
given ($settings) {
|
|
||||||
when (/^\$2(?:a|x|y)\$/) {
|
|
||||||
$scheme = 'Bcrypt';
|
|
||||||
$settings =~ m{\A\$(2a|2x|2y)\$([0-9]{2})\$([./A-Za-z0-9]{22})}x;
|
|
||||||
|
|
||||||
($self->{type}, $self->{cost}, $self->{salt}) = ($1, $2, _de_bcrypt_base64($3));
|
|
||||||
}
|
|
||||||
default { croak "Unknown CRYPT format: $_"; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
my $scheme_meta = {
|
|
||||||
'MD5' => { algorithm => 'MD5', octets => 128 / 8 },
|
|
||||||
'SMD5' => { algorithm => 'MD5', octets => 128 / 8 },
|
|
||||||
'SHA' => { algorithm => 'SHA-1', octets => 160 / 8 },
|
|
||||||
'SSHA' => { algorithm => 'SHA-1', octets => 160 / 8 },
|
|
||||||
'SHA224' => { algorithm => 'SHA-224', octets => 224 / 8 },
|
|
||||||
'SSHA224' => { algorithm => 'SHA-224', octets => 224 / 8 },
|
|
||||||
'SHA256' => { algorithm => 'SHA-256', octets => 256 / 8 },
|
|
||||||
'SSHA256' => { algorithm => 'SHA-256', octets => 256 / 8 },
|
|
||||||
'SHA384' => { algorithm => 'SHA-384', octets => 384 / 8 },
|
|
||||||
'SSHA384' => { algorithm => 'SHA-384', octets => 384 / 8 },
|
|
||||||
'SHA512' => { algorithm => 'SHA-512', octets => 512 / 8 },
|
|
||||||
'SSHA512' => { algorithm => 'SHA-512', octets => 512 / 8 },
|
|
||||||
'Bcrypt' => { algorithm => 'Bcrypt', octets => 128 / 8 },
|
|
||||||
};
|
|
||||||
|
|
||||||
$self->{scheme} = $scheme;
|
|
||||||
$self->{algorithm} = $scheme_meta->{$scheme}->{algorithm};
|
|
||||||
|
|
||||||
if (!defined $self->{salt}) {
|
|
||||||
$self->{salt} = substr(decode_base64($settings), $scheme_meta->{$scheme}->{octets});
|
|
||||||
}
|
|
||||||
|
|
||||||
return $self;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
# Gets the settings from config.yml, and merges them with any custom
|
|
||||||
# settings given to the constructor
|
|
||||||
sub _get_settings {
|
|
||||||
my ($self, $options) = @_;
|
|
||||||
|
|
||||||
$self->{algorithm} = $options->{algorithm} ||
|
|
||||||
plugin_setting->{algorithm} ||
|
|
||||||
'Bcrypt';
|
|
||||||
|
|
||||||
my $plugin_setting = plugin_setting->{$self->algorithm};
|
|
||||||
|
|
||||||
# Specify empty string to get an unsalted hash
|
|
||||||
# Leaving it undefs results in 128 random bits being used as salt
|
|
||||||
# bcrypt requires this amount, and is reasonable for other algorithms
|
|
||||||
$self->{salt} = $options->{salt} //
|
|
||||||
$plugin_setting->{salt} //
|
|
||||||
rand_bits(128);
|
|
||||||
|
|
||||||
# RFC 2307 scheme is based on the algorithm, with a prefixed 'S' for salted
|
|
||||||
$self->{scheme} = join '', $self->algorithm =~ /[\w]+/g;
|
|
||||||
$self->{scheme} = 'S'.$self->{scheme} if $self->{salt};
|
|
||||||
|
|
||||||
given ($self->{scheme}) {
|
|
||||||
when ('SHA1') { $self->{scheme} = 'SHA'; }
|
|
||||||
when ('SSHA1') { $self->{scheme} = 'SSHA'; }
|
|
||||||
}
|
|
||||||
|
|
||||||
# Bcrypt requires a cost parameter
|
|
||||||
if ($self->algorithm eq 'Bcrypt') {
|
|
||||||
$self->{scheme} = 'CRYPT';
|
|
||||||
$self->{type} = '2a';
|
|
||||||
$self->{cost} = $options->{cost} ||
|
|
||||||
$plugin_setting->{cost} ||
|
|
||||||
4;
|
|
||||||
|
|
||||||
$self->{cost} = 31 if $self->cost > 31;
|
|
||||||
$self->{cost} = sprintf("%02d", $self->cost);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $self;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
# From Crypt::Eksblowfish::Bcrypt.
|
|
||||||
# Bcrypt uses it's own variation on base64
|
|
||||||
sub _en_bcrypt_base64 {
|
|
||||||
my ($octets) = @_;
|
|
||||||
my $text = encode_base64($octets, '');
|
|
||||||
$text =~ tr{A-Za-z0-9+/=}{./A-Za-z0-9}d;
|
|
||||||
return $text;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
# And the decoder of bcrypt's custom base64
|
|
||||||
sub _de_bcrypt_base64 {
|
|
||||||
my ($text) = @_;
|
|
||||||
$text =~ tr{./A-Za-z0-9}{A-Za-z0-9+/};
|
|
||||||
$text .= "=" x (3 - (length($text) + 3) % 4);
|
|
||||||
return decode_base64($text);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
register_plugin for_versions => [ 1, 2 ];
|
|
||||||
|
|
||||||
1;
|
|
||||||
|
|
||||||
|
|
||||||
=head1 MORE INFORMATION
|
|
||||||
|
|
||||||
=head2 Purpose
|
|
||||||
|
|
||||||
The aim of this module is to help you store new passwords in a secure manner,
|
|
||||||
whilst still being able to verify and upgrade older passwords.
|
|
||||||
|
|
||||||
Cryptography is a vast and complex field. Many people try to roll their own
|
|
||||||
methods for securing user data, but succeed only in coming up with
|
|
||||||
a system that has little real security.
|
|
||||||
|
|
||||||
This plugin provides a simple way of managing that complexity, allowing
|
|
||||||
developers to follow crypto best practice without having to become an expert.
|
|
||||||
|
|
||||||
|
|
||||||
=head2 Rationale
|
|
||||||
|
|
||||||
The module defaults to hashing passwords using the bcrypt algorithm, returning them
|
|
||||||
in RFC 2307 format.
|
|
||||||
|
|
||||||
RFC 2307 describes an encoding system for passphrase hashes, as used in the "userPassword"
|
|
||||||
attribute in LDAP databases. It encodes hashes as ASCII text, and supports several
|
|
||||||
passphrase schemes by starting the encoding with an alphanumeric scheme identifier enclosed
|
|
||||||
in braces.
|
|
||||||
|
|
||||||
RFC 2307 only specifies the C<MD5>, and C<SHA> schemes - however in real-world usage,
|
|
||||||
schemes that are salted are widely supported, and are thus provided by this module.
|
|
||||||
|
|
||||||
Bcrypt is an adaptive hashing algorithm that is designed to resist brute
|
|
||||||
force attacks by including a cost (aka work factor). This cost increases
|
|
||||||
the computational effort it takes to compute the hash.
|
|
||||||
|
|
||||||
SHA and MD5 are designed to be fast, and modern machines compute a billion
|
|
||||||
hashes a second. With computers getting faster every day, brute forcing
|
|
||||||
SHA hashes is a very real problem that cannot be easily solved.
|
|
||||||
|
|
||||||
Increasing the cost of generating a bcrypt hash is a trivial way to make
|
|
||||||
brute forcing ineffective. With a low cost setting, bcrypt is just as secure
|
|
||||||
as a more traditional SHA+salt scheme, and just as fast. Increasing the cost
|
|
||||||
as computers become more powerful keeps you one step ahead
|
|
||||||
|
|
||||||
For a more detailed description of why bcrypt is preferred, see this article:
|
|
||||||
L<http://codahale.com/how-to-safely-store-a-password/>
|
|
||||||
|
|
||||||
|
|
||||||
=head2 Configuration
|
|
||||||
|
|
||||||
In your applications config file, you can set the default hashing algorithm,
|
|
||||||
and the default settings for every supported algorithm. Calls to
|
|
||||||
L<generate()|/"passphrase__generate"> will use the default settings
|
|
||||||
for that algorithm specified in here.
|
|
||||||
|
|
||||||
You can override these defaults when you call L<generate()|/"passphrase__generate">.
|
|
||||||
|
|
||||||
If you do no configuration at all, the default is to bcrypt with a cost of 4, and
|
|
||||||
a strong psuedo-random salt.
|
|
||||||
|
|
||||||
plugins:
|
|
||||||
Passphrase:
|
|
||||||
default: Bcrypt
|
|
||||||
|
|
||||||
Bcrypt:
|
|
||||||
cost: 8
|
|
||||||
|
|
||||||
|
|
||||||
=head2 Storage in a database
|
|
||||||
|
|
||||||
You should be storing the RFC 2307 string in your database, it's the easiest way
|
|
||||||
to use this module. You could store the C<raw_salt>, C<raw_hash>, and C<scheme>
|
|
||||||
separately, but this strongly discouraged. RFC 2307 strings are specifically
|
|
||||||
designed for storing hashed passwords, and should be used wherever possible.
|
|
||||||
|
|
||||||
The length of the string produced by L<generate()|/"passphrase__generate"> can
|
|
||||||
vary dependent on your settings. Below is a table of the lengths generated
|
|
||||||
using default settings.
|
|
||||||
|
|
||||||
You will need to make sure your database columns are at least this long.
|
|
||||||
If the string gets truncated, the password can I<never> be validated.
|
|
||||||
|
|
||||||
ALGORITHM LENGTH EXAMPLE RFC 2307 STRING
|
|
||||||
|
|
||||||
Bcrypt 68 {CRYPT}$2a$04$MjkMhQxasFQod1qq56DXCOvWu6YTWk9X.EZGnmSSIbbtyEBIAixbS
|
|
||||||
SHA-512 118 {SSHA512}lZG4dZ5EU6dPEbJ1kBPPzEcupFloFSIJjiXCwMVxJXOy/x5qhBA5XH8FiUWj7u59onQxa97xYdqje/fwY5TDUcW1Urplf3KHMo9NO8KO47o=
|
|
||||||
SHA-384 98 {SSHA384}SqZF5YYyk4NdjIM8YgQVfRieXDxNG0dKH4XBcM40Eblm+ribCzdyf0JV7i2xJvVHZsFSQNcuZPKtiTMzDyOU+w==
|
|
||||||
SHA-256 74 {SSHA256}xsJHNzPlNCpOZ41OkTfQOU35ZY+nRyZFaM8lHg5U2pc0xT3DKNlGW2UTY0NPYsxU
|
|
||||||
SHA-224 70 {SSHA224}FTHNkvKOdyX1d6f45iKLVxpaXZiHel8pfilUT1dIZ5u+WIUyhDGxLnx72X0=
|
|
||||||
SHA-1 55 {SSHA}Qsaao/Xi/bYTRMQnpHuD3y5nj02wbdcw5Cek2y2nLs3pIlPh
|
|
||||||
MD5 51 {SMD5}bgfLiUQWgzUm36+nBhFx62bi0xdwTp+UpEeNKDxSLfM=
|
|
||||||
|
|
||||||
=head2 Common Mistakes
|
|
||||||
|
|
||||||
Common mistakes people make when creating their own solution. If any of these
|
|
||||||
seem familiar, you should probably be using this module
|
|
||||||
|
|
||||||
=over
|
|
||||||
|
|
||||||
=item Passwords are stored as plain text for a reason
|
|
||||||
|
|
||||||
There is never a valid reason to store a password as plain text.
|
|
||||||
Passwords should be reset and not emailed to customers when they forget.
|
|
||||||
Support people should be able to login as a user without knowing the users password.
|
|
||||||
No-one except the user should know the password - that is the point of authentication.
|
|
||||||
|
|
||||||
=item No-one will ever guess our super secret algorithm!
|
|
||||||
|
|
||||||
Unless you're a cryptography expert with many years spent studying
|
|
||||||
super-complex maths, your algorithm is almost certainly not as secure
|
|
||||||
as you think. Just because it's hard for you to break doesn't mean
|
|
||||||
it's difficult for a computer.
|
|
||||||
|
|
||||||
=item Our application-wide salt is "Sup3r_S3cret_L0ng_Word" - No-one will ever guess that.
|
|
||||||
|
|
||||||
This is common misunderstanding of what a salt is meant to do. The purpose of a
|
|
||||||
salt is to make sure the same password doesn't always generate the same hash.
|
|
||||||
A fresh salt needs to be created each time you hash a password. It isn't meant
|
|
||||||
to be a secret key.
|
|
||||||
|
|
||||||
=item We generate our random salt using C<rand>.
|
|
||||||
|
|
||||||
C<rand> isn't actually random, it's a non-unform pseudo-random number generator,
|
|
||||||
and not suitable for cryptographic applications. Whilst this module also defaults to
|
|
||||||
a PRNG, it is better than the one provided by C<rand>. Using a true RNG is a config
|
|
||||||
option away, but is not the default as it it could potentially block output if the
|
|
||||||
system does not have enough entropy to generate a truly random number
|
|
||||||
|
|
||||||
=item We use C<md5(pass.salt)>, and the salt is from C</dev/random>
|
|
||||||
|
|
||||||
MD5 has been broken for many years. Commodity hardware can find a
|
|
||||||
hash collision in seconds, meaning an attacker can easily generate
|
|
||||||
the correct MD5 hash without using the correct password.
|
|
||||||
|
|
||||||
=item We use C<sha(pass.salt)>, and the salt is from C</dev/random>
|
|
||||||
|
|
||||||
SHA isn't quite as broken as MD5, but it shares the same theoretical
|
|
||||||
weaknesses. Even without hash collisions, it is vulnerable to brute forcing.
|
|
||||||
Modern hardware is so powerful it can try around a billion hashes a second.
|
|
||||||
That means every 7 chracter password in the range [A-Za-z0-9] can be cracked
|
|
||||||
in one hour on your average desktop computer.
|
|
||||||
|
|
||||||
=item If the only way to break the hash is to brute-force it, it's secure enough
|
|
||||||
|
|
||||||
It is unlikely that your database will be hacked and your hashes brute forced.
|
|
||||||
However, in the event that it does happen, or SHA512 is broken, using this module
|
|
||||||
gives you an easy way to change to a different algorithm, while still allowing
|
|
||||||
you to validate old passphrases
|
|
||||||
|
|
||||||
=back
|
|
||||||
|
|
||||||
|
|
||||||
=head1 KNOWN ISSUES
|
|
||||||
|
|
||||||
If you see errors like this
|
|
||||||
|
|
||||||
Wide character in subroutine entry
|
|
||||||
|
|
||||||
or
|
|
||||||
|
|
||||||
Input must contain only octets
|
|
||||||
|
|
||||||
The C<MD5>, C<bcrypt>, and C<SHA> algorithms can't handle chracters with an ordinal
|
|
||||||
value above 255, producing errors like this if they encounter them.
|
|
||||||
It is not possible for this plugin to automagically work out the correct
|
|
||||||
encoding for a given string.
|
|
||||||
|
|
||||||
If you see errors like this, then you probably need to use the L<Encode> module
|
|
||||||
to encode your text as UTF-8 (or whatever encoding it is) before giving it
|
|
||||||
to C<passphrase>.
|
|
||||||
|
|
||||||
Text encoding is a bag of hurt, and errors like this are probably indicitive
|
|
||||||
of deeper problems within your app's code.
|
|
||||||
|
|
||||||
You will save yourself a lot of trouble if you read up on the
|
|
||||||
L<Encode> module sooner rather than later.
|
|
||||||
|
|
||||||
For further reading on UTF-8, unicode, and text encoding in perl,
|
|
||||||
see L<http://training.perl.com/OSCON2011/index.html>
|
|
||||||
|
|
||||||
|
|
||||||
=head1 SEE ALSO
|
|
||||||
|
|
||||||
L<Dancer>, L<Digest>, L<Crypt::Eksblowfish::Bcrypt>, L<Dancer::Plugin::Bcrypt>
|
|
||||||
|
|
||||||
|
|
||||||
=head1 AUTHOR
|
|
||||||
|
|
||||||
James Aitken <jaitken@cpan.org>
|
|
||||||
|
|
||||||
|
|
||||||
=head1 COPYRIGHT AND LICENSE
|
|
||||||
|
|
||||||
This software is copyright (c) 2012 by James Aitken.
|
|
||||||
|
|
||||||
This is free software; you can redistribute it and/or modify it under
|
|
||||||
the same terms as the Perl 5 programming language system itself.
|
|
||||||
|
|
||||||
=cut
|
|
||||||
Reference in New Issue
Block a user