Add U2F to WebAuthn migration script (#1411)
This commit is contained in:
parent
2cc2a5804b
commit
f7852b3302
|
@ -84,7 +84,6 @@ sub _search {
|
||||||
}
|
}
|
||||||
|
|
||||||
return $res;
|
return $res;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sub search {
|
sub search {
|
||||||
|
@ -264,12 +263,86 @@ sub _del_psession_special {
|
||||||
if ($deleted) {
|
if ($deleted) {
|
||||||
$data->{$specialKeyName} = to_json( [@new] );
|
$data->{$specialKeyName} = to_json( [@new] );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# TODO should this be in the if???
|
||||||
$psession->update($data);
|
$psession->update($data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sub _migrateu2f_device {
|
||||||
|
my ( $self, $device ) = @_;
|
||||||
|
|
||||||
|
my $credential_id = $device->{_keyHandle};
|
||||||
|
my $_userKey = $device->{_userKey};
|
||||||
|
|
||||||
|
eval { require Authen::WebAuthn };
|
||||||
|
if ($@) {
|
||||||
|
die "Missing Authen::WebAuthn dependency: $@";
|
||||||
|
}
|
||||||
|
|
||||||
|
my $credential_pubkey =
|
||||||
|
Authen::WebAuthn::convert_raw_ecc_to_cose($_userKey);
|
||||||
|
|
||||||
|
return {
|
||||||
|
type => "WebAuthn",
|
||||||
|
name => "$device->{name}",
|
||||||
|
_credentialId => "$credential_id",
|
||||||
|
_credentialPublicKey => "$credential_pubkey",
|
||||||
|
_signCount => 0,
|
||||||
|
epoch => "$device->{epoch}",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
sub _migrateu2f {
|
||||||
|
my $self = shift;
|
||||||
|
my $target = shift;
|
||||||
|
|
||||||
|
my $psession = $self->_get_psession($target);
|
||||||
|
my $data = $psession->data;
|
||||||
|
my $migrated = 0;
|
||||||
|
|
||||||
|
my $_2fDevices = $data->{_2fDevices} || "[]";
|
||||||
|
$_2fDevices = from_json($_2fDevices);
|
||||||
|
|
||||||
|
die "Expecting JSON array in _2fDevices"
|
||||||
|
unless ref($_2fDevices) eq "ARRAY";
|
||||||
|
|
||||||
|
my @new_2fDevices = @{$_2fDevices};
|
||||||
|
my @u2f_devices = grep { $_->{type} eq "U2F" } @{$_2fDevices};
|
||||||
|
|
||||||
|
my %migrated_devices;
|
||||||
|
for my $u2f_device (@u2f_devices) {
|
||||||
|
my $migrated_device = $self->_migrateu2f_device($u2f_device);
|
||||||
|
$migrated_devices{ $migrated_device->{_credentialId} } =
|
||||||
|
$migrated_device;
|
||||||
|
}
|
||||||
|
|
||||||
|
for my $migrated_device ( keys %migrated_devices ) {
|
||||||
|
|
||||||
|
# If credentialId is not already present
|
||||||
|
unless (
|
||||||
|
grep {
|
||||||
|
$_->{type} eq "WebAuthn"
|
||||||
|
and $_->{_credentialId} eq $migrated_device
|
||||||
|
} @new_2fDevices
|
||||||
|
)
|
||||||
|
{
|
||||||
|
push @new_2fDevices, $migrated_devices{$migrated_device};
|
||||||
|
$migrated = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($migrated) {
|
||||||
|
$data->{_2fDevices} = to_json( [@new_2fDevices] );
|
||||||
|
$psession->update($data);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
sub consents_get {
|
sub consents_get {
|
||||||
my $self = shift;
|
my $self = shift;
|
||||||
my $target = shift;
|
my $target = shift;
|
||||||
|
return 0 unless $target;
|
||||||
|
|
||||||
my $o = $self->stdout;
|
my $o = $self->stdout;
|
||||||
my $consents = $self->_get_psession_special( $target, '_oidcConsents',
|
my $consents = $self->_get_psession_special( $target, '_oidcConsents',
|
||||||
sub { $_[0]->{rp} } );
|
sub { $_[0]->{rp} } );
|
||||||
|
@ -280,6 +353,8 @@ sub consents_get {
|
||||||
sub secondfactors_get {
|
sub secondfactors_get {
|
||||||
my $self = shift;
|
my $self = shift;
|
||||||
my $target = shift;
|
my $target = shift;
|
||||||
|
return 0 unless $target;
|
||||||
|
|
||||||
my $o = $self->stdout;
|
my $o = $self->stdout;
|
||||||
my $consents = $self->_get_psession_special( $target, '_2fDevices',
|
my $consents = $self->_get_psession_special( $target, '_2fDevices',
|
||||||
sub { genId2F( $_[0] ) } );
|
sub { genId2F( $_[0] ) } );
|
||||||
|
@ -290,8 +365,11 @@ sub secondfactors_get {
|
||||||
sub consents_delete {
|
sub consents_delete {
|
||||||
my $self = shift;
|
my $self = shift;
|
||||||
my $target = shift;
|
my $target = shift;
|
||||||
|
return 0 unless $target;
|
||||||
|
|
||||||
my @ids = @_;
|
my @ids = @_;
|
||||||
return unless @ids;
|
return 0 unless @ids;
|
||||||
|
|
||||||
$self->_del_psession_special( $target, '_oidcConsents',
|
$self->_del_psession_special( $target, '_oidcConsents',
|
||||||
sub { $_[0]->{rp} }, @ids );
|
sub { $_[0]->{rp} }, @ids );
|
||||||
return 0;
|
return 0;
|
||||||
|
@ -300,23 +378,66 @@ sub consents_delete {
|
||||||
sub secondfactors_delete {
|
sub secondfactors_delete {
|
||||||
my $self = shift;
|
my $self = shift;
|
||||||
my $target = shift;
|
my $target = shift;
|
||||||
|
return 0 unless $target;
|
||||||
|
|
||||||
my @ids = @_;
|
my @ids = @_;
|
||||||
return unless @ids;
|
return 0 unless @ids;
|
||||||
$self->_del_psession_special( $target, '_2fDevices',
|
$self->_del_psession_special( $target, '_2fDevices',
|
||||||
sub { genId2F( $_[0] ) }, @ids );
|
sub { genId2F( $_[0] ) }, @ids );
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sub _get_psession_targets {
|
||||||
|
my ( $self, @args ) = @_;
|
||||||
|
|
||||||
|
if ( $self->opts->{where} or $self->opts->{all} ) {
|
||||||
|
$self->opts->{persistent} = 1;
|
||||||
|
|
||||||
|
if ( $self->opts->{all} ) {
|
||||||
|
delete $self->opts->{where};
|
||||||
|
}
|
||||||
|
|
||||||
|
my $res = $self->_search();
|
||||||
|
return ( map { $res->{$_}->{_session_uid} } keys %{$res} );
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return @args;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
sub secondfactors_delType {
|
sub secondfactors_delType {
|
||||||
my $self = shift;
|
my $self = shift;
|
||||||
my $target = shift;
|
|
||||||
|
my $target;
|
||||||
|
unless ( $self->opts->{where} or $self->opts->{all} ) {
|
||||||
|
$target = shift;
|
||||||
|
}
|
||||||
|
|
||||||
my @types = @_;
|
my @types = @_;
|
||||||
return unless @types;
|
return 0 unless @types;
|
||||||
$self->_del_psession_special( $target, '_2fDevices', sub { $_[0]->{type} },
|
|
||||||
@types );
|
my @targets = $self->_get_psession_targets($target);
|
||||||
|
for my $target (@targets) {
|
||||||
|
$self->_del_psession_special( $target, '_2fDevices',
|
||||||
|
sub { $_[0]->{type} }, @types );
|
||||||
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sub secondfactors_migrateu2f {
|
||||||
|
my ( $self, @ids ) = @_;
|
||||||
|
my $result = 0;
|
||||||
|
my @sessions = $self->_get_psession_targets(@ids);
|
||||||
|
|
||||||
|
for my $id (@sessions) {
|
||||||
|
if ( !$self->_migrateu2f($id) ) {
|
||||||
|
$result = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
sub setKey {
|
sub setKey {
|
||||||
my $self = shift;
|
my $self = shift;
|
||||||
my $id = shift;
|
my $id = shift;
|
||||||
|
@ -380,8 +501,8 @@ sub run {
|
||||||
# Subcommands and target
|
# Subcommands and target
|
||||||
elsif ( $action =~ /^(?:secondfactors|consents)$/ ) {
|
elsif ( $action =~ /^(?:secondfactors|consents)$/ ) {
|
||||||
my $subcommand = shift;
|
my $subcommand = shift;
|
||||||
unless ( $subcommand and @_ ) {
|
unless ($subcommand) {
|
||||||
die "Missing subcommand and target for $action";
|
die "Missing subcommand $action";
|
||||||
}
|
}
|
||||||
my $func = "${action}_${subcommand}";
|
my $func = "${action}_${subcommand}";
|
||||||
if ( $self->can($func) ) {
|
if ( $self->can($func) ) {
|
||||||
|
|
|
@ -22,6 +22,7 @@ GetOptions(
|
||||||
'help|h' => \$help,
|
'help|h' => \$help,
|
||||||
'select|s=s@' => \$opts->{select},
|
'select|s=s@' => \$opts->{select},
|
||||||
'where|w=s' => \$opts->{where},
|
'where|w=s' => \$opts->{where},
|
||||||
|
'all|a' => \$opts->{all},
|
||||||
'backend|b=s' => \$opts->{backend},
|
'backend|b=s' => \$opts->{backend},
|
||||||
'persistent|p' => \$opts->{persistent},
|
'persistent|p' => \$opts->{persistent},
|
||||||
'id-only|i' => \$opts->{idonly},
|
'id-only|i' => \$opts->{idonly},
|
||||||
|
@ -80,7 +81,7 @@ if ( $action eq "setKey" ) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( $action eq "secondfactors" ) {
|
if ( $action eq "secondfactors" ) {
|
||||||
unless ( @ARGV >= 2 ) {
|
unless ( @ARGV >= 1 ) {
|
||||||
pod2usage(
|
pod2usage(
|
||||||
-exitval => 1,
|
-exitval => 1,
|
||||||
-verbose => 99,
|
-verbose => 99,
|
||||||
|
@ -238,8 +239,10 @@ Commands:
|
||||||
delete <user> <id> [<id> ...]
|
delete <user> <id> [<id> ...]
|
||||||
delete second factors for a user. The ID must match one of the
|
delete second factors for a user. The ID must match one of the
|
||||||
IDs returned by the "show" command.
|
IDs returned by the "show" command.
|
||||||
delType <user> <type> [<type> ...]
|
delType [<user>|--all] <type> [<type> ...]
|
||||||
delete all second factors of a given type for a user
|
delete all second factors of a given type for a user
|
||||||
|
migrateu2f [<user>|--all]
|
||||||
|
migrate U2F device registrations to WebAuthn device registrations
|
||||||
|
|
||||||
=head2 Consents
|
=head2 Consents
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user