Update ACL\Login class with _GET/_POST login parameter

loginUserId parameter in _GET or _POST for direct login without username
and password.

This can be secured by:
- must login after x days from set loginUserId on
- can only login with loginUserId in given time range
- flag lock loginUserId
This commit is contained in:
Clemens Schwaighofer
2022-06-22 13:52:47 +09:00
parent c35d3c9324
commit 04e4fe46f2
21 changed files with 1597 additions and 280 deletions

View File

@@ -5,6 +5,7 @@ function/set_edit_generic.sql
function/edit_access_set_uid.sql function/edit_access_set_uid.sql
function/edit_group_set_uid.sql function/edit_group_set_uid.sql
function/edit_log_partition_insert.sql function/edit_log_partition_insert.sql
function/edit_user_set_login_user_id_set_date.sql
# generic tables # generic tables
table/edit_temp_files.sql table/edit_temp_files.sql
table/edit_generic.sql table/edit_generic.sql

View File

@@ -2,7 +2,8 @@
-- create random string with length X -- create random string with length X
CREATE FUNCTION random_string(randomLength int) CREATE FUNCTION random_string(randomLength int)
RETURNS text AS $$ RETURNS text AS
$$
SELECT array_to_string( SELECT array_to_string(
ARRAY( ARRAY(
SELECT substring( SELECT substring(
@@ -14,53 +15,58 @@ SELECT array_to_string(
), ),
'' ''
) )
$$ LANGUAGE SQL $$
LANGUAGE SQL
RETURNS NULL ON NULL INPUT RETURNS NULL ON NULL INPUT
VOLATILE; -- LEAKPROOF;-- END: function/random_string.sql VOLATILE; -- LEAKPROOF;
-- END: function/random_string.sql
-- START: function/set_edit_generic.sql -- START: function/set_edit_generic.sql
-- adds the created or updated date tags -- adds the created or updated date tags
CREATE OR REPLACE FUNCTION set_edit_generic() RETURNS TRIGGER AS ' CREATE OR REPLACE FUNCTION set_edit_generic()
DECLARE RETURNS TRIGGER AS
random_length INT = 12; -- that should be long enough $$
BEGIN DECLARE
IF TG_OP = ''INSERT'' THEN random_length INT = 12; -- that should be long enough
NEW.date_created := ''now''; BEGIN
NEW.cuid := random_string(random_length); IF TG_OP = 'INSERT' THEN
ELSIF TG_OP = ''UPDATE'' THEN NEW.date_created := 'now';
NEW.date_updated := ''now''; NEW.cuid := random_string(random_length);
END IF; ELSIF TG_OP = 'UPDATE' THEN
RETURN NEW; NEW.date_updated := 'now';
END; END IF;
' LANGUAGE 'plpgsql'; RETURN NEW;
END;
$$
LANGUAGE 'plpgsql';
-- END: function/set_edit_generic.sql -- END: function/set_edit_generic.sql
-- START: function/edit_access_set_uid.sql -- START: function/edit_access_set_uid.sql
-- add uid add for edit_access table -- add uid add for edit_access table
CREATE OR REPLACE FUNCTION set_edit_access_uid() RETURNS TRIGGER AS CREATE OR REPLACE FUNCTION set_edit_access_uid() RETURNS TRIGGER AS
$$ $$
DECLARE DECLARE
myrec RECORD; myrec RECORD;
v_uid VARCHAR; v_uid VARCHAR;
BEGIN BEGIN
-- skip if NEW.name is not set -- skip if NEW.name is not set
IF NEW.name IS NOT NULL AND NEW.name <> '' THEN IF NEW.name IS NOT NULL AND NEW.name <> '' THEN
-- use NEW.name as base, remove all spaces -- use NEW.name as base, remove all spaces
-- name data is already unique, so we do not need to worry about this here -- name data is already unique, so we do not need to worry about this here
v_uid := REPLACE(NEW.name, ' ', ''); v_uid := REPLACE(NEW.name, ' ', '');
IF TG_OP = 'INSERT' THEN IF TG_OP = 'INSERT' THEN
-- always set -- always set
NEW.uid := v_uid;
ELSIF TG_OP = 'UPDATE' THEN
-- check if not set, then set
SELECT INTO myrec t.* FROM edit_access t WHERE edit_access_id = NEW.edit_access_id;
IF FOUND THEN
NEW.uid := v_uid; NEW.uid := v_uid;
ELSIF TG_OP = 'UPDATE' THEN
-- check if not set, then set
SELECT INTO myrec t.* FROM edit_access t WHERE edit_access_id = NEW.edit_access_id;
IF FOUND THEN
NEW.uid := v_uid;
END IF;
END IF; END IF;
END IF; END IF;
RETURN NEW; END IF;
END; RETURN NEW;
END;
$$ $$
LANGUAGE 'plpgsql'; LANGUAGE 'plpgsql';
-- END: function/edit_access_set_uid.sql -- END: function/edit_access_set_uid.sql
@@ -69,28 +75,28 @@ $$
CREATE OR REPLACE FUNCTION set_edit_group_uid() RETURNS TRIGGER AS CREATE OR REPLACE FUNCTION set_edit_group_uid() RETURNS TRIGGER AS
$$ $$
DECLARE DECLARE
myrec RECORD; myrec RECORD;
v_uid VARCHAR; v_uid VARCHAR;
BEGIN BEGIN
-- skip if NEW.name is not set -- skip if NEW.name is not set
IF NEW.name IS NOT NULL AND NEW.name <> '' THEN IF NEW.name IS NOT NULL AND NEW.name <> '' THEN
-- use NEW.name as base, remove all spaces -- use NEW.name as base, remove all spaces
-- name data is already unique, so we do not need to worry about this here -- name data is already unique, so we do not need to worry about this here
v_uid := REPLACE(NEW.name, ' ', ''); v_uid := REPLACE(NEW.name, ' ', '');
IF TG_OP = 'INSERT' THEN IF TG_OP = 'INSERT' THEN
-- always set -- always set
NEW.uid := v_uid;
ELSIF TG_OP = 'UPDATE' THEN
-- check if not set, then set
SELECT INTO myrec t.* FROM edit_group t WHERE edit_group_id = NEW.edit_group_id;
IF FOUND THEN
NEW.uid := v_uid; NEW.uid := v_uid;
ELSIF TG_OP = 'UPDATE' THEN
-- check if not set, then set
SELECT INTO myrec t.* FROM edit_group t WHERE edit_group_id = NEW.edit_group_id;
IF FOUND THEN
NEW.uid := v_uid;
END IF;
END IF; END IF;
END IF; END IF;
RETURN NEW; END IF;
END; RETURN NEW;
END;
$$ $$
LANGUAGE 'plpgsql'; LANGUAGE 'plpgsql';
-- END: function/edit_group_set_uid.sql -- END: function/edit_group_set_uid.sql
@@ -246,6 +252,32 @@ END
$$ $$
LANGUAGE 'plpgsql'; LANGUAGE 'plpgsql';
-- END: function/edit_log_partition_insert.sql -- END: function/edit_log_partition_insert.sql
-- START: function/edit_user_set_login_user_id_set_date.sql
-- set edit user login_user_id_set_date if login_user_id is set
-- NOW() if not empty
CREATE OR REPLACE FUNCTION set_login_user_id_set_date()
RETURNS TRIGGER AS
$$
BEGIN
-- if new is not null/empty
-- and old one is null or old one different new one
-- set NOW()
-- if new one is NULL
-- set NULL
IF
NEW.login_user_id IS NOT NULL AND NEW.login_user_id <> '' AND
(OLD.login_user_id IS NULL OR NEW.login_user_id <> OLD.login_user_id)
THEN
NEW.login_user_id_set_date = NOW();
ELSIF NEW.login_user_id IS NULL OR NEW.login_user_id = '' THEN
NEW.login_user_id_set_date = NULL;
END IF;
RETURN NEW;
END;
$$
LANGUAGE 'plpgsql';
-- END: function/edit_user_set_login_user_id_set_date.sql
-- START: table/edit_temp_files.sql -- START: table/edit_temp_files.sql
-- AUTHOR: Clemens Schwaighofer -- AUTHOR: Clemens Schwaighofer
-- DATE: 2005/07/08 -- DATE: 2005/07/08
@@ -526,34 +558,80 @@ CREATE TABLE edit_user (
FOREIGN KEY (edit_scheme_id) REFERENCES edit_scheme (edit_scheme_id) MATCH FULL ON DELETE CASCADE ON UPDATE CASCADE, FOREIGN KEY (edit_scheme_id) REFERENCES edit_scheme (edit_scheme_id) MATCH FULL ON DELETE CASCADE ON UPDATE CASCADE,
edit_access_right_id INT NOT NULL, edit_access_right_id INT NOT NULL,
FOREIGN KEY (edit_access_right_id) REFERENCES edit_access_right (edit_access_right_id) MATCH FULL ON DELETE CASCADE ON UPDATE CASCADE, FOREIGN KEY (edit_access_right_id) REFERENCES edit_access_right (edit_access_right_id) MATCH FULL ON DELETE CASCADE ON UPDATE CASCADE,
enabled SMALLINT NOT NULL DEFAULT 0, -- username/password
deleted SMALLINT NOT NULL DEFAULT 0,
username VARCHAR UNIQUE, username VARCHAR UNIQUE,
password VARCHAR, password VARCHAR,
-- name block
first_name VARCHAR, first_name VARCHAR,
last_name VARCHAR, last_name VARCHAR,
first_name_furigana VARCHAR, first_name_furigana VARCHAR,
last_name_furigana VARCHAR, last_name_furigana VARCHAR,
-- email
email VARCHAR,
-- eanbled/deleted flag
enabled SMALLINT NOT NULL DEFAULT 0,
deleted SMALLINT NOT NULL DEFAULT 0,
-- general flags
strict SMALLINT DEFAULT 0,
locked SMALLINT DEFAULT 0,
protected SMALLINT NOT NULL DEFAULT 0,
-- legacy, debug flags
debug SMALLINT NOT NULL DEFAULT 0, debug SMALLINT NOT NULL DEFAULT 0,
db_debug SMALLINT NOT NULL DEFAULT 0, db_debug SMALLINT NOT NULL DEFAULT 0,
email VARCHAR, -- is admin user
protected SMALLINT NOT NULL DEFAULT 0,
admin SMALLINT NOT NULL DEFAULT 0, admin SMALLINT NOT NULL DEFAULT 0,
-- last login log
last_login TIMESTAMP WITHOUT TIME ZONE, last_login TIMESTAMP WITHOUT TIME ZONE,
-- login error
login_error_count INT DEFAULT 0, login_error_count INT DEFAULT 0,
login_error_date_last TIMESTAMP WITHOUT TIME ZONE, login_error_date_last TIMESTAMP WITHOUT TIME ZONE,
login_error_date_first TIMESTAMP WITHOUT TIME ZONE, login_error_date_first TIMESTAMP WITHOUT TIME ZONE,
strict SMALLINT DEFAULT 0, -- time locked
locked SMALLINT DEFAULT 0, lock_until TIMESTAMP WITHOUT TIME ZONE,
lock_after TIMESTAMP WITHOUT TIME ZONE,
-- password change
password_change_date TIMESTAMP WITHOUT TIME ZONE, -- only when password is first set or changed password_change_date TIMESTAMP WITHOUT TIME ZONE, -- only when password is first set or changed
password_change_interval INTERVAL, -- null if no change is needed, or d/m/y time interval password_change_interval INTERVAL, -- null if no change is needed, or d/m/y time interval
password_reset_time TIMESTAMP WITHOUT TIME ZONE, -- when the password reset was requested password_reset_time TIMESTAMP WITHOUT TIME ZONE, -- when the password reset was requested
password_reset_uid VARCHAR, -- the uid to access the password reset page password_reset_uid VARCHAR, -- the uid to access the password reset page
-- _GET login id for direct login
login_user_id VARCHAR, -- the login uid, at least 32 chars
login_user_id_set_date TIMESTAMP WITHOUT TIME ZONE, -- when above uid was set
login_user_id_valid_from TIMESTAMP WITHOUT TIME ZONE, -- if set, from when the above uid is valid
login_user_id_valid_until TIMESTAMP WITHOUT TIME ZONE, -- if set, until when the above uid is valid
login_user_id_revalidate_after INTERVAL DEFAULT '0 days', -- user must login to revalidated login id after set days, 0 for forever
login_user_id_locked SMALLINT DEFAULT 0, -- lock for login user id, but still allow normal login
-- additional ACL json block
additional_acl JSONB -- additional ACL as JSON string (can be set by other pages) additional_acl JSONB -- additional ACL as JSON string (can be set by other pages)
) INHERITS (edit_generic) WITHOUT OIDS; ) INHERITS (edit_generic) WITHOUT OIDS;
COMMENT ON COLUMN edit_user.username IS 'Login username, must set';
COMMENT ON COLUMN edit_user.password IS 'Login password, must set';
COMMENT ON COLUMN edit_user.enabled IS 'Login is enabled (master switch)';
COMMENT ON COLUMN edit_user.deleted IS 'Login is deleted (master switch), overrides all other';
COMMENT ON COLUMN edit_user.strict IS 'If too many failed logins user will be locked, default off';
COMMENT ON COLUMN edit_user.locked IS 'Locked from too many wrong password logins';
COMMENT ON COLUMN edit_user.protected IS 'User can only be chnaged by admin user';
COMMENT ON COLUMN edit_user.debug IS 'Turn debug flag on (legacy)';
COMMENT ON COLUMN edit_user.db_debug IS 'Turn DB debug flag on (legacy)';
COMMENT ON COLUMN edit_user.admin IS 'If set, this user is SUPER admin';
COMMENT ON COLUMN edit_user.last_login IS 'Last succesfull login tiemstamp';
COMMENT ON COLUMN edit_user.login_error_count IS 'Number of failed logins, reset on successful login';
COMMENT ON COLUMN edit_user.login_error_date_last IS 'Last login error date';
COMMENT ON COLUMN edit_user.login_error_date_first IS 'First login error date, reset on successfull login';
COMMENT ON COLUMN edit_user.lock_until IS 'Account is locked until this date, <';
COMMENT ON COLUMN edit_user.lock_after IS 'Account is locked after this date, >';
COMMENT ON COLUMN edit_user.password_change_date IS 'Password was changed on';
COMMENT ON COLUMN edit_user.password_change_interval IS 'After how many days the password has to be changed';
COMMENT ON COLUMN edit_user.password_reset_time IS 'When the password reset was requested. For reset page uid valid check'; COMMENT ON COLUMN edit_user.password_reset_time IS 'When the password reset was requested. For reset page uid valid check';
COMMENT ON COLUMN edit_user.password_reset_uid IS 'Password reset page uid'; COMMENT ON COLUMN edit_user.password_reset_uid IS 'Password reset page uid, one time, invalid after reset successful or time out';
COMMENT ON COLUMN edit_user.login_user_id IS 'Min 32 character UID to be used to login without password. Via GET/POST parameter';
COMMENT ON COLUMN edit_user.login_user_id_set_date IS 'login id was set at what date';
COMMENT ON COLUMN edit_user.login_user_id_valid_from IS 'login id is valid from this date, >=';
COMMENT ON COLUMN edit_user.login_user_id_valid_until IS 'login id is valid until this date, <=';
COMMENT ON COLUMN edit_user.login_user_id_revalidate_after IS 'If set to a number greater 0 then user must login after given amount of days to revalidate, set to 0 for valid forver';
COMMENT ON COLUMN edit_user.login_user_id_locked IS 'A separte lock flag for login id, user can still login normal';
COMMENT ON COLUMN edit_user.additional_acl IS 'Additional Access Control List stored in JSON format';
-- END: table/edit_user.sql -- END: table/edit_user.sql
-- START: table/edit_log.sql -- START: table/edit_log.sql
-- AUTHOR: Clemens Schwaighofer -- AUTHOR: Clemens Schwaighofer
@@ -774,6 +852,11 @@ FOR EACH ROW EXECUTE PROCEDURE set_edit_generic();
CREATE TRIGGER trg_edit_user CREATE TRIGGER trg_edit_user
BEFORE INSERT OR UPDATE ON edit_user BEFORE INSERT OR UPDATE ON edit_user
FOR EACH ROW EXECUTE PROCEDURE set_edit_generic(); FOR EACH ROW EXECUTE PROCEDURE set_edit_generic();
-- DROP TRIGGER IF EXISTS trg_edit_user_set_login_user_id_set_date ON edit_user;
CREATE TRIGGER trg_edit_user_set_login_user_id_set_date
BEFORE INSERT OR UPDATE ON edit_user
FOR EACH ROW EXECUTE PROCEDURE set_login_user_id_set_date();
-- END: trigger/trg_edit_user.sql -- END: trigger/trg_edit_user.sql
-- START: trigger/trg_edit_visible_group.sql -- START: trigger/trg_edit_visible_group.sql
-- DROP TRIGGER IF EXISTS trg_edit_visible_group ON edit_visible_group; -- DROP TRIGGER IF EXISTS trg_edit_visible_group ON edit_visible_group;

View File

@@ -2,27 +2,27 @@
CREATE OR REPLACE FUNCTION set_edit_access_uid() RETURNS TRIGGER AS CREATE OR REPLACE FUNCTION set_edit_access_uid() RETURNS TRIGGER AS
$$ $$
DECLARE DECLARE
myrec RECORD; myrec RECORD;
v_uid VARCHAR; v_uid VARCHAR;
BEGIN BEGIN
-- skip if NEW.name is not set -- skip if NEW.name is not set
IF NEW.name IS NOT NULL AND NEW.name <> '' THEN IF NEW.name IS NOT NULL AND NEW.name <> '' THEN
-- use NEW.name as base, remove all spaces -- use NEW.name as base, remove all spaces
-- name data is already unique, so we do not need to worry about this here -- name data is already unique, so we do not need to worry about this here
v_uid := REPLACE(NEW.name, ' ', ''); v_uid := REPLACE(NEW.name, ' ', '');
IF TG_OP = 'INSERT' THEN IF TG_OP = 'INSERT' THEN
-- always set -- always set
NEW.uid := v_uid;
ELSIF TG_OP = 'UPDATE' THEN
-- check if not set, then set
SELECT INTO myrec t.* FROM edit_access t WHERE edit_access_id = NEW.edit_access_id;
IF FOUND THEN
NEW.uid := v_uid; NEW.uid := v_uid;
ELSIF TG_OP = 'UPDATE' THEN
-- check if not set, then set
SELECT INTO myrec t.* FROM edit_access t WHERE edit_access_id = NEW.edit_access_id;
IF FOUND THEN
NEW.uid := v_uid;
END IF;
END IF; END IF;
END IF; END IF;
RETURN NEW; END IF;
END; RETURN NEW;
END;
$$ $$
LANGUAGE 'plpgsql'; LANGUAGE 'plpgsql';

View File

@@ -2,27 +2,27 @@
CREATE OR REPLACE FUNCTION set_edit_group_uid() RETURNS TRIGGER AS CREATE OR REPLACE FUNCTION set_edit_group_uid() RETURNS TRIGGER AS
$$ $$
DECLARE DECLARE
myrec RECORD; myrec RECORD;
v_uid VARCHAR; v_uid VARCHAR;
BEGIN BEGIN
-- skip if NEW.name is not set -- skip if NEW.name is not set
IF NEW.name IS NOT NULL AND NEW.name <> '' THEN IF NEW.name IS NOT NULL AND NEW.name <> '' THEN
-- use NEW.name as base, remove all spaces -- use NEW.name as base, remove all spaces
-- name data is already unique, so we do not need to worry about this here -- name data is already unique, so we do not need to worry about this here
v_uid := REPLACE(NEW.name, ' ', ''); v_uid := REPLACE(NEW.name, ' ', '');
IF TG_OP = 'INSERT' THEN IF TG_OP = 'INSERT' THEN
-- always set -- always set
NEW.uid := v_uid;
ELSIF TG_OP = 'UPDATE' THEN
-- check if not set, then set
SELECT INTO myrec t.* FROM edit_group t WHERE edit_group_id = NEW.edit_group_id;
IF FOUND THEN
NEW.uid := v_uid; NEW.uid := v_uid;
ELSIF TG_OP = 'UPDATE' THEN
-- check if not set, then set
SELECT INTO myrec t.* FROM edit_group t WHERE edit_group_id = NEW.edit_group_id;
IF FOUND THEN
NEW.uid := v_uid;
END IF;
END IF; END IF;
END IF; END IF;
RETURN NEW; END IF;
END; RETURN NEW;
END;
$$ $$
LANGUAGE 'plpgsql'; LANGUAGE 'plpgsql';

View File

@@ -0,0 +1,24 @@
-- set edit user login_user_id_set_date if login_user_id is set
-- NOW() if not empty
CREATE OR REPLACE FUNCTION set_login_user_id_set_date()
RETURNS TRIGGER AS
$$
BEGIN
-- if new is not null/empty
-- and old one is null or old one different new one
-- set NOW()
-- if new one is NULL
-- set NULL
IF
NEW.login_user_id IS NOT NULL AND NEW.login_user_id <> '' AND
(OLD.login_user_id IS NULL OR NEW.login_user_id <> OLD.login_user_id)
THEN
NEW.login_user_id_set_date = NOW();
ELSIF NEW.login_user_id IS NULL OR NEW.login_user_id = '' THEN
NEW.login_user_id_set_date = NULL;
END IF;
RETURN NEW;
END;
$$
LANGUAGE 'plpgsql';

View File

@@ -1,7 +1,8 @@
-- create random string with length X -- create random string with length X
CREATE FUNCTION random_string(randomLength int) CREATE FUNCTION random_string(randomLength int)
RETURNS text AS $$ RETURNS text AS
$$
SELECT array_to_string( SELECT array_to_string(
ARRAY( ARRAY(
SELECT substring( SELECT substring(
@@ -13,6 +14,7 @@ SELECT array_to_string(
), ),
'' ''
) )
$$ LANGUAGE SQL $$
LANGUAGE SQL
RETURNS NULL ON NULL INPUT RETURNS NULL ON NULL INPUT
VOLATILE; -- LEAKPROOF; VOLATILE; -- LEAKPROOF;

View File

@@ -1,12 +1,15 @@
-- adds the created or updated date tags -- adds the created or updated date tags
CREATE OR REPLACE FUNCTION set_date() RETURNS TRIGGER AS ' CREATE OR REPLACE FUNCTION set_date()
BEGIN RETURNS TRIGGER AS
IF TG_OP = ''INSERT'' THEN $$
NEW.date_created := ''now''; BEGIN
ELSIF TG_OP = ''UPDATE'' THEN IF TG_OP = 'INSERT' THEN
NEW.date_updated := ''now''; NEW.date_created := 'now';
END IF; ELSIF TG_OP = 'UPDATE' THEN
RETURN NEW; NEW.date_updated := 'now';
END; END IF;
' LANGUAGE 'plpgsql'; RETURN NEW;
END;
$$
LANGUAGE 'plpgsql';

View File

@@ -1,15 +1,18 @@
-- adds the created or updated date tags -- adds the created or updated date tags
CREATE OR REPLACE FUNCTION set_edit_generic() RETURNS TRIGGER AS ' CREATE OR REPLACE FUNCTION set_edit_generic()
DECLARE RETURNS TRIGGER AS
random_length INT = 12; -- that should be long enough $$
BEGIN DECLARE
IF TG_OP = ''INSERT'' THEN random_length INT = 12; -- that should be long enough
NEW.date_created := ''now''; BEGIN
NEW.cuid := random_string(random_length); IF TG_OP = 'INSERT' THEN
ELSIF TG_OP = ''UPDATE'' THEN NEW.date_created := 'now';
NEW.date_updated := ''now''; NEW.cuid := random_string(random_length);
END IF; ELSIF TG_OP = 'UPDATE' THEN
RETURN NEW; NEW.date_updated := 'now';
END; END IF;
' LANGUAGE 'plpgsql'; RETURN NEW;
END;
$$
LANGUAGE 'plpgsql';

View File

@@ -1,18 +1,21 @@
-- set generic with date and uid combined -- set generic with date and uid combined
-- don't use with set_generic/set_uid together -- don't use with set_generic/set_uid together
CREATE OR REPLACE FUNCTION set_generic() RETURNS TRIGGER AS ' CREATE OR REPLACE FUNCTION set_generic()
DECLARE RETURNS TRIGGER AS
random_length INT = 32; -- long for massive data $$
BEGIN DECLARE
IF TG_OP = ''INSERT'' THEN random_length INT = 32; -- long for massive data
NEW.date_created := ''now''; BEGIN
IF NEW.uid IS NULL THEN IF TG_OP = 'INSERT' THEN
NEW.uid := random_string(random_length); NEW.date_created := 'now';
END IF; IF NEW.uid IS NULL THEN
ELSIF TG_OP = ''UPDATE'' THEN NEW.uid := random_string(random_length);
NEW.date_updated := ''now'';
END IF; END IF;
RETURN NEW; ELSIF TG_OP = 'UPDATE' THEN
END; NEW.date_updated := 'now';
' LANGUAGE 'plpgsql'; END IF;
RETURN NEW;
END;
$$
LANGUAGE 'plpgsql';

View File

@@ -1,12 +1,15 @@
-- adds the created or updated date tags -- adds the created or updated date tags
CREATE OR REPLACE FUNCTION set_uid() RETURNS TRIGGER AS ' CREATE OR REPLACE FUNCTION set_uid()
DECLARE RETURNS TRIGGER AS
random_length INT = 32; -- that should be long enough $$
BEGIN DECLARE
IF TG_OP = ''INSERT'' THEN random_length INT = 32; -- that should be long enough
NEW.uid := random_string(random_length); BEGIN
END IF; IF TG_OP = 'INSERT' THEN
RETURN NEW; NEW.uid := random_string(random_length);
END; END IF;
' LANGUAGE 'plpgsql'; RETURN NEW;
END;
$$
LANGUAGE 'plpgsql';

View File

@@ -2,15 +2,18 @@
-- OLD, DEPRECATED, use set_generic.sql -- OLD, DEPRECATED, use set_generic.sql
-- CREATE OR REPLACE FUNCTION set_generic() RETURNS TRIGGER AS ' -- CREATE OR REPLACE FUNCTION set_generic()
-- BEGIN -- RETURNS TRIGGER AS
-- IF TG_OP = ''INSERT'' THEN -- $$
-- NEW.date_created := clock_timestamp(); -- BEGIN
-- NEW.user_created := current_user; -- IF TG_OP = 'INSERT' THEN
-- ELSIF TG_OP = ''UPDATE'' THEN -- NEW.date_created := clock_timestamp();
-- NEW.date_updated := clock_timestamp(); -- NEW.user_created := current_user;
-- NEW.user_updated := current_user; -- ELSIF TG_OP = 'UPDATE' THEN
-- END IF; -- NEW.date_updated := clock_timestamp();
-- RETURN NEW; -- NEW.user_updated := current_user;
-- END; -- END IF;
-- ' LANGUAGE 'plpgsql'; -- RETURN NEW;
-- END;
-- $$
-- LANGUAGE 'plpgsql';

View File

@@ -18,31 +18,77 @@ CREATE TABLE edit_user (
FOREIGN KEY (edit_scheme_id) REFERENCES edit_scheme (edit_scheme_id) MATCH FULL ON DELETE CASCADE ON UPDATE CASCADE, FOREIGN KEY (edit_scheme_id) REFERENCES edit_scheme (edit_scheme_id) MATCH FULL ON DELETE CASCADE ON UPDATE CASCADE,
edit_access_right_id INT NOT NULL, edit_access_right_id INT NOT NULL,
FOREIGN KEY (edit_access_right_id) REFERENCES edit_access_right (edit_access_right_id) MATCH FULL ON DELETE CASCADE ON UPDATE CASCADE, FOREIGN KEY (edit_access_right_id) REFERENCES edit_access_right (edit_access_right_id) MATCH FULL ON DELETE CASCADE ON UPDATE CASCADE,
enabled SMALLINT NOT NULL DEFAULT 0, -- username/password
deleted SMALLINT NOT NULL DEFAULT 0,
username VARCHAR UNIQUE, username VARCHAR UNIQUE,
password VARCHAR, password VARCHAR,
-- name block
first_name VARCHAR, first_name VARCHAR,
last_name VARCHAR, last_name VARCHAR,
first_name_furigana VARCHAR, first_name_furigana VARCHAR,
last_name_furigana VARCHAR, last_name_furigana VARCHAR,
-- email
email VARCHAR,
-- eanbled/deleted flag
enabled SMALLINT NOT NULL DEFAULT 0,
deleted SMALLINT NOT NULL DEFAULT 0,
-- general flags
strict SMALLINT DEFAULT 0,
locked SMALLINT DEFAULT 0,
protected SMALLINT NOT NULL DEFAULT 0,
-- legacy, debug flags
debug SMALLINT NOT NULL DEFAULT 0, debug SMALLINT NOT NULL DEFAULT 0,
db_debug SMALLINT NOT NULL DEFAULT 0, db_debug SMALLINT NOT NULL DEFAULT 0,
email VARCHAR, -- is admin user
protected SMALLINT NOT NULL DEFAULT 0,
admin SMALLINT NOT NULL DEFAULT 0, admin SMALLINT NOT NULL DEFAULT 0,
-- last login log
last_login TIMESTAMP WITHOUT TIME ZONE, last_login TIMESTAMP WITHOUT TIME ZONE,
-- login error
login_error_count INT DEFAULT 0, login_error_count INT DEFAULT 0,
login_error_date_last TIMESTAMP WITHOUT TIME ZONE, login_error_date_last TIMESTAMP WITHOUT TIME ZONE,
login_error_date_first TIMESTAMP WITHOUT TIME ZONE, login_error_date_first TIMESTAMP WITHOUT TIME ZONE,
strict SMALLINT DEFAULT 0, -- time locked
locked SMALLINT DEFAULT 0, lock_until TIMESTAMP WITHOUT TIME ZONE,
lock_after TIMESTAMP WITHOUT TIME ZONE,
-- password change
password_change_date TIMESTAMP WITHOUT TIME ZONE, -- only when password is first set or changed password_change_date TIMESTAMP WITHOUT TIME ZONE, -- only when password is first set or changed
password_change_interval INTERVAL, -- null if no change is needed, or d/m/y time interval password_change_interval INTERVAL, -- null if no change is needed, or d/m/y time interval
password_reset_time TIMESTAMP WITHOUT TIME ZONE, -- when the password reset was requested password_reset_time TIMESTAMP WITHOUT TIME ZONE, -- when the password reset was requested
password_reset_uid VARCHAR, -- the uid to access the password reset page password_reset_uid VARCHAR, -- the uid to access the password reset page
-- _GET login id for direct login
login_user_id VARCHAR, -- the login uid, at least 32 chars
login_user_id_set_date TIMESTAMP WITHOUT TIME ZONE, -- when above uid was set
login_user_id_valid_from TIMESTAMP WITHOUT TIME ZONE, -- if set, from when the above uid is valid
login_user_id_valid_until TIMESTAMP WITHOUT TIME ZONE, -- if set, until when the above uid is valid
login_user_id_revalidate_after INTERVAL DEFAULT '0 days', -- user must login to revalidated login id after set days, 0 for forever
login_user_id_locked SMALLINT DEFAULT 0, -- lock for login user id, but still allow normal login
-- additional ACL json block
additional_acl JSONB -- additional ACL as JSON string (can be set by other pages) additional_acl JSONB -- additional ACL as JSON string (can be set by other pages)
) INHERITS (edit_generic) WITHOUT OIDS; ) INHERITS (edit_generic) WITHOUT OIDS;
COMMENT ON COLUMN edit_user.username IS 'Login username, must set';
COMMENT ON COLUMN edit_user.password IS 'Login password, must set';
COMMENT ON COLUMN edit_user.enabled IS 'Login is enabled (master switch)';
COMMENT ON COLUMN edit_user.deleted IS 'Login is deleted (master switch), overrides all other';
COMMENT ON COLUMN edit_user.strict IS 'If too many failed logins user will be locked, default off';
COMMENT ON COLUMN edit_user.locked IS 'Locked from too many wrong password logins';
COMMENT ON COLUMN edit_user.protected IS 'User can only be chnaged by admin user';
COMMENT ON COLUMN edit_user.debug IS 'Turn debug flag on (legacy)';
COMMENT ON COLUMN edit_user.db_debug IS 'Turn DB debug flag on (legacy)';
COMMENT ON COLUMN edit_user.admin IS 'If set, this user is SUPER admin';
COMMENT ON COLUMN edit_user.last_login IS 'Last succesfull login tiemstamp';
COMMENT ON COLUMN edit_user.login_error_count IS 'Number of failed logins, reset on successful login';
COMMENT ON COLUMN edit_user.login_error_date_last IS 'Last login error date';
COMMENT ON COLUMN edit_user.login_error_date_first IS 'First login error date, reset on successfull login';
COMMENT ON COLUMN edit_user.lock_until IS 'Account is locked until this date, <';
COMMENT ON COLUMN edit_user.lock_after IS 'Account is locked after this date, >';
COMMENT ON COLUMN edit_user.password_change_date IS 'Password was changed on';
COMMENT ON COLUMN edit_user.password_change_interval IS 'After how many days the password has to be changed';
COMMENT ON COLUMN edit_user.password_reset_time IS 'When the password reset was requested. For reset page uid valid check'; COMMENT ON COLUMN edit_user.password_reset_time IS 'When the password reset was requested. For reset page uid valid check';
COMMENT ON COLUMN edit_user.password_reset_uid IS 'Password reset page uid'; COMMENT ON COLUMN edit_user.password_reset_uid IS 'Password reset page uid, one time, invalid after reset successful or time out';
COMMENT ON COLUMN edit_user.login_user_id IS 'Min 32 character UID to be used to login without password. Via GET/POST parameter';
COMMENT ON COLUMN edit_user.login_user_id_set_date IS 'login id was set at what date';
COMMENT ON COLUMN edit_user.login_user_id_valid_from IS 'login id is valid from this date, >=';
COMMENT ON COLUMN edit_user.login_user_id_valid_until IS 'login id is valid until this date, <=';
COMMENT ON COLUMN edit_user.login_user_id_revalidate_after IS 'If set to a number greater 0 then user must login after given amount of days to revalidate, set to 0 for valid forver';
COMMENT ON COLUMN edit_user.login_user_id_locked IS 'A separte lock flag for login id, user can still login normal';
COMMENT ON COLUMN edit_user.additional_acl IS 'Additional Access Control List stored in JSON format';

View File

@@ -2,3 +2,8 @@
CREATE TRIGGER trg_edit_user CREATE TRIGGER trg_edit_user
BEFORE INSERT OR UPDATE ON edit_user BEFORE INSERT OR UPDATE ON edit_user
FOR EACH ROW EXECUTE PROCEDURE set_edit_generic(); FOR EACH ROW EXECUTE PROCEDURE set_edit_generic();
-- DROP TRIGGER IF EXISTS trg_edit_user_set_login_user_id_set_date ON edit_user;
CREATE TRIGGER trg_edit_user_set_login_user_id_set_date
BEFORE INSERT OR UPDATE ON edit_user
FOR EACH ROW EXECUTE PROCEDURE set_login_user_id_set_date();

View File

@@ -0,0 +1,47 @@
-- 2022/6/17 update edit_user with login uid
-- the login uid, at least 32 chars
ALTER TABLE edit_user ADD login_user_id VARCHAR;
-- when above uid was set
ALTER TABLE edit_user ADD login_user_id_set_date TIMESTAMP WITHOUT TIME ZONE;
-- if set, from/until when the above uid is valid
ALTER TABLE edit_user ADD login_user_id_valid_from TIMESTAMP WITHOUT TIME ZONE;
ALTER TABLE edit_user ADD login_user_id_valid_until TIMESTAMP WITHOUT TIME ZONE;
-- user must login to revalidated login id after set days, 0 for forever
ALTER TABLE edit_user ADD login_user_id_revalidate_after INTERVAL NOT NULL DEFAULT '0 days';
-- lock for login user id, but still allow normal login
ALTER TABLE edit_user ADD login_user_id_locked SMALLINT NOT NULL DEFAULT 0;
-- disable login before date
ALTER TABLE edit_user ADD lock_until TIMESTAMP WITHOUT TIME ZONE;
-- disable login after date
ALTER TABLE edit_user ADD lock_after TIMESTAMP WITHOUT TIME ZONE;
CREATE OR REPLACE FUNCTION set_login_user_id_set_date()
RETURNS TRIGGER AS
$$
BEGIN
-- if new is not null/empty
-- and old one is null or old one different new one
-- set NOW()
-- if new one is NULL
-- set NULL
IF
NEW.login_user_id IS NOT NULL AND NEW.login_user_id <> '' AND
(OLD.login_user_id IS NULL OR NEW.login_user_id <> OLD.login_user_id)
THEN
NEW.login_user_id_set_date = NOW();
ELSIF NEW.login_user_id IS NULL OR NEW.login_user_id = '' THEN
NEW.login_user_id_set_date = NULL;
END IF;
RETURN NEW;
END;
$$
LANGUAGE 'plpgsql';
CREATE TRIGGER trg_edit_user_set_login_user_id_set_date
BEFORE INSERT OR UPDATE ON edit_user
FOR EACH ROW EXECUTE PROCEDURE set_login_user_id_set_date();
-- __END__

View File

@@ -16,8 +16,6 @@ final class CoreLibsACLLoginTest extends TestCase
{ {
private static $db; private static $db;
private static $log; private static $log;
/** @var \CoreLibs\Create\Session&MockObject */
private static $session;
/** /**
* start DB conneciton, setup DB, etc * start DB conneciton, setup DB, etc
@@ -101,6 +99,8 @@ final class CoreLibsACLLoginTest extends TestCase
'Cannot find edit_user table in ACL\Login database for testing' 'Cannot find edit_user table in ACL\Login database for testing'
); );
} }
// always disable max query calls
self::$db->dbSetMaxQueryCall(-1);
// insert additional content for testing (locked user, etc) // insert additional content for testing (locked user, etc)
$queries = [ $queries = [
"INSERT INTO edit_access_data " "INSERT INTO edit_access_data "
@@ -158,6 +158,7 @@ final class CoreLibsACLLoginTest extends TestCase
public function loginProvider(): array public function loginProvider(): array
{ {
// 0: mock settings/override flag settings // 0: mock settings/override flag settings
// 2: get array IN
// 1: post array IN // 1: post array IN
// login_login, login_username, login_password, login_logout // login_login, login_username, login_password, login_logout
// change_password, pw_username, pw_old_password, pw_new_password, // change_password, pw_username, pw_old_password, pw_new_password,
@@ -174,6 +175,7 @@ final class CoreLibsACLLoginTest extends TestCase
], ],
[], [],
[], [],
[],
3000, 3000,
[ [
'login_error' => 0, 'login_error' => 0,
@@ -191,6 +193,7 @@ final class CoreLibsACLLoginTest extends TestCase
], ],
[], [],
[], [],
[],
3000, 3000,
[ [
'login_error' => 0, 'login_error' => 0,
@@ -213,6 +216,7 @@ final class CoreLibsACLLoginTest extends TestCase
], ],
[], [],
[], [],
[],
3000, 3000,
[ [
'login_error' => 0, 'login_error' => 0,
@@ -230,6 +234,7 @@ final class CoreLibsACLLoginTest extends TestCase
'page_name' => 'edit_users.php', 'page_name' => 'edit_users.php',
], ],
[], [],
[],
[ [
'EUID' => 1, 'EUID' => 1,
], ],
@@ -246,6 +251,7 @@ final class CoreLibsACLLoginTest extends TestCase
'page_access' => 'list', 'page_access' => 'list',
], ],
[], [],
[],
[ [
'EUID' => 1, 'EUID' => 1,
'USER_NAME' => '', 'USER_NAME' => '',
@@ -288,6 +294,7 @@ final class CoreLibsACLLoginTest extends TestCase
[ [
'page_name' => 'edit_users.php', 'page_name' => 'edit_users.php',
], ],
[],
[ [
'login_login' => 'Login', 'login_login' => 'Login',
'login_username' => '', 'login_username' => '',
@@ -308,6 +315,7 @@ final class CoreLibsACLLoginTest extends TestCase
[ [
'page_name' => 'edit_users.php', 'page_name' => 'edit_users.php',
], ],
[],
[ [
'login_login' => 'Login', 'login_login' => 'Login',
'login_username' => '', 'login_username' => '',
@@ -328,6 +336,7 @@ final class CoreLibsACLLoginTest extends TestCase
[ [
'page_name' => 'edit_users.php', 'page_name' => 'edit_users.php',
], ],
[],
[ [
'login_login' => 'Login', 'login_login' => 'Login',
'login_username' => 'abc', 'login_username' => 'abc',
@@ -348,6 +357,7 @@ final class CoreLibsACLLoginTest extends TestCase
[ [
'page_name' => 'edit_users.php', 'page_name' => 'edit_users.php',
], ],
[],
[ [
'login_login' => 'Login', 'login_login' => 'Login',
'login_username' => 'abc', 'login_username' => 'abc',
@@ -371,6 +381,7 @@ final class CoreLibsACLLoginTest extends TestCase
[ [
'page_name' => 'edit_users.php', 'page_name' => 'edit_users.php',
], ],
[],
[ [
'login_login' => 'Login', 'login_login' => 'Login',
'login_username' => 'admin', 'login_username' => 'admin',
@@ -387,6 +398,31 @@ final class CoreLibsACLLoginTest extends TestCase
. 'Login Failed - Wrong Username or Password' . 'Login Failed - Wrong Username or Password'
] ]
], ],
// login: ok (but deleted)
'login: ok, but deleted' => [
[
'page_name' => 'edit_users.php',
'edit_access_id' => 1,
'base_access' => 'list',
'page_access' => 'list',
'test_deleted' => true
],
[],
[
'login_login' => 'Login',
'login_username' => 'admin',
'login_password' => 'admin',
],
[],
3000,
[
'login_error' => 106,
'error_string' => '<span style="color: red;">Fatal Error:</span> '
. '<b>Login Failed - User is deleted</b>',
'error_string_text' => 'Fatal Error: '
. 'Login Failed - User is deleted'
]
],
// login: ok (but not enabled) // login: ok (but not enabled)
'login: ok, but not enabled' => [ 'login: ok, but not enabled' => [
[ [
@@ -396,6 +432,7 @@ final class CoreLibsACLLoginTest extends TestCase
'page_access' => 'list', 'page_access' => 'list',
'test_enabled' => true 'test_enabled' => true
], ],
[],
[ [
'login_login' => 'Login', 'login_login' => 'Login',
'login_username' => 'admin', 'login_username' => 'admin',
@@ -420,6 +457,7 @@ final class CoreLibsACLLoginTest extends TestCase
'page_access' => 'list', 'page_access' => 'list',
'test_locked' => true 'test_locked' => true
], ],
[],
[ [
'login_login' => 'Login', 'login_login' => 'Login',
'login_username' => 'admin', 'login_username' => 'admin',
@@ -446,6 +484,7 @@ final class CoreLibsACLLoginTest extends TestCase
'max_login_error_count' => 2, 'max_login_error_count' => 2,
'test_locked_strict' => true, 'test_locked_strict' => true,
], ],
[],
[ [
'login_login' => 'Login', 'login_login' => 'Login',
'login_username' => 'admin', 'login_username' => 'admin',
@@ -458,6 +497,136 @@ final class CoreLibsACLLoginTest extends TestCase
'login_error' => 105, 'login_error' => 105,
] ]
], ],
// login ok, but in locked period (until)
'login: ok, but locked period (until:on)' => [
[
'page_name' => 'edit_users.php',
'edit_access_id' => 1,
'base_access' => 'list',
'page_access' => 'list',
'test_locked_period_until' => 'on'
],
[],
[
'login_login' => 'Login',
'login_username' => 'admin',
'login_password' => 'admin',
],
[],
3000,
[
'login_error' => 107,
'error_string' => '<span style="color: red;">Fatal Error:</span> '
. '<b>Login Failed - User in locked via date period</b>',
'error_string_text' => 'Fatal Error: '
. 'Login Failed - User in locked via date period'
]
],
// login ok, but in locked period (until)
'login: ok, but locked period (until:off)' => [
[
'page_name' => 'edit_users.php',
'edit_access_id' => 1,
'edit_access_uid' => 'AdminAccess',
'edit_access_data' => 'test',
'base_access' => 'list',
'page_access' => 'list',
'test_locked_period_until' => 'off'
],
[],
[
'login_login' => 'Login',
'login_username' => 'admin',
'login_password' => 'admin',
],
[],
0,
[
'login_error' => 0,
'admin_flag' => true,
'check_access' => true,
'check_access_id' => 1,
'check_access_data' => 'value',
'base_access' => true,
'page_access' => true,
]
],
// login ok, but in locked period (after)
'login: ok, but locked period (after:on)' => [
[
'page_name' => 'edit_users.php',
'edit_access_id' => 1,
'base_access' => 'list',
'page_access' => 'list',
'test_locked_period_after' => 'on'
],
[],
[
'login_login' => 'Login',
'login_username' => 'admin',
'login_password' => 'admin',
],
[],
3000,
[
'login_error' => 107,
'error_string' => '<span style="color: red;">Fatal Error:</span> '
. '<b>Login Failed - User in locked via date period</b>',
'error_string_text' => 'Fatal Error: '
. 'Login Failed - User in locked via date period'
]
],
// login ok, but in locked period (until, after)
'login: ok, but locked period (until:on, after:on)' => [
[
'page_name' => 'edit_users.php',
'edit_access_id' => 1,
'base_access' => 'list',
'page_access' => 'list',
'test_locked_period_until' => 'on',
'test_locked_period_after' => 'on'
],
[],
[
'login_login' => 'Login',
'login_username' => 'admin',
'login_password' => 'admin',
],
[],
3000,
[
'login_error' => 107,
'error_string' => '<span style="color: red;">Fatal Error:</span> '
. '<b>Login Failed - User in locked via date period</b>',
'error_string_text' => 'Fatal Error: '
. 'Login Failed - User in locked via date period'
]
],
// login ok, but login user id locked
'login: ok, but login user id locked' => [
[
'page_name' => 'edit_users.php',
'edit_access_id' => 1,
'base_access' => 'list',
'page_access' => 'list',
'test_login_user_id_locked' => true
],
[],
[
'login_login' => 'Login',
'login_username' => 'admin',
'login_password' => 'admin',
],
[],
3000,
[
'login_error' => 108,
'error_string' => '<span style="color: red;">Fatal Error:</span> '
. '<b>Login Failed - User is locked via Login User ID</b>',
'error_string_text' => 'Fatal Error: '
. 'Login Failed - User is locked via Login User ID'
]
],
// login: ok // login: ok
'login: ok' => [ 'login: ok' => [
[ [
@@ -468,6 +637,148 @@ final class CoreLibsACLLoginTest extends TestCase
'base_access' => 'list', 'base_access' => 'list',
'page_access' => 'list', 'page_access' => 'list',
], ],
[],
[
'login_login' => 'Login',
'login_username' => 'admin',
'login_password' => 'admin',
],
[],
0,
[
'login_error' => 0,
'admin_flag' => true,
'check_access' => true,
'check_access_id' => 1,
'check_access_data' => 'value',
'base_access' => true,
'page_access' => true,
]
],
// login check via _GET loginUserId
'login: ok, _GET loginUserId' => [
[
'page_name' => 'edit_users.php',
'edit_access_id' => 1,
'edit_access_uid' => 'AdminAccess',
'edit_access_data' => 'test',
'base_access' => 'list',
'page_access' => 'list',
'test_login_user_id' => true,
'test_username' => 'admin',
'loginUserId' => '1234567890ABCDEFG',
],
[
'loginUserId' => '1234567890ABCDEFG',
],
[],
[],
0,
[
'login_error' => 0,
'admin_flag' => true,
'check_access' => true,
'check_access_id' => 1,
'check_access_data' => 'value',
'base_access' => true,
'page_access' => true,
]
],
// login check via _POST loginUserId
'login: ok, _POST loginUserId' => [
[
'page_name' => 'edit_users.php',
'edit_access_id' => 1,
'edit_access_uid' => 'AdminAccess',
'edit_access_data' => 'test',
'base_access' => 'list',
'page_access' => 'list',
'test_login_user_id' => true,
'test_username' => 'admin',
'loginUserId' => '1234567890ABCDEFG',
],
[],
[
'loginUserId' => '1234567890ABCDEFG',
],
[],
0,
[
'login_error' => 0,
'admin_flag' => true,
'check_access' => true,
'check_access_id' => 1,
'check_access_data' => 'value',
'base_access' => true,
'page_access' => true,
]
],
// login: wrong GET loginUserId
'login: ok, illegal chars in _GET loginUserId' => [
[
'page_name' => 'edit_users.php',
'edit_access_id' => 1,
'edit_access_uid' => 'AdminAccess',
'edit_access_data' => 'test',
'base_access' => 'list',
'page_access' => 'list',
'test_login_user_id' => true,
'test_username' => 'admin',
'loginUserId' => '1234567890ABCDEFG'
],
[
'loginUserId' => '123$%_/45678¥\-^9~~0$AB&CDEFG',
],
[],
[],
0,
[
'login_error' => 0,
'admin_flag' => true,
'check_access' => true,
'check_access_id' => 1,
'check_access_data' => 'value',
'base_access' => true,
'page_access' => true,
]
],
'login: not matching _GET loginUserId' => [
[
'page_name' => 'edit_users.php',
'test_login_user_id' => true,
'test_username' => 'admin',
'loginUserId' => '1234567890ABCDEFG'
],
[
'loginUserId' => 'ABC'
],
[],
[],
3000,
[
'login_error' => 1010,
'error_string' => '<span style="color: red;">Fatal Error:</span> '
. '<b>Login Failed - Wrong Username or Password</b>',
'error_string_text' => 'Fatal Error: '
. 'Login Failed - Wrong Username or Password'
]
],
// login ok with both _GET loginUserId and _POST login username/password
'login: ok, _GET loginUserId AND login post user data' => [
[
'page_name' => 'edit_users.php',
'edit_access_id' => 1,
'edit_access_uid' => 'AdminAccess',
'edit_access_data' => 'test',
'base_access' => 'list',
'page_access' => 'list',
'test_login_user_id' => true,
'test_username' => 'admin',
'loginUserId' => '1234567890ABCDEFG',
],
[
'loginUserId' => '1234567890ABCDEFG',
],
[ [
'login_login' => 'Login', 'login_login' => 'Login',
'login_username' => 'admin', 'login_username' => 'admin',
@@ -485,9 +796,208 @@ final class CoreLibsACLLoginTest extends TestCase
'page_access' => true, 'page_access' => true,
] ]
], ],
// login with invalid loginUserId but valid username/password
'login: ok, bad _GET loginUserId AND good login post user data' => [
[
'page_name' => 'edit_users.php',
'edit_access_id' => 1,
'edit_access_uid' => 'AdminAccess',
'edit_access_data' => 'test',
'base_access' => 'list',
'page_access' => 'list',
'test_login_user_id' => true,
'test_username' => 'admin',
'loginUserId' => '1234567890ABCDEFG',
],
[
'loginUserId' => 'ABCS',
],
[
'login_login' => 'Login',
'login_username' => 'admin',
'login_password' => 'admin',
],
[],
0,
[
'login_error' => 0,
'admin_flag' => true,
'check_access' => true,
'check_access_id' => 1,
'check_access_data' => 'value',
'base_access' => true,
'page_access' => true,
]
],
// loginUserId check with revalidate on/off
'login: ok, but revalidate trigger, _GET loginUserId' => [
[
'page_name' => 'edit_users.php',
'edit_access_id' => 1,
'base_access' => 'list',
'page_access' => 'list',
'test_login_user_id_revalidate_after' => 'on',
'test_login_user_id' => true,
'test_username' => 'admin',
'loginUserId' => '1234567890ABCDEFG',
],
[
'loginUserId' => '1234567890ABCDEFG',
],
[],
[],
3000,
[
'login_error' => 1101,
'error_string' => '<span style="color: red;">Fatal Error:</span> '
. '<b>Login Failed - Login User ID must be validated</b>',
'error_string_text' => 'Fatal Error: '
. 'Login Failed - Login User ID must be validated'
]
],
// loginUserId check with revalidate on/off
'login: ok, revalidate set (outside), _GET loginUserId' => [
[
'page_name' => 'edit_users.php',
'edit_access_id' => 1,
'edit_access_uid' => 'AdminAccess',
'edit_access_data' => 'test',
'base_access' => 'list',
'page_access' => 'list',
'test_login_user_id_revalidate_after' => 'off',
'test_login_user_id' => true,
'test_username' => 'admin',
'loginUserId' => '1234567890ABCDEFG',
],
[
'loginUserId' => '1234567890ABCDEFG',
],
[],
[],
0,
[
'login_error' => 0,
'admin_flag' => true,
'check_access' => true,
'check_access_id' => 1,
'check_access_data' => 'value',
'base_access' => true,
'page_access' => true,
]
],
// loginUserId check with active time from only
'login: ok, _GET loginUserId, but outside valid (from:on) ' => [
[
'page_name' => 'edit_users.php',
'edit_access_id' => 1,
'base_access' => 'list',
'page_access' => 'list',
'test_login_user_id_valid_from' => 'on',
'test_login_user_id' => true,
'test_username' => 'admin',
'loginUserId' => '1234567890ABCDEFG',
],
[
'loginUserId' => '1234567890ABCDEFG',
],
[],
[],
3000,
[
'login_error' => 1102,
'error_string' => '<span style="color: red;">Fatal Error:</span> '
. '<b>Login Failed - Login User ID is outside valid date range</b>',
'error_string_text' => 'Fatal Error: '
. 'Login Failed - Login User ID is outside valid date range'
]
],
// loginUserId check with inactive time from only
'login: ok, _GET loginUserId, but outside valid (from:off) ' => [
[
'page_name' => 'edit_users.php',
'edit_access_id' => 1,
'edit_access_uid' => 'AdminAccess',
'edit_access_data' => 'test',
'base_access' => 'list',
'page_access' => 'list',
'test_login_user_id_valid_from' => 'off',
'test_login_user_id' => true,
'test_username' => 'admin',
'loginUserId' => '1234567890ABCDEFG',
],
[
'loginUserId' => '1234567890ABCDEFG',
],
[],
[],
0,
[
'login_error' => 0,
'admin_flag' => true,
'check_access' => true,
'check_access_id' => 1,
'check_access_data' => 'value',
'base_access' => true,
'page_access' => true,
]
],
// loginUserId check with active time until only
'login: ok, _GET loginUserId, but outside valid (until:on) ' => [
[
'page_name' => 'edit_users.php',
'edit_access_id' => 1,
'base_access' => 'list',
'page_access' => 'list',
'test_login_user_id_valid_until' => 'on',
'test_login_user_id' => true,
'test_username' => 'admin',
'loginUserId' => '1234567890ABCDEFG',
],
[
'loginUserId' => '1234567890ABCDEFG',
],
[],
[],
3000,
[
'login_error' => 1102,
'error_string' => '<span style="color: red;">Fatal Error:</span> '
. '<b>Login Failed - Login User ID is outside valid date range</b>',
'error_string_text' => 'Fatal Error: '
. 'Login Failed - Login User ID is outside valid date range'
]
],
// loginUserId check with active time from/until
'login: ok, _GET loginUserId, but outside valid (from:on,until:on) ' => [
[
'page_name' => 'edit_users.php',
'edit_access_id' => 1,
'base_access' => 'list',
'page_access' => 'list',
'test_login_user_id_valid_from' => 'on',
'test_login_user_id_valid_until' => 'on',
'test_login_user_id' => true,
'test_username' => 'admin',
'loginUserId' => '1234567890ABCDEFG',
],
[
'loginUserId' => '1234567890ABCDEFG',
],
[],
[],
3000,
[
'login_error' => 1102,
'error_string' => '<span style="color: red;">Fatal Error:</span> '
. '<b>Login Failed - Login User ID is outside valid date range</b>',
'error_string_text' => 'Fatal Error: '
. 'Login Failed - Login User ID is outside valid date range'
]
],
// //
// other: // other:
// login check edit access id of ID not null and not in array // login check edit access id of ID not null and not in array
// login OK, but during action user gets disabled/deleted/etc
]; ];
} }
@@ -498,14 +1008,16 @@ final class CoreLibsACLLoginTest extends TestCase
* @testdox ACL\Login Class tests [$_dataName] * @testdox ACL\Login Class tests [$_dataName]
* *
* @param array<string,mixed> $mock_settings * @param array<string,mixed> $mock_settings
* @param array<string,string> $get
* @param array<string,string> $post * @param array<string,string> $post
* @param array<string,mixed> $session * @param array<string,mixed> $session
* @param int $error * @param int $error
* @param array<string,mixed> $expected * @param array<string,mixed> $expected
* @return void * @return void
*/ */
public function testACLLogin( public function testACLLoginFlow(
array $mock_settings, array $mock_settings,
array $get,
array $post, array $post,
array $session, array $session,
int $error, int $error,
@@ -534,6 +1046,11 @@ final class CoreLibsACLLoginTest extends TestCase
}) })
); );
// set _GET data
foreach ($get as $get_var => $get_value) {
$_GET[$get_var] = $get_value;
}
// set _POST data // set _POST data
foreach ($post as $post_var => $post_value) { foreach ($post as $post_var => $post_value) {
$_POST[$post_var] = $post_value; $_POST[$post_var] = $post_value;
@@ -574,6 +1091,47 @@ final class CoreLibsACLLoginTest extends TestCase
. self::$db->dbEscapeLiteral($post['login_username']) . self::$db->dbEscapeLiteral($post['login_username'])
); );
} }
if (!empty($mock_settings['test_deleted'])) {
self::$db->dbExec(
"UPDATE edit_user SET deleted = 1 WHERE LOWER(username) = "
. self::$db->dbEscapeLiteral($post['login_username'])
);
}
if (!empty($mock_settings['test_login_user_id_locked'])) {
self::$db->dbExec(
"UPDATE edit_user SET login_user_id_locked = 1 WHERE LOWER(username) = "
. self::$db->dbEscapeLiteral($post['login_username'])
);
}
if (
!empty($mock_settings['test_locked_period_until']) ||
!empty($mock_settings['test_locked_period_after'])
) {
$q_sub = '';
if (!empty($mock_settings['test_locked_period_until'])) {
if ($mock_settings['test_locked_period_until'] == 'on') {
$q_sub .= "lock_until = NOW() + '1 day'::interval ";
} elseif ($mock_settings['test_locked_period_until'] == 'off') {
$q_sub .= "lock_until = NOW() - '1 day'::interval ";
}
}
if (!empty($mock_settings['test_locked_period_after'])) {
if (!empty($q_sub)) {
$q_sub .= ", ";
}
if ($mock_settings['test_locked_period_after'] == 'on') {
$q_sub .= "lock_after = NOW() - '1 day'::interval ";
} elseif ($mock_settings['test_locked_period_after'] == 'off') {
$q_sub .= "lock_after = NOW() + '1 day'::interval ";
}
}
self::$db->dbExec(
"UPDATE edit_user SET "
. $q_sub
. "WHERE LOWER(username) = "
. self::$db->dbEscapeLiteral($post['login_username'])
);
}
// test locked already // test locked already
if (!empty($mock_settings['test_locked'])) { if (!empty($mock_settings['test_locked'])) {
self::$db->dbExec( self::$db->dbExec(
@@ -635,6 +1193,62 @@ final class CoreLibsACLLoginTest extends TestCase
// set correct password next locked login // set correct password next locked login
$_POST['login_password'] = $post['login_password']; $_POST['login_password'] = $post['login_password'];
} }
if (!empty($mock_settings['test_login_user_id'])) {
self::$db->dbExec(
"UPDATE edit_user SET "
. "login_user_id_set_date = NOW(), "
. "login_user_id = "
. self::$db->dbEscapeLiteral($mock_settings['loginUserId'])
. " "
. "WHERE LOWER(username) = "
. self::$db->dbEscapeLiteral($mock_settings['test_username'])
);
}
if (!empty($mock_settings['test_login_user_id_revalidate_after'])) {
$q_sub = '';
if ($mock_settings['test_login_user_id_revalidate_after'] == 'on') {
$q_sub = "login_user_id_set_date = NOW() - '1 day'::interval, "
. "login_user_id_revalidate_after = '1 day'::interval ";
} else {
$q_sub = "login_user_id_set_date = NOW(), "
. "login_user_id_revalidate_after = '6 day'::interval ";
}
self::$db->dbExec(
"UPDATE edit_user SET "
. $q_sub
. "WHERE LOWER(username) = "
. self::$db->dbEscapeLiteral($mock_settings['test_username'])
);
}
if (
!empty($mock_settings['test_login_user_id_valid_from']) ||
!empty($mock_settings['test_login_user_id_valid_until'])
) {
$q_sub = '';
if (!empty($mock_settings['test_login_user_id_valid_from'])) {
if ($mock_settings['test_login_user_id_valid_from'] == 'on') {
$q_sub .= "login_user_id_valid_from = NOW() + '1 day'::interval ";
} elseif ($mock_settings['test_login_user_id_valid_from'] == 'off') {
$q_sub .= "login_user_id_valid_from = NOW() - '1 day'::interval ";
}
}
if (!empty($mock_settings['test_login_user_id_valid_until'])) {
if (!empty($q_sub)) {
$q_sub .= ", ";
}
if ($mock_settings['test_login_user_id_valid_until'] == 'on') {
$q_sub .= "login_user_id_valid_until = NOW() - '1 day'::interval ";
} elseif ($mock_settings['test_login_user_id_valid_until'] == 'off') {
$q_sub .= "login_user_id_valid_until = NOW() + '1 day'::interval ";
}
}
self::$db->dbExec(
"UPDATE edit_user SET "
. $q_sub
. "WHERE LOWER(username) = "
. self::$db->dbEscapeLiteral($mock_settings['test_username'])
);
}
// run test // run test
try { try {
@@ -734,6 +1348,13 @@ final class CoreLibsACLLoginTest extends TestCase
$login_mock->loginGetAcl(), $login_mock->loginGetAcl(),
'Assert get acl is array' 'Assert get acl is array'
); );
// if loginUserId in _GET or _POST check that it is set
if (!empty($get['loginUserId']) || !empty($post['loginUserId'])) {
$this->assertNotEmpty(
$login_mock->loginGetLoginUserId(),
'Assert loginUserId is set'
);
}
// TODO: detail match of ACL array (loginGetAcl) // TODO: detail match of ACL array (loginGetAcl)
// .. end with: loginLogoutUser // .. end with: loginLogoutUser
@@ -840,6 +1461,35 @@ final class CoreLibsACLLoginTest extends TestCase
); );
} }
// always check, even on error or not set
if (!$login_mock->loginGetLoginUserIdUnclean()) {
$this->assertEquals(
$_GET['loginUserId'] ?? $_POST['loginUserId'] ?? '',
$login_mock->loginGetLoginUserId(),
'Assert loginUserId matches'
);
} else {
$this->assertTrue(
$login_mock->loginGetLoginUserIdUnclean(),
'Assert loginUserId is unclear'
);
$this->assertNotEquals(
$_GET['loginUserId'] ?? $_POST['loginUserId'] ?? '',
$login_mock->loginGetLoginUserId(),
'Assert loginUserId does not matche _GET/_POST'
);
}
// check get/post login user id
$this->assertEquals(
(!empty($_GET['loginUserId']) ?
'GET' :
(!empty($_POST['loginUserId']) ?
'POST' : '')
),
$login_mock->loginGetLoginUserIdSource(),
'Assert loginUserId source matches'
);
// enable user again if flag set // enable user again if flag set
if (!empty($mock_settings['test_enabled'])) { if (!empty($mock_settings['test_enabled'])) {
self::$db->dbExec( self::$db->dbExec(
@@ -848,6 +1498,30 @@ final class CoreLibsACLLoginTest extends TestCase
. self::$db->dbEscapeLiteral($post['login_username']) . self::$db->dbEscapeLiteral($post['login_username'])
); );
} }
if (!empty($mock_settings['test_deleted'])) {
self::$db->dbExec(
"UPDATE edit_user SET deleted = 0 WHERE LOWER(username) = "
. self::$db->dbEscapeLiteral($post['login_username'])
);
}
if (!empty($mock_settings['test_login_user_id_locked'])) {
self::$db->dbExec(
"UPDATE edit_user SET login_user_id_locked = 0 WHERE LOWER(username) = "
. self::$db->dbEscapeLiteral($post['login_username'])
);
}
if (
!empty($mock_settings['test_locked_period_until']) ||
!empty($mock_settings['test_locked_period_after'])
) {
self::$db->dbExec(
"UPDATE edit_user SET "
. "lock_until = NULL, "
. "lock_after = NULL "
. "WHERE LOWER(username) = "
. self::$db->dbEscapeLiteral($post['login_username'])
);
}
// reset lock flag // reset lock flag
if (!empty($mock_settings['test_locked'])) { if (!empty($mock_settings['test_locked'])) {
self::$db->dbExec( self::$db->dbExec(
@@ -866,6 +1540,36 @@ final class CoreLibsACLLoginTest extends TestCase
. self::$db->dbEscapeLiteral($post['login_username']) . self::$db->dbEscapeLiteral($post['login_username'])
); );
} }
// if (!empty($mock_settings['test_login_user_id'])) {
// self::$db->dbExec(
// "UPDATE edit_user SET "
// . "login_user_id = NULL, "
// . "login_user_id_set_date = NULL "
// . "WHERE LOWER(username) = "
// . self::$db->dbEscapeLiteral($mock_settings['test_username'])
// );
// }
// if (!empty($mock_settings['test_login_user_id_revalidate_after'])) {
// self::$db->dbExec(
// "UPDATE edit_user SET "
// . "login_user_id_set_date = NULL, "
// . "login_user_id_revalidate_after = NULL "
// . "WHERE LOWER(username) = "
// . self::$db->dbEscapeLiteral($mock_settings['test_username'])
// );
// }
// if (
// !empty($mock_settings['test_login_user_id_valid_from']) ||
// !empty($mock_settings['test_login_user_id_valid_until'])
// ) {
// self::$db->dbExec(
// "UPDATE edit_user SET "
// . "login_user_id_valid_from = NULL, "
// . "login_user_id_valid_until = NULL "
// . "WHERE LOWER(username) = "
// . self::$db->dbEscapeLiteral($mock_settings['test_username'])
// );
// }
} }
// - loginGetAclList (null, invalid,) // - loginGetAclList (null, invalid,)
@@ -1126,7 +1830,7 @@ final class CoreLibsACLLoginTest extends TestCase
* @param string $input * @param string $input
* @return void * @return void
*/ */
public function testACLLoginGetPasswordLenght(string $input): void public function testACLLoginGetPasswordLength(string $input): void
{ {
$_SESSION = []; $_SESSION = [];
// init session (as MOCK) // init session (as MOCK)

View File

@@ -7,6 +7,7 @@
# PARAMETER 2: db user WHO MUST BE ABLE TO CREATE A DATABASE # PARAMETER 2: db user WHO MUST BE ABLE TO CREATE A DATABASE
# PARAMETER 3: db name # PARAMETER 3: db name
# PARAMETER 4: db host # PARAMETER 4: db host
# PARAMETER 5: print out for testing
load_sql="${1}"; load_sql="${1}";
# abort with 1 if we cannot find the file # abort with 1 if we cannot find the file
@@ -34,8 +35,13 @@ if [ $? -ne 0 ]; then
echo 4; echo 4;
exit 4; exit 4;
fi; fi;
# load data (redirect ALL error to null), on error exit with 5 # if error 5 thrown, test with enabled below
psql -U ${db_user} -h ${db_host} -f ${load_sql} ${db_name} 2>&1 1>/dev/null 2>/dev/null; if [ ! -z "${5}" ]; then
psql -U ${db_user} -h ${db_host} -f ${load_sql} ${db_name};
else
# load data (redirect ALL error to null), on error exit with 5
psql -U ${db_user} -h ${db_host} -f ${load_sql} ${db_name} 2>&1 1>/dev/null 2>/dev/null;
fi;
if [ $? -ne 0 ]; then if [ $? -ne 0 ]; then
echo 5; echo 5;
exit 5; exit 5;

View File

@@ -2,7 +2,8 @@
-- create random string with length X -- create random string with length X
CREATE FUNCTION random_string(randomLength int) CREATE FUNCTION random_string(randomLength int)
RETURNS text AS $$ RETURNS text AS
$$
SELECT array_to_string( SELECT array_to_string(
ARRAY( ARRAY(
SELECT substring( SELECT substring(
@@ -14,53 +15,58 @@ SELECT array_to_string(
), ),
'' ''
) )
$$ LANGUAGE SQL $$
LANGUAGE SQL
RETURNS NULL ON NULL INPUT RETURNS NULL ON NULL INPUT
VOLATILE; -- LEAKPROOF;-- END: function/random_string.sql VOLATILE; -- LEAKPROOF;
-- END: function/random_string.sql
-- START: function/set_edit_generic.sql -- START: function/set_edit_generic.sql
-- adds the created or updated date tags -- adds the created or updated date tags
CREATE OR REPLACE FUNCTION set_edit_generic() RETURNS TRIGGER AS ' CREATE OR REPLACE FUNCTION set_edit_generic()
DECLARE RETURNS TRIGGER AS
random_length INT = 12; -- that should be long enough $$
BEGIN DECLARE
IF TG_OP = ''INSERT'' THEN random_length INT = 12; -- that should be long enough
NEW.date_created := ''now''; BEGIN
NEW.cuid := random_string(random_length); IF TG_OP = 'INSERT' THEN
ELSIF TG_OP = ''UPDATE'' THEN NEW.date_created := 'now';
NEW.date_updated := ''now''; NEW.cuid := random_string(random_length);
END IF; ELSIF TG_OP = 'UPDATE' THEN
RETURN NEW; NEW.date_updated := 'now';
END; END IF;
' LANGUAGE 'plpgsql'; RETURN NEW;
END;
$$
LANGUAGE 'plpgsql';
-- END: function/set_edit_generic.sql -- END: function/set_edit_generic.sql
-- START: function/edit_access_set_uid.sql -- START: function/edit_access_set_uid.sql
-- add uid add for edit_access table -- add uid add for edit_access table
CREATE OR REPLACE FUNCTION set_edit_access_uid() RETURNS TRIGGER AS CREATE OR REPLACE FUNCTION set_edit_access_uid() RETURNS TRIGGER AS
$$ $$
DECLARE DECLARE
myrec RECORD; myrec RECORD;
v_uid VARCHAR; v_uid VARCHAR;
BEGIN BEGIN
-- skip if NEW.name is not set -- skip if NEW.name is not set
IF NEW.name IS NOT NULL AND NEW.name <> '' THEN IF NEW.name IS NOT NULL AND NEW.name <> '' THEN
-- use NEW.name as base, remove all spaces -- use NEW.name as base, remove all spaces
-- name data is already unique, so we do not need to worry about this here -- name data is already unique, so we do not need to worry about this here
v_uid := REPLACE(NEW.name, ' ', ''); v_uid := REPLACE(NEW.name, ' ', '');
IF TG_OP = 'INSERT' THEN IF TG_OP = 'INSERT' THEN
-- always set -- always set
NEW.uid := v_uid;
ELSIF TG_OP = 'UPDATE' THEN
-- check if not set, then set
SELECT INTO myrec t.* FROM edit_access t WHERE edit_access_id = NEW.edit_access_id;
IF FOUND THEN
NEW.uid := v_uid; NEW.uid := v_uid;
ELSIF TG_OP = 'UPDATE' THEN
-- check if not set, then set
SELECT INTO myrec t.* FROM edit_access t WHERE edit_access_id = NEW.edit_access_id;
IF FOUND THEN
NEW.uid := v_uid;
END IF;
END IF; END IF;
END IF; END IF;
RETURN NEW; END IF;
END; RETURN NEW;
END;
$$ $$
LANGUAGE 'plpgsql'; LANGUAGE 'plpgsql';
-- END: function/edit_access_set_uid.sql -- END: function/edit_access_set_uid.sql
@@ -69,28 +75,28 @@ $$
CREATE OR REPLACE FUNCTION set_edit_group_uid() RETURNS TRIGGER AS CREATE OR REPLACE FUNCTION set_edit_group_uid() RETURNS TRIGGER AS
$$ $$
DECLARE DECLARE
myrec RECORD; myrec RECORD;
v_uid VARCHAR; v_uid VARCHAR;
BEGIN BEGIN
-- skip if NEW.name is not set -- skip if NEW.name is not set
IF NEW.name IS NOT NULL AND NEW.name <> '' THEN IF NEW.name IS NOT NULL AND NEW.name <> '' THEN
-- use NEW.name as base, remove all spaces -- use NEW.name as base, remove all spaces
-- name data is already unique, so we do not need to worry about this here -- name data is already unique, so we do not need to worry about this here
v_uid := REPLACE(NEW.name, ' ', ''); v_uid := REPLACE(NEW.name, ' ', '');
IF TG_OP = 'INSERT' THEN IF TG_OP = 'INSERT' THEN
-- always set -- always set
NEW.uid := v_uid;
ELSIF TG_OP = 'UPDATE' THEN
-- check if not set, then set
SELECT INTO myrec t.* FROM edit_group t WHERE edit_group_id = NEW.edit_group_id;
IF FOUND THEN
NEW.uid := v_uid; NEW.uid := v_uid;
ELSIF TG_OP = 'UPDATE' THEN
-- check if not set, then set
SELECT INTO myrec t.* FROM edit_group t WHERE edit_group_id = NEW.edit_group_id;
IF FOUND THEN
NEW.uid := v_uid;
END IF;
END IF; END IF;
END IF; END IF;
RETURN NEW; END IF;
END; RETURN NEW;
END;
$$ $$
LANGUAGE 'plpgsql'; LANGUAGE 'plpgsql';
-- END: function/edit_group_set_uid.sql -- END: function/edit_group_set_uid.sql
@@ -246,6 +252,32 @@ END
$$ $$
LANGUAGE 'plpgsql'; LANGUAGE 'plpgsql';
-- END: function/edit_log_partition_insert.sql -- END: function/edit_log_partition_insert.sql
-- START: function/edit_user_set_login_user_id_set_date.sql
-- set edit user login_user_id_set_date if login_user_id is set
-- NOW() if not empty
CREATE OR REPLACE FUNCTION set_login_user_id_set_date()
RETURNS TRIGGER AS
$$
BEGIN
-- if new is not null/empty
-- and old one is null or old one different new one
-- set NOW()
-- if new one is NULL
-- set NULL
IF
NEW.login_user_id IS NOT NULL AND NEW.login_user_id <> '' AND
(OLD.login_user_id IS NULL OR NEW.login_user_id <> OLD.login_user_id)
THEN
NEW.login_user_id_set_date = NOW();
ELSIF NEW.login_user_id IS NULL OR NEW.login_user_id = '' THEN
NEW.login_user_id_set_date = NULL;
END IF;
RETURN NEW;
END;
$$
LANGUAGE 'plpgsql';
-- END: function/edit_user_set_login_user_id_set_date.sql
-- START: table/edit_temp_files.sql -- START: table/edit_temp_files.sql
-- AUTHOR: Clemens Schwaighofer -- AUTHOR: Clemens Schwaighofer
-- DATE: 2005/07/08 -- DATE: 2005/07/08
@@ -526,34 +558,80 @@ CREATE TABLE edit_user (
FOREIGN KEY (edit_scheme_id) REFERENCES edit_scheme (edit_scheme_id) MATCH FULL ON DELETE CASCADE ON UPDATE CASCADE, FOREIGN KEY (edit_scheme_id) REFERENCES edit_scheme (edit_scheme_id) MATCH FULL ON DELETE CASCADE ON UPDATE CASCADE,
edit_access_right_id INT NOT NULL, edit_access_right_id INT NOT NULL,
FOREIGN KEY (edit_access_right_id) REFERENCES edit_access_right (edit_access_right_id) MATCH FULL ON DELETE CASCADE ON UPDATE CASCADE, FOREIGN KEY (edit_access_right_id) REFERENCES edit_access_right (edit_access_right_id) MATCH FULL ON DELETE CASCADE ON UPDATE CASCADE,
enabled SMALLINT NOT NULL DEFAULT 0, -- username/password
deleted SMALLINT NOT NULL DEFAULT 0,
username VARCHAR UNIQUE, username VARCHAR UNIQUE,
password VARCHAR, password VARCHAR,
-- name block
first_name VARCHAR, first_name VARCHAR,
last_name VARCHAR, last_name VARCHAR,
first_name_furigana VARCHAR, first_name_furigana VARCHAR,
last_name_furigana VARCHAR, last_name_furigana VARCHAR,
-- email
email VARCHAR,
-- eanbled/deleted flag
enabled SMALLINT NOT NULL DEFAULT 0,
deleted SMALLINT NOT NULL DEFAULT 0,
-- general flags
strict SMALLINT DEFAULT 0,
locked SMALLINT DEFAULT 0,
protected SMALLINT NOT NULL DEFAULT 0,
-- legacy, debug flags
debug SMALLINT NOT NULL DEFAULT 0, debug SMALLINT NOT NULL DEFAULT 0,
db_debug SMALLINT NOT NULL DEFAULT 0, db_debug SMALLINT NOT NULL DEFAULT 0,
email VARCHAR, -- is admin user
protected SMALLINT NOT NULL DEFAULT 0,
admin SMALLINT NOT NULL DEFAULT 0, admin SMALLINT NOT NULL DEFAULT 0,
-- last login log
last_login TIMESTAMP WITHOUT TIME ZONE, last_login TIMESTAMP WITHOUT TIME ZONE,
-- login error
login_error_count INT DEFAULT 0, login_error_count INT DEFAULT 0,
login_error_date_last TIMESTAMP WITHOUT TIME ZONE, login_error_date_last TIMESTAMP WITHOUT TIME ZONE,
login_error_date_first TIMESTAMP WITHOUT TIME ZONE, login_error_date_first TIMESTAMP WITHOUT TIME ZONE,
strict SMALLINT DEFAULT 0, -- time locked
locked SMALLINT DEFAULT 0, lock_until TIMESTAMP WITHOUT TIME ZONE,
lock_after TIMESTAMP WITHOUT TIME ZONE,
-- password change
password_change_date TIMESTAMP WITHOUT TIME ZONE, -- only when password is first set or changed password_change_date TIMESTAMP WITHOUT TIME ZONE, -- only when password is first set or changed
password_change_interval INTERVAL, -- null if no change is needed, or d/m/y time interval password_change_interval INTERVAL, -- null if no change is needed, or d/m/y time interval
password_reset_time TIMESTAMP WITHOUT TIME ZONE, -- when the password reset was requested password_reset_time TIMESTAMP WITHOUT TIME ZONE, -- when the password reset was requested
password_reset_uid VARCHAR, -- the uid to access the password reset page password_reset_uid VARCHAR, -- the uid to access the password reset page
-- _GET login id for direct login
login_user_id VARCHAR, -- the login uid, at least 32 chars
login_user_id_set_date TIMESTAMP WITHOUT TIME ZONE, -- when above uid was set
login_user_id_valid_from TIMESTAMP WITHOUT TIME ZONE, -- if set, from when the above uid is valid
login_user_id_valid_until TIMESTAMP WITHOUT TIME ZONE, -- if set, until when the above uid is valid
login_user_id_revalidate_after INTERVAL DEFAULT '0 days', -- user must login to revalidated login id after set days, 0 for forever
login_user_id_locked SMALLINT DEFAULT 0, -- lock for login user id, but still allow normal login
-- additional ACL json block
additional_acl JSONB -- additional ACL as JSON string (can be set by other pages) additional_acl JSONB -- additional ACL as JSON string (can be set by other pages)
) INHERITS (edit_generic) WITHOUT OIDS; ) INHERITS (edit_generic) WITHOUT OIDS;
COMMENT ON COLUMN edit_user.username IS 'Login username, must set';
COMMENT ON COLUMN edit_user.password IS 'Login password, must set';
COMMENT ON COLUMN edit_user.enabled IS 'Login is enabled (master switch)';
COMMENT ON COLUMN edit_user.deleted IS 'Login is deleted (master switch), overrides all other';
COMMENT ON COLUMN edit_user.strict IS 'If too many failed logins user will be locked, default off';
COMMENT ON COLUMN edit_user.locked IS 'Locked from too many wrong password logins';
COMMENT ON COLUMN edit_user.protected IS 'User can only be chnaged by admin user';
COMMENT ON COLUMN edit_user.debug IS 'Turn debug flag on (legacy)';
COMMENT ON COLUMN edit_user.db_debug IS 'Turn DB debug flag on (legacy)';
COMMENT ON COLUMN edit_user.admin IS 'If set, this user is SUPER admin';
COMMENT ON COLUMN edit_user.last_login IS 'Last succesfull login tiemstamp';
COMMENT ON COLUMN edit_user.login_error_count IS 'Number of failed logins, reset on successful login';
COMMENT ON COLUMN edit_user.login_error_date_last IS 'Last login error date';
COMMENT ON COLUMN edit_user.login_error_date_first IS 'First login error date, reset on successfull login';
COMMENT ON COLUMN edit_user.lock_until IS 'Account is locked until this date, <';
COMMENT ON COLUMN edit_user.lock_after IS 'Account is locked after this date, >';
COMMENT ON COLUMN edit_user.password_change_date IS 'Password was changed on';
COMMENT ON COLUMN edit_user.password_change_interval IS 'After how many days the password has to be changed';
COMMENT ON COLUMN edit_user.password_reset_time IS 'When the password reset was requested. For reset page uid valid check'; COMMENT ON COLUMN edit_user.password_reset_time IS 'When the password reset was requested. For reset page uid valid check';
COMMENT ON COLUMN edit_user.password_reset_uid IS 'Password reset page uid'; COMMENT ON COLUMN edit_user.password_reset_uid IS 'Password reset page uid, one time, invalid after reset successful or time out';
COMMENT ON COLUMN edit_user.login_user_id IS 'Min 32 character UID to be used to login without password. Via GET/POST parameter';
COMMENT ON COLUMN edit_user.login_user_id_set_date IS 'login id was set at what date';
COMMENT ON COLUMN edit_user.login_user_id_valid_from IS 'login id is valid from this date, >=';
COMMENT ON COLUMN edit_user.login_user_id_valid_until IS 'login id is valid until this date, <=';
COMMENT ON COLUMN edit_user.login_user_id_revalidate_after IS 'If set to a number greater 0 then user must login after given amount of days to revalidate, set to 0 for valid forver';
COMMENT ON COLUMN edit_user.login_user_id_locked IS 'A separte lock flag for login id, user can still login normal';
COMMENT ON COLUMN edit_user.additional_acl IS 'Additional Access Control List stored in JSON format';
-- END: table/edit_user.sql -- END: table/edit_user.sql
-- START: table/edit_log.sql -- START: table/edit_log.sql
-- AUTHOR: Clemens Schwaighofer -- AUTHOR: Clemens Schwaighofer
@@ -774,6 +852,11 @@ FOR EACH ROW EXECUTE PROCEDURE set_edit_generic();
CREATE TRIGGER trg_edit_user CREATE TRIGGER trg_edit_user
BEFORE INSERT OR UPDATE ON edit_user BEFORE INSERT OR UPDATE ON edit_user
FOR EACH ROW EXECUTE PROCEDURE set_edit_generic(); FOR EACH ROW EXECUTE PROCEDURE set_edit_generic();
-- DROP TRIGGER IF EXISTS trg_edit_user_set_login_user_id_set_date ON edit_user;
CREATE TRIGGER trg_edit_user_set_login_user_id_set_date
BEFORE INSERT OR UPDATE ON edit_user
FOR EACH ROW EXECUTE PROCEDURE set_login_user_id_set_date();
-- END: trigger/trg_edit_user.sql -- END: trigger/trg_edit_user.sql
-- START: trigger/trg_edit_visible_group.sql -- START: trigger/trg_edit_visible_group.sql
-- DROP TRIGGER IF EXISTS trg_edit_visible_group ON edit_visible_group; -- DROP TRIGGER IF EXISTS trg_edit_visible_group ON edit_visible_group;

View File

@@ -0,0 +1,61 @@
<?php // phpcs:ignore PSR1.Files.SideEffects
// this is an empty test page for login tests only
declare(strict_types=1);
$DEBUG_ALL_OVERRIDE = false; // set to 1 to debug on live/remote server locations
$DEBUG_ALL = true;
$PRINT_ALL = true;
$DB_DEBUG = true;
if ($DEBUG_ALL) {
error_reporting(E_ALL | E_STRICT | E_ERROR | E_WARNING | E_PARSE | E_COMPILE_ERROR);
}
ob_start();
// basic class test file
define('USE_DATABASE', true);
// sample config
require 'config.php';
// define log file id
$LOG_FILE_ID = 'classTest';
$SET_SESSION_NAME = EDIT_SESSION_NAME;
// init login & backend class
$session = new CoreLibs\Create\Session($SET_SESSION_NAME);
$log = new CoreLibs\Debug\Logging([
'log_folder' => BASE . LOG,
'file_id' => $LOG_FILE_ID,
// add file date
'print_file_date' => true,
// set debug and print flags
'debug_all' => $DEBUG_ALL ?? false,
'echo_all' => $ECHO_ALL ?? false,
'print_all' => $PRINT_ALL ?? false,
]);
$db = new CoreLibs\DB\IO(DB_CONFIG, $log);
$login = new CoreLibs\ACL\Login($db, $log, $session);
$locale = \CoreLibs\Language\GetLocale::setLocale();
$l10n = new \CoreLibs\Language\L10n(
$locale['locale'],
$locale['domain'],
$locale['path'],
);
print "<!DOCTYPE html>";
print "<html><head><title>GROUP TESTER</title><head>";
print "<body>";
print '<form method="post" name="loginlogout">';
print '<a href="javascript:document.loginlogout.login_logout.value=\'Logou\';'
. 'document.loginlogout.submit();">Logout</a>';
print '<input type="hidden" name="login_logout" value="">';
print '</form>';
print "<h1>TEST Login</h1>";
print "</body></html>";
// __END__

View File

@@ -72,30 +72,36 @@ use CoreLibs\Check\Password;
class Login class Login
{ {
/** @var string */ /** @var string the user id var*/
private $euid; // the user id var private $euid;
/** @var string _GET/_POST loginUserId parameter for non password login */
private $login_user_id;
/** @var string source, either _GET or _POST or empty */
private $login_user_id_source;
/** @var bool set to true if illegal characters where found in the login user id string */
private $login_unclear = false;
// is set to one if login okay, or EUID is set and user is okay to access this page // is set to one if login okay, or EUID is set and user is okay to access this page
/** @var bool */ /** @var bool */
private $permission_okay = false; private $permission_okay = false;
/** @var string */ /** @var string pressed login */
public $login; // pressed login public $login;
/** @var string */ /** @var string master action command */
private $action; // master action command private $action;
/** @var string */ /** @var string login name */
private $username; // login name private $username;
/** @var string */ /** @var string login password */
private $password; // login password private $password;
/** @var string */ /** @var string logout button */
private $logout; // logout button private $logout;
/** @var bool */ /** @var bool if this is set to true, the user can change passwords */
private $password_change = false; // if this is set to true, the user can change passwords private $password_change = false;
/** @var bool */ /** @var bool password change was successful */
private $password_change_ok = false; // password change was successful private $password_change_ok = false;
// can we reset password and mail to user with new password set screen // can we reset password and mail to user with new password set screen
/** @var bool */ /** @var bool */
private $password_forgot = false; private $password_forgot = false;
/** @var bool */ /** @var bool password forgot mail send ok */
// private $password_forgot_ok = false; // password forgot mail send ok // private $password_forgot_ok = false;
/** @var string */ /** @var string */
private $change_password; private $change_password;
/** @var string */ /** @var string */
@@ -106,8 +112,8 @@ class Login
private $pw_new_password; private $pw_new_password;
/** @var string */ /** @var string */
private $pw_new_password_confirm; private $pw_new_password_confirm;
/** @var array<string> */ /** @var array<string> array of users for which the password change is forbidden */
private $pw_change_deny_users = []; // array of users for which the password change is forbidden private $pw_change_deny_users = [];
/** @var string */ /** @var string */
private $logout_target = ''; private $logout_target = '';
/** @var int */ /** @var int */
@@ -117,8 +123,7 @@ class Login
/** @var string */ /** @var string */
private $page_name = ''; private $page_name = '';
// if we have password change we need to define some rules /** @var int if we have password change we need to define some rules */
/** @var int */
private $password_min_length = 9; private $password_min_length = 9;
/** @var int an true maxium min, can never be set below this */ /** @var int an true maxium min, can never be set below this */
private $password_min_length_max = 9; private $password_min_length_max = 9;
@@ -126,8 +131,7 @@ class Login
// it will be set back to 255 // it will be set back to 255
/** @var int */ /** @var int */
private $password_max_length = 255; private $password_max_length = 255;
// can have several regexes, if nothing set, all is ok /** @var array<string> can have several regexes, if nothing set, all is ok */
/** @var array<string> */
private $password_valid_chars = [ private $password_valid_chars = [
// '^(?=.*\d)(?=.*[A-Za-z])[0-9A-Za-z!@#$%]{8,}$', // '^(?=.*\d)(?=.*[A-Za-z])[0-9A-Za-z!@#$%]{8,}$',
// '^(?.*(\pL)u)(?=.*(\pN)u)(?=.*([^\pL\pN])u).{8,}', // '^(?.*(\pL)u)(?=.*(\pN)u)(?=.*([^\pL\pN])u).{8,}',
@@ -234,6 +238,14 @@ class Login
'msg' => 'Login Failed - Wrong Username or Password', 'msg' => 'Login Failed - Wrong Username or Password',
'flag' => 'e' 'flag' => 'e'
], ],
'1101' => [
'msg' => 'Login Failed - Login User ID must be validated',
'flag' => 'e'
],
'1102' => [
'msg' => 'Login Failed - Login User ID is outside valid date range',
'flag' => 'e'
],
'102' => [ '102' => [
'msg' => 'Login Failed - Please enter username and password', 'msg' => 'Login Failed - Please enter username and password',
'flag' => 'e' 'flag' => 'e'
@@ -250,6 +262,18 @@ class Login
'msg' => 'Login Failed - User is locked', 'msg' => 'Login Failed - User is locked',
'flag' => 'e' 'flag' => 'e'
], ],
'106' => [
'msg' => 'Login Failed - User is deleted',
'flag' => 'e'
],
'107' => [
'msg' => 'Login Failed - User in locked via date period',
'flag' => 'e'
],
'108' => [
'msg' => 'Login Failed - User is locked via Login User ID',
'flag' => 'e'
],
'109' => [ '109' => [
'msg' => 'Check permission query reading failed', 'msg' => 'Check permission query reading failed',
'flag' => 'e' 'flag' => 'e'
@@ -360,6 +384,47 @@ class Login
// **** PRIVATE INTERNAL // **** PRIVATE INTERNAL
// ************************************************************************* // *************************************************************************
/**
* Checks for all flags and sets error codes for each
* In order:
* delete > enable > lock > period lock > login user id lock
*
* @param int $deleted User deleted check
* @param int $enabled User not enabled check
* @param int $locked Locked because of too many invalid passwords
* @param int $locked_period Locked because of time period set
* @param int $login_user_id_locked Locked from using Login User Id
* @return bool
*/
private function loginValidationCheck(
int $deleted,
int $enabled,
int $locked,
int $locked_period,
int $login_user_id_locked
): bool {
$validation = false;
if ($deleted) {
// user is deleted
$this->login_error = 106;
} elseif (!$enabled) {
// user is not enabled
$this->login_error = 104;
} elseif ($locked) {
// user is locked, either set or auto set
$this->login_error = 105;
} elseif ($locked_period) {
// locked date trigger
$this->login_error = 107;
} elseif ($login_user_id_locked) {
// user is locked, either set or auto set
$this->login_error = 108;
} else {
$validation = true;
}
return $validation;
}
/** /**
* checks if password is valid, sets internal error login variable * checks if password is valid, sets internal error login variable
* *
@@ -393,7 +458,6 @@ class Login
) { ) {
// this means password cannot be decrypted because of missing crypt methods // this means password cannot be decrypted because of missing crypt methods
$this->login_error = 9999; $this->login_error = 9999;
$password_ok = false;
} elseif ( } elseif (
preg_match("/^\\$2y\\$/", $hash) && preg_match("/^\\$2y\\$/", $hash) &&
!Password::passwordVerify($password, $hash) !Password::passwordVerify($password, $hash)
@@ -401,7 +465,6 @@ class Login
// this is the new password hash method, is only $2y$ // this is the new password hash method, is only $2y$
// all others are not valid anymore // all others are not valid anymore
$this->login_error = 1013; $this->login_error = 1013;
$password_ok = false;
} elseif ( } elseif (
!preg_match("/^\\$2(a|y)\\$/", $hash) && !preg_match("/^\\$2(a|y)\\$/", $hash) &&
!preg_match("/^\\$1\\$/", $hash) && !preg_match("/^\\$1\\$/", $hash) &&
@@ -410,7 +473,6 @@ class Login
) { ) {
// check old plain password, case sensitive // check old plain password, case sensitive
$this->login_error = 1012; $this->login_error = 1012;
$password_ok = false;
} else { } else {
// all ok // all ok
$password_ok = true; $password_ok = true;
@@ -418,6 +480,28 @@ class Login
return $password_ok; return $password_ok;
} }
/**
* Check if Login User ID is allowed to login
*
* @param int $login_user_id_valid_date
* @param int $login_user_id_revalidate
* @return bool
*/
private function loginLoginUserIdCheck(
int $login_user_id_valid_date,
int $login_user_id_revalidate
): bool {
$login_id_ok = false;
if ($login_user_id_revalidate) {
$this->login_error = 1101;
} elseif (!$login_user_id_valid_date) {
$this->login_error = 1102;
} else {
$login_id_ok = true;
}
return $login_id_ok;
}
/** /**
* if user pressed login button this script is called, * if user pressed login button this script is called,
* but only if there is no preview euid set * but only if there is no preview euid set
@@ -427,11 +511,12 @@ class Login
private function loginLoginUser(): void private function loginLoginUser(): void
{ {
// if pressed login at least and is not yet loggined in // if pressed login at least and is not yet loggined in
if ($this->euid || !$this->login) { if ($this->euid || (!$this->login && !$this->login_user_id)) {
return; return;
} }
// if not username AND password where given // if not username AND password where given
if (!($this->password && $this->username)) { // OR no login_user_id
if (!($this->username && $this->password) && !$this->login_user_id) {
$this->login_error = 102; $this->login_error = 102;
$this->permission_okay = false; $this->permission_okay = false;
return; return;
@@ -441,12 +526,39 @@ class Login
$q = "SELECT eu.edit_user_id, eu.username, eu.password, " $q = "SELECT eu.edit_user_id, eu.username, eu.password, "
. "eu.edit_group_id, " . "eu.edit_group_id, "
. "eg.name AS edit_group_name, admin, " . "eg.name AS edit_group_name, admin, "
// login error + locked
. "eu.login_error_count, eu.login_error_date_last, " . "eu.login_error_count, eu.login_error_date_last, "
. "eu.login_error_date_first, eu.strict, eu.locked, " . "eu.login_error_date_first, eu.strict, eu.locked, "
// date based lock
. "CASE WHEN ("
. "(eu.lock_until IS NULL "
. "OR (eu.lock_until IS NOT NULL AND NOW() >= eu.lock_until)) "
. "AND (eu.lock_after IS NULL "
. "OR (eu.lock_after IS NOT NULL AND NOW() <= eu.lock_after))"
. ") THEN 0::INT ELSE 1::INT END locked_period, "
// debug (legacy)
. "eu.debug, eu.db_debug, " . "eu.debug, eu.db_debug, "
// enabled
. "eu.enabled, eu.deleted, "
// login id validation
. "CASE WHEN ("
. "(eu.login_user_id_valid_from IS NULL "
. "OR (eu.login_user_id_valid_from IS NOT NULL AND NOW() >= eu.login_user_id_valid_from)) "
. "AND (eu.login_user_id_valid_until IS NULL "
. "OR (eu.login_user_id_valid_until IS NOT NULL AND NOW() <= eu.login_user_id_valid_until))"
. ") THEN 1::INT ELSE 0::INT END AS login_user_id_valid_date, "
// check if user must login
. "CASE WHEN eu.login_user_id_revalidate_after > '0 days'::INTERVAL "
. "AND (eu.login_user_id_set_date + eu.login_user_id_revalidate_after)::DATE "
. "<= NOW()::DATE "
. "THEN 1::INT ELSE 0::INT END AS login_user_id_revalidate, "
. "eu.login_user_id_locked, "
// language
. "el.short_name AS locale, el.iso_name AS encoding, "
// levels
. "eareu.level AS user_level, eareu.type AS user_type, " . "eareu.level AS user_level, eareu.type AS user_type, "
. "eareg.level AS group_level, eareg.type AS group_type, " . "eareg.level AS group_level, eareg.type AS group_type, "
. "eu.enabled, el.short_name AS locale, el.iso_name AS encoding, " // colors
. "first.header_color AS first_header_color, " . "first.header_color AS first_header_color, "
. "second.header_color AS second_header_color, second.template " . "second.header_color AS second_header_color, second.template "
. "FROM edit_user eu " . "FROM edit_user eu "
@@ -458,11 +570,17 @@ class Login
. "edit_scheme first " . "edit_scheme first "
. "WHERE first.edit_scheme_id = eg.edit_scheme_id " . "WHERE first.edit_scheme_id = eg.edit_scheme_id "
. "AND eu.edit_group_id = eg.edit_group_id " . "AND eu.edit_group_id = eg.edit_group_id "
. "AND eu.edit_language_id = el.edit_language_id AND " . "AND eu.edit_language_id = el.edit_language_id "
. "eu.edit_access_right_id = eareu.edit_access_right_id AND " . "AND eu.edit_access_right_id = eareu.edit_access_right_id "
. "eg.edit_access_right_id = eareg.edit_access_right_id AND " . "AND eg.edit_access_right_id = eareg.edit_access_right_id "
// password match is done in script, against old plain or new blowfish encypted . "AND "
. "(LOWER(username) = '" . $this->db->dbEscapeString(strtolower($this->username)) . "') "; // either login_user_id OR password must be given
. (!empty($this->login_user_id && empty($this->username)) ?
// check with login id if set and NO username
"eu.login_user_id = " . $this->db->dbEscapeLiteral($this->login_user_id) . " " :
// password match is done in script, against old plain or new blowfish encypted
"LOWER(username) = " . $this->db->dbEscapeLiteral(strtolower($this->username)) . " "
);
// reset any query data that might exist // reset any query data that might exist
$this->db->dbCacheReset($q); $this->db->dbCacheReset($q);
// never cache return data // never cache return data
@@ -488,17 +606,34 @@ class Login
// - password is readable // - password is readable
// - encrypted password matches // - encrypted password matches
// - plain password matches // - plain password matches
if (
if (!$res['enabled']) { !$this->loginValidationCheck(
// user is enabled (int)$res['deleted'],
$this->login_error = 104; (int)$res['enabled'],
} elseif ($res['locked']) { (int)$res['locked'],
// user is locked, either set or auto set (int)$res['locked_period'],
$this->login_error = 105; (int)$res['login_user_id_locked']
} elseif (!$this->loginPasswordCheck($res['password'])) { )
) {
// error set in method (104, 105, 106, 107, 108)
} elseif (
empty($this->username) &&
!empty($this->login_user_id) &&
!$this->loginLoginUserIdCheck(
(int)$res['login_user_id_valid_date'],
(int)$res['login_user_id_revalidate']
)
) {
// check done in loginLoginIdCheck method
// aborts on must revalidate and not valid (date range)
} elseif (
!empty($this->username) &&
!$this->loginPasswordCheck($res['password'])
) {
// none to be set, set in login password check // none to be set, set in login password check
// this is not valid password input error here // this is not valid password input error here
// all error codes are set in loginPasswordCheck method // all error codes are set in loginPasswordCheck method
// also valid if login_user_id is ok
} else { } else {
// check if the current password is an invalid hash and do a rehash and set password // check if the current password is an invalid hash and do a rehash and set password
// $this->debug('LOGIN', 'Hash: '.$res['password'].' -> VERIFY: ' // $this->debug('LOGIN', 'Hash: '.$res['password'].' -> VERIFY: '
@@ -1396,6 +1531,34 @@ EOM;
$this->login_is_ajax_page = true; $this->login_is_ajax_page = true;
} }
// attach outside uid for login post > get > empty
$this->login_user_id = $_POST['loginUserId'] ?? $_GET['loginUserId'] ?? '';
// cleanup only alphanumeric
if (!empty($this->login_user_id)) {
// set post/get only if actually set
if (isset($_POST['loginUserId'])) {
$this->login_user_id_source = 'POST';
} elseif (isset($_GET['loginUserId'])) {
$this->login_user_id_source = 'GET';
}
// clean login user id
$login_user_id_changed = 0;
$this->login_user_id = preg_replace(
"/[^A-Za-z0-9]/",
'',
$this->login_user_id,
-1,
$login_user_id_changed
);
// flag unclean input data
if ($login_user_id_changed > 0) {
$this->login_unclear = true;
// error for invalid user id?
$this->log->debug('LOGIN USER ID', 'Invalid characters: '
. $login_user_id_changed . ' in loginUserId: '
. $this->login_user_id . ' (' . $this->login_user_id_source . ')');
}
}
// if there is none, there is none, saves me POST/GET check // if there is none, there is none, saves me POST/GET check
$this->euid = array_key_exists('EUID', $_SESSION) ? $_SESSION['EUID'] : 0; $this->euid = array_key_exists('EUID', $_SESSION) ? $_SESSION['EUID'] : 0;
// get login vars, are so, can't be changed // get login vars, are so, can't be changed
@@ -1706,8 +1869,30 @@ EOM;
if ($this->login_error == 103) { if ($this->login_error == 103) {
return $this->permission_okay; return $this->permission_okay;
} }
// if ($this->euid && $this->login_error != 103) { $q = "SELECT ep.filename, "
$q = "SELECT ep.filename " // base lock flags
. "eu.deleted, eu.enabled, eu.locked, "
// date based lock
. "CASE WHEN ("
. "(eu.lock_until IS NULL "
. "OR (eu.lock_until IS NOT NULL AND NOW() >= eu.lock_until)) "
. "AND (eu.lock_after IS NULL "
. "OR (eu.lock_after IS NOT NULL AND NOW() <= eu.lock_after))"
. ") THEN 0::INT ELSE 1::INT END locked_period, "
// login id validation
. "login_user_id, "
. "CASE WHEN ("
. "(eu.login_user_id_valid_from IS NULL "
. "OR (eu.login_user_id_valid_from IS NOT NULL AND NOW() >= eu.login_user_id_valid_from)) "
. "AND (eu.login_user_id_valid_until IS NULL "
. "OR (eu.login_user_id_valid_until IS NOT NULL AND NOW() <= eu.login_user_id_valid_until))"
. ") THEN 1::INT ELSE 0::INT END AS login_user_id_valid_date, "
// check if user must login
. "CASE WHEN eu.login_user_id_revalidate_after > '0 days'::INTERVAL "
. "AND eu.login_user_id_set_date + eu.login_user_id_revalidate_after <= NOW()::DATE "
. "THEN 1::INT ELSE 0::INT END AS login_user_id_revalidate, "
. "eu.login_user_id_locked "
//
. "FROM edit_page ep, edit_page_access epa, edit_group eg, edit_user eu " . "FROM edit_page ep, edit_page_access epa, edit_group eg, edit_user eu "
. "WHERE ep.edit_page_id = epa.edit_page_id " . "WHERE ep.edit_page_id = epa.edit_page_id "
. "AND eg.edit_group_id = epa.edit_group_id " . "AND eg.edit_group_id = epa.edit_group_id "
@@ -1720,6 +1905,30 @@ EOM;
$this->login_error = 109; $this->login_error = 109;
return $this->permission_okay; return $this->permission_okay;
} }
if (
!$this->loginValidationCheck(
(int)$res['deleted'],
(int)$res['enabled'],
(int)$res['locked'],
(int)$res['locked_period'],
(int)$res['login_user_id_locked']
)
) {
// errors set in method
return $this->permission_okay;
}
// if login user id parameter and no username, check period here
if (
empty($this->username) &&
!empty($this->login_user_id) &&
!$this->loginLoginUserIdCheck(
(int)$res['login_user_id_valid_date'],
(int)$res['login_user_id_revalidate']
)
) {
// errors set in method
return $this->permission_okay;
}
if (isset($res['filename']) && $res['filename'] == $this->page_name) { if (isset($res['filename']) && $res['filename'] == $this->page_name) {
$this->permission_okay = true; $this->permission_okay = true;
} else { } else {
@@ -1917,6 +2126,37 @@ EOM;
return false; return false;
} }
/**
* Returns current set loginUserId or empty if unset
*
* @return string loginUserId or empty string for not set
*/
public function loginGetLoginUserId(): string
{
return $this->login_user_id;
}
/**
* Returns GET/POST for where the loginUserId was set
*
* @return string GET or POST or empty string for not set
*/
public function loginGetLoginUserIdSource(): string
{
return $this->login_user_id_source;
}
/**
* Returns unclear login user id state. If true then illegal characters
* where present in the loginUserId parameter
*
* @return bool False for clear, True if illegal characters found
*/
public function loginGetLoginUserIdUnclean(): bool
{
return $this->login_unclear;
}
/** /**
* old name for loginGetEditAccessData * old name for loginGetEditAccessData
* *