From f72acb6006a10907fc90932a7572ecdb1524f2f8 Mon Sep 17 00:00:00 2001 From: Oliver Gorwits Date: Tue, 18 Jul 2023 10:32:02 +0100 Subject: [PATCH] #1064 implement tags in database and ACL --- lib/App/Netdisco/DB.pm | 2 +- lib/App/Netdisco/DB/Result/Device.pm | 2 ++ lib/App/Netdisco/DB/Result/DevicePort.pm | 2 ++ lib/App/Netdisco/Util/Permission.pm | 27 +++++++++++++++++++ .../App-Netdisco-DB-80-81-PostgreSQL.sql | 7 +++++ xt/20-checkacl.t | 17 ++++++++++++ 6 files changed, 56 insertions(+), 1 deletion(-) create mode 100644 share/schema_versions/App-Netdisco-DB-80-81-PostgreSQL.sql diff --git a/lib/App/Netdisco/DB.pm b/lib/App/Netdisco/DB.pm index b9a6e1e2..67e0099e 100644 --- a/lib/App/Netdisco/DB.pm +++ b/lib/App/Netdisco/DB.pm @@ -11,7 +11,7 @@ __PACKAGE__->load_namespaces( ); our # try to hide from kwalitee - $VERSION = 80; # schema version used for upgrades, keep as integer + $VERSION = 81; # schema version used for upgrades, keep as integer use Path::Class; use File::ShareDir 'dist_dir'; diff --git a/lib/App/Netdisco/DB/Result/Device.pm b/lib/App/Netdisco/DB/Result/Device.pm index 5623fcc9..4e7d1343 100644 --- a/lib/App/Netdisco/DB/Result/Device.pm +++ b/lib/App/Netdisco/DB/Result/Device.pm @@ -87,6 +87,8 @@ __PACKAGE__->add_columns( { data_type => "boolean", is_nullable => 1 }, "custom_fields", { data_type => "jsonb", is_nullable => 0, default_value => \"{}" }, + "tags", + { data_type => "text[]", is_nullable => 0, default_value => \"'{}'::text[]" }, ); __PACKAGE__->set_primary_key("ip"); diff --git a/lib/App/Netdisco/DB/Result/DevicePort.pm b/lib/App/Netdisco/DB/Result/DevicePort.pm index 202003c2..2f8d8a34 100644 --- a/lib/App/Netdisco/DB/Result/DevicePort.pm +++ b/lib/App/Netdisco/DB/Result/DevicePort.pm @@ -73,6 +73,8 @@ __PACKAGE__->add_columns( { data_type => "bigint", is_nullable => 1 }, "custom_fields", { data_type => "jsonb", is_nullable => 0, default_value => \"{}" }, + "tags", + { data_type => "text[]", is_nullable => 0, default_value => \"'{}'::text[]" }, ); __PACKAGE__->set_primary_key("port", "ip"); diff --git a/lib/App/Netdisco/Util/Permission.pm b/lib/App/Netdisco/Util/Permission.pm index 653ad6b2..009fd0b0 100644 --- a/lib/App/Netdisco/Util/Permission.pm +++ b/lib/App/Netdisco/Util/Permission.pm @@ -201,6 +201,33 @@ sub check_acl { next RULE; } + if ($rule =~ m/^tag:(.+)$/) { + my $tag = $1; + my $found = false; + + ITEM: foreach my $item (@$things) { + if (blessed $item) { + if ($neg xor ($item->can('tags') and ref [] eq ref $item->tags + and scalar grep {$_ eq $tag} @{ $item->tags })) { + return true if not $all; + $found = true; + last ITEM; + } + } + elsif (ref {} eq ref $item) { + if ($neg xor (exists $item->{'tags'} and ref [] eq ref $item->{'tags'} + and scalar grep {$_ eq $tag} @{ $item->{'tags'} })) { + return true if not $all; + $found = true; + last ITEM; + } + } + } + + return false if $all and not $found; + next RULE; + } + # prop:val # with a check that prop isn't just the first part of a v6 addr if ($rule =~ m/^([^:]+):(.*)$/ and $1 !~ m/^[a-f0-9]+$/i) { diff --git a/share/schema_versions/App-Netdisco-DB-80-81-PostgreSQL.sql b/share/schema_versions/App-Netdisco-DB-80-81-PostgreSQL.sql new file mode 100644 index 00000000..62bbc373 --- /dev/null +++ b/share/schema_versions/App-Netdisco-DB-80-81-PostgreSQL.sql @@ -0,0 +1,7 @@ +BEGIN; + +ALTER TABLE device ADD COLUMN "tags" text[] DEFAULT '{}' NOT NULL; + +ALTER TABLE device_port ADD COLUMN "tags" text[] DEFAULT '{}' NOT NULL; + +COMMIT; diff --git a/xt/20-checkacl.t b/xt/20-checkacl.t index 523ed96e..823ca27e 100644 --- a/xt/20-checkacl.t +++ b/xt/20-checkacl.t @@ -150,6 +150,12 @@ my $dp = App::Netdisco::DB->resultset('DevicePort')->new_result({ ip => '127.0.0.1', port => 'TenGigabitEthernet1/10', type => 'l3ipvlan', + tags => [qw/ foo bar baz /], +}); + +my $d = App::Netdisco::DB->resultset('Device')->new_result({ + ip => '127.0.0.1', + tags => [qw/ quux /], }); # device properties @@ -167,6 +173,7 @@ is(acl_matches([$dip2, $dp], ['foobar:xyz']), 0, '2obj unknown property'); my $dip2c = { $dip2->get_inflated_columns }; my $dpc = { $dp->get_inflated_columns }; +my $dc = { $d->get_inflated_columns }; # device properties ok(acl_matches([$dip2c, $dpc], [$conf[23]]), 'hh instance anon property deviceport:alias'); @@ -204,4 +211,14 @@ ok(acl_matches([$dip2c, $dp], ['remote_ip:']), 'ho related item field empty'); ok(acl_matches([$dip2c, $dp], ['!type:']), 'ho related item field not empty'); is(acl_matches([$dip2c, $dp], ['foobar:xyz']), 0, 'ho unknown property'); +# tags + +ok(acl_matches([$dip2, $dp], ['tag:foo']), '2obj tag exists'); +ok(acl_matches([$dip2, $dp], ['!tag:quux']), '2obj tag not existing'); +is(acl_matches([$dp], ['tag:quux']), 0, '1obh tag does not exist'); + +ok(acl_matches([$dip2c, $dpc], ['tag:foo']), 'hh tag exists'); +ok(acl_matches([$dip2c, $dpc], ['!tag:quux']), 'hh tag not existing'); +is(acl_matches([$dpc], ['tag:quux']), 0, 'hh tag does not exist'); + done_testing;