server/libfic/flag_key.go

300 lines
9.5 KiB
Go

package fic
import (
"bytes"
"errors"
"fmt"
"regexp"
"time"
"golang.org/x/crypto/blake2b"
)
// FlagKey represents a flag's challenge, stored as hash.
type FlagKey struct {
Id int64 `json:"id"`
// IdExercice is the identifier of the underlying challenge
IdExercice int64 `json:"idExercice"`
// Label is the title of the flag as displayed to players
Label string `json:"label"`
// Placeholder is a small piece of text that aims to add useful information like flag format, ...
Placeholder string `json:"placeholder"`
// IgnoreCase indicates if the case is sensitive to case or not
IgnoreCase bool `json:"ignorecase"`
// Multiline indicates if the flag is stored on multiple lines
Multiline bool `json:"multiline"`
// ValidatorRegexp extracts a subset of the player's answer, that will be checked.
ValidatorRegexp *string `json:"validator_regexp"`
// Checksum is the expected hashed flag
Checksum []byte `json:"value"`
// ChoicesCost is the number of points lost to display choices.
ChoicesCost int64 `json:"choices_cost"`
}
// GetFlagKeys returns a list of key's flags comming with the challenge.
func (e Exercice) GetFlagKeys() ([]FlagKey, error) {
if rows, err := DBQuery("SELECT id_flag, id_exercice, type, help, ignorecase, multiline, validator_regexp, cksum, choices_cost FROM exercice_flags WHERE id_exercice = ?", e.Id); err != nil {
return nil, err
} else {
defer rows.Close()
var flags = make([]FlagKey, 0)
for rows.Next() {
var k FlagKey
k.IdExercice = e.Id
if err := rows.Scan(&k.Id, &k.IdExercice, &k.Label, &k.Placeholder, &k.IgnoreCase, &k.Multiline, &k.ValidatorRegexp, &k.Checksum, &k.ChoicesCost); err != nil {
return nil, err
}
flags = append(flags, k)
}
if err := rows.Err(); err != nil {
return nil, err
}
return flags, nil
}
}
// GetFlagKey returns a list of flags comming with the challenge.
func GetFlagKey(id int64) (k FlagKey, err error) {
err = DBQueryRow("SELECT id_flag, id_exercice, type, help, ignorecase, multiline, validator_regexp, cksum, choices_cost FROM exercice_flags WHERE id_flag = ?", id).Scan(&k.Id, &k.IdExercice, &k.Label, &k.Placeholder, &k.IgnoreCase, &k.Multiline, &k.ValidatorRegexp, &k.Checksum, &k.ChoicesCost)
return
}
// GetFlagKeyByLabel returns a flag matching the given label.
func (e Exercice) GetFlagKeyByLabel(label string) (k FlagKey, err error) {
err = DBQueryRow("SELECT id_flag, id_exercice, type, help, ignorecase, multiline, validator_regexp, cksum, choices_cost FROM exercice_flags WHERE type LIKE ? AND id_exercice = ?", label, e.Id).Scan(&k.Id, &k.IdExercice, &k.Label, &k.Placeholder, &k.IgnoreCase, &k.Multiline, &k.ValidatorRegexp, &k.Checksum, &k.ChoicesCost)
return
}
// ComputeHashedFlag calculates the expected checksum for the given raw_value.
func ComputeHashedFlag(raw_value []byte, ignorecase bool, validator_regexp *string) (hash [blake2b.Size]byte, err error) {
if ignorecase {
raw_value = bytes.ToLower(raw_value)
}
// Check that raw value passes through the regexp
if validator_regexp != nil {
if raw_value, err = ExecValidatorRegexp(*validator_regexp, raw_value, ignorecase); err != nil {
return
}
}
// Check that the value is not empty
if len(raw_value) == 0 {
err = errors.New("Empty flag after applying filters")
}
hash = blake2b.Sum512(raw_value)
return
}
func ExecValidatorRegexp(vre string, val []byte, ignorecase bool) ([]byte, error) {
if ignorecase {
vre = "(?i)" + vre
}
if re, err := regexp.Compile(vre); err != nil {
return val, err
} else if res := re.FindSubmatch(val); res == nil {
return val, errors.New("Expected flag doesn't pass through the validator_regexp")
} else {
return bytes.Join(res[1:], []byte("+")), nil
}
}
// AddRawFlagKey creates and fills a new struct FlagKey, from a non-hashed flag, and registers it into the database.
func (e Exercice) AddRawFlagKey(name string, placeholder string, ignorecase bool, multiline bool, validator_regexp *string, raw_value []byte, choicescost int64) (f FlagKey, err error) {
hash, errr := ComputeHashedFlag(raw_value, ignorecase, validator_regexp)
if errr != nil {
return f, err
}
f = FlagKey{
Label: name,
Placeholder: placeholder,
IgnoreCase: ignorecase,
Multiline: multiline,
ValidatorRegexp: validator_regexp,
Checksum: hash[:],
ChoicesCost: choicescost,
}
_, err = f.Create(e)
return
}
// GetId returns the Flag identifier.
func (k FlagKey) GetId() int64 {
return k.Id
}
// RecoverId returns the Flag identifier as register in DB.
func (k FlagKey) RecoverId() (Flag, error) {
if err := DBQueryRow("SELECT id_flag FROM exercice_flags WHERE type LIKE ? AND id_exercice = ?", k.Label, k.IdExercice).Scan(&k.Id); err != nil {
return FlagKey{}, err
} else {
return k, err
}
}
// AddFlagKey creates and fills a new struct Flag, from a hashed flag, and registers it into the database.
func (k FlagKey) Create(e Exercice) (Flag, error) {
// Check the regexp compile
if k.ValidatorRegexp != nil {
if _, err := regexp.Compile(*k.ValidatorRegexp); err != nil {
return k, err
}
}
if res, err := DBExec("INSERT INTO exercice_flags (id_exercice, type, help, ignorecase, multiline, validator_regexp, cksum, choices_cost) VALUES (?, ?, ?, ?, ?, ?, ?, ?)", e.Id, k.Label, k.Placeholder, k.IgnoreCase, k.Multiline, k.ValidatorRegexp, k.Checksum, k.ChoicesCost); err != nil {
return k, err
} else if kid, err := res.LastInsertId(); err != nil {
return k, err
} else {
k.Id = kid
k.IdExercice = e.Id
return k, nil
}
}
// ComputeChecksum calculates the checksum for a given value.
func (k FlagKey) ComputeChecksum(val []byte) ([]byte, error) {
cksum, err := ComputeHashedFlag(val, k.IgnoreCase, k.ValidatorRegexp)
return cksum[:], err
}
// Update applies modifications back to the database.
func (k FlagKey) Update() (int64, error) {
if k.ValidatorRegexp != nil {
if _, err := regexp.Compile(*k.ValidatorRegexp); err != nil {
return 0, err
}
}
if res, err := DBExec("UPDATE exercice_flags SET id_exercice = ?, type = ?, help = ?, ignorecase = ?, multiline = ?, validator_regexp = ?, cksum = ?, choices_cost = ? WHERE id_flag = ?", k.IdExercice, k.Label, k.Placeholder, k.IgnoreCase, k.Multiline, k.ValidatorRegexp, k.Checksum, k.ChoicesCost, k.Id); err != nil {
return 0, err
} else if nb, err := res.RowsAffected(); err != nil {
return 0, err
} else {
return nb, err
}
}
// Delete the flag from the database.
func (k FlagKey) Delete() (int64, error) {
if _, err := DBExec("DELETE FROM exercice_files_okey_deps WHERE id_flag = ?", k.Id); err != nil {
return 0, err
} else if _, err := DBExec("DELETE FROM exercice_mcq_okey_deps WHERE id_flag_dep = ?", k.Id); err != nil {
return 0, err
} else if _, err := DBExec("DELETE FROM exercice_flags_omcq_deps WHERE id_flag = ?", k.Id); err != nil {
return 0, err
} else if _, err := DBExec("DELETE FROM exercice_flags_deps WHERE id_flag = ? OR id_flag_dep = ?", k.Id, k.Id); err != nil {
return 0, err
} else if _, err := DBExec("DELETE FROM flag_choices WHERE id_flag = ?", k.Id); err != nil {
return 0, err
} else if res, err := DBExec("DELETE FROM exercice_flags WHERE id_flag = ?", k.Id); err != nil {
return 0, err
} else if nb, err := res.RowsAffected(); err != nil {
return 0, err
} else {
return nb, err
}
}
// AddDepend insert a new dependency to a given flag.
func (k FlagKey) AddDepend(j Flag) (err error) {
if d, ok := j.(FlagKey); ok {
_, err = DBExec("INSERT INTO exercice_flags_deps (id_flag, id_flag_dep) VALUES (?, ?)", k.Id, d.Id)
} else if d, ok := j.(MCQ); ok {
_, err = DBExec("INSERT INTO exercice_flags_omcq_deps (id_flag, id_mcq_dep) VALUES (?, ?)", k.Id, d.Id)
} else {
err = fmt.Errorf("Dependancy type for key (%T) not implemented for this flag.", j)
}
return
}
// GetDepends retrieve the flag's dependency list.
func (k FlagKey) GetDepends() ([]Flag, error) {
var deps = make([]Flag, 0)
if rows, err := DBQuery("SELECT id_flag_dep FROM exercice_flags_deps WHERE id_flag = ?", k.Id); err != nil {
return nil, err
} else {
defer rows.Close()
for rows.Next() {
var d int64
if err := rows.Scan(&d); err != nil {
return nil, err
}
deps = append(deps, FlagKey{Id: d, IdExercice: k.IdExercice})
}
if err := rows.Err(); err != nil {
return nil, err
}
}
if rows, err := DBQuery("SELECT id_mcq_dep FROM exercice_flags_omcq_deps WHERE id_flag = ?", k.Id); err != nil {
return nil, err
} else {
defer rows.Close()
for rows.Next() {
var d int64
if err := rows.Scan(&d); err != nil {
return nil, err
}
deps = append(deps, MCQ{Id: d, IdExercice: k.IdExercice})
}
if err := rows.Err(); err != nil {
return nil, err
}
}
return deps, nil
}
// Check if the given val is the expected one for this flag.
func (k FlagKey) Check(v interface{}) int {
var val []byte
if va, ok := v.([]byte); !ok {
return -1
} else {
val = va
}
hash, err := k.ComputeChecksum(val)
if err != nil {
return 1
}
if len(k.Checksum) != len(hash) {
return 1
}
for i := range hash {
if k.Checksum[i] != hash[i] {
return 1
}
}
return 0
}
// FoundBy registers in the database that the given Team solved the flag.
func (k FlagKey) FoundBy(t Team) {
DBExec("INSERT INTO flag_found (id_flag, id_team, time) VALUES (?, ?, ?)", k.Id, t.Id, time.Now())
}
// GetExercice returns the parent Exercice where this flag can be found.
func (k FlagKey) GetExercice() (Exercice, error) {
var eid int64
if err := DBQueryRow("SELECT id_exercice FROM exercice_flags WHERE id_flag = ?", k.Id).Scan(&eid); err != nil {
return Exercice{}, err
}
return GetExercice(eid)
}