From 18f5404261b3ed2e97ba5d00d4761a0b43b7157f Mon Sep 17 00:00:00 2001 From: Galen Charlton Date: Thu, 10 Dec 2020 17:23:47 -0500 Subject: [PATCH] LP#1474029: teach Evergreen how to prevent expired staff from logging in This patch adds the ability to prevent staff users whose accounts have expired from logging in. This is controlled by the new global flag "auth.block_expired_staff_login", which is not enabled by default. If that flag is turned on, accounts that have the `STAFF_LOGIN` permission and whose expiration date is in the past are prevented from logging into any Evergreen interface, including the staff client, the public catalog, and SIP2. It should be noted that ordinary patrons are allowed to log into the public catalog if their circulation privileges have expired. This feature prevents expired staff users from logging into the public catalog (and all other Evergreen interfaces and APIs) outright in order to prevent them from getting into the staff interface anyway by creative use of Evergreen's authentication APIs. Evergreen admins are advised to check the expiration status of staff accounts before turning on the global flag, as otherwise it is possible to lock staff users out unexpectedly. Signed-off-by: Galen Charlton Signed-off-by: Terran McCanna Signed-off-by: Bill Erickson --- Open-ILS/src/c-apps/oils_auth_internal.c | 89 ++++++++++++++++++- Open-ILS/src/sql/Pg/950.data.seed-values.sql | 11 +++ ...XX.data.block_expired_staff_login_flag.sql | 17 ++++ ...Block_Login_of_Expired_Staff_Accounts.adoc | 39 ++++++++ 4 files changed, 155 insertions(+), 1 deletion(-) create mode 100644 Open-ILS/src/sql/Pg/upgrade/XXXX.data.block_expired_staff_login_flag.sql create mode 100644 docs/RELEASE_NOTES_NEXT/Architecture/Block_Login_of_Expired_Staff_Accounts.adoc diff --git a/Open-ILS/src/c-apps/oils_auth_internal.c b/Open-ILS/src/c-apps/oils_auth_internal.c index d0c46f80f3..948860f556 100644 --- a/Open-ILS/src/c-apps/oils_auth_internal.c +++ b/Open-ILS/src/c-apps/oils_auth_internal.c @@ -1,3 +1,7 @@ +#define _XOPEN_SOURCE +#include +#include +#include #include "opensrf/osrf_app_session.h" #include "opensrf/osrf_application.h" #include "opensrf/osrf_settings.h" @@ -17,6 +21,8 @@ #define OILS_AUTH_TEMP "temp" #define OILS_AUTH_PERSIST "persist" +#define BLOCK_EXPIRED_STAFF_LOGIN_FLAG "auth.block_expired_staff_login" + // Default time for extending a persistent session: ten minutes #define DEFAULT_RESET_INTERVAL 10 * 60 @@ -374,6 +380,73 @@ int oilsAuthInternalCreateSession(osrfMethodContext* ctx) { return 0; } +int _checkIfExpiryDatePassed(const char *expire_date) { + + struct tm expire_tm; + memset(&expire_tm, 0, sizeof(expire_tm)); + strptime(expire_date, "%FT%T%z", &expire_tm); + time_t now = time(NULL); + time_t expire_time_t = mktime(&expire_tm); + if (now > expire_time_t) { + return 1; + } else { + return 0; + } +} + +int _blockExpiredStaffLogin(osrfMethodContext* ctx, int user_id) { + // check global flag whether we're supposed to block or not + jsonObject *cgfObj = NULL, *params = NULL; + params = jsonNewObject(BLOCK_EXPIRED_STAFF_LOGIN_FLAG); + cgfObj = oilsUtilsCStoreReqCtx( + ctx, "open-ils.cstore.direct.config.global_flag.retrieve", params); + jsonObjectFree(params); + + int may_block_login = 0; + char* tmp_str = NULL; + if (cgfObj && cgfObj->type != JSON_NULL) { + tmp_str = oilsFMGetString(cgfObj, "enabled"); + if (oilsUtilsIsDBTrue(tmp_str)) { + may_block_login = 1; + } + free(tmp_str); + } + jsonObjectFree(cgfObj); + + if (!may_block_login) { + return 0; + } + + // OK, we're supposed to block logins by expired staff accounts, + // so let's see if the account is one. We'll do so by seeing + // if the account has the STAFF_LOGIN permission anywhere. We + // are _not_ checking the login_type, as blocking 'staff' and + // 'temp' logins still leaves open the possibility of constructing + // an 'opac'-type login that _also_ sets a workstation, which + // in turn could be used to set an authtoken cookie that works + // in the staff interface. This means, that unlike ordinary patrons, + // a staff account that expires will not be able to log into + // the public catalog... but then, staff members really ought + // to be using a separate account when acting as a library patron + // anyway. + + int block_login = 0; + + // using the root org unit as the context org unit. + int org_id = -1; + char* perms[1]; + perms[0] = "STAFF_LOGIN"; + oilsEvent* response = oilsUtilsCheckPerms(user_id, org_id, perms, 1); + + if (!response) { + // user has STAFF_LOGIN, so should be blocked + block_login = 1; + } else { + oilsEventFree(response); + } + + return block_login; +} int oilsAuthInternalValidate(osrfMethodContext* ctx) { OSRF_METHOD_VERIFY_CONTEXT(ctx); @@ -394,7 +467,8 @@ int oilsAuthInternalValidate(osrfMethodContext* ctx) { jsonObject *userObj = NULL, *params = NULL; char* tmp_str = NULL; int user_exists = 0, user_active = 0, - user_barred = 0, user_deleted = 0; + user_barred = 0, user_deleted = 0, + expired = 0; // Confirm user exists, active=true, barred=false, deleted=false params = jsonNewNumberStringObject(user_id); @@ -416,12 +490,25 @@ int oilsAuthInternalValidate(osrfMethodContext* ctx) { tmp_str = oilsFMGetString(userObj, "deleted"); user_deleted = oilsUtilsIsDBTrue(tmp_str); free(tmp_str); + + tmp_str = oilsFMGetString(userObj, "expire_date"); + expired = _checkIfExpiryDatePassed(tmp_str); + free(tmp_str); } if (!user_exists || user_barred || user_deleted) { response = oilsNewEvent(OILS_LOG_MARK_SAFE, OILS_EVENT_AUTH_FAILED); } + if (!response && expired) { + if (_blockExpiredStaffLogin(ctx, atoi(user_id))) { + tmp_str = oilsFMGetString(userObj, "usrname"); + osrfLogWarning( OSRF_LOG_MARK, "Blocked login for expired staff user %s", tmp_str ); + free(tmp_str); + response = oilsNewEvent(OILS_LOG_MARK_SAFE, OILS_EVENT_AUTH_FAILED); + } + } + if (!response && !user_active) { // In some cases, it's useful for the caller to know if the // patron was unable to login becuase the account is inactive. diff --git a/Open-ILS/src/sql/Pg/950.data.seed-values.sql b/Open-ILS/src/sql/Pg/950.data.seed-values.sql index bf3e7ab61b..4f2948e957 100644 --- a/Open-ILS/src/sql/Pg/950.data.seed-values.sql +++ b/Open-ILS/src/sql/Pg/950.data.seed-values.sql @@ -21249,3 +21249,14 @@ VALUES ( ) ); +INSERT INTO config.global_flag (name, value, enabled, label) +VALUES ( + 'auth.block_expired_staff_login', + NULL, + FALSE, + oils_i18n_gettext( + 'auth.block_expired_staff_login', + 'Block the ability of expired user with the STAFF_LOGIN permission to log into Evergreen.', + 'cgf', 'label' + ) +); diff --git a/Open-ILS/src/sql/Pg/upgrade/XXXX.data.block_expired_staff_login_flag.sql b/Open-ILS/src/sql/Pg/upgrade/XXXX.data.block_expired_staff_login_flag.sql new file mode 100644 index 0000000000..b7ea9b12fc --- /dev/null +++ b/Open-ILS/src/sql/Pg/upgrade/XXXX.data.block_expired_staff_login_flag.sql @@ -0,0 +1,17 @@ +BEGIN; + +SELECT evergreen.upgrade_deps_block_check('XXXX', :eg_version); + +INSERT INTO config.global_flag (name, value, enabled, label) +VALUES ( + 'auth.block_expired_staff_login', + NULL, + FALSE, + oils_i18n_gettext( + 'auth.block_expired_staff_login', + 'Block the ability of expired user with the STAFF_LOGIN permission to log into Evergreen.', + 'cgf', 'label' + ) +); + +COMMIT; diff --git a/docs/RELEASE_NOTES_NEXT/Architecture/Block_Login_of_Expired_Staff_Accounts.adoc b/docs/RELEASE_NOTES_NEXT/Architecture/Block_Login_of_Expired_Staff_Accounts.adoc new file mode 100644 index 0000000000..b2c18c4cc7 --- /dev/null +++ b/docs/RELEASE_NOTES_NEXT/Architecture/Block_Login_of_Expired_Staff_Accounts.adoc @@ -0,0 +1,39 @@ +Block Login of Expired Staff Accounts +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Evergreen now has the ability to prevent staff users whose +accounts have expired from logging in. This is controlled +by the new global flag "auth.block_expired_staff_login", which +is not enabled by default. If that flag is turned on, accounts +that have the `STAFF_LOGIN` permission and whose expiration date +is in the past are prevented from logging into any Evergreen +interface, including the staff client, the public catalog, and SIP2. + +It should be noted that ordinary patrons are allowed to log into +the public catalog if their circulation privileges have expired. This +feature prevents expired staff users from logging into the public catalog +(and all other Evergreen interfaces and APIs) outright in order to +prevent them from getting into the staff interface anyway by +creative use of Evergreen's authentication APIs. + +Evergreen admins are advised to check the expiration status of staff +accounts before turning on the global flag, as otherwise it is +possible to lock staff users out unexpectedly. The following SQL +query will identify expired but otherwise un-deleted users that +would be blocked by turning on the flag: + +[source,sql] +---- +SELECT DISTINCT usrname, expire_date +FROM actor.usr au, permission.usr_has_perm_at_all(id, 'STAFF_LOGIN') +WHERE active +AND NOT deleted +AND NOT barred +AND expire_date < NOW() +---- + +Note that this query can take a long time to run in large databases +given the general way that it checks for users that have the +`STAFF_LOGIN` permission. Replacing the use of +`permission.usr_has_perm_at_all()` with a query on expired users +with profiles known to have the `STAFF_LOGIN` permission will +be much faster. -- 2.43.2