parsav  Diff

Differences From Artifact [2c2a215381]:

To Artifact [0fdc39456b]:


     6      6   		params = {rawstring}, sql = [[
     7      7   			select value from parsav_config
     8      8   				where key = $1::text limit 1
     9      9   		]];
    10     10   	};
    11     11   
    12     12   	conf_set = {
    13         -		params = {rawstring,rawstring}, sql = [[
           13  +		params = {rawstring,rawstring}, cmd=true, sql = [[
    14     14   			insert into parsav_config (key, value)
    15     15   				values ($1::text, $2::text)
    16     16   				on conflict (key) do update set value = $2::text
    17     17   		]];
    18     18   	};
    19     19   
    20     20   	conf_reset = {
    21         -		params = {rawstring}, sql = [[
           21  +		params = {rawstring}, cmd=true, sql = [[
    22     22   			delete from parsav_config where
    23     23   				key = $1::text 
    24     24   		]];
    25     25   	};
    26     26   
    27     27   	actor_fetch_uid = {
    28     28   		params = {uint64}, sql = [[
    29     29   			select a.id, a.nym, a.handle, a.origin, a.bio,
    30         -			       a.avataruri, a.rank, a.quota, a.key, 
           30  +			       a.avataruri, a.rank, a.quota, a.key, a.epithet,
    31     31   			       extract(epoch from a.knownsince)::bigint,
    32     32   				   coalesce(a.handle || '@' || s.domain,
    33     33   				            '@' || a.handle) as xid
    34     34   
    35     35   			from      parsav_actors  as a
    36     36   			left join parsav_servers as s
    37     37   				on a.origin = s.id
................................................................................
    38     38   			where a.id = $1::bigint
    39     39   		]];
    40     40   	};
    41     41   
    42     42   	actor_fetch_xid = {
    43     43   		params = {pstring}, sql = [[
    44     44   			select a.id, a.nym, a.handle, a.origin, a.bio,
    45         -			       a.avataruri, a.rank, a.quota, a.key, 
           45  +			       a.avataruri, a.rank, a.quota, a.key, a.epithet,
    46     46   			       extract(epoch from a.knownsince)::bigint,
    47     47   				   coalesce(a.handle || '@' || s.domain,
    48     48   				            '@' || a.handle) as xid,
    49     49   
    50     50   				coalesce(s.domain,
    51     51   				        (select value from parsav_config
    52     52   							where key='domain' limit 1)) as domain
................................................................................
    70     70   			rawstring, uint16, uint32
    71     71   		};
    72     72   		sql = [[
    73     73   			insert into parsav_actors (
    74     74   				nym,handle,
    75     75   				origin,knownsince,
    76     76   				bio,avataruri,key,
    77         -				title,rank,quota
           77  +				epithet,rank,quota
    78     78   			) values ($1::text, $2::text,
    79     79   				case when $3::bigint = 0 then null
    80     80   				     else $3::bigint end,
    81     81   				to_timestamp($4::bigint),
    82     82   				$5::bigint, $6::bigint, $7::bytea,
    83     83   				$8::text, $9::smallint, $10::integer
    84     84   			) returning id
................................................................................
    98     98   			order by blacklist desc limit 1
    99     99   		]];
   100    100   	};
   101    101   
   102    102   	actor_enum_local = {
   103    103   		params = {}, sql = [[
   104    104   			select id, nym, handle, origin, bio,
   105         -			       null::text, rank, quota, key,
          105  +			       null::text, rank, quota, key, epithet,
   106    106   			       extract(epoch from knownsince)::bigint,
   107    107   				handle ||'@'||
   108    108   				(select value from parsav_config
   109    109   					where key='domain' limit 1) as xid
   110    110   			from parsav_actors where origin is null
   111    111   		]];
   112    112   	};
   113    113   
   114    114   	actor_enum = {
   115    115   		params = {}, sql = [[
   116    116   			select a.id, a.nym, a.handle, a.origin, a.bio,
   117         -			       a.avataruri, a.rank, a.quota, a.key,
          117  +			       a.avataruri, a.rank, a.quota, a.key, a.epithet,
   118    118   			       extract(epoch from a.knownsince)::bigint,
   119    119   				   coalesce(a.handle || '@' || s.domain,
   120    120   				            '@' || a.handle) as xid
   121    121   			from parsav_actors a
   122    122   			left join parsav_servers s on s.id = a.origin
   123    123   		]];
   124    124   	};
................................................................................
   163    163   				(select count(*) from mts where kind = 'trust') > 0
   164    164   		]]; -- cheat
   165    165   	};
   166    166   
   167    167   	actor_session_fetch = {
   168    168   		params = {uint64, lib.store.inet}, sql = [[
   169    169   			select a.id, a.nym, a.handle, a.origin, a.bio,
   170         -			       a.avataruri, a.rank, a.quota, a.key,
          170  +			       a.avataruri, a.rank, a.quota, a.key, a.epithet,
   171    171   			       extract(epoch from a.knownsince)::bigint,
   172    172   				   coalesce(a.handle || '@' || s.domain,
   173    173   				            '@' || a.handle) as xid,
   174    174   
   175    175   			       au.restrict,
   176    176   						array['post'  ] <@ au.restrict as can_post,
   177    177   						array['edit'  ] <@ au.restrict as can_edit,
................................................................................
   190    190   	};
   191    191   
   192    192   	actor_powers_fetch = {
   193    193   		params = {uint64}, sql = [[
   194    194   			select key, allow from parsav_rights where actor = $1::bigint
   195    195   		]]
   196    196   	};
          197  +
          198  +	actor_power_insert = {
          199  +		params = {uint64,lib.mem.ptr(int8),uint16}, cmd = true, sql = [[
          200  +			insert into parsav_rights (actor, key, allow) values (
          201  +				$1::bigint, $2::text, ($3::smallint)::integer::bool
          202  +			)
          203  +		]]
          204  +	};
          205  +
          206  +	auth_create_pw = {
          207  +		params = {uint64, lib.mem.ptr(uint8)}, cmd = true, sql = [[
          208  +			insert into parsav_auth (uid, name, kind, cred) values (
          209  +				$1::bigint,
          210  +				(select handle from parsav_actors where id = $1::bigint),
          211  +				'pw-sha256', $2::bytea
          212  +			)
          213  +		]]
          214  +	};
   197    215   
   198    216   	post_create = {
   199    217   		params = {uint64, rawstring, rawstring, rawstring}, sql = [[
   200    218   			insert into parsav_posts (
   201    219   				author, subject, acl, body,
   202    220   				posted, discovered,
   203    221   				circles, mentions
................................................................................
   339    357   			return buf
   340    358   		end
   341    359   	end;
   342    360   }
   343    361   
   344    362   local con = symbol(&lib.pq.PGconn)
   345    363   local prep = {}
   346         -local sqlsquash = function(s) return s:gsub('%s+',' '):gsub('^%s*(.-)%s*$','%1') end
          364  +local function sqlsquash(s) return s
          365  +	:gsub('%%include (.-)%%',function(f)
          366  +		return sqlsquash(lib.util.ingest('backend/schema/' .. f))
          367  +	end) -- include dependencies
          368  +	:gsub('%-%-.-\n','') -- remove disruptive line comments
          369  +	:gsub('%-%-.-$','') -- remove unnecessary terminal comments
          370  +	:gsub('%s+',' ') -- remove whitespace
          371  +	:gsub('^%s*(.-)%s*$','%1') -- chomp
          372  +end
          373  +
   347    374   for k,q in pairs(queries) do
   348    375   	local qt = sqlsquash(q.sql)
   349    376   	local stmt = 'parsavpg_' .. k
   350         -	prep[#prep + 1] = quote
          377  +	terra q.prep([con])
   351    378   		var res = lib.pq.PQprepare([con], stmt, qt, [#q.params], nil)
   352    379   		defer lib.pq.PQclear(res)
   353    380   		if res == nil or lib.pq.PQresultStatus(res) ~= lib.pq.PGRES_COMMAND_OK then
   354    381   			if res == nil then
   355    382   				lib.bail('grievous error occurred preparing ',k,' statement')
   356    383   			end
   357    384   			lib.bail('could not prepare PGSQL statement ',k,': ',lib.pq.PQresultErrorMessage(res))
   358    385   		end
   359    386   		lib.dbg('prepared PGSQL statement ',k) 
   360    387   	end
          388  +	prep[#prep + 1] = quote q.prep([con]) end
   361    389   
   362    390   	local args, casts, counters, fixers, ft, yield = {}, {}, {}, {}, {}, {}
   363    391   	local dumpers = {}
   364    392   	for i, ty in ipairs(q.params) do
   365    393   		args[i] = symbol(ty)
   366    394   		ft[i] = `1
   367    395   		if ty == rawstring then
................................................................................
   389    417   			dumpers[#dumpers+1] = `lib.io.fmt([tostring(i)..'. got int %llu\n'], [args[i]])
   390    418   			fixers[#fixers + 1] = quote
   391    419   				[args[i]] = lib.math.netswap(ty, [args[i]])
   392    420   			end
   393    421   		end
   394    422   	end
   395    423   
          424  +	local okconst = lib.pq.PGRES_TUPLES_OK
          425  +	if q.cmd then okconst = lib.pq.PGRES_COMMAND_OK end
   396    426   	terra q.exec(src: &lib.store.source, [args])
   397    427   		var params = arrayof([&int8], [casts])
   398    428   		var params_sz = arrayof(int, [counters])
   399    429   		var params_ft = arrayof(int, [ft])
   400    430   		[fixers]
   401    431   		--[dumpers]
   402    432   		var res = lib.pq.PQexecPrepared([&lib.pq.PGconn](src.handle), stmt,
   403    433   			[#args], params, params_sz, params_ft, 1)
   404    434   		if res == nil then
   405    435   			lib.bail(['grievous error occurred executing '..k..' against database'])
   406         -		elseif lib.pq.PQresultStatus(res) ~= lib.pq.PGRES_TUPLES_OK then
          436  +		elseif lib.pq.PQresultStatus(res) ~= okconst then
   407    437   			lib.bail(['PGSQL database procedure '..k..' failed\n'],
   408    438   			lib.pq.PQresultErrorMessage(res))
   409    439   		end
   410    440   
   411    441   		var ct = lib.pq.PQntuples(res)
   412    442   		if ct == 0 then
   413    443   			lib.pq.PQclear(res)
................................................................................
   448    478   	return p
   449    479   end
   450    480   local terra row_to_actor(r: &pqr, row: intptr): lib.mem.ptr(lib.store.actor)
   451    481   	var a: lib.mem.ptr(lib.store.actor)
   452    482   	var av: rawstring, avlen: intptr
   453    483   	var nym: rawstring, nymlen: intptr
   454    484   	var bio: rawstring, biolen: intptr
          485  +	var epi: rawstring, epilen: intptr
   455    486   	if r:null(row,5) then avlen = 0 av = nil else
   456    487   		av = r:string(row,5)
   457    488   		avlen = r:len(row,5)+1
   458    489   	end
   459    490   	if r:null(row,1) then nymlen = 0 nym = nil else
   460    491   		nym = r:string(row,1)
   461    492   		nymlen = r:len(row,1)+1
   462    493   	end
   463    494   	if r:null(row,4) then biolen = 0 bio = nil else
   464    495   		bio = r:string(row,4)
   465    496   		biolen = r:len(row,4)+1
          497  +	end
          498  +	if r:null(row,9) then epilen = 0 epi = nil else
          499  +		epi = r:string(row,9)
          500  +		epilen = r:len(row,9)+1
   466    501   	end
   467    502   	a = [ lib.str.encapsulate(lib.store.actor, {
   468    503   		nym = {`nym, `nymlen};
   469    504   		bio = {`bio, `biolen};
          505  +		epithet = {`epi, `epilen};
   470    506   		avatar = {`av,`avlen};
   471    507   		handle = {`r:string(row, 2); `r:len(row,2) + 1};
   472         -		xid = {`r:string(row, 10); `r:len(row,10) + 1};
          508  +		xid = {`r:string(row, 11); `r:len(row,11) + 1};
   473    509   	}) ]
   474    510   	a.ptr.id = r:int(uint64, row, 0);
   475    511   	a.ptr.rights = lib.store.rights_default();
   476    512   	a.ptr.rights.rank = r:int(uint16, row, 6);
   477    513   	a.ptr.rights.quota = r:int(uint32, row, 7);
   478         -	a.ptr.knownsince = r:int(int64,row, 9);
          514  +	a.ptr.knownsince = r:int(int64,row, 10);
   479    515   	if r:null(row,8) then
   480    516   		a.ptr.key.ct = 0 a.ptr.key.ptr = nil
   481    517   	else
   482    518   		a.ptr.key = r:bin(row,8)
   483    519   	end
   484    520   	if r:null(row,3) then a.ptr.origin = 0
   485    521   	else a.ptr.origin = r:int(uint64,row,3) end
................................................................................
   530    566   		lib.dbg(['searching for hashed password credentials in format SHA' .. tostring(hash)])
   531    567   		var [out]
   532    568   		[vdrs]
   533    569   		lib.dbg(['could not find password hash'])
   534    570   	end
   535    571   end
   536    572   
          573  +local schema = sqlsquash(lib.util.ingest('backend/schema/pgsql.sql'))
          574  +local obliterator = sqlsquash(lib.util.ingest('backend/schema/pgsql-drop.sql'))
          575  +
   537    576   local b = `lib.store.backend {
   538    577   	id = "pgsql";
   539    578   	open = [terra(src: &lib.store.source): &opaque
   540    579   		lib.report('connecting to postgres database: ', src.string.ptr)
   541    580   		var [con] = lib.pq.PQconnectdb(src.string.ptr)
   542    581   		if lib.pq.PQstatus(con) ~= lib.pq.CONNECTION_OK then
   543    582   			lib.warn('postgres backend connection failed')
................................................................................
   556    595   		defer lib.pq.PQclear(res)
   557    596   		if lib.pq.PQresultStatus(res) ~= lib.pq.PGRES_TUPLES_OK then
   558    597   			lib.warn('failed to secure postgres connection')
   559    598   			lib.pq.PQfinish(con)
   560    599   			return nil
   561    600   		end
   562    601   
   563         -		[prep]
   564    602   		return con
   565    603   	end];
          604  +
   566    605   	close = [terra(src: &lib.store.source) lib.pq.PQfinish([&lib.pq.PGconn](src.handle)) end];
          606  +
          607  +	conprep = [terra(src: &lib.store.source, mode: lib.store.prepmode.t)
          608  +		var [con] = [&lib.pq.PGconn](src.handle)
          609  +		if mode == lib.store.prepmode.full then [prep]
          610  +		elseif mode == lib.store.prepmode.conf or
          611  +		       mode == lib.store.prepmode.admin then 
          612  +			queries.conf_get.prep(con)
          613  +			queries.conf_set.prep(con)
          614  +			queries.conf_reset.prep(con)
          615  +			if mode == lib.store.prepmode.admin then 
          616  +			end
          617  +		else lib.bail('unsupported connection preparation mode') end
          618  +	end];
          619  +
          620  +	dbsetup = [terra(src: &lib.store.source)
          621  +		var res = lib.pq.PQexec([&lib.pq.PGconn](src.handle), schema)
          622  +		if lib.pq.PQresultStatus(res) == lib.pq.PGRES_COMMAND_OK then
          623  +			lib.report('successfully instantiated schema in database')
          624  +			return true
          625  +		else
          626  +			lib.warn('backend pgsql - failed to initialize database: \n', lib.pq.PQresultErrorMessage(res))
          627  +			return false
          628  +		end
          629  +	end];
          630  +
          631  +	obliterate_everything = [terra(src: &lib.store.source)
          632  +		var res = lib.pq.PQexec([&lib.pq.PGconn](src.handle), obliterator)
          633  +		if lib.pq.PQresultStatus(res) == lib.pq.PGRES_COMMAND_OK then
          634  +			lib.report('successfully wiped out everything parsav-related in database')
          635  +			return true
          636  +		else
          637  +			lib.warn('backend pgsql - failed to obliterate database: \n', lib.pq.PQresultErrorMessage(res))
          638  +			return false
          639  +		end
          640  +	end];
   567    641   
   568    642   	conf_get = [terra(src: &lib.store.source, key: rawstring)
   569    643   		var r = queries.conf_get.exec(src, key)
   570    644   		if r.sz == 0 then return [lib.mem.ptr(int8)] { ptr = nil, ct = 0 } else
   571    645   			defer r:free()
   572    646   			return r:String(0,0)
   573    647   		end
................................................................................
   678    752   
   679    753   			var a = row_to_actor(&r, 0)
   680    754   			a.ptr.source = src
   681    755   
   682    756   			var au = [lib.stat(lib.store.auth)] { ok = true }
   683    757   			au.val.aid = aid
   684    758   			au.val.uid = a.ptr.id
   685         -			if not r:null(0,12) then -- restricted?
          759  +			if not r:null(0,13) then -- restricted?
   686    760   				au.val.privs:clear()
   687         -				(au.val.privs.post   << r:bool(0,13)) 
   688         -				(au.val.privs.edit   << r:bool(0,14))
   689         -				(au.val.privs.acct   << r:bool(0,15))
   690         -				(au.val.privs.upload << r:bool(0,16))
   691         -				(au.val.privs.censor << r:bool(0,17))
   692         -				(au.val.privs.admin  << r:bool(0,18))
          761  +				(au.val.privs.post   << r:bool(0,14)) 
          762  +				(au.val.privs.edit   << r:bool(0,15))
          763  +				(au.val.privs.acct   << r:bool(0,16))
          764  +				(au.val.privs.upload << r:bool(0,17))
          765  +				(au.val.privs.censor << r:bool(0,18))
          766  +				(au.val.privs.admin  << r:bool(0,19))
   693    767   			else au.val.privs:fill() end
   694    768   
   695    769   			return au, a
   696    770   		end
   697    771   
   698    772   		::fail:: return [lib.stat   (lib.store.auth) ] { ok = false        },
   699    773   			            [lib.mem.ptr(lib.store.actor)] { ptr = nil, ct = 0 }
................................................................................
   759    833   		return powers
   760    834   	end];
   761    835   
   762    836   	actor_create = [terra(
   763    837   		src: &lib.store.source,
   764    838   		ac: &lib.store.actor
   765    839   	): uint64
   766         -		var r = queries.actor_create.exec(src,ac.nym, ac.handle, ac.origin, ac.knownsince, ac.bio, ac.avatar, ac.key, ac.title, ac.rights.rank, ac.rights.quota)
          840  +		var r = queries.actor_create.exec(src,ac.nym, ac.handle, ac.origin, ac.knownsince, ac.bio, ac.avatar, ac.key, ac.epithet, ac.rights.rank, ac.rights.quota)
   767    841   		if r.sz == 0 then lib.bail('failed to create actor!') end
   768         -		return r:int(uint64,0,0)
          842  +		var uid = r:int(uint64,0,0)
          843  +
          844  +		-- check against default rights, insert records for wherever powers differ
          845  +		lib.dbg('created new actor, establishing powers')
          846  +		var pdef = lib.store.rights_default().powers
          847  +		var map = array([privmap])
          848  +		for i=0, [map.type.N] do
          849  +			var d = pdef and map[i].priv
          850  +			var u = ac.rights.powers and map[i].priv
          851  +			if d:sz() > 0 and u:sz() == 0 then
          852  +				lib.dbg('blocking power ', {map[i].name.ptr, map[i].name.ct})
          853  +				queries.actor_power_insert.exec(src, uid, map[i].name, 0)
          854  +			elseif d:sz() == 0 and u:sz() > 0 then
          855  +				lib.dbg('granting power ', {map[i].name.ptr, map[i].name.ct})
          856  +				queries.actor_power_insert.exec(src, uid, map[i].name, 1)
          857  +			end
          858  +		end
          859  +
          860  +		lib.dbg('powers established')
          861  +		return uid
          862  +	end];
          863  +
          864  +	auth_create_pw = [terra(
          865  +		src: &lib.store.source,
          866  +		uid: uint64,
          867  +		reset: bool,
          868  +		pw: lib.mem.ptr(int8)
          869  +	): {}
          870  +		-- TODO impl reset support
          871  +		var hash: uint8[lib.crypt.algsz.sha256]
          872  +		if lib.md.mbedtls_md(lib.md.mbedtls_md_info_from_type(lib.crypt.alg.sha256.id),
          873  +			[&uint8](pw.ptr), pw.ct, &hash[0]) ~= 0 then
          874  +			lib.bail('cannot hash password')
          875  +		end
          876  +		queries.auth_create_pw.exec(src, uid, [lib.mem.ptr(uint8)] {ptr = &hash[0], ct = [hash.type.N]})
   769    877   	end];
   770    878   
   771    879   	actor_auth_register_uid = nil; -- not necessary for view-based auth
          880  +
   772    881   }
   773    882   
   774    883   return b