package fic import ( "bytes" "errors" "fmt" "regexp" "sort" "strings" "time" "golang.org/x/crypto/blake2b" ) // FlagKey represents a flag's challenge, stored as hash. type FlagKey struct { Id int `json:"id"` // IdExercice is the identifier of the underlying challenge IdExercice int64 `json:"idExercice"` // Order is used to sort the flag between them Order int8 `json:"order"` // Label is the title of the flag as displayed to players Label string `json:"label"` // Type is the kind of flag Type string `json:"type,omitempty"` // Placeholder is a small piece of text that aims to add useful information like flag format, ... Placeholder string `json:"placeholder"` // Help is a description of the flag Help string `json:"help"` // Unit is another indication appended to the input Unit string `json:"unit"` // IgnoreCase indicates if the case is sensitive to case or not IgnoreCase bool `json:"ignorecase"` // NoTrim indicates if the flag should not be trimed to avoid mistakes NoTrim bool `json:"notrim,omitempty"` // 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"` // SortReGroups indicates if groups resulting of the validator regexp should be sorted. SortReGroups bool `json:"sort_re_grps"` // 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, ordre, label, type, placeholder, help, unit, ignorecase, notrim, multiline, validator_regexp, sort_re_grps, cksum, choices_cost FROM exercice_flags WHERE id_exercice = ?", e.Id); err != nil { return nil, err } else { defer rows.Close() flags := []*FlagKey{} for rows.Next() { k := &FlagKey{} k.IdExercice = e.Id 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); 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 int) (k *FlagKey, err error) { k = &FlagKey{} err = DBQueryRow("SELECT id_flag, id_exercice, ordre, label, type, placeholder, help, unit, ignorecase, notrim, multiline, validator_regexp, sort_re_grps, cksum, choices_cost 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) return } // GetFlagKeyByLabel returns a flag matching the given label. func (e *Exercice) GetFlagKeyByLabel(label string) (k *FlagKey, err error) { k = &FlagKey{} err = DBQueryRow("SELECT id_flag, id_exercice, ordre, label, type, placeholder, help, unit, ignorecase, notrim, multiline, validator_regexp, sort_re_grps, cksum, choices_cost 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) return } // ComputeHashedFlag calculates the expected checksum for the given raw_value. func ComputeHashedFlag(raw_value []byte, ignorecase bool, notrim bool, validator_regexp *string, sortregroups bool) (hash [blake2b.Size]byte, err error) { if ignorecase { raw_value = bytes.ToLower(raw_value) } if !notrim { raw_value = bytes.TrimSpace(raw_value) } // Check that raw value passes through the regexp if validator_regexp != nil { if raw_value, err = ExecValidatorRegexp(*validator_regexp, raw_value, ignorecase, sortregroups); 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, sortregroups 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 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 } 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, t string, placeholder string, ignorecase bool, multiline bool, notrim bool, validator_regexp *string, sortregroups bool, raw_value []byte, choicescost int64) (*FlagKey, error) { hash, err := ComputeHashedFlag(raw_value, ignorecase, notrim, validator_regexp, sortregroups) if err != nil { return nil, err } f := &FlagKey{ Type: t, Label: name, Placeholder: placeholder, IgnoreCase: ignorecase, NoTrim: notrim, Multiline: multiline, ValidatorRegexp: validator_regexp, SortReGroups: sortregroups, Checksum: hash[:], ChoicesCost: choicescost, } _, err = f.Create(e) return f, err } // GetId returns the Flag identifier. func (k *FlagKey) GetId() int { 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 label LIKE ? AND id_exercice = ?", k.Label, k.IdExercice).Scan(&k.Id); err != nil { return nil, 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, ordre, label, type, placeholder, help, unit, ignorecase, notrim, multiline, validator_regexp, sort_re_grps, cksum, choices_cost) 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); err != nil { return k, err } else if kid, err := res.LastInsertId(); err != nil { return k, err } else { k.Id = int(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.NoTrim, k.ValidatorRegexp, k.SortReGroups) 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 = ?, ordre = ?, label = ?, type = ?, placeholder = ?, help = ?, unit = ?, ignorecase = ?, notrim = ?, multiline = ?, validator_regexp = ?, sort_re_grps = ?, cksum = ?, choices_cost = ? 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.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 exercice_hints_okey_deps WHERE id_flag_dep = ?", 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 } } func (k *FlagKey) GetOrder() int8 { return k.Order } // 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 []Flag 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 int 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 int 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 nil, err } return GetExercice(eid) }