LP#1843818: remote authentication fixes user/jeffdavis/lp1843818-remoteauth-fixes-rebased
authorJeff Davis <jeff.davis@bc.libraries.coop>
Tue, 30 Jun 2020 22:20:00 +0000 (15:20 -0700)
committerJeff Davis <jeff.davis@bc.libraries.coop>
Wed, 30 Sep 2020 20:28:39 +0000 (13:28 -0700)
- Remove the "allow_inactive" option.  It didn't work, supporting it
  would require a lot of extra code, and we don't allow inactive users
  to authenticate in other contexts.

- Use a SERIAL primary key on config.remoteauth_profile so that profile
  names can be changed in the client.

- Simplify the reporter label to "Remote Authentication Profile" while
  we're at it.

Signed-off-by: Jeff Davis <jeff.davis@bc.libraries.coop>
Open-ILS/examples/fm_IDL.xml
Open-ILS/src/perlmods/lib/OpenILS/WWW/RemoteAuth.pm
Open-ILS/src/sql/Pg/150.remoteauth.sql
Open-ILS/src/sql/Pg/upgrade/XXXX.schema.remoteauth_pkey.sql [new file with mode: 0644]
Open-ILS/tests/datasets/sql/remoteauth.sql
docs/TechRef/remoteauth.adoc

index a312c10..dde2bd0 100644 (file)
@@ -13407,15 +13407,15 @@ SELECT  usr,
                </permacrud>
        </class>
 
-       <class id="cra" controller="open-ils.cstore open-ils.pcrud" oils_obj:fieldmapper="config::remoteauth_profile" oils_persist:tablename="config.remoteauth_profile" reporter:label="Remote Patron Authentication Configuration Profile">
-               <fields oils_persist:primary="name">
+       <class id="cra" controller="open-ils.cstore open-ils.pcrud" oils_obj:fieldmapper="config::remoteauth_profile" oils_persist:tablename="config.remoteauth_profile" reporter:label="Remote Authentication Profile">
+               <fields oils_persist:primary="id">
+                       <field name="id"                reporter:datatype="id"       reporter:label="ID" oils_obj:required="true"/>
                        <field name="name"              reporter:datatype="text"     reporter:label="Name" oils_obj:required="true"/>
                        <field name="description"       reporter:datatype="text"     reporter:label="Description"/>
                        <field name="context_org"       reporter:datatype="org_unit" reporter:label="Context Org" oils_obj:required="true"/>
                        <field name="enabled"           reporter:datatype="bool"     reporter:label="Enabled"/>
                        <field name="perm"              reporter:datatype="link"     reporter:label="Permission Required by User" oils_obj:required="true"/>
                        <field name="restrict_to_org"   reporter:datatype="bool"     reporter:label="Restrict by Home Library"/>
-                       <field name="allow_inactive"    reporter:datatype="bool"     reporter:label="Allow Inactive Users"/>
                        <field name="allow_expired"     reporter:datatype="bool"     reporter:label="Allow Expired Users"/>
                        <field name="block_list"        reporter:datatype="text"     reporter:label="Block List"/>
                        <field name="usr_activity_type" reporter:datatype="link"     reporter:label="User Activity Type"/>
index 863796d..e91ceed 100644 (file)
@@ -93,7 +93,7 @@ sub load_config {
     return undef unless $name;
 
     # load config
-    my $config = $e->retrieve_config_remoteauth_profile($name);
+    my $config = $e->search_config_remoteauth_profile({name => $name})->[0];
     if ($config and $U->is_true($config->enabled)) {
         return $config;
     }
index 0b36c49..4f08f36 100644 (file)
@@ -1,13 +1,13 @@
 BEGIN;
 
 CREATE TABLE config.remoteauth_profile (
-    name TEXT PRIMARY KEY,
+    id SERIAL PRIMARY KEY,
+    name TEXT UNIQUE NOT NULL,
     description TEXT,
     context_org INT NOT NULL REFERENCES actor.org_unit(id) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
     enabled BOOLEAN NOT NULL DEFAULT FALSE,
     perm INT NOT NULL REFERENCES permission.perm_list(id) ON UPDATE CASCADE ON DELETE RESTRICT DEFERRABLE INITIALLY DEFERRED,
     restrict_to_org BOOLEAN NOT NULL DEFAULT TRUE,
-    allow_inactive BOOL NOT NULL DEFAULT FALSE,
     allow_expired BOOL NOT NULL DEFAULT FALSE,
     block_list TEXT,
     usr_activity_type INT REFERENCES config.usr_activity_type(id) ON UPDATE CASCADE ON DELETE RESTRICT DEFERRABLE INITIALLY DEFERRED
@@ -50,10 +50,6 @@ BEGIN
         RETURN 'expired';
     END IF;
 
-    IF usr.active IS FALSE AND profile.allow_inactive IS FALSE THEN
-        RETURN 'blocked';
-    END IF;
-
     -- Proximity of user's home_ou to context_org to see if penalties should be ignored.
     SELECT INTO home_prox prox FROM actor.org_unit_proximity WHERE from_org = usr.home_ou AND to_org = profile.context_org;
 
diff --git a/Open-ILS/src/sql/Pg/upgrade/XXXX.schema.remoteauth_pkey.sql b/Open-ILS/src/sql/Pg/upgrade/XXXX.schema.remoteauth_pkey.sql
new file mode 100644 (file)
index 0000000..5340edd
--- /dev/null
@@ -0,0 +1,114 @@
+BEGIN;
+
+SELECT evergreen.upgrade_deps_block_check('XXXX', :eg_version);
+
+-- The allow_inactive rule is deprecated, warn if any profile is using it.
+DO $SQL$
+BEGIN
+    PERFORM * FROM config.remoteauth_profile WHERE allow_inactive IS TRUE;
+    IF FOUND THEN
+        RAISE NOTICE 'Remote authentication is no longer supported for inactive users.';
+    END IF;
+END;
+$SQL$;
+
+
+DROP FUNCTION IF EXISTS actor.permit_remoteauth(TEXT, BIGINT);
+
+
+-- Add a new SERIAL primary key (the "id" column) to config.remoteauth_profile.
+-- The primary key is generally the first column, so we drop and recreate the
+-- config.remoteauth_profile table (preserving any existing data, of course) in
+-- order to get our desired column order.
+CREATE TABLE config.remoteauth_profile_temp (
+    id SERIAL PRIMARY KEY,
+    name TEXT UNIQUE NOT NULL,
+    description TEXT,
+    context_org INT NOT NULL REFERENCES actor.org_unit(id) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
+    enabled BOOLEAN NOT NULL DEFAULT FALSE,
+    perm INT NOT NULL REFERENCES permission.perm_list(id) ON UPDATE CASCADE ON DELETE RESTRICT DEFERRABLE INITIALLY DEFERRED,
+    restrict_to_org BOOLEAN NOT NULL DEFAULT TRUE,
+    allow_expired BOOL NOT NULL DEFAULT FALSE,
+    block_list TEXT,
+    usr_activity_type INT REFERENCES config.usr_activity_type(id) ON UPDATE CASCADE ON DELETE RESTRICT DEFERRABLE INITIALLY DEFERRED
+);
+
+INSERT INTO config.remoteauth_profile_temp (
+    name, description, context_org, enabled, perm, restrict_to_org,
+    allow_expired, block_list, usr_activity_type
+) SELECT
+    name, description, context_org, enabled, perm, restrict_to_org,
+    allow_expired, block_list, usr_activity_type
+FROM config.remoteauth_profile;
+
+DROP TABLE config.remoteauth_profile;
+ALTER TABLE config.remoteauth_profile_temp RENAME TO remoteauth_profile;
+
+
+CREATE OR REPLACE FUNCTION actor.permit_remoteauth (profile_name TEXT, userid BIGINT) RETURNS TEXT AS $func$
+DECLARE
+    usr               actor.usr%ROWTYPE;
+    profile           config.remoteauth_profile%ROWTYPE;
+    perm              TEXT;
+    context_org_list  INT[];
+    home_prox         INT;
+    block             TEXT;
+    penalty_count     INT;
+BEGIN
+
+    SELECT INTO usr * FROM actor.usr WHERE id = userid AND NOT deleted;
+    IF usr IS NULL THEN
+        RETURN 'not_found';
+    END IF;
+
+    IF usr.barred IS TRUE THEN
+        RETURN 'blocked';
+    END IF;
+
+    SELECT INTO profile * FROM config.remoteauth_profile WHERE name = profile_name;
+    SELECT INTO context_org_list ARRAY_AGG(id) FROM actor.org_unit_full_path( profile.context_org );
+
+    -- user's home library must be within the context org
+    IF profile.restrict_to_org IS TRUE AND usr.home_ou NOT IN (SELECT * FROM UNNEST(context_org_list)) THEN
+        RETURN 'not_found';
+    END IF;
+
+    SELECT INTO perm code FROM permission.perm_list WHERE id = profile.perm;
+    IF permission.usr_has_perm(usr.id, perm, profile.context_org) IS FALSE THEN
+        RETURN 'not_found';
+    END IF;
+    
+    IF usr.expire_date < NOW() AND profile.allow_expired IS FALSE THEN
+        RETURN 'expired';
+    END IF;
+
+    -- Proximity of user's home_ou to context_org to see if penalties should be ignored.
+    SELECT INTO home_prox prox FROM actor.org_unit_proximity WHERE from_org = usr.home_ou AND to_org = profile.context_org;
+
+    -- Loop through the block list to see if the user has any matching penalties.
+    IF profile.block_list IS NOT NULL THEN
+        FOR block IN SELECT UNNEST(STRING_TO_ARRAY(profile.block_list, '|')) LOOP
+            SELECT INTO penalty_count COUNT(DISTINCT csp.*)
+                FROM  actor.usr_standing_penalty usp
+                        JOIN config.standing_penalty csp ON (csp.id = usp.standing_penalty)
+                WHERE usp.usr = usr.id
+                        AND usp.org_unit IN ( SELECT * FROM UNNEST(context_org_list) )
+                        AND ( usp.stop_date IS NULL or usp.stop_date > NOW() )
+                        AND ( csp.ignore_proximity IS NULL OR csp.ignore_proximity < home_prox )
+                        AND csp.block_list ~ block;
+            IF penalty_count > 0 THEN
+                -- User has penalties that match this block, so auth is not permitted.
+                -- Don't bother testing the rest of the block list.
+                RETURN 'blocked';
+            END IF;
+        END LOOP;
+    END IF;
+
+    -- User has passed all tests.
+    RETURN 'success';
+
+END;
+$func$ LANGUAGE plpgsql;
+
+COMMIT;
+
index 93c2e81..349c51d 100644 (file)
@@ -5,9 +5,9 @@ INSERT INTO config.usr_activity_type (id, ewho, ewhat, ehow, egroup, label) VALU
 -- config for Basic HTTP Authentication (SYS1)
 INSERT INTO config.remoteauth_profile
     (name, description, context_org, enabled, perm,
-        restrict_to_org, allow_inactive, allow_expired, block_list, usr_activity_type)
+        restrict_to_org, allow_expired, block_list, usr_activity_type)
     VALUES ('Basic', 'Basic HTTP Authentication for SYS1', 2, TRUE, 1,
-        TRUE, FALSE, FALSE, NULL, 1001);
+        TRUE, FALSE, NULL, 1001);
 
 INSERT INTO config.usr_activity_type (id, ewho, ewhat, ehow, egroup, label) VALUES
  ( 1002, 'ezproxy', 'login', 'apache', 'authen',
@@ -16,9 +16,9 @@ INSERT INTO config.usr_activity_type (id, ewho, ewhat, ehow, egroup, label) VALU
 -- config for EZProxy CGI Authentication (SYS2)
 INSERT INTO config.remoteauth_profile
     (name, description, context_org, enabled, perm,
-        restrict_to_org, allow_inactive, allow_expired, block_list, usr_activity_type)
+        restrict_to_org, allow_expired, block_list, usr_activity_type)
     VALUES ('EZProxyCGI', 'EZProxy CGI Authentication for SYS2', 3, TRUE, 1,
-        TRUE, FALSE, FALSE, NULL, 1002);
+        TRUE, FALSE, NULL, 1002);
 
 INSERT INTO config.usr_activity_type (id, ewho, ewhat, ehow, egroup, label) VALUES
  ( 1003, 'patronapi', 'login', 'apache', 'authen',
@@ -27,7 +27,7 @@ INSERT INTO config.usr_activity_type (id, ewho, ewhat, ehow, egroup, label) VALU
 -- config for PatronAPI Authentication (SYS1)
 INSERT INTO config.remoteauth_profile
     (name, description, context_org, enabled, perm,
-        restrict_to_org, allow_inactive, allow_expired, block_list, usr_activity_type)
+        restrict_to_org, allow_expired, block_list, usr_activity_type)
     VALUES ('PatronAPI', 'PatronAPI Authentication for SYS1', 2, TRUE, 1,
-        TRUE, FALSE, FALSE, NULL, 1003);
+        TRUE, FALSE, NULL, 1003);
 
index ceb61d6..4b0989d 100644 (file)
@@ -66,7 +66,6 @@ In each endpoint's Apache configuration, the OILSRemoteAuthProfile variable spec
 
 * *perm:* a permission which the user must have in order to be authenticated via RemoteAuth
 * *restrict_to_org:* only allow users belonging to this location to be authenticated, even if the client can retrieve users at other locations
-* *allow_inactive:* allow authentication for inactive users
 * *allow_expired:* allow authentication for expired users
 * *block_list:* authentication is not permitted if the user has a standing penalty at this location with one of these blocks