Files
Recruitment-HC/dev/test-exam-protection.py

44 lines
3.6 KiB
Python

import subprocess,urllib.request,urllib.parse,urllib.error,http.cookiejar,re,json,os,pathlib
os.chdir(pathlib.Path(__file__).resolve().parent.parent)
cli=['mysql','--defaults-extra-file=.local/mysql-client.cnf','recruitment_local','-N','-B']
def sql(q):return subprocess.check_output(cli,input=q.encode()).decode().strip()
pw=subprocess.check_output(['php','-r','echo password_hash("QA-security-test!",PASSWORD_DEFAULT);']).decode()
sql("INSERT INTO in_daper(kode,nama,jk,email,nowa,posisi,cek,level,password) VALUES(99,'QA security','Pria','qa-security@example.test','0800','QA',0,0,'"+pw+"')")
pid=sql("SELECT id FROM in_daper WHERE email='qa-security@example.test'")
base='http://127.0.0.1:8086/'
def session():return urllib.request.build_opener(urllib.request.HTTPCookieProcessor(http.cookiejar.CookieJar()))
def get(s,path):return s.open(base+path).read().decode()
def post(s,path,data):
try:
r=s.open(base+path,urllib.parse.urlencode(data).encode());return r.status,r.read().decode()
except urllib.error.HTTPError as ex:return ex.code,ex.read().decode()
def field(h,name):return re.search('name="'+name+'" value="([^"]*)"',h).group(1)
def login(s):
h=get(s,'index.php');return post(s,'login.php',{'csrf':field(h,'csrf'),'username':'qa-security@example.test','password':'QA-security-test!'})
def form(h,phase=1,action='save'):return {'csrf':field(h,'csrf'),'attempt_token':field(h,'attempt_token'),'phase':str(phase),'action':action}
try:
s=session();login(s);h=get(s,'test1.php');d=form(h);qid=re.search(r'name="answers\[(\d+)\]"',h).group(1)
deadline=sql('SELECT deadline FROM rec_exam_attempt WHERE participant_id='+pid)
code,_=post(s,'test1_add.php',{**d,'csrf':'wrong'});assert code==403
code,_=post(s,'test1_add.php',{**d,'answers[999999]':'forged'});assert code==422
code,body=post(s,'test1_add.php',{**d,'answers['+qid+']':'Before deadline'});assert code==200
h2=get(s,'test1.php');assert 'Before deadline' in h2
assert deadline==sql('SELECT deadline FROM rec_exam_attempt WHERE participant_id='+pid)
code,body=post(s,'test1_add.php',d);assert code==409 and json.loads(body)['stale']
s2=session();login(s2);h3=get(s2,'test1.php');assert 'Before deadline' in h3
assert deadline==sql('SELECT deadline FROM rec_exam_attempt WHERE participant_id='+pid)
assert qid==re.search(r'name="answers\[(\d+)\]"',h3).group(1)
d3=form(h3);post(s2,'test1_add.php',{**d3,'action':'event','event':'tab_hidden'})
assert sql('SELECT COUNT(*) FROM rec_exam_event e JOIN rec_exam_attempt a ON a.id=e.attempt_id WHERE a.participant_id='+pid)!='0'
sql('UPDATE rec_exam_attempt SET deadline=UNIX_TIMESTAMP()-1 WHERE participant_id='+pid)
code,body=post(s2,'test1_add.php',{**d3,'answers['+qid+']':'Forged late answer'});assert code==200 and json.loads(body)['closed']
assert sql('SELECT jawab FROM in_jawab WHERE idap='+pid+' AND isoal='+qid)=='Before deadline'
assert sql('SELECT COUNT(*) FROM in_jawab WHERE idap='+pid)=='15'
assert sql('SELECT level FROM in_daper WHERE id='+pid)=='1'
code,_=post(s2,'test1_add.php',{**d3,'action':'submit'});assert code==200
assert sql('SELECT COUNT(*) FROM in_jawab WHERE idap='+pid)=='15'
assert 'Matematika' not in get(s2,'test1.php').split('<h1>')[-1].split('</h1>')[0]
print('PASS: CSRF, forged question ID, durable drafts/deadlines across reload and fresh login, stale tab rejection, review event log, server timeout discards late payload, idempotent finalization, phase guard.')
finally:
sql('DELETE e FROM rec_exam_event e JOIN rec_exam_attempt a ON a.id=e.attempt_id WHERE a.participant_id='+pid+'; DELETE FROM rec_exam_attempt WHERE participant_id='+pid+'; DELETE FROM in_jawab WHERE idap='+pid+'; DELETE FROM in_daper WHERE id='+pid)