Dependancy between flags
This commit is contained in:
parent
711db60a4c
commit
f9abdd23c6
|
@ -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 ;
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
10
libfic/db.go
10
libfic/db.go
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 {
|
||||
|
|
Loading…
Reference in New Issue