44 lines
3.6 KiB
Python
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)
|