2019-01-02 20:51:09 +00:00
package fic
import (
"bytes"
"errors"
2019-01-16 05:02:06 +00:00
"fmt"
2019-01-02 20:51:09 +00:00
"regexp"
"time"
"golang.org/x/crypto/blake2b"
)
// FlagKey represents a flag's challenge, stored as hash.
type FlagKey struct {
2019-07-05 20:28:56 +00:00
Id int64 ` json:"id" `
2019-01-02 20:51:09 +00:00
// IdExercice is the identifier of the underlying challenge
2019-07-05 20:28:56 +00:00
IdExercice int64 ` json:"idExercice" `
2019-01-02 20:51:09 +00:00
// Label is the title of the flag as displayed to players
2019-07-05 20:28:56 +00:00
Label string ` json:"label" `
2019-01-02 20:51:09 +00:00
// Help is a small piece of text that aims to add useful information like flag format, ...
2019-07-05 20:28:56 +00:00
Help string ` json:"help" `
2019-01-02 20:51:09 +00:00
// IgnoreCase indicates if the case is sensitive to case or not
2019-07-05 20:28:56 +00:00
IgnoreCase bool ` json:"ignorecase" `
2020-01-16 14:33:28 +00:00
// Multiline indicates if the flag is stored on multiple lines
Multiline bool ` json:"multiline" `
2019-01-02 20:51:09 +00:00
// ValidatorRegexp extracts a subset of the player's answer, that will be checked.
ValidatorRegexp * string ` json:"validator_regexp" `
// Checksum is the expected hashed flag
2019-07-05 20:28:56 +00:00
Checksum [ ] byte ` json:"value" `
2019-01-02 20:51:09 +00:00
// ChoicesCost is the number of points lost to display choices.
2019-07-05 20:28:56 +00:00
ChoicesCost int64 ` json:"choices_cost" `
2019-01-02 20:51:09 +00:00
}
// GetFlagKeys returns a list of key's flags comming with the challenge.
func ( e Exercice ) GetFlagKeys ( ) ( [ ] FlagKey , error ) {
2020-01-16 14:33:28 +00:00
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 {
2019-01-02 20:51:09 +00:00
return nil , err
} else {
defer rows . Close ( )
var flags = make ( [ ] FlagKey , 0 )
for rows . Next ( ) {
var k FlagKey
k . IdExercice = e . Id
2020-01-16 14:33:28 +00:00
if err := rows . Scan ( & k . Id , & k . IdExercice , & k . Label , & k . Help , & k . IgnoreCase , & k . Multiline , & k . ValidatorRegexp , & k . Checksum , & k . ChoicesCost ) ; err != nil {
2019-01-02 20:51:09 +00:00
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 ) {
2020-01-16 14:33:28 +00:00
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 . Help , & k . IgnoreCase , & k . Multiline , & k . ValidatorRegexp , & k . Checksum , & k . ChoicesCost )
2019-01-02 20:51:09 +00:00
return
}
// GetFlagKeyByLabel returns a flag matching the given label.
func ( e Exercice ) GetFlagKeyByLabel ( label string ) ( k FlagKey , err error ) {
2020-01-16 14:33:28 +00:00
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 . Help , & k . IgnoreCase , & k . Multiline , & k . ValidatorRegexp , & k . Checksum , & k . ChoicesCost )
2019-01-02 20:51:09 +00:00
return
}
2019-07-05 20:28:56 +00:00
// ComputeHashedFlag calculates the expected checksum for the given raw_value.
2019-10-12 11:37:24 +00:00
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
2019-01-02 20:51:09 +00:00
}
2019-01-25 06:54:27 +00:00
func ExecValidatorRegexp ( vre string , val [ ] byte , ignorecase bool ) ( [ ] byte , error ) {
2019-07-05 20:28:56 +00:00
if ignorecase {
2019-01-25 06:54:27 +00:00
vre = "(?i)" + vre
}
2019-01-16 03:52:34 +00:00
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
}
}
2019-01-02 20:51:09 +00:00
// AddRawFlagKey creates and fills a new struct FlagKey, from a non-hashed flag, and registers it into the database.
2020-01-16 14:33:28 +00:00
func ( e Exercice ) AddRawFlagKey ( name string , help string , ignorecase bool , multiline bool , validator_regexp * string , raw_value [ ] byte , choicescost int64 ) ( f FlagKey , err error ) {
2019-10-12 11:37:24 +00:00
hash , errr := ComputeHashedFlag ( raw_value , ignorecase , validator_regexp )
if errr != nil {
return f , err
2019-01-02 20:51:09 +00:00
}
2019-07-05 20:28:56 +00:00
f = FlagKey {
Label : name ,
Help : help ,
IgnoreCase : ignorecase ,
2020-01-16 14:33:28 +00:00
Multiline : multiline ,
2019-07-05 20:28:56 +00:00
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
2019-01-02 20:51:09 +00:00
}
2019-11-25 13:19:29 +00:00
// 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
}
}
2019-01-02 20:51:09 +00:00
// AddFlagKey creates and fills a new struct Flag, from a hashed flag, and registers it into the database.
2019-07-05 20:28:56 +00:00
func ( k FlagKey ) Create ( e Exercice ) ( Flag , error ) {
2019-01-02 20:51:09 +00:00
// Check the regexp compile
2019-07-05 20:28:56 +00:00
if k . ValidatorRegexp != nil {
if _ , err := regexp . Compile ( * k . ValidatorRegexp ) ; err != nil {
return k , err
2019-01-02 20:51:09 +00:00
}
}
2020-01-16 14:33:28 +00:00
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 . Help , k . IgnoreCase , k . Multiline , k . ValidatorRegexp , k . Checksum , k . ChoicesCost ) ; err != nil {
2019-07-05 20:28:56 +00:00
return k , err
2019-01-02 20:51:09 +00:00
} else if kid , err := res . LastInsertId ( ) ; err != nil {
2019-07-05 20:28:56 +00:00
return k , err
2019-01-02 20:51:09 +00:00
} else {
2019-07-05 20:28:56 +00:00
k . Id = kid
k . IdExercice = e . Id
return k , nil
2019-01-02 20:51:09 +00:00
}
}
2019-10-12 11:37:24 +00:00
// 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
2019-01-21 12:35:09 +00:00
}
2019-01-02 20:51:09 +00:00
// 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
}
}
2020-01-16 14:33:28 +00:00
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 . Help , k . IgnoreCase , k . Multiline , k . ValidatorRegexp , k . Checksum , k . ChoicesCost , k . Id ) ; err != nil {
2019-01-02 20:51:09 +00:00
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_deps WHERE id_flag = ?" , k . Id ) ; err != nil {
return 0 , err
2019-01-16 05:02:06 +00:00
} 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 {
2019-01-02 20:51:09 +00:00
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 )
2019-01-16 05:02:06 +00:00
} 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 )
2019-01-02 20:51:09 +00:00
} else {
2019-01-16 05:02:06 +00:00
err = errors . New ( fmt . Sprintf ( "Dependancy type for key (%T) not implemented for this flag." , j ) )
2019-01-02 20:51:09 +00:00
}
return
}
// GetDepends retrieve the flag's dependency list.
func ( k FlagKey ) GetDepends ( ) ( [ ] Flag , error ) {
2019-01-16 05:02:06 +00:00
var deps = make ( [ ] Flag , 0 )
2019-01-02 20:51:09 +00:00
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
}
2019-01-20 00:28:46 +00:00
deps = append ( deps , FlagKey { Id : d , IdExercice : k . IdExercice } )
2019-01-02 20:51:09 +00:00
}
if err := rows . Err ( ) ; err != nil {
return nil , err
}
2019-01-16 05:02:06 +00:00
}
2019-01-02 20:51:09 +00:00
2019-01-16 05:02:06 +00:00
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
}
2019-01-20 00:28:46 +00:00
deps = append ( deps , MCQ { Id : d , IdExercice : k . IdExercice } )
2019-01-16 05:02:06 +00:00
}
if err := rows . Err ( ) ; err != nil {
return nil , err
}
2019-01-02 20:51:09 +00:00
}
2019-01-16 05:02:06 +00:00
return deps , nil
2019-01-02 20:51:09 +00:00
}
// 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
}
2019-01-21 12:35:09 +00:00
hash , err := k . ComputeChecksum ( val )
if err != nil {
2019-01-02 20:51:09 +00:00
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 ( ) )
}
2019-02-05 02:24:52 +00:00
// 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 )
}