¶
14.1.5.Writing Your Own Authentication and Session Handling
We will explain the following precompiled procedures in Virtuoso used to authenticate three branches of users.
HP_AUTH_SQL_USER - VSP authentication based on DB users |
HP_AUTH_DAV_ADMIN - VSP authentication based on WebDAV users |
HP_AUTH_DAV_PROTOCOL - WebDAV repository authentication |
-- Authenticate against names and passwords in SYS_USERS, using HP_SECURITY for level create procedure DB.DBA.HP_AUTH_SQL_USER (in realm varchar) { declare _u_name, _u_password varchar; declare _u_group, _u_id, allow_basic integer; declare auth, sec, lev varchar; declare _user varchar; declare ses_dta, lines any; lines := http_request_header (); -- got a request header lines sec := http_map_get ('security_level'); -- got a security level from http mapping if (isstring (sec)) sec := ucase (sec); if (sec = 'DIGEST') allow_basic := 0; -- if security is only digest then allow basic is false else allow_basic := 1; auth := DB.DBA.vsp_auth_vec (lines); -- parse request header to got authentication credentials if (0 <> auth) -- and if client sent any authentication information try it { lev := get_keyword ('authtype', auth, ''); -- check if client tried basic but -- domain restricted to digest only if (allow_basic = 0 and 'basic' = lev) goto nf; _user := get_keyword ('username', auth, ''); if ('' = _user) -- if no user name then 'bye' return 0; whenever not found goto nf; -- if no such user in table - 'bye' -- got the password from users table select U_NAME, PWD_MAGIC_CALC (U_NAME, U_PASSWORD, 1), U_GROUP, U_ID into _u_name, _u_password, _u_group, _u_id from DB.DBA.SYS_USERS where u_name = _user; -- check credential against user name and password if (0 = _u_group and 1 = DB.DBA.vsp_auth_verify_pass (auth, _u_name, get_keyword ('realm', auth, ''), get_keyword ('uri', auth, ''), get_keyword ('nonce', auth, ''), get_keyword ('nc', auth, ''), get_keyword ('cnonce', auth, ''), get_keyword ('qop', auth, ''), _u_password)) { if (http_map_get ('persist_ses_vars')) { -- if all is ok check for persistent variables storage declare vars any; declare sid varchar; vars := null; sid := http_param ('sid'); -- got from session table stored variables vars := coalesce ((select deserialize (ASES_VARS) from DB.DBA.ADMIN_SESSION where ASES_ID = sid), null); if (sid is not null and vars is null or isarray (vars)) connection_vars_set (vars); if (sid is not null and connection_get ('sid') is null) { connection_set ('sid', sid); } } return 1; -- return authenticated } } nf: -- all errors go there, we compose authentication header -- and send 401 unauthorized to the client DB.DBA.vsp_auth_get (realm, http_path (), md5 (datestring (now ())), md5 ('eEsSlLAaf'), 'false', lines, allow_basic); return 0; -- return not authenticated } ;
-- Authenticate against SYS_DAV_USER, using HP_SECURITY for level, -- in the context of DAV administration pages accessed through regular HTTP -- The logic is the same but in difference of password retrieval. create procedure DB.DBA.HP_AUTH_DAV_ADMIN (in realm varchar) { declare _u_name, _u_pwd varchar; declare _u_group, _u_id, allow_basic integer; declare auth, sec, lev varchar; declare _user varchar; declare ses_dta, lines any; lines := http_request_header (); sec := http_map_get ('security_level'); if (isstring (sec)) sec := ucase (sec); if (sec = 'DIGEST') allow_basic := 0; else allow_basic := 1; auth := DB.DBA.vsp_auth_vec (lines); if (0 <> auth) { lev := get_keyword ('authtype', auth, ''); if (allow_basic = 0 and 'basic' = lev) goto nf; _user := get_keyword ('username', auth, ''); if ('' = _user) return 0; whenever not found goto nf; -- we tried to find out password for enabled valid WebDAV user select U_NAME, U_PWD, U_GROUP, U_ID into _u_name, _u_pwd, _u_group, _u_id from WS.WS.SYS_DAV_USER where u_name = _user and U_ACCOUNT_DISABLED = 0; if (DB.DBA.vsp_auth_verify_pass (auth, _u_name, get_keyword ('realm', auth, ''), get_keyword ('uri', auth, ''), get_keyword ('nonce', auth, ''), get_keyword ('nc', auth, ''), get_keyword ('cnonce', auth, ''), get_keyword ('qop', auth, ''), _u_pwd)) { if (http_map_get ('persist_ses_vars')) { declare vars any; declare sid varchar; vars := null; sid := http_param ('sid'); vars := coalesce ((select deserialize (ASES_VARS) from DB.DBA.ADMIN_SESSION where ASES_ID = sid), null); if (vars is null or isarray (vars)) connection_vars_set (vars); if (connection_get ('sid') is null) { connection_set ('sid', sid); } } return (_u_id); } } nf: DB.DBA.vsp_auth_get (realm, '/admin/admin_dav', md5 (datestring (now ())), md5 ('vVAadAnIimMDdaNnimda'), 'false', lines, allow_basic); return 0; } ;
-- The same as for DAV admin pages but in the context of DAV protocol requests. create procedure DB.DBA.HP_AUTH_DAV_PROTOCOL (in realm varchar) { declare _u_name, _u_password, _perms varchar; declare _u_id, _u_group, req_user, req_group, what integer; declare auth varchar; declare _user varchar; declare our_auth_vec, lines, sec, path, req_perms, req_meth, cmp_perms, def_page varchar; declare _method, lev, allow_basic, authenticated integer; declare c cursor for select 1, COL_OWNER, COL_GROUP, COL_PERMS from WS.WS.SYS_DAV_COL where WS.WS.COL_PATH (COL_ID) = path; declare r cursor for select 2, RES_OWNER, RES_GROUP, RES_PERMS from WS.WS.SYS_DAV_RES where RES_FULL_PATH = path; authenticated := 0; lines := http_request_header (); path := http_physical_path (); if (isarray (lines)) { req_meth := aref (lines, 0); if (strchr (req_meth, ' ') is not null) req_meth := lower (substring (req_meth, 1, strchr (req_meth, ' '))); } -- there we compose mask for permissions compared against -- resource or collection permission if (req_meth = 'get' or req_meth = 'post' or req_meth = 'options' or req_meth = 'propfind' or req_meth = 'head' or req_meth = 'trace' or req_meth = 'copy') cmp_perms := '1__'; -- in this case only read access needed else if (req_meth = 'mkcol' or req_meth = 'put') { if (length (path) > 1 and strrchr (substring (path, 1, length(path) - 1), '/') is not null) path := substring (path, 1, strrchr (substring (path, 1, length(path) - 1), '/') + 1); cmp_perms := '11_'; } else cmp_perms := '11_'; -- but in this we needs a read/write access what := 0; whenever not found goto fr; open c (prefetch 1); fetch c into what, req_user, req_group, req_perms; -- get from collections owner , owner group and permissions def_page := http_map_get ('default_page'); if (isstring (def_page)) { path := concat (path, def_page); what := 0; } fr: close c; if (not what) { whenever not found goto fe; open r (prefetch 1); fetch r into what, req_user, req_group, req_perms; -- if not a collection try a resource fe: close r; } sec := http_map_get ('security_level'); -- see DB user authentication if (isstring (sec)) sec := ucase (sec); if (sec = 'DIGEST') allow_basic := 0; else allow_basic := 1; auth := DB.DBA.vsp_auth_vec (lines); if (0 <> auth) { lev := get_keyword ('authtype', auth, ''); if (allow_basic = 0 and 'basic' = lev) goto nf; _user := get_keyword ('username', auth); if (_user = '' or isnull (_user)) { goto nf; } whenever not found goto nf; select U_NAME, U_PWD, U_GROUP, U_ID, U_METHODS, U_DEF_PERMS into _u_name, _u_password, _u_group, _u_id, _method, _perms from WS.WS.SYS_DAV_USER where U_NAME = _user and U_ACCOUNT_DISABLED = 0; if (_u_password is null) goto nf; if (DB.DBA.vsp_auth_verify_pass (auth, _u_name, coalesce(get_keyword ('realm', auth), ''), coalesce(get_keyword ('uri', auth), ''), coalesce(get_keyword ('nonce', auth), ''), coalesce(get_keyword ('nc', auth),''), coalesce(get_keyword ('cnonce', auth), ''), coalesce(get_keyword ('qop', auth), ''), _u_password)) { update WS.WS.SYS_DAV_USER set U_LOGIN_TIME = now () where U_NAME = _user; if (http_map_get ('persist_ses_vars')) { declare vars any; declare sid varchar; vars := null; sid := http_param ('sid'); vars := coalesce ((select deserialize (ASES_VARS) from DB.DBA.ADMIN_SESSION where ASES_ID = sid), null); if (vars is null or isarray (vars)) connection_vars_set (vars); if (connection_get ('sid') is null) { connection_set ('sid', sid); } } if (connection_get ('DAVUserID') <> _u_id) connection_set ('DAVUserID', _u_id); authenticated := 1; } } -- Check permissions if (authenticated and _u_id = 1) -- If user is DAV admin all rights granted return 1; else if (not authenticated and req_perms like concat ('______', cmp_perms, '%')) return -1; -- if not authenticated and resource -- does have public access return authenticated else if (authenticated and ((_u_id = req_user and req_perms like concat (cmp_perms, '%')) or (req_group = _u_group and req_perms like concat ('___', cmp_perms, '%')) or (req_perms like concat ('______', cmp_perms, '%')))) return (_u_id); -- if user is owner or belongs to group -- ownership return authenticated else if (authenticated) -- if authenticated but does not access -- return false but set 403 forbidden { http_request_status ('HTTP/1.1 403 Forbidden'); http ( concat ('<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">', '<HTML><HEAD>', '<TITLE>403 Forbidden</TITLE>', '</HEAD><BODY><H1>Forbidden</H1>', 'Access to the resource is forbidden.</BODY></HTML>')); return 0; } -- End check permissions nf: -- all error cases go there, we send authentication credentials -- and turn status 401 Unauthorized, and return false DB.DBA.vsp_auth_get (realm, '/DAV', md5 (datestring(now())), md5 ('opaakki'), 'false', lines, allow_basic); return 0; } ;
-- Post-processing function, this will executed after processing -- the request but before sending result to the client. create procedure DB.DBA.HP_SES_VARS_STORE () { declare vars any; declare sid varchar; if (http_map_get ('persist_ses_vars') and connection_is_dirty ()) -- if connection variables changed in request and persistent variables stored { vars := connection_vars (); -- get all variables connection_vars_set (null); -- set immediately to empty vector (prevent changing) sid := get_keyword ('sid', vars, null); -- got the session ID -- store the variables in session table if (sid is not null) update DB.DBA.ADMIN_SESSION set ASES_VARS = serialize (vars) where ASES_ID = sid; } } ;
These functions are implemented as part of Virtuoso server by default. The user can freely define their own procedures based on this logic to use for special authentication (different users information table etc.) and session management.