Dependancy between flags

This commit is contained in:
nemunaire 2018-12-02 19:21:07 +01:00
parent 711db60a4c
commit f9abdd23c6
8 changed files with 111 additions and 1 deletions

View File

@ -18,6 +18,7 @@ Tous les textes doivent utiliser l'encodage UTF8.
* `id = CHID` : identifiant du challenge ;
* `theme = "NomDuTheme"` : (facultatif) nom du thème dans lequel aller chercher l'identifiant (par défaut, on prend le thème courant) ;
- `[[flag]]` : drapeau classique à valider pour résoudre le challenge :
* `id = 42` : (facultatif) identifiant du flag au sein de l'exercice, pour définir des dépendances ;
* `label = "Intitulé"` : (facultatif, par défaut : `Flag`) intitulé du drapeau ;
* `raw = 'MieH2athxuPhai6u'` : drapeau exact à trouver ;
* `validator_regexp = "^(?:sudo +)?(.*)$"` : (facultatif) expression rationnelle dont les groupes capturés serviront comme chaîne à valider (notez que `?:` au début d'un groupe ne le capturera pas) ;
@ -25,6 +26,8 @@ Tous les textes doivent utiliser l'encodage UTF8.
* `help = "Indication"` : (facultatif) chaîne de caractères placée sous le champ du formulaire, idéale pour donner une indication de format ;
* `[[flag.unlock_file]]` : bloque l'accès à un fichier tant que le flag n'est pas obtenu :
+ `filename = "toto.txt"` : nom du fichier tel qu'il apparaît dans le dossier `files` ;
* `[[flag.need_flag]]` : liste des flags devant être validés avant de débloquer celui-ci :
+ `id = 23` : identifiant du flag tel qu'il a été défini plus tôt dans le fichier ;
- `[[flag_mcq]]` : drapeau sous forme de question à choix multiple (cases à cocher) :
* `label = "Intitulé du groupe"` : (facultatif) intitulé du groupe de choix ;
* `[[flag_mcq.choice]]` : représente un choix, répétez autant de fois qu'il y a de choix :
@ -32,6 +35,7 @@ Tous les textes doivent utiliser l'encodage UTF8.
+ `value = true` : (facultatif, par défaut `false`) valeur attendue pour ce choix ; pour un QCM justifié, utilisez une chaîne de caractères (notez qu'il n'est pas possible de combiner des réponses vraies justifiées et justifiées),
+ `help = "Flag correspondant"` : (facultatif) indication affichée dans le champ de texte des QCM justifiés ;
- `[[flag_ucq]]` : drapeau sous forme de question à choix unique :
* `id = 42` : (facultatif) identifiant du flag au sein de l'exercice, pour définir des dépendances ;
* `label = "Intitulé du groupe"` : (facultatif) intitulé du groupe de choix ;
* `raw = 'MieH2athxuPhai6u'` : drapeau attendu parmi les propositions ;
* `validator_regexp = "^(?:sudo +)?(.*)$"` : (facultatif) expression rationnelle dont les groupes capturés serviront comme chaîne à valider (notez que `?:` au début d'un groupe ne le capturera pas) ;
@ -43,6 +47,8 @@ Tous les textes doivent utiliser l'encodage UTF8.
+ `label = "Intitulé de la réponse"` : (facultatif, par défaut identique à `value`) ;
* `[[flag_ucq.unlock_file]]` : bloque l'accès à un fichier tant que le flag n'est pas obtenu :
+ `filename = "toto.txt"` : nom du fichier tel qu'il apparaît dans le dossier `files` ;
* `[[flag_ucq.need_flag]]` : liste des flags devant être validés avant de débloquer celui-ci :
+ `id = 23` : identifiant du flag tel qu'il a été défini plus tôt dans le fichier ;
- `[[hint]]` : paramètres pour un indice :
* `filename = "toto.txt"` : (mutuellement exclusif avec `content`) nom du fichier tel qu'il apparaît dans le dossier `hints` ;
* `content = "Contenu de l'indice"` : (mutuellement exclusif avec `filename`) contenu de l'indice affiché, en markdown ;

View File

@ -27,12 +27,14 @@ type ExerciceUnlockFile struct {
// ExerciceFlag holds informations about a "classic" flag.
type ExerciceFlag struct {
Id int64
Label string `toml:",omitempty"`
Raw string
IgnoreCase bool `toml:",omitempty"`
ValidatorRe string `toml:"validator_regexp,omitempty"`
Help string `toml:",omitempty"`
LockedFile []ExerciceUnlockFile `toml:"unlock_file,omitempty"`
NeedFlag []ExerciceDependency `toml:"need_flag,omitempty"`
}
// ExerciceFlagMCQChoice holds a choice for an MCQ flag.
@ -57,6 +59,7 @@ type ExerciceFlagUCQChoice struct {
// ExerciceFlagUCQ holds information about a UCQ flag.
type ExerciceFlagUCQ struct {
Id int64
Label string `toml:",omitempty"`
Raw string
IgnoreCase bool `toml:",omitempty"`
@ -66,6 +69,7 @@ type ExerciceFlagUCQ struct {
Choices_Cost int64 `toml:",omitempty"`
Choice []ExerciceFlagUCQChoice
LockedFile []ExerciceUnlockFile `toml:"unlock_file,omitempty"`
NeedFlag []ExerciceDependency `toml:"need_flag,omitempty"`
NoShuffle bool
}

View File

@ -40,6 +40,8 @@ func SyncExerciceFlags(i Importer, exercice fic.Exercice) (errs []string) {
} else if len(params.Flags) == 0 && len(params.FlagsUCQ) == 0 && len(params.FlagsMCQ) == 0 {
errs = append(errs, fmt.Sprintf("%q: has no flag", path.Base(exercice.Path)))
} else {
kmap := map[int64]fic.Flag{}
// Import normal flags
for nline, flag := range params.Flags {
if len(flag.Label) == 0 {
@ -54,6 +56,21 @@ func SyncExerciceFlags(i Importer, exercice fic.Exercice) (errs []string) {
errs = append(errs, fmt.Sprintf("%q: error flag #%d: %s", path.Base(exercice.Path), nline + 1, err))
continue
} else {
if flag.Id != 0 {
kmap[flag.Id] = k
}
// Import dependency to flag
for _, nf := range flag.NeedFlag {
if rf, ok := kmap[nf.Id]; !ok {
errs = append(errs, fmt.Sprintf("%q: error flag #%d dependency to flag id=%s: id not defined, perhaps not available at time of processing", path.Base(exercice.Path), nline + 1, nf.Id))
continue
} else if err := k.AddDepend(rf); err != nil {
errs = append(errs, fmt.Sprintf("%q: error flag #%d dependency to %s: %s", path.Base(exercice.Path), nline + 1, nf.Id, err))
continue
}
}
// Import dependency to file
for _, lf := range flag.LockedFile {
if rf, err := exercice.GetFileByFilename(lf.Filename); err != nil {
@ -81,6 +98,22 @@ func SyncExerciceFlags(i Importer, exercice fic.Exercice) (errs []string) {
errs = append(errs, fmt.Sprintf("%q: error flag UCQ #%d: %s", path.Base(exercice.Path), nline + 1, err))
continue
} else {
// Import dependency to file
if flag.Id != 0 {
kmap[flag.Id] = k
}
// Import dependency to flag
for _, nf := range flag.NeedFlag {
if rf, ok := kmap[nf.Id]; !ok {
errs = append(errs, fmt.Sprintf("%q: error flag #%d dependency to flag id=%s: id not defined, perhaps not available at time of processing", path.Base(exercice.Path), nline + 1, nf.Id))
continue
} else if err := k.AddDepend(rf); err != nil {
errs = append(errs, fmt.Sprintf("%q: error flag #%d dependency to %s: %s", path.Base(exercice.Path), nline + 1, nf.Id, err))
continue
}
}
// Import dependency to file
for _, lf := range flag.LockedFile {
if rf, err := exercice.GetFileByFilename(lf.Filename); err != nil {

View File

@ -172,6 +172,16 @@ CREATE TABLE IF NOT EXISTS exercice_flags(
cksum BINARY(64) NOT NULL,
FOREIGN KEY(id_exercice) REFERENCES exercices(id_exercice)
) DEFAULT CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci;
`); err != nil {
return err
}
if _, err := db.Exec(`
CREATE TABLE IF NOT EXISTS exercice_flags_deps(
id_flag INTEGER NOT NULL,
id_flag_dep INTEGER NOT NULL,
FOREIGN KEY(id_flag) REFERENCES exercice_flags(id_flag),
FOREIGN KEY(id_flag_dep) REFERENCES exercice_flags(id_flag)
) DEFAULT CHARACTER SET = utf8 COLLATE = utf8_bin;
`); err != nil {
return err
}

View File

@ -124,6 +124,8 @@ func (k Flag) Update() (int64, error) {
func (k Flag) Delete() (int64, error) {
if _, err := DBExec("DELETE FROM exercice_files_deps WHERE id_flag = ?", k.Id); err != nil {
return 0, err
} else if _, err := DBExec("DELETE FROM exercice_flags_deps WHERE id_flag = ?", 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 {
@ -139,6 +141,8 @@ func (k Flag) Delete() (int64, error) {
func (e Exercice) WipeFlags() (int64, error) {
if _, err := DBExec("DELETE FROM exercice_files_deps WHERE id_flag IN (SELECT id_flag FROM exercice_flags WHERE id_exercice = ?)", e.Id); err != nil {
return 0, err
} else if _, err := DBExec("DELETE FROM exercice_flags_deps WHERE id_flag IN (SELECT id_flag FROM exercice_flags WHERE id_exercice = ?)", e.Id); err != nil {
return 0, err
} else if _, err := DBExec("DELETE FROM flag_choices WHERE id_flag IN (SELECT id_flag FROM exercice_flags WHERE id_exercice = ?)", e.Id); err != nil {
return 0, err
} else if res, err := DBExec("DELETE FROM exercice_flags WHERE id_exercice = ?", e.Id); err != nil {
@ -150,6 +154,35 @@ func (e Exercice) WipeFlags() (int64, error) {
}
}
// AddDepend insert a new dependency to a given flag.
func (k Flag) AddDepend(d Flag) (err error) {
_, err = DBExec("INSERT INTO exercice_flags_deps (id_flag, id_flag_dep) VALUES (?, ?)", k.Id, d.Id)
return
}
// GetDepends retrieve the flag's dependency list.
func (k Flag) GetDepends() ([]Flag, error) {
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()
var deps = make([]Flag, 0)
for rows.Next() {
var d int64
if err := rows.Scan(&d); err != nil {
return nil, err
}
deps = append(deps, Flag{d, k.IdExercice, "", "", false, nil, []byte{}})
}
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 Flag) Check(val []byte) bool {
if k.IgnoreCase {

View File

@ -34,7 +34,7 @@ func ResetGame() (error) {
// ResetExercices wipes out all challenges (both attempts and statements).
func ResetExercices() (error) {
return truncateTable("team_hints", "exercice_files_deps", "exercice_files", "flag_found", "flag_choices", "exercice_flags", "exercice_solved", "exercice_tries", "exercice_hints", "mcq_found", "mcq_entries", "exercice_mcq", "exercice_tags", "exercices", "themes")
return truncateTable("team_hints", "exercice_files_deps", "exercice_files", "flag_found", "exercice_flags_deps", "flag_choices", "exercice_flags", "exercice_solved", "exercice_tries", "exercice_hints", "mcq_found", "mcq_entries", "exercice_mcq", "exercice_tags", "exercices", "themes")
}
// ResetTeams wipes out all teams, incluings members and attempts.

View File

@ -124,6 +124,25 @@ func (t Team) CanDownload(f EFile) bool {
}
}
// CanSeeFlag checks if the Team has access to the given flag.
func (t Team) CanSeeFlag(k Flag) bool {
if deps, err := k.GetDepends(); err != nil {
log.Printf("Unable to retrieve flag dependencies: %s\n", err)
return false
} else {
res := true
for _, dep := range deps {
if t.HasPartiallySolved(dep) == nil {
res = false
break
}
}
return res
}
}
// NbTry retrieves the number of attempts made by the Team to the given challenge.
func NbTry(t *Team, e Exercice) int {
var cnt *int

View File

@ -175,6 +175,11 @@ func MyJSONTeam(t *Team, started bool) (interface{}, error) {
for _, k := range flags {
var flag myTeamFlag
if t != nil && !t.CanSeeFlag(k) {
// Dependancy missing, skip the flag for now
continue
}
if fl, err := k.GetMCQJustification(); err == nil {
fl.Checksum = k.Checksum
if t != nil && t.HasPartiallySolved(k) != nil {