AmberDB
view release on metacpan or search on metacpan
lib/AmberDB/Base.pm view on Meta::CPAN
my ( $algo, $salt, $expected ) = split /\$/, $stored_shadow;
return 0 unless defined $algo && defined $salt && defined $expected;
if ( $algo eq 'sha256' ) {
my $computed = sha256_hex( $salt . $password );
return lc($computed) eq lc($expected) ? 1 : 0;
}
return $stored_shadow eq $password ? 1 : 0;
}
# ============================================================================
# DATABASE CONNECTION & CLIENT AUTH PROFILE (connect.pl)
# ============================================================================
sub _connect_file {
my ($self) = @_;
my $conf_dir = $self->path('conf_dir') || ( ( $self->path('dbase_dir') || "." ) . "/config" );
return "$conf_dir/connect.pl";
}
sub _load_connect_config {
my ($self) = @_;
my $file = $self->_connect_file();
return undef unless -f $file;
my $target = ( $file =~ m{^(?:\./|[a-zA-Z]:|/|\\)} ) ? $file : "./$file";
my $data = do $target;
return undef unless ref($data) eq 'HASH';
# Auto-upgrade: if any user entry or top-level entry has plain 'password', hash into 'shadow' and delete 'password'
my $modified = 0;
if ( defined $data->{password} && length $data->{password} ) {
$data->{shadow} = $self->hash_password( delete $data->{password} );
$modified = 1;
}
if ( $data->{users} && ref( $data->{users} ) eq 'HASH' ) {
for my $u ( keys %{ $data->{users} } ) {
my $u_info = $data->{users}->{$u};
next unless ref($u_info) eq 'HASH';
if ( defined $u_info->{password} && length $u_info->{password} ) {
$u_info->{shadow} = $self->hash_password( delete $u_info->{password} );
$modified = 1;
}
}
}
if ($modified) {
$self->_save_connect_config($data);
}
return $data;
}
sub _save_connect_config {
my ( $self, $data ) = @_;
my $file = $self->_connect_file();
return unless defined $file && ref($data) eq 'HASH';
my ($dir) = $file =~ m{^(.+)[/\\][^/\\]+$};
$self->make_path($dir) if $dir && !$self->dir_exist($dir);
require Data::Dumper;
local $Data::Dumper::Terse = 1;
local $Data::Dumper::Indent = 1;
local $Data::Dumper::Sortkeys = 1;
my $dump = Data::Dumper::Dumper($data);
$dump =~ s/^\s+|\s+$//g;
if ( open my $fh, '>', $file ) {
print $fh "# AmberDB Database Connection & Client Auth Profile\n";
print $fh "return " . $dump . ";\n";
close $fh;
return 1;
}
return 0;
}
# my $sess_path = $adb->session_file($token);
# ---------------------------------------------------------------------
sub session_file {
my ( $self, $token ) = @_;
return '' unless defined $token && length $token;
my $sess_dir = $self->path('session_dir') || ( ( $self->path('dbase_dir') || "." ) . "/session" );
return "$sess_dir/cli_$token";
}
# my $token = $adb->generate_token();
# ---------------------------------------------------------------------
sub generate_token {
my ($self) = @_;
for ( 1 .. 1000 ) {
my $token = sprintf( "%04d", int( rand(9000) ) + 1000 );
my $sf = $self->session_file($token);
next if $sf && -f $sf;
my $cf = ".amberdb/session/sess_$token";
next if -f $cf;
return $token;
}
return sprintf( "%04d", int( rand(9000) ) + 1000 );
}
sub _load_session {
my ( $self, $token ) = @_;
return undef unless defined $token && length $token;
my @files = ( $self->session_file($token), ".amberdb/session/sess_$token" );
for my $sf (@files) {
next unless $sf && -f $sf;
my $target = ( $sf =~ m{^(?:\./|[a-zA-Z]:|/|\\)} ) ? $sf : "./$sf";
my $data = do $target;
return $data if ref($data) eq 'HASH';
if ( open my $fh, '<', $sf ) {
local $/;
my $raw = <$fh>;
close $fh;
if ( $raw && $raw =~ /^\s*\{/ ) {
require JSON::PP;
my $jdata = eval { JSON::PP::decode_json($raw) };
return $jdata if $jdata && ref($jdata) eq 'HASH';
}
}
}
return undef;
}
sub _save_session {
my ( $self, $token, $data ) = @_;
return unless defined $token && length $token;
my $sf = $self->session_file($token);
return unless $sf;
my ($dir) = $sf =~ m{^(.+)[/\\][^/\\]+$};
$self->make_path($dir) if $dir && !$self->dir_exist($dir);
require Data::Dumper;
local $Data::Dumper::Terse = 1;
local $Data::Dumper::Indent = 1;
local $Data::Dumper::Sortkeys = 1;
my $dump = Data::Dumper::Dumper($data);
$dump =~ s/^\s+|\s+$//g;
if ( open my $fh, '>', $sf ) {
print $fh "# AmberDB Active CLI Session Token: $token\n";
print $fh "return " . $dump . ";\n";
close $fh;
}
# Also sync local .amberdb/session/sess_$token
my $local_dir = ".amberdb/session";
$self->make_path($local_dir) unless -d $local_dir;
my $local_file = "$local_dir/sess_$token";
if ( open my $lfh, '>', $local_file ) {
require JSON::PP;
print $lfh JSON::PP::encode_json($data);
close $lfh;
}
return 1;
}
sub _delete_session {
my ( $self, $token ) = @_;
return unless defined $token && length $token;
my $sf = $self->session_file($token);
unlink $sf if $sf && -f $sf;
my $local_file = ".amberdb/session/sess_$token";
unlink $local_file if -f $local_file;
}
# my $val_or_token = $adb->connect([$key | %args]);
# ---------------------------------------------------------------------
sub connect {
my ( $self, @args ) = @_;
# 1. Single scalar argument: attribute getter -> $adb->connect('database')
if ( @args == 1 && !ref( $args[0] ) ) {
my $k = $args[0];
$k = 'database' if $k eq 'dbase' || $k eq 'dbname';
$k = 'username' if $k eq 'user' || $k eq 'usr';
$k = 'password' if $k eq 'pass' || $k eq 'passwd';
return $self->{_connect}->{$k} // '';
}
# If called with no args and already connected with valid token, return active token
if ( !@args && defined $self->{_connect}->{token} && length $self->{_connect}->{token} ) {
return $self->{_connect}->{token};
}
# 2. Connection / Authentication Action -> $adb->connect(%args) or $adb->connect()
my %opts = ( @args == 1 && ref( $args[0] ) eq 'HASH' ) ? %{ $args[0] } : @args;
my $token = delete $opts{token} // delete $opts{tok};
my $database = delete $opts{database} // delete $opts{dbase} // delete $opts{dbname} // delete $opts{db} // $self->{_connect}->{database};
my $username = delete $opts{username} // delete $opts{user} // delete $opts{usr} // $self->{_connect}->{username};
my $password = delete $opts{password} // delete $opts{pass} // delete $opts{passwd} // $self->{_connect}->{password};
# Sub-case A: Resume session via token
if ( defined $token && length $token ) {
my $sess = $self->_load_session($token);
( run in 1.312 second using v1.01-cache-2.11-cpan-80ec619307d )