Add initial tests for the web UI

Use Test::WWW::Mechanize to test the UI.

Closes #310
This commit is contained in:
Andrew Ruthven 2024-02-24 14:37:28 +13:00
parent 3a08e80d5a
commit c2a054d28e
17 changed files with 709 additions and 5 deletions

View File

@ -45,7 +45,7 @@ test:
always
script:
- testing/gitlab_ci_script.sh
- cd testing && su testrunner -c 'IS_CI=yes ALLSUITES="regression-suite binding carddav scheduling" ./run_regressions.sh all x'
- cd testing && su testrunner -c 'IS_CI=yes ALLSUITES="regression-suite binding carddav scheduling webui" ./run_regressions.sh all x'
after_script:
- testing/gitlab_ci_after_script.sh build
@ -118,7 +118,7 @@ test_bullseye_latestphp:
always
script:
- testing/gitlab_ci_script.sh
- cd testing && su testrunner -c 'IS_CI=yes ALLSUITES="regression-suite binding carddav scheduling" ./run_regressions.sh all x'
- cd testing && su testrunner -c 'IS_CI=yes ALLSUITES="regression-suite binding carddav scheduling webui" ./run_regressions.sh all x'
after_script:
- testing/gitlab_ci_after_script.sh
@ -136,7 +136,7 @@ test_memcache:
always
script:
- testing/gitlab_ci_script.sh memcache
- cd testing && su testrunner -c 'IS_CI=yes ALLSUITES="regression-suite binding carddav scheduling" ./run_regressions.sh all x'
- cd testing && su testrunner -c 'IS_CI=yes ALLSUITES="regression-suite binding carddav scheduling webui" ./run_regressions.sh all x'
after_script:
- testing/gitlab_ci_after_script.sh

View File

@ -23,6 +23,7 @@ my $testdef;
my $suite;
my $case;
my $helpmeplease = 0;
my $testmode = 'DAVICAL';
# Hash for eval'd Perl code to store long lived variables in
my %evaled;
@ -137,6 +138,22 @@ while( <TEST> ) {
push @arguments, "--include";
};
$line =~ /^\s*MODE\s*=\s*(\S*)(?:,(\d+))/ && do {
my $mode = $1;
my $args = $2;
if (uc($mode) =~ /^TAP$/) {
$testmode = 'TAP';
use Test::More;
if ($args =~ /^\d+$/) {
plan tests => $args;
}
} else {
die "Unknown test mode: $1";
}
};
$line =~ /^\s*VERBOSE\s*(#|$|=)/ && do {
push @arguments, "--verbose";
};
@ -332,6 +349,10 @@ if ( defined($queries) && @{$queries} ) {
}
}
if ($testmode eq 'TAP') {
done_testing();
}
exit(0);
@ -382,12 +403,46 @@ Opens the database connection to the global $dbh handle.
Note that the standard PostgreSQL environment variables will also work
with DBD::Pg.
=cut
sub opendb {
$dsn = "dbi:Pg:dbname=$dsn";
$dbh = DBI->connect($dsn, $dbuser, $dbpass, { AutoCommit => 1 } ) or die "Can't connect to database $dsn";
$dbh->do("SET TIMEZONE TO 'Pacific/Auckland'");
}
sub webui_login {
my %args = (
username => undef,
password => undef,
url => undef,
failauth => 0,
@_,
);
use Test::WWW::Mechanize;
my $mech = Test::WWW::Mechanize->new;
$mech->get_ok($args{url}, "Fetch first page");
$mech->text_contains('Log On Please', "Not logged in");
$mech->submit_form_ok(
{
fields => {
username => $args{username},
password => $args{password},
},
}, "Login to site"
);
if ($args{failauth}) {
# We expected failure.
$mech->text_contains("You must log in to use this system", "Failed to login");
} else {
$mech->text_contains("You are logged on as " . $args{username}, "Logged in");
}
return $mech;
}
sub usage {
print <<EOERROR ;

View File

@ -51,7 +51,7 @@ echo "LANG=en_NZ.UTF-8" > /etc/default/locale
###
packages="libdbd-pg-perl libyaml-perl postgresql-client postgresql
curl xmlstarlet netcat-openbsd"
curl xmlstarlet netcat-openbsd libtest-www-mechanize-perl"
if [[ $mode =~ "latestphp" ]]; then
# PHP pgsql package is built from source, needs libpq-dev.

View File

@ -18,7 +18,7 @@ REPORTFILE=report.xml
export PGTZ=Pacific/Auckland
#export TZ=Pacific/Auckland
ALLSUITES=${ALLSUITES:-"regression-suite binding carddav scheduling timezone"}
ALLSUITES=${ALLSUITES:-"regression-suite binding carddav scheduling timezone webui"}
# who wants meld if they can have xxdiff? Go on, override it in regression.conf
MELD=meld

View File

@ -0,0 +1,5 @@
1..4
ok 1 - Fetch first page
ok 2 - Not logged in
ok 3 - Login to site
ok 4 - Logged in

View File

@ -0,0 +1,11 @@
MODE=TAP,4
BEGINPERL
my $mech = webui_login(
username => 'user1',
password => 'user1',
url => "http://$webhost",
);
ENDPERL

View File

@ -0,0 +1,5 @@
1..4
ok 1 - Fetch first page
ok 2 - Not logged in
ok 3 - Login to site
ok 4 - Failed to login

View File

@ -0,0 +1,12 @@
MODE=TAP,4
BEGINPERL
my $mech = webui_login(
username => 'user1',
password => 'bogus_password',
url => "http://$webhost",
failauth => 1,
);
ENDPERL

View File

@ -0,0 +1,95 @@
1..30
# Subtest: Login
1..4
ok 1 - Fetch first page
ok 2 - Not logged in
ok 3 - Login to site
ok 4 - Logged in
ok 1 - Login
ok 2 - Username field correct
ok 3 - princial_id hidden field correct
ok 4 - New password field 1 correct
ok 5 - New password field 2 correct
ok 6 - Fullname field correct
ok 7 - Email field correct
ok 8 - Locale field correct
ok 9 - Date format type field correct
ok 10 - Type field correct
ok 11 - Is Admin field missing
ok 12 - User Active field missing
ok 13 - Submit mismatching passwords
ok 14 - Password change error displayed
# Subtest: Login with old password
1..4
ok 1 - Fetch first page
ok 2 - Not logged in
ok 3 - Login to site
ok 4 - Logged in
ok 15 - Login with old password
ok 16 - Submit matching passwords
ok 17 - Password change error displayed
# Subtest: Login with old password
1..4
ok 1 - Fetch first page
ok 2 - Not logged in
ok 3 - Login to site
ok 4 - Failed to login
ok 18 - Login with old password
# Subtest: Login with new password
1..4
ok 1 - Fetch first page
ok 2 - Not logged in
ok 3 - Login to site
ok 4 - Logged in
ok 19 - Login with new password
ok 20 - Submit updated fields
# Subtest: Login with new username
1..4
ok 1 - Fetch first page
ok 2 - Not logged in
ok 3 - Login to site
ok 4 - Logged in
ok 21 - Login with new username
ok 22 - Username field correct
ok 23 - princial_id hidden field correct
ok 24 - Fullname field correct
ok 25 - Email field correct
ok 26 - Locale field correct
ok 27 - Date format type field correct
ok 28 - Type field correct
ok 29 - Submit updated fields on a user we have no access to
ok 30 - Error message denying access displayed
date_format_type: >I<
dav_name: >/user99/<
displayname: >User 99<
email: >user99@example.net<
fullname: >User 99<
locale: >en<
password_changed: >1<
type_id: >2<
user_active: >1<
username: >user99<
date_format_type: >E<
dav_name: >/user1/<
displayname: >User 1<
email: >user1@example.net<
fullname: >User 1<
locale: >NULL<
password_same: >1<
type_id: >1<
user_active: >1<
username: >user1<
date_format_type: >E<
dav_name: >/user2/<
displayname: >User 2<
email: >user2@example.net<
fullname: >User 2<
locale: >NULL<
password_same: >0<
type_id: >1<
user_active: >1<
username: >user2<

View File

@ -0,0 +1,192 @@
MODE=TAP,30
BEGINPERL
my $mech;
subtest 'Login' => sub {
plan tests => 4;
$mech = webui_login(
username => 'user4',
password => 'user4',
url => "http://$webhost",
);
};
$mech->follow_link( text_regex => qr/View My Details/ );
$mech->form_number(1);
is($mech->value('xxxxusername'), 'user4', 'Username field correct');
is($mech->value('principal_id'), '1005', 'princial_id hidden field correct');
is($mech->value('newpass1'), '@@@@@@@@@@', 'New password field 1 correct');
is($mech->value('newpass2'), '@@@@@@@@@@', 'New password field 2 correct');
is($mech->value('fullname'), 'User 4', 'Fullname field correct');
is($mech->value('email'), 'user4@example.net', 'Email field correct');
is($mech->value('locale'), '', 'Locale field correct');
is($mech->value('date_format_type'), 'E', 'Date format type field correct');
is($mech->value('type_id'), 1, 'Type field correct');
$mech->content_lacks('is_admin', 'Is Admin field missing');
$mech->content_lacks('user_active', 'User Active field missing');
# Test invalid password change
$mech->submit_form_ok(
{
form_number => 1,
button => 'submit',
fields => {
newpass1 => 'a password',
newpass2 => 'a different password',
},
}, "Submit mismatching passwords"
);
#$mech->save_content('/tmp/form.html');
$mech->content_contains(
'Password not updated. The supplied passwords do not match.',
'Password change error displayed')
|| BAIL_OUT("Password error not returned, all hope is lost");
# Ensure we can still login with the old password.
subtest 'Login with old password' => sub {
plan tests => 4;
$mech = webui_login(
username => 'user4',
password => 'user4',
url => "http://$webhost",
);
};
$mech->follow_link( text_regex => qr/View My Details/ );
# Test valid password change
$mech->submit_form_ok(
{
form_number => 1,
button => 'submit',
fields => {
newpass1 => 'a new password',
newpass2 => 'a new password',
},
}, "Submit matching passwords"
);
$mech->content_lacks(
'Password not updated. The supplied passwords do not match.',
'Password change error displayed')
|| BAIL_OUT("Password has probably not changed, all hope is lost");
# Ensure we can't login with the old password.
subtest 'Login with old password' => sub {
plan tests => 4;
$mech = webui_login(
username => 'user4',
password => 'user4',
url => "http://$webhost",
failauth => 1,
);
};
# Ensure we can login with the new password.
subtest 'Login with new password' => sub {
plan tests => 4;
$mech = webui_login(
username => 'user4',
password => 'a new password',
url => "http://$webhost",
);
};
$mech->follow_link( text_regex => qr/View My Details/ );
$mech->form_number(1);
# Non-admin user shouldn't be able to change is_admin or user_active flags.
$mech->submit_form_ok(
{
form_number => 1,
button => 'submit',
fields => {
xxxxusername => 'user99',
fullname => 'User 99',
email => 'user99@example.net',
locale => 'en',
date_format_type => 'I',
type_id => 2,
is_admin => 'off',
user_active => 'off',
},
}, "Submit updated fields"
);
# Ensure we can login with new username.
subtest 'Login with new username' => sub {
plan tests => 4;
$mech = webui_login(
username => 'user99',
password => 'a new password',
url => "http://$webhost",
);
};
$mech->follow_link( text_regex => qr/View My Details/ );
$mech->form_number(1);
is($mech->value('xxxxusername'), 'user99', 'Username field correct');
is($mech->value('principal_id'), '1005', 'princial_id hidden field correct');
is($mech->value('fullname'), 'User 99', 'Fullname field correct');
is($mech->value('email'), 'user99@example.net', 'Email field correct');
is($mech->value('locale'), 'en', 'Locale field correct');
is($mech->value('date_format_type'), 'I', 'Date format type field correct');
is($mech->value('type_id'), 2, 'Type field correct');
my $action = $mech->form_number(1)->action;
$action =~ s/id=1005/id=1003/;
$mech->form_number(1)->action($action);
# Submit changes to another user we don't have access to.
$mech->submit_form_ok(
{
form_number => 1,
button => 'submit',
fields => {
principal_id => '1003',
xxxxusername => 'user98',
newpass1 => 'another password',
newpass2 => 'another password',
fullname => 'User 98',
email => 'user98@example.net',
locale => 'en',
date_format_type => 'I',
type_id => 2,
is_admin => 'off',
user_active => 'off',
},
}, "Submit updated fields on a user we have no access to"
);
$mech->content_contains(
'You do not have permission to modify this record.',
'Error message denying access displayed');
ENDPERL
# Test the user we modified to ensure fields are set.
BEGINQUERY
SELECT user_active, username, password <> '**user4' AS password_changed, fullname, email,
date_format_type, locale, type_id, displayname, dav_name
FROM dav_principal
WHERE principal_id = 1005;
ENDQUERY
# Test some other users we've not modified to ensure they've stayed the same.
BEGINQUERY
SELECT user_active, username, password = '**user1' AS password_same, fullname, email,
date_format_type, locale, type_id, displayname, dav_name
FROM dav_principal
WHERE principal_id IN (1002, 1003)
ORDER BY principal_id;
ENDQUERY

View File

@ -0,0 +1,22 @@
Supported locales updated.
Updated view: dav_principal.sql applied.
CalDAV functions updated.
RRULE functions updated.
Database permissions updated.
The database is version XX currently at revision 1.3.5.
Applying patch 1.3.6.sql ... succeeded.
Successfully applied 1 patches.
Supported locales updated.
Updated view: dav_principal.sql applied.
CalDAV functions updated.
RRULE functions updated.
Database permissions updated.
NOTE
====
* The password for the 'admin' user has been set to 'nimda'
Thanks for trying DAViCal! Check the configuration in /etc/davical/config.php.
For help, look at our website and wiki, or visit #davical on irc.oftc.net.

View File

View File

@ -0,0 +1,15 @@
setval
--------
1000
(1 row)
setval
--------
1000
(1 row)
setval
--------
10
(1 row)

View File

@ -0,0 +1,7 @@
The database is version XX currently at revision 1.3.6.
No patches were applied.
Supported locales updated.
Updated view: dav_principal.sql applied.
CalDAV functions updated.
RRULE functions updated.
Database permissions updated.

View File

@ -0,0 +1,140 @@
set_config
------------
public
(1 row)
setval
--------
1015
(1 row)
setval
--------
1
(1 row)
setval
--------
1
(1 row)
setval
--------
1
(1 row)
setval
--------
1
(1 row)
setval
--------
1
(1 row)
setval
--------
1
(1 row)
setval
--------
1
(1 row)
setval
--------
1
(1 row)
setval
--------
1
(1 row)
setval
--------
1
(1 row)
setval
--------
1
(1 row)
setval
--------
1
(1 row)
setval
--------
1
(1 row)
setval
--------
1
(1 row)
setval
--------
1
(1 row)
setval
--------
1
(1 row)
setval
--------
1
(1 row)
setval
--------
1
(1 row)
setval
--------
1
(1 row)
setval
--------
4
(1 row)
setval
--------
10
(1 row)
setval
--------
10
(1 row)
setval
--------
1
(1 row)
setval
--------
1
(1 row)
setval
--------
1
(1 row)
setval
--------
1000
(1 row)

View File

@ -0,0 +1,5 @@
Supported locales updated.
Updated view: dav_principal.sql applied.
CalDAV functions updated.
RRULE functions updated.
Database permissions updated.

View File

@ -0,0 +1,140 @@
-- Some sample data to prime the database...
-- base-data.sql should be processed before this
INSERT INTO usr ( user_no, active, email_ok, updated, username, password, fullname, email )
VALUES( 2, TRUE, current_date, current_date, 'andrew', '**x', 'Andrew McMillan', 'andrew@catalyst.net.nz' );
INSERT INTO role_member (user_no, role_no) VALUES( 2, 1);
INSERT INTO usr ( user_no, active, email_ok, updated, username, password, fullname, email )
VALUES( 10, TRUE, current_date, current_date, 'user1', '**user1', 'User 1', 'user1@example.net' );
INSERT INTO usr ( user_no, active, email_ok, updated, username, password, fullname, email )
VALUES( 11, TRUE, current_date, current_date, 'user2', '**user2', 'User 2', 'user2@example.net' );
INSERT INTO usr ( user_no, active, email_ok, updated, username, password, fullname, email )
VALUES( 12, TRUE, current_date, current_date, 'user3', '**user3', 'User 3', 'user3@example.net' );
INSERT INTO usr ( user_no, active, email_ok, updated, username, password, fullname, email )
VALUES( 13, TRUE, current_date, current_date, 'user4', '**user4', 'User 4', 'user4@example.net' );
INSERT INTO usr ( user_no, active, email_ok, updated, username, password, fullname, email )
VALUES( 14, TRUE, current_date, current_date, 'user5', '**user5', 'User 5', 'user5@example.net' );
INSERT INTO usr ( user_no, active, email_ok, updated, username, password, fullname, email )
VALUES( 15, TRUE, current_date, current_date, 'User Six', '**user6', 'User 6', 'user6@example.net' );
INSERT INTO usr ( user_no, active, email_ok, updated, username, password, fullname, email )
VALUES( 20, TRUE, current_date, current_date, 'manager1', '**manager1', 'Manager 1', 'manager1@example.net' );
INSERT INTO usr ( user_no, active, email_ok, updated, username, password, fullname, email )
VALUES( 30, TRUE, current_date, current_date, 'assistant1', '**assistant1', 'Assistant 1', 'assistant1@example.net' );
INSERT INTO usr ( user_no, active, email_ok, updated, username, password, fullname, email )
VALUES( 100, TRUE, current_date, current_date, 'resource1', '*salt*unpossible', 'Resource 1', 'resource1@example.net' );
INSERT INTO role_member (user_no, role_no) VALUES( 100, 4);
INSERT INTO usr ( user_no, active, email_ok, updated, username, password, fullname, email )
VALUES( 101, TRUE, current_date, current_date, 'resource2', '*salt*unpossible', 'Resource 2', 'resource2@example.net' );
INSERT INTO role_member (user_no, role_no) VALUES( 101, 4);
INSERT INTO usr ( user_no, active, email_ok, updated, username, password, fullname, email )
VALUES( 150, TRUE, current_date, current_date, 'room1', '*salt*unpossible', 'Room 1', 'room1@example.net' );
INSERT INTO role_member (user_no, role_no) VALUES( 150, 5);
INSERT INTO usr ( user_no, active, email_ok, updated, username, password, fullname, email )
VALUES( 151, TRUE, current_date, current_date, 'room2', '*salt*unpossible', 'Room 2', 'room2@example.net' );
INSERT INTO role_member (user_no, role_no) VALUES( 151, 5);
INSERT INTO usr ( user_no, active, email_ok, updated, username, password, fullname, email )
VALUES( 200, TRUE, current_date, current_date, 'resmgr1', '*salt*unpossible', 'Resource Managers', 'resource-managers@example.net' );
INSERT INTO role_member (user_no, role_no) VALUES( 200, 2);
INSERT INTO usr ( user_no, active, email_ok, updated, username, password, fullname, email )
VALUES( 300, TRUE, current_date, current_date, 'teamclient1', '*salt*unpossible', 'Team for Client1', 'team-client1@example.net' );
INSERT INTO role_member (user_no, role_no) VALUES( 300, 2);
SELECT setval('usr_user_no_seq', 1000);
SELECT setval('dav_id_seq', 1000);
UPDATE usr SET joined = '2009-06-01', updated = '2009-06-02';
INSERT INTO collection (user_no, parent_container, dav_name, dav_etag,
dav_displayname, is_calendar, created, modified,
public_events_only, publicly_readable, collection_id, resourcetypes )
SELECT user_no, '/' || username || '/', '/' || username || '/home/', md5(username),
username || ' home', TRUE, '2009-06-03', '2009-06-04',
FALSE, FALSE, user_no + 150, '<DAV::collection/><urn:ietf:params:xml:ns:caldav:calendar/>'
FROM usr ORDER BY user_no;
INSERT INTO collection (user_no, parent_container, dav_name, dav_etag,
dav_displayname, is_calendar, is_addressbook, created, modified,
public_events_only, publicly_readable, collection_id, resourcetypes )
SELECT user_no, '/' || username || '/', '/' || username || '/addresses/', md5(username),
username || ' addresses', FALSE, TRUE, '1957-07-26', '1998-03-16',
FALSE, FALSE, user_no + 450, '<DAV::collection/><urn:ietf:params:xml:ns:carddav:addressbook/>'
FROM usr ORDER BY user_no;
INSERT INTO principal (type_id, user_no, displayname, default_privileges)
SELECT 1, user_no, fullname, privilege_to_bits(ARRAY['read-free-busy','schedule-send','schedule-deliver']) FROM usr
WHERE NOT EXISTS(SELECT 1 FROM role_member JOIN roles USING(role_no) WHERE role_name = 'Group' AND role_member.user_no = usr.user_no)
AND NOT EXISTS(SELECT 1 FROM role_member JOIN roles USING(role_no) WHERE role_name = 'Resource' AND role_member.user_no = usr.user_no)
AND NOT EXISTS(SELECT 1 FROM role_member JOIN roles USING(role_no) WHERE role_name = 'Room' AND role_member.user_no = usr.user_no)
AND NOT EXISTS(SELECT 1 FROM principal WHERE principal.user_no = usr.user_no) ORDER BY user_no;
INSERT INTO principal (type_id, user_no, displayname, default_privileges)
SELECT 2, user_no, fullname, privilege_to_bits(ARRAY['read','schedule-send','schedule-deliver']) FROM usr
WHERE EXISTS(SELECT 1 FROM role_member JOIN roles USING(role_no) WHERE role_name = 'Resource' AND role_member.user_no = usr.user_no)
AND NOT EXISTS(SELECT 1 FROM principal WHERE principal.user_no = usr.user_no) ORDER BY user_no;
INSERT INTO principal (type_id, user_no, displayname, default_privileges)
SELECT 3, user_no, fullname, privilege_to_bits(ARRAY['read-free-busy','schedule-send','schedule-deliver']) FROM usr
WHERE EXISTS(SELECT 1 FROM role_member JOIN roles USING(role_no) WHERE role_name = 'Group' AND role_member.user_no = usr.user_no)
AND NOT EXISTS(SELECT 1 FROM principal WHERE principal.user_no = usr.user_no) ORDER BY user_no;
INSERT INTO principal (type_id, user_no, displayname, default_privileges)
SELECT 4, user_no, fullname, privilege_to_bits(ARRAY['read-free-busy','schedule-send','schedule-deliver']) FROM usr
WHERE EXISTS(SELECT 1 FROM role_member JOIN roles USING(role_no) WHERE role_name = 'Room' AND role_member.user_no = usr.user_no)
AND NOT EXISTS(SELECT 1 FROM principal WHERE principal.user_no = usr.user_no) ORDER BY user_no;
-- Set the insert sequence to the next number, with a minimum of 1000
SELECT setval('relationship_type_rt_id_seq', (SELECT 10 UNION SELECT rt_id FROM relationship_type ORDER BY 1 DESC LIMIT 1) );
-- The resources for meetings
INSERT INTO relationship ( from_user, to_user, rt_id ) VALUES( 200, 100, 1 );
INSERT INTO relationship ( from_user, to_user, rt_id ) VALUES( 200, 101, 1 );
-- The people who administer meetings
INSERT INTO relationship ( from_user, to_user, rt_id ) VALUES( 10, 200, 1 );
INSERT INTO relationship ( from_user, to_user, rt_id ) VALUES( 11, 200, 1 );
INSERT INTO relationship ( from_user, to_user, rt_id ) VALUES( 30, 200, 1 );
-- Between a PA and their Manager
INSERT INTO relationship ( from_user, to_user, rt_id ) VALUES( 30, 20, 2 );
INSERT INTO relationship ( from_user, to_user, rt_id ) VALUES( 30, 10, 2 );
-- Between a team
INSERT INTO relationship ( from_user, to_user, rt_id ) VALUES( 20, 300, 3 );
INSERT INTO relationship ( from_user, to_user, rt_id ) VALUES( 10, 300, 3 );
INSERT INTO relationship ( from_user, to_user, rt_id ) VALUES( 30, 300, 3 );
INSERT INTO relationship ( from_user, to_user, rt_id ) VALUES( 300, 20, 3 );
INSERT INTO relationship ( from_user, to_user, rt_id ) VALUES( 300, 10, 3 );
INSERT INTO relationship ( from_user, to_user, rt_id ) VALUES( 300, 30, 3 );
-- Granting explicit free/busy permission
INSERT INTO relationship ( from_user, to_user, rt_id ) VALUES( 11, 10, 4 );
INSERT INTO relationship ( from_user, to_user, rt_id ) VALUES( 10, 11, 4 );
UPDATE relationship r SET confers = (SELECT bit_confers FROM relationship_type rt WHERE rt.rt_id=r.rt_id);
INSERT INTO group_member ( group_id, member_id)
SELECT g.principal_id, m.principal_id
FROM relationship JOIN principal g ON(to_user=g.user_no AND g.type_id = 3) -- Group
JOIN principal m ON(from_user=m.user_no AND m.type_id IN (1,2)) ORDER BY 1, 2; -- Person | Resource
INSERT INTO grants ( by_principal, to_principal, privileges, is_group )
SELECT pby.principal_id AS by_principal, pto.principal_id AS to_principal,
confers AS privileges, pto.type_id > 2 AS is_group
FROM relationship r JOIN usr f ON(f.user_no=r.from_user)
JOIN usr t ON(t.user_no=r.to_user)
JOIN principal pby ON(t.user_no=pby.user_no)
JOIN principal pto ON(pto.user_no=f.user_no)
WHERE rt_id < 4 AND pby.type_id < 3 ORDER BY 1, 2;