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('

')[-1].split('

')[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)