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"
2022-01-21 07:28:39 +00:00
"sort"
"strings"
2019-01-02 20:51:09 +00:00
"time"
"golang.org/x/crypto/blake2b"
)
// FlagKey represents a flag's challenge, stored as hash.
type FlagKey struct {
2021-08-30 16:33:14 +00:00
Id int ` 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" `
2021-08-30 16:33:14 +00:00
// Order is used to sort the flag between them
Order int8 ` json:"order" `
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" `
2021-08-30 16:33:14 +00:00
// Type is the kind of flag
Type string ` json:"type,omitempty" `
2020-09-07 17:33:09 +00:00
// Placeholder is a small piece of text that aims to add useful information like flag format, ...
Placeholder string ` json:"placeholder" `
2021-08-30 16:33:14 +00:00
// Help is a description of the flag
Help string ` json:"help" `
2021-11-12 22:52:22 +00:00
// Unit is another indication appended to the input
Unit string ` json:"unit" `
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" `
2021-12-07 15:33:30 +00:00
// NoTrim indicates if the flag should not be trimed to avoid mistakes
NoTrim bool ` json:"notrim,omitempty" `
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" `
2022-01-21 07:28:39 +00:00
// SortReGroups indicates if groups resulting of the validator regexp should be sorted.
SortReGroups bool ` json:"sort_re_grps" `
2019-01-02 20:51:09 +00:00
// 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.
2022-05-31 20:03:51 +00:00
ChoicesCost int32 ` json:"choices_cost" `
// BonusGain makes the flag completion optionnal. If it is filled and correct, it gives some points.
BonusGain int32 ` json:"bonus_gain" `
2019-01-02 20:51:09 +00:00
}
// GetFlagKeys returns a list of key's flags comming with the challenge.
2021-11-22 14:35:07 +00:00
func ( e * Exercice ) GetFlagKeys ( ) ( [ ] * FlagKey , error ) {
2022-05-31 20:03:51 +00:00
if rows , err := DBQuery ( "SELECT id_flag, id_exercice, ordre, label, type, placeholder, help, unit, ignorecase, notrim, multiline, validator_regexp, sort_re_grps, cksum, choices_cost, bonus_gain 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 ( )
2021-11-22 14:35:07 +00:00
flags := [ ] * FlagKey { }
2019-01-02 20:51:09 +00:00
for rows . Next ( ) {
2021-11-22 14:35:07 +00:00
k := & FlagKey { }
2019-01-02 20:51:09 +00:00
k . IdExercice = e . Id
2022-05-31 20:03:51 +00:00
if err := rows . Scan ( & k . Id , & k . IdExercice , & k . Order , & k . Label , & k . Type , & k . Placeholder , & k . Help , & k . Unit , & k . IgnoreCase , & k . NoTrim , & k . Multiline , & k . ValidatorRegexp , & k . SortReGroups , & k . Checksum , & k . ChoicesCost , & k . BonusGain ) ; 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.
2021-11-22 14:35:07 +00:00
func GetFlagKey ( id int ) ( k * FlagKey , err error ) {
k = & FlagKey { }
2022-05-31 20:03:51 +00:00
err = DBQueryRow ( "SELECT id_flag, id_exercice, ordre, label, type, placeholder, help, unit, ignorecase, notrim, multiline, validator_regexp, sort_re_grps, cksum, choices_cost, bonus_gain FROM exercice_flags WHERE id_flag = ?" , id ) . Scan ( & k . Id , & k . IdExercice , & k . Order , & k . Label , & k . Type , & k . Placeholder , & k . Help , & k . Unit , & k . IgnoreCase , & k . Multiline , & k . NoTrim , & k . ValidatorRegexp , & k . SortReGroups , & k . Checksum , & k . ChoicesCost , & k . BonusGain )
2019-01-02 20:51:09 +00:00
return
}
2022-05-16 09:38:46 +00:00
// GetFlagKey returns a flag.
func ( e * Exercice ) GetFlagKey ( id int ) ( k * FlagKey , err error ) {
k = & FlagKey { }
2022-05-31 20:03:51 +00:00
err = DBQueryRow ( "SELECT id_flag, id_exercice, ordre, label, type, placeholder, help, unit, ignorecase, notrim, multiline, validator_regexp, sort_re_grps, cksum, choices_cost, bonus_gain FROM exercice_flags WHERE id_flag = ? AND id_exercice = ?" , id , e . Id ) . Scan ( & k . Id , & k . IdExercice , & k . Order , & k . Label , & k . Type , & k . Placeholder , & k . Help , & k . Unit , & k . IgnoreCase , & k . NoTrim , & k . Multiline , & k . ValidatorRegexp , & k . SortReGroups , & k . Checksum , & k . ChoicesCost , & k . BonusGain )
2022-05-16 09:38:46 +00:00
return
}
2019-01-02 20:51:09 +00:00
// GetFlagKeyByLabel returns a flag matching the given label.
2021-11-22 14:35:07 +00:00
func ( e * Exercice ) GetFlagKeyByLabel ( label string ) ( k * FlagKey , err error ) {
k = & FlagKey { }
2022-05-31 20:03:51 +00:00
err = DBQueryRow ( "SELECT id_flag, id_exercice, ordre, label, type, placeholder, help, unit, ignorecase, notrim, multiline, validator_regexp, sort_re_grps, cksum, choices_cost, bonus_gain FROM exercice_flags WHERE type LIKE ? AND id_exercice = ?" , label , e . Id ) . Scan ( & k . Id , & k . IdExercice , & k . Order , & k . Label , & k . Type , & k . Placeholder , & k . Help , & k . Unit , & k . IgnoreCase , & k . NoTrim , & k . Multiline , & k . ValidatorRegexp , & k . SortReGroups , & k . Checksum , & k . ChoicesCost , & k . BonusGain )
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.
2022-01-21 07:28:39 +00:00
func ComputeHashedFlag ( raw_value [ ] byte , ignorecase bool , notrim bool , validator_regexp * string , sortregroups bool ) ( hash [ blake2b . Size ] byte , err error ) {
2019-10-12 11:37:24 +00:00
if ignorecase {
raw_value = bytes . ToLower ( raw_value )
}
2021-12-07 15:33:30 +00:00
if ! notrim {
raw_value = bytes . TrimSpace ( raw_value )
}
2019-10-12 11:37:24 +00:00
// Check that raw value passes through the regexp
if validator_regexp != nil {
2022-01-21 07:28:39 +00:00
if raw_value , err = ExecValidatorRegexp ( * validator_regexp , raw_value , ignorecase , sortregroups ) ; err != nil {
2019-10-12 11:37:24 +00:00
return
}
}
// Check that the value is not empty
if len ( raw_value ) == 0 {
2021-11-22 14:35:07 +00:00
err = errors . New ( "empty flag after applying filters" )
2019-10-12 11:37:24 +00:00
}
hash = blake2b . Sum512 ( raw_value )
return
2019-01-02 20:51:09 +00:00
}
2022-01-21 07:28:39 +00:00
func ExecValidatorRegexp ( vre string , val [ ] byte , ignorecase bool , sortregroups 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 {
2021-11-22 14:35:07 +00:00
return val , errors . New ( "expected flag doesn't pass through the validator_regexp" )
2022-01-21 07:28:39 +00:00
} else if sortregroups && len ( res ) > 2 {
var tab [ ] string
for _ , v := range res [ 1 : ] {
tab = append ( tab , string ( v ) )
}
sort . Strings ( tab )
return [ ] byte ( strings . Join ( tab , "+" ) ) , nil
2019-01-16 03:52:34 +00:00
} 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.
2022-05-31 20:03:51 +00:00
func ( e * Exercice ) AddRawFlagKey ( name string , t string , placeholder string , ignorecase bool , multiline bool , notrim bool , validator_regexp * string , sortregroups bool , raw_value [ ] byte , choicescost int32 , bonusgain int32 ) ( * FlagKey , error ) {
2022-01-21 07:28:39 +00:00
hash , err := ComputeHashedFlag ( raw_value , ignorecase , notrim , validator_regexp , sortregroups )
2021-11-22 14:35:07 +00:00
if err != nil {
return nil , err
2019-01-02 20:51:09 +00:00
}
2021-11-22 14:35:07 +00:00
f := & FlagKey {
2021-11-12 20:36:27 +00:00
Type : t ,
2019-07-05 20:28:56 +00:00
Label : name ,
2020-09-07 17:33:09 +00:00
Placeholder : placeholder ,
2019-07-05 20:28:56 +00:00
IgnoreCase : ignorecase ,
2021-12-07 15:33:30 +00:00
NoTrim : notrim ,
2020-01-16 14:33:28 +00:00
Multiline : multiline ,
2019-07-05 20:28:56 +00:00
ValidatorRegexp : validator_regexp ,
2022-01-21 07:28:39 +00:00
SortReGroups : sortregroups ,
2019-07-05 20:28:56 +00:00
Checksum : hash [ : ] ,
ChoicesCost : choicescost ,
2022-05-31 20:03:51 +00:00
BonusGain : bonusgain ,
2019-07-05 20:28:56 +00:00
}
_ , err = f . Create ( e )
2021-11-22 14:35:07 +00:00
return f , err
2019-07-05 20:28:56 +00:00
}
// GetId returns the Flag identifier.
2021-11-22 14:35:07 +00:00
func ( k * FlagKey ) GetId ( ) int {
2019-07-05 20:28:56 +00:00
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.
2021-11-22 14:35:07 +00:00
func ( k * FlagKey ) RecoverId ( ) ( Flag , error ) {
2021-08-30 16:33:14 +00:00
if err := DBQueryRow ( "SELECT id_flag FROM exercice_flags WHERE label LIKE ? AND id_exercice = ?" , k . Label , k . IdExercice ) . Scan ( & k . Id ) ; err != nil {
2021-11-22 14:35:07 +00:00
return nil , err
2019-11-25 13:19:29 +00:00
} 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.
2021-11-22 14:35:07 +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
}
}
2022-05-31 20:03:51 +00:00
if res , err := DBExec ( "INSERT INTO exercice_flags (id_exercice, ordre, label, type, placeholder, help, unit, ignorecase, notrim, multiline, validator_regexp, sort_re_grps, cksum, choices_cost, bonus_gain) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)" , e . Id , k . Order , k . Label , k . Type , k . Placeholder , k . Help , k . Unit , k . IgnoreCase , k . NoTrim , k . Multiline , k . ValidatorRegexp , k . SortReGroups , k . Checksum , k . ChoicesCost , k . BonusGain ) ; 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 {
2021-08-30 16:33:14 +00:00
k . Id = int ( kid )
2019-07-05 20:28:56 +00:00
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.
2021-11-22 14:35:07 +00:00
func ( k * FlagKey ) ComputeChecksum ( val [ ] byte ) ( [ ] byte , error ) {
2022-01-21 07:28:39 +00:00
cksum , err := ComputeHashedFlag ( val , k . IgnoreCase , k . NoTrim , k . ValidatorRegexp , k . SortReGroups )
2019-10-12 11:37:24 +00:00
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.
2021-11-22 14:35:07 +00:00
func ( k * FlagKey ) Update ( ) ( int64 , error ) {
2019-01-02 20:51:09 +00:00
if k . ValidatorRegexp != nil {
if _ , err := regexp . Compile ( * k . ValidatorRegexp ) ; err != nil {
return 0 , err
}
}
2022-05-31 20:03:51 +00:00
if res , err := DBExec ( "UPDATE exercice_flags SET id_exercice = ?, ordre = ?, label = ?, type = ?, placeholder = ?, help = ?, unit = ?, ignorecase = ?, notrim = ?, multiline = ?, validator_regexp = ?, sort_re_grps = ?, cksum = ?, choices_cost = ?, bonus_gain = ? WHERE id_flag = ?" , k . IdExercice , k . Order , k . Label , k . Type , k . Placeholder , k . Help , k . Unit , k . IgnoreCase , k . NoTrim , k . Multiline , k . ValidatorRegexp , k . SortReGroups , k . Checksum , k . ChoicesCost , k . BonusGain , 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.
2021-11-22 14:35:07 +00:00
func ( k * FlagKey ) Delete ( ) ( int64 , error ) {
2020-01-20 16:28:37 +00:00
if _ , err := DBExec ( "DELETE FROM exercice_files_okey_deps WHERE id_flag = ?" , k . Id ) ; err != nil {
2019-01-02 20:51:09 +00:00
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
2021-09-02 00:25:18 +00:00
} else if _ , err := DBExec ( "DELETE FROM exercice_hints_okey_deps WHERE id_flag_dep = ?" , k . Id ) ; err != nil {
return 0 , err
2019-01-02 20:51:09 +00:00
} 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
}
}
2021-11-22 14:35:07 +00:00
func ( k * FlagKey ) GetOrder ( ) int8 {
2021-08-30 16:33:14 +00:00
return k . Order
}
2019-01-02 20:51:09 +00:00
// AddDepend insert a new dependency to a given flag.
2021-11-22 14:35:07 +00:00
func ( k * FlagKey ) AddDepend ( j Flag ) ( err error ) {
if d , ok := j . ( * FlagKey ) ; ok {
2019-01-02 20:51:09 +00:00
_ , err = DBExec ( "INSERT INTO exercice_flags_deps (id_flag, id_flag_dep) VALUES (?, ?)" , k . Id , d . Id )
2021-11-22 14:35:07 +00:00
} else if d , ok := j . ( * MCQ ) ; ok {
2019-01-16 05:02:06 +00:00
_ , 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 {
2021-11-22 14:35:07 +00:00
err = fmt . Errorf ( "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.
2021-11-22 14:35:07 +00:00
func ( k * FlagKey ) GetDepends ( ) ( [ ] Flag , error ) {
var deps [ ] Flag
2019-01-16 05:02:06 +00:00
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 ( ) {
2021-08-30 16:33:14 +00:00
var d int
2019-01-02 20:51:09 +00:00
if err := rows . Scan ( & d ) ; err != nil {
return nil , err
}
2021-11-22 14:35:07 +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 ( ) {
2021-08-30 16:33:14 +00:00
var d int
2019-01-16 05:02:06 +00:00
if err := rows . Scan ( & d ) ; err != nil {
return nil , err
}
2021-11-22 14:35:07 +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
}
2022-05-31 20:03:51 +00:00
// IsOptionnal to know if the flag can be omitted when validating the step.
func ( k * FlagKey ) IsOptionnal ( ) bool {
return k . BonusGain != 0
}
2019-01-02 20:51:09 +00:00
// Check if the given val is the expected one for this flag.
2021-11-22 14:35:07 +00:00
func ( k * FlagKey ) Check ( v interface { } ) int {
2019-01-02 20:51:09 +00:00
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.
2022-06-08 14:38:00 +00:00
func ( k * FlagKey ) FoundBy ( t * Team ) ( err error ) {
_ , err = DBExec ( "INSERT INTO flag_found (id_flag, id_team, time) VALUES (?, ?, ?)" , k . Id , t . Id , time . Now ( ) )
return
2019-01-02 20:51:09 +00:00
}
2019-02-05 02:24:52 +00:00
// GetExercice returns the parent Exercice where this flag can be found.
2021-11-22 14:35:07 +00:00
func ( k * FlagKey ) GetExercice ( ) ( * Exercice , error ) {
2019-02-05 02:24:52 +00:00
var eid int64
if err := DBQueryRow ( "SELECT id_exercice FROM exercice_flags WHERE id_flag = ?" , k . Id ) . Scan ( & eid ) ; err != nil {
2021-11-22 14:35:07 +00:00
return nil , err
2019-02-05 02:24:52 +00:00
}
return GetExercice ( eid )
}