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.