Refactor flags
authorPierre-Olivier Mercier <nemunaire@nemunai.re>
Wed, 2 Jan 2019 20:51:09 +0000 (21:51 +0100)
committerPierre-Olivier Mercier <nemunaire@nemunai.re>
Wed, 2 Jan 2019 23:58:36 +0000 (00:58 +0100)
Both QCM and Key are Flag

13 files changed:
admin/api/exercice.go
admin/api/handlers.go
admin/sync/exercice_keys.go
backend/choices.go
libfic/exercice.go
libfic/file.go
libfic/flag.go
libfic/flag_choice.go
libfic/flag_key.go [new file with mode: 0644]
libfic/mcq.go
libfic/mcq_justification.go
libfic/team.go
libfic/team_my.go

index 7bb43b0..2a1fb27 100644 (file)
@@ -33,13 +33,13 @@ func init() {
 
        router.GET("/api/exercices/:eid/flags", apiHandler(exerciceHandler(listExerciceFlags)))
        router.POST("/api/exercices/:eid/flags", apiHandler(exerciceHandler(createExerciceFlag)))
-       router.GET("/api/exercices/:eid/flags/:kid", apiHandler(flagHandler(showExerciceFlag)))
-       router.PUT("/api/exercices/:eid/flags/:kid", apiHandler(flagHandler(updateExerciceFlag)))
-       router.POST("/api/exercices/:eid/flags/:kid/try", apiHandler(flagHandler(tryExerciceFlag)))
-       router.DELETE("/api/exercices/:eid/flags/:kid", apiHandler(flagHandler(deleteExerciceFlag)))
-       router.GET("/api/exercices/:eid/flags/:kid/choices/", apiHandler(flagHandler(listFlagChoices)))
+       router.GET("/api/exercices/:eid/flags/:kid", apiHandler(flagKeyHandler(showExerciceFlag)))
+       router.PUT("/api/exercices/:eid/flags/:kid", apiHandler(flagKeyHandler(updateExerciceFlag)))
+       router.POST("/api/exercices/:eid/flags/:kid/try", apiHandler(flagKeyHandler(tryExerciceFlag)))
+       router.DELETE("/api/exercices/:eid/flags/:kid", apiHandler(flagKeyHandler(deleteExerciceFlag)))
+       router.GET("/api/exercices/:eid/flags/:kid/choices/", apiHandler(flagKeyHandler(listFlagChoices)))
        router.GET("/api/exercices/:eid/flags/:kid/choices/:cid", apiHandler(choiceHandler(showFlagChoice)))
-       router.POST("/api/exercices/:eid/flags/:kid/choices/", apiHandler(flagHandler(createFlagChoice)))
+       router.POST("/api/exercices/:eid/flags/:kid/choices/", apiHandler(flagKeyHandler(createFlagChoice)))
        router.PUT("/api/exercices/:eid/flags/:kid/choices/:cid", apiHandler(choiceHandler(updateFlagChoice)))
        router.DELETE("/api/exercices/:eid/flags/:kid/choices/:cid", apiHandler(choiceHandler(deleteFlagChoice)))
 
@@ -94,10 +94,10 @@ func listExerciceHints(exercice fic.Exercice, body []byte) (interface{}, error)
 }
 
 func listExerciceFlags(exercice fic.Exercice, body []byte) (interface{}, error) {
-       return exercice.GetFlags()
+       return exercice.GetFlagKeys()
 }
 
-func listFlagChoices(flag fic.Flag, _ fic.Exercice, body []byte) (interface{}, error) {
+func listFlagChoices(flag fic.FlagKey, _ fic.Exercice, body []byte) (interface{}, error) {
        return flag.GetChoices()
 }
 
@@ -294,14 +294,14 @@ func createExerciceFlag(exercice fic.Exercice, body []byte) (interface{}, error)
                vre = uk.ValidatorRe
        }
 
-       return exercice.AddRawFlag(uk.Label, uk.Help, uk.IgnoreCase, vre, []byte(uk.Flag), uk.ChoicesCost)
+       return exercice.AddRawFlagKey(uk.Label, uk.Help, uk.IgnoreCase, vre, []byte(uk.Flag), uk.ChoicesCost)
 }
 
-func showExerciceFlag(flag fic.Flag, _ fic.Exercice, body []byte) (interface{}, error) {
+func showExerciceFlag(flag fic.FlagKey, _ fic.Exercice, body []byte) (interface{}, error) {
        return flag, nil
 }
 
-func tryExerciceFlag(flag fic.Flag, _ fic.Exercice, body []byte) (interface{}, error) {
+func tryExerciceFlag(flag fic.FlagKey, _ fic.Exercice, body []byte) (interface{}, error) {
        var uk uploadedFlag
        if err := json.Unmarshal(body, &uk); err != nil {
                return nil, err
@@ -311,14 +311,14 @@ func tryExerciceFlag(flag fic.Flag, _ fic.Exercice, body []byte) (interface{}, e
                return nil, errors.New("Empty submission")
        }
 
-       if flag.Check([]byte(uk.Flag)) {
+       if flag.Check([]byte(uk.Flag)) == 0 {
                return true, nil
        } else {
                return nil, errors.New("Bad submission")
        }
 }
 
-func updateExerciceFlag(flag fic.Flag, exercice fic.Exercice, body []byte) (interface{}, error) {
+func updateExerciceFlag(flag fic.FlagKey, exercice fic.Exercice, body []byte) (interface{}, error) {
        var uk uploadedFlag
        if err := json.Unmarshal(body, &uk); err != nil {
                return nil, err
@@ -348,7 +348,7 @@ func updateExerciceFlag(flag fic.Flag, exercice fic.Exercice, body []byte) (inte
        return flag, nil
 }
 
-func deleteExerciceFlag(flag fic.Flag, _ fic.Exercice, _ []byte) (interface{}, error) {
+func deleteExerciceFlag(flag fic.FlagKey, _ fic.Exercice, _ []byte) (interface{}, error) {
        return flag.Delete()
 }
 
@@ -357,7 +357,7 @@ type uploadedChoice struct {
        Value       string
 }
 
-func createFlagChoice(flag fic.Flag, exercice fic.Exercice, body []byte) (interface{}, error) {
+func createFlagChoice(flag fic.FlagKey, exercice fic.Exercice, body []byte) (interface{}, error) {
        var uc uploadedChoice
        if err := json.Unmarshal(body, &uc); err != nil {
                return nil, err
index f0bbe8b..b04a686 100644 (file)
@@ -165,7 +165,7 @@ func hintHandler(f func(fic.EHint, []byte) (interface{}, error)) func(httprouter
        }
 }
 
-func flagHandler(f func(fic.Flag, fic.Exercice, []byte) (interface{}, error)) func(httprouter.Params, []byte) (interface{}, error) {
+func flagKeyHandler(f func(fic.FlagKey, fic.Exercice, []byte) (interface{}, error)) func(httprouter.Params, []byte) (interface{}, error) {
        return func(ps httprouter.Params, body []byte) (interface{}, error) {
                var exercice fic.Exercice
                exerciceHandler(func(ex fic.Exercice, _ []byte) (interface{}, error) {
@@ -175,7 +175,7 @@ func flagHandler(f func(fic.Flag, fic.Exercice, []byte) (interface{}, error)) fu
 
                if kid, err := strconv.ParseInt(string(ps.ByName("kid")), 10, 64); err != nil {
                        return nil, err
-               } else if flags, err := exercice.GetFlags(); err != nil {
+               } else if flags, err := exercice.GetFlagKeys(); err != nil {
                        return nil, err
                } else {
                        for _, flag := range flags {
@@ -191,8 +191,8 @@ func flagHandler(f func(fic.Flag, fic.Exercice, []byte) (interface{}, error)) fu
 func choiceHandler(f func(fic.FlagChoice, fic.Exercice, []byte) (interface{}, error)) func(httprouter.Params, []byte) (interface{}, error) {
        return func(ps httprouter.Params, body []byte) (interface{}, error) {
                var exercice fic.Exercice
-               var flag fic.Flag
-               flagHandler(func(fl fic.Flag, ex fic.Exercice, _ []byte) (interface{}, error) {
+               var flag fic.FlagKey
+               flagKeyHandler(func(fl fic.FlagKey, ex fic.Exercice, _ []byte) (interface{}, error) {
                        exercice = ex
                        flag = fl
                        return nil, nil
index b422933..71b9a3e 100644 (file)
@@ -108,7 +108,7 @@ func SyncExerciceFlags(i Importer, exercice fic.Exercice) (errs []string) {
                                errs = append(errs, fmt.Sprintf("%q: WARNING flag #%d: non-printable characters in flag, is this really expected?", path.Base(exercice.Path), nline + 1))
                        }
 
-                       if k, err := exercice.AddRawFlag(flag.Label, flag.Help, flag.IgnoreCase, validatorRegexp(flag.ValidatorRe), []byte(raw), 0); err != nil {
+                       if k, err := exercice.AddRawFlagKey(flag.Label, flag.Help, flag.IgnoreCase, validatorRegexp(flag.ValidatorRe), []byte(raw), 0); err != nil {
                                errs = append(errs, fmt.Sprintf("%q: error flag #%d: %s", path.Base(exercice.Path), nline + 1, err))
                                continue
                        } else {
@@ -150,7 +150,7 @@ func SyncExerciceFlags(i Importer, exercice fic.Exercice) (errs []string) {
                                errs = append(errs, fmt.Sprintf("%q: WARNING flag UCQ #%d: non-printable characters in flag, is this really expected?", path.Base(exercice.Path), nline + 1))
                        }
 
-                       if k, err := exercice.AddRawFlag(flag.Label, flag.Help, flag.IgnoreCase, validatorRegexp(flag.ValidatorRe), []byte(flag.Raw), flag.ChoicesCost); err != nil {
+                       if k, err := exercice.AddRawFlagKey(flag.Label, flag.Help, flag.IgnoreCase, validatorRegexp(flag.ValidatorRe), []byte(flag.Raw), flag.ChoicesCost); err != nil {
                                errs = append(errs, fmt.Sprintf("%q: error flag UCQ #%d: %s", path.Base(exercice.Path), nline + 1, err))
                                continue
                        } else {
@@ -261,7 +261,7 @@ func SyncExerciceFlags(i Importer, exercice fic.Exercice) (errs []string) {
                                                errs = append(errs, fmt.Sprintf("%q: error in MCQ %d choice %d: %s", path.Base(exercice.Path), nline + 1, cid, err))
                                                continue
                                        } else if len(justify) > 0 {
-                                               if _, err := exercice.AddRawFlag(fmt.Sprintf("%%%d%%%s", e.Id, choice.Help), "", false, nil, []byte(justify), 0); err != nil {
+                                               if _, err := exercice.AddRawFlagKey(fmt.Sprintf("%%%d%%%s", e.Id, choice.Help), "", false, nil, []byte(justify), 0); err != nil {
                                                        errs = append(errs, fmt.Sprintf("%q: error MCQ #%d: %s", path.Base(exercice.Path), nline + 1, err))
                                                        continue
                                                }
index 1db09ce..92ba075 100644 (file)
@@ -23,7 +23,7 @@ func treatWantChoices(pathname string, team fic.Team) {
        } else if ask.FlagId == 0 {
                log.Println("[WRN] Invalid content in wantChoices file: ", pathname)
                os.Remove(pathname)
-       } else if flag, err := fic.GetFlag(ask.FlagId); err != nil {
+       } else if flag, err := fic.GetFlagKey(ask.FlagId); err != nil {
                log.Println("[ERR]", err)
        } else if err := team.DisplayChoices(flag); err != nil {
                log.Println("[ERR]", err)
index 335a4e4..0a6fcb4 100644 (file)
@@ -362,7 +362,7 @@ func (e Exercice) TriedCount() int64 {
 func (e Exercice) CheckResponse(cksum []byte, respflags map[int64]string, respmcq map[int64]bool, t Team) (bool, error) {
        if err := e.NewTry(t, cksum); err != nil {
                return false, err
-       } else if flags, err := e.GetFlags(); err != nil {
+       } else if flags, err := e.GetFlagKeys(); err != nil {
                return false, err
        } else if mcqs, err := e.GetMCQ(); err != nil {
                return false, err
@@ -375,11 +375,11 @@ func (e Exercice) CheckResponse(cksum []byte, respflags map[int64]string, respmc
                // Check MCQs
                for _, mcq := range mcqs {
                        if d := mcq.Check(respmcq); d > 0 {
-                               if !PartialValidation || t.HasPartiallyRespond(mcq) == nil {
+                               if !PartialValidation || t.HasPartiallySolved(mcq) == nil {
                                        valid = false
                                        diff += d
                                }
-                       } else if !PartialMCQValidation {
+                       } else if !PartialMCQValidation && d == 0 {
                                mcq.FoundBy(t)
                        }
                }
@@ -395,7 +395,7 @@ func (e Exercice) CheckResponse(cksum []byte, respflags map[int64]string, respmc
                for _, flag := range flags {
                        if res, ok := respflags[flag.Id]; !ok && (!PartialValidation || t.HasPartiallySolved(flag) == nil) {
                                valid = false
-                       } else if !flag.Check([]byte(res)) {
+                       } else if flag.Check([]byte(res)) != 0 {
                                if !PartialValidation || t.HasPartiallySolved(flag) == nil {
                                        valid = false
                                }
index e9541b3..472b9e3 100644 (file)
@@ -272,8 +272,12 @@ func (f EFile) GetOrigin() string {
 }
 
 // AddDepend insert a new dependency to a given flag.
-func (f EFile) AddDepend(k Flag) (err error) {
-       _, err = DBExec("INSERT INTO exercice_files_deps (id_file, id_flag) VALUES (?, ?)", f.Id, k.Id)
+func (f EFile) AddDepend(j Flag) (err error) {
+       if k, ok := j.(FlagKey); ok {
+               _, err = DBExec("INSERT INTO exercice_files_deps (id_file, id_flag) VALUES (?, ?)", f.Id, k.Id)
+       } else {
+               err = errors.New("Dependancy type not implemented for this file.")
+       }
        return
 }
 
@@ -290,7 +294,7 @@ func (f EFile) GetDepends() ([]Flag, error) {
                        if err := rows.Scan(&d); err != nil {
                                return nil, err
                        }
-                       deps = append(deps, Flag{d, f.IdExercice, "", "", false, nil, []byte{}, 0})
+                       deps = append(deps, FlagKey{d, f.IdExercice, "", "", false, nil, []byte{}, 0})
                }
                if err := rows.Err(); err != nil {
                        return nil, err
index d5ad15d..14a675c 100644 (file)
 package fic
 
-import (
-       "bytes"
-       "errors"
-       "regexp"
-       "time"
+import ()
 
-       "golang.org/x/crypto/blake2b"
-)
-
-// Flag represents a flag's challenge, stored as hash.
-type Flag struct {
-       Id              int64   `json:"id"`
-       // IdExercice is the identifier of the underlying challenge
-       IdExercice      int64   `json:"idExercice"`
-       // Label is the title of the flag as displayed to players
-       Label           string  `json:"label"`
-       // Help is a small piece of text that aims to add useful information like flag format, ...
-       Help            string  `json:"help"`
-       // IgnoreCase indicates if the case is sensitive to case or not
-       IgnoreCase      bool    `json:"ignorecase"`
-       // ValidatorRegexp extracts a subset of the player's answer, that will be checked.
-       ValidatorRegexp *string `json:"validator_regexp"`
-       // 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"`
+type Flag interface {
+       Update() (int64, error)
+       Delete() (int64, error)
+       AddDepend(d Flag) (error)
+       GetDepends() ([]Flag, error)
+       Check(val interface{}) int
+       FoundBy(t Team)
 }
 
-// GetFlags returns a list of flags comming with the challenge.
+// GetFlag returns a list of flags comming with the challenge.
 func (e Exercice) GetFlags() ([]Flag, error) {
-       if rows, err := DBQuery("SELECT id_flag, id_exercice, type, help, ignorecase, validator_regexp, cksum, choices_cost FROM exercice_flags WHERE id_exercice = ?", e.Id); err != nil {
+       var flags = make([]Flag, 0)
+
+       if ks, err := e.GetFlagKeys(); err != nil {
                return nil, err
        } else {
-               defer rows.Close()
-
-               var flags = make([]Flag, 0)
-               for rows.Next() {
-                       var k Flag
-                       k.IdExercice = e.Id
-
-                       if err := rows.Scan(&k.Id, &k.IdExercice, &k.Label, &k.Help, &k.IgnoreCase, &k.ValidatorRegexp, &k.Checksum, &k.ChoicesCost); err != nil {
-                               return nil, err
-                       }
-
+               for _, k := range ks {
                        flags = append(flags, k)
                }
-               if err := rows.Err(); err != nil {
-                       return nil, err
-               }
-
-               return flags, nil
        }
-}
 
-// GetFlag returns a list of flags comming with the challenge.
-func GetFlag(id int64) (k Flag, err error) {
-       err = DBQueryRow("SELECT id_flag, id_exercice, type, help, ignorecase, validator_regexp, cksum, choices_cost FROM exercice_flags WHERE id_flag = ?", id).Scan(&k.Id, &k.IdExercice, &k.Label, &k.Help, &k.IgnoreCase, &k.ValidatorRegexp, &k.Checksum, &k.ChoicesCost)
-       return
-}
-
-// GetFlagByLabel returns a flag matching the given label.
-func (e Exercice) GetFlagByLabel(label string) (k Flag, err error) {
-       err = DBQueryRow("SELECT id_flag, id_exercice, type, help, ignorecase, 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.ValidatorRegexp, &k.Checksum, &k.ChoicesCost)
-       return
-}
-
-// getHashedFlag calculates the expected checksum for the given raw_value.
-func getHashedFlag(raw_value []byte) [blake2b.Size]byte {
-       hash := blake2b.Sum512(raw_value)
-       return hash
-}
-
-// AddRawFlag creates and fills a new struct Flag, from a non-hashed flag, and registers it into the database.
-func (e Exercice) AddRawFlag(name string, help string, ignorecase bool, validator_regexp *string, raw_value []byte, choicescost int64) (Flag, error) {
-       if ignorecase {
-               raw_value = bytes.ToLower(raw_value)
-       }
-
-       // Check that raw value passes through the regexp
-       if validator_regexp != nil {
-               if re, err := regexp.Compile(*validator_regexp); err != nil {
-                       return Flag{}, err
-               } else if res := re.FindSubmatch(raw_value); res == nil {
-                       return Flag{}, errors.New("Expected flag doesn't pass through the validator_regexp")
-               } else {
-                       raw_value = bytes.Join(res[1:], []byte("+"))
-               }
-       }
-
-       hash := getHashedFlag(raw_value)
-       return e.AddFlag(name, help, ignorecase, validator_regexp, hash[:], choicescost)
-}
-
-// AddFlag creates and fills a new struct Flag, from a hashed flag, and registers it into the database.
-func (e Exercice) AddFlag(name string, help string, ignorecase bool, validator_regexp *string, checksum []byte, choicescost int64) (Flag, error) {
-       // Check the regexp compile
-       if validator_regexp != nil {
-               if _, err := regexp.Compile(*validator_regexp); err != nil {
-                       return Flag{}, err
-               }
-       }
-
-       if res, err := DBExec("INSERT INTO exercice_flags (id_exercice, type, help, ignorecase, validator_regexp, cksum, choices_cost) VALUES (?, ?, ?, ?, ?, ?, ?)", e.Id, name, help, ignorecase, validator_regexp, checksum, choicescost); err != nil {
-               return Flag{}, err
-       } else if kid, err := res.LastInsertId(); err != nil {
-               return Flag{}, err
+       if ms, err := e.GetMCQ(); err != nil {
+               return nil, err
        } else {
-               return Flag{kid, e.Id, name, help, ignorecase, validator_regexp, checksum, choicescost}, nil
-       }
-}
-
-// Update applies modifications back to the database.
-func (k Flag) Update() (int64, error) {
-       if k.ValidatorRegexp != nil {
-               if _, err := regexp.Compile(*k.ValidatorRegexp); err != nil {
-                       return 0, err
+               for _, m := range ms {
+                       flags = append(flags, m)
                }
        }
 
-       if res, err := DBExec("UPDATE exercice_flags SET id_exercice = ?, type = ?, help = ?, ignorecase = ?, validator_regexp = ?, cksum = ?, choices_cost = ? WHERE id_flag = ?", k.IdExercice, k.Label, k.Help, k.IgnoreCase, k.ValidatorRegexp, 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 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 {
-               return 0, err
-       } else if nb, err := res.RowsAffected(); err != nil {
-               return 0, err
-       } else {
-               return nb, err
-       }
+       return flags, nil
 }
 
 // WipeFlags deletes flags coming with the challenge.
@@ -163,70 +52,3 @@ func (e Exercice) WipeFlags() (int64, error) {
                return nb, err
        }
 }
-
-// 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{}, 0})
-               }
-               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 {
-               val = bytes.ToLower(val)
-       }
-
-       // Check that raw value passes through the regexp
-       if k.ValidatorRegexp != nil {
-               re := regexp.MustCompile(*k.ValidatorRegexp)
-               if res := re.FindSubmatch(val); res != nil {
-                       val = bytes.Join(res[1:], []byte("+"))
-               }
-       }
-
-       // Check that the value is not empty
-       if len(val) == 0 {
-               return false
-       }
-
-       hash := getHashedFlag(val)
-       if len(k.Checksum) != len(hash) {
-               return false
-       }
-
-       for i := range hash {
-               if k.Checksum[i] != hash[i] {
-                       return false
-               }
-       }
-
-       return true
-}
-
-// FoundBy registers in the database that the given Team solved the flag.
-func (k Flag) FoundBy(t Team) {
-       DBExec("INSERT INTO flag_found (id_flag, id_team, time) VALUES (?, ?, ?)", k.Id, t.Id, time.Now())
-}
index ef58cff..ce8c80d 100644 (file)
@@ -14,7 +14,7 @@ type FlagChoice struct {
 }
 
 // GetChoices returns a list of choices for the given Flag.
-func (f Flag) GetChoices() ([]FlagChoice, error) {
+func (f FlagKey) GetChoices() ([]FlagChoice, error) {
        if rows, err := DBQuery("SELECT id_choice, id_flag, label, response FROM flag_choices WHERE id_flag = ?", f.Id); err != nil {
                return nil, err
        } else {
@@ -40,7 +40,7 @@ func (f Flag) GetChoices() ([]FlagChoice, error) {
 }
 
 // GetChoice returns a choice for the given Flag.
-func (f Flag) GetChoice(id int64) (c FlagChoice, err error) {
+func (f FlagKey) GetChoice(id int64) (c FlagChoice, err error) {
        if errr := DBQueryRow("SELECT id_choice, id_flag, label, response FROM flag_choices WHERE id_choice = ?", id).Scan(&c.Id, &c.IdFlag, &c.Label, &c.Value); errr != nil {
                return c, errr
        }
@@ -48,7 +48,7 @@ func (f Flag) GetChoice(id int64) (c FlagChoice, err error) {
 }
 
 // AddChoice creates and fills a new struct FlagChoice, from a label and a value.
-func (f Flag) AddChoice(label string, value string) (FlagChoice, error) {
+func (f FlagKey) AddChoice(label string, value string) (FlagChoice, error) {
        if res, err := DBExec("INSERT INTO flag_choices (id_flag, label, response) VALUES (?, ?, ?)", f.Id, label, value); err != nil {
                return FlagChoice{}, err
        } else if cid, err := res.LastInsertId(); err != nil {
@@ -81,7 +81,7 @@ func (c FlagChoice) Delete() (int64, error) {
 }
 
 // WipeFlags deletes flags coming with the challenge.
-func (f Flag) WipeChoices() (int64, error) {
+func (f FlagKey) WipeChoices() (int64, error) {
        if res, err := DBExec("DELETE FROM flag_choices WHERE id_flag = ?", f.Id); err != nil {
                return 0, err
        } else if nb, err := res.RowsAffected(); err != nil {
diff --git a/libfic/flag_key.go b/libfic/flag_key.go
new file mode 100644 (file)
index 0000000..739abe1
--- /dev/null
@@ -0,0 +1,225 @@
+package fic
+
+import (
+       "bytes"
+       "errors"
+       "regexp"
+       "time"
+
+       "golang.org/x/crypto/blake2b"
+)
+
+// FlagKey represents a flag's challenge, stored as hash.
+type FlagKey struct {
+       Id              int64   `json:"id"`
+       // IdExercice is the identifier of the underlying challenge
+       IdExercice      int64   `json:"idExercice"`
+       // Label is the title of the flag as displayed to players
+       Label           string  `json:"label"`
+       // Help is a small piece of text that aims to add useful information like flag format, ...
+       Help            string  `json:"help"`
+       // IgnoreCase indicates if the case is sensitive to case or not
+       IgnoreCase      bool    `json:"ignorecase"`
+       // ValidatorRegexp extracts a subset of the player's answer, that will be checked.
+       ValidatorRegexp *string `json:"validator_regexp"`
+       // 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, type, help, ignorecase, validator_regexp, cksum, choices_cost FROM exercice_flags WHERE id_exercice = ?", e.Id); err != nil {
+               return nil, err
+       } else {
+               defer rows.Close()
+
+               var flags = make([]FlagKey, 0)
+               for rows.Next() {
+                       var k FlagKey
+                       k.IdExercice = e.Id
+
+                       if err := rows.Scan(&k.Id, &k.IdExercice, &k.Label, &k.Help, &k.IgnoreCase, &k.ValidatorRegexp, &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 int64) (k FlagKey, err error) {
+       err = DBQueryRow("SELECT id_flag, id_exercice, type, help, ignorecase, validator_regexp, cksum, choices_cost FROM exercice_flags WHERE id_flag = ?", id).Scan(&k.Id, &k.IdExercice, &k.Label, &k.Help, &k.IgnoreCase, &k.ValidatorRegexp, &k.Checksum, &k.ChoicesCost)
+       return
+}
+
+// GetFlagKeyByLabel returns a flag matching the given label.
+func (e Exercice) GetFlagKeyByLabel(label string) (k FlagKey, err error) {
+       err = DBQueryRow("SELECT id_flag, id_exercice, type, help, ignorecase, 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.ValidatorRegexp, &k.Checksum, &k.ChoicesCost)
+       return
+}
+
+// getHashedFlag calculates the expected checksum for the given raw_value.
+func getHashedFlag(raw_value []byte) [blake2b.Size]byte {
+       hash := blake2b.Sum512(raw_value)
+       return hash
+}
+
+// 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, help string, ignorecase bool, validator_regexp *string, raw_value []byte, choicescost int64) (FlagKey, error) {
+       if ignorecase {
+               raw_value = bytes.ToLower(raw_value)
+       }
+
+       // Check that raw value passes through the regexp
+       if validator_regexp != nil {
+               if re, err := regexp.Compile(*validator_regexp); err != nil {
+                       return FlagKey{}, err
+               } else if res := re.FindSubmatch(raw_value); res == nil {
+                       return FlagKey{}, errors.New("Expected flag doesn't pass through the validator_regexp")
+               } else {
+                       raw_value = bytes.Join(res[1:], []byte("+"))
+               }
+       }
+
+       hash := getHashedFlag(raw_value)
+       return e.AddFlagKey(name, help, ignorecase, validator_regexp, hash[:], choicescost)
+}
+
+// AddFlagKey creates and fills a new struct Flag, from a hashed flag, and registers it into the database.
+func (e Exercice) AddFlagKey(name string, help string, ignorecase bool, validator_regexp *string, checksum []byte, choicescost int64) (FlagKey, error) {
+       // Check the regexp compile
+       if validator_regexp != nil {
+               if _, err := regexp.Compile(*validator_regexp); err != nil {
+                       return FlagKey{}, err
+               }
+       }
+
+       if res, err := DBExec("INSERT INTO exercice_flags (id_exercice, type, help, ignorecase, validator_regexp, cksum, choices_cost) VALUES (?, ?, ?, ?, ?, ?, ?)", e.Id, name, help, ignorecase, validator_regexp, checksum, choicescost); err != nil {
+               return FlagKey{}, err
+       } else if kid, err := res.LastInsertId(); err != nil {
+               return FlagKey{}, err
+       } else {
+               return FlagKey{kid, e.Id, name, help, ignorecase, validator_regexp, checksum, choicescost}, nil
+       }
+}
+
+// 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 = ?, type = ?, help = ?, ignorecase = ?, validator_regexp = ?, cksum = ?, choices_cost = ? WHERE id_flag = ?", k.IdExercice, k.Label, k.Help, k.IgnoreCase, k.ValidatorRegexp, 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_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 {
+               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)
+       } else {
+               err = errors.New("Dependancy type not implemented for this flag.")
+       }
+       return
+}
+
+// GetDepends retrieve the flag's dependency list.
+func (k FlagKey) 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, FlagKey{d, k.IdExercice, "", "", false, nil, []byte{}, 0})
+               }
+               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
+       }
+
+       if k.IgnoreCase {
+               val = bytes.ToLower(val)
+       }
+
+       // Check that raw value passes through the regexp
+       if k.ValidatorRegexp != nil {
+               re := regexp.MustCompile(*k.ValidatorRegexp)
+               if res := re.FindSubmatch(val); res != nil {
+                       val = bytes.Join(res[1:], []byte("+"))
+               }
+       }
+
+       // Check that the value is not empty
+       if len(val) == 0 {
+               return 1
+       }
+
+       hash := getHashedFlag(val)
+       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())
+}
index 91c322d..4f1968c 100644 (file)
@@ -1,6 +1,7 @@
 package fic
 
 import (
+       "errors"
        "fmt"
        "time"
 )
@@ -175,13 +176,30 @@ func (e Exercice) WipeMCQs() (int64, error) {
        }
 }
 
+// AddDepend insert a new dependency to a given flag.
+func (m MCQ) AddDepend(j Flag) (err error) {
+       return errors.New("Dependancy type not implemented for this flag.")
+}
+
+// GetDepends retrieve the flag's dependency list.
+func (m MCQ) GetDepends() ([]Flag, error) {
+       return nil, errors.New("Dependancy not implemented for MCQs.")
+}
+
 // GetJustifiedFlag searchs for a flag in the scope of the given exercice.
-func (c MCQ_entry) GetJustifiedFlag(e Exercice) (Flag, error) {
-       return e.GetFlagByLabel(fmt.Sprintf("\\%%%d\\%%%%", c.Id))
+func (c MCQ_entry) GetJustifiedFlag(e Exercice) (FlagKey, error) {
+       return e.GetFlagKeyByLabel(fmt.Sprintf("\\%%%d\\%%%%", c.Id))
 }
 
 // Check if the given vals are the expected ones to validate this flag.
-func (m MCQ) Check(vals map[int64]bool) int {
+func (m MCQ) Check(v interface{}) int {
+       var vals map[int64]bool
+       if va, ok := v.(map[int64]bool); !ok {
+               return -1
+       } else {
+               vals = va
+       }
+
        diff := 0
 
        for _, n := range m.Entries {
index 4d19739..36ff1ec 100644 (file)
@@ -14,12 +14,12 @@ type FlagLabel struct {
 }
 
 // IsMCQJustification tells you if this key represent a justification from a MCQ.
-func (k Flag) IsMCQJustification() (bool) {
+func (k FlagKey) IsMCQJustification() (bool) {
        return len(k.Label) > 0 && k.Label[0] == '%'
 }
 
 // GetMCQJustification returns the structure corresponding to the given flag.
-func (k Flag) GetMCQJustification() (fl FlagLabel, err error) {
+func (k FlagKey) GetMCQJustification() (fl FlagLabel, err error) {
        spl := strings.Split(k.Label, "%")
        if len(spl) >= 3 && len(spl[0]) == 0 {
                fl.IdChoice, err = strconv.ParseInt(spl[1], 10, 64)
index b455bc9..d861144 100644 (file)
@@ -179,14 +179,14 @@ func (t Team) OpenHint(h EHint) (error) {
 }
 
 // SeeChoices checks if the Team has revealed the given choices.
-func (t Team) SeeChoices(k Flag) (bool) {
+func (t Team) SeeChoices(k FlagKey) (bool) {
        var tm *time.Time
        DBQueryRow("SELECT MIN(time) FROM team_wchoices WHERE id_team = ? AND id_flag = ?", t.Id, k.Id).Scan(&tm)
        return tm != nil
 }
 
 // DisplayChoices registers to the database that the Team has now revealed.
-func (t Team) DisplayChoices(k Flag) (error) {
+func (t Team) DisplayChoices(k FlagKey) (error) {
        _, err := DBExec("INSERT INTO team_wchoices (id_team, id_flag, time) VALUES (?, ?, ?)", t.Id, k.Id, time.Now())
        return err
 }
@@ -248,12 +248,12 @@ func (t Team) GetSolvedRank(e Exercice) (nb int64, err error) {
 
 // HasPartiallySolved checks if the Team already has unlocked the given flag and returns the validation's timestamp.
 func (t Team) HasPartiallySolved(f Flag) (tm *time.Time) {
-       DBQueryRow("SELECT MIN(time) FROM flag_found WHERE id_team = ? AND id_flag = ?", t.Id, f.Id).Scan(&tm)
-       return
-}
-
-// HasPartiallyRespond checks if the Team already has unlocked the given MCQ  and returns the validation's timestamp.
-func (t Team) HasPartiallyRespond(m MCQ) (tm *time.Time) {
-       DBQueryRow("SELECT MIN(time) FROM mcq_found WHERE id_team = ? AND id_mcq = ?", t.Id, m.Id).Scan(&tm)
+       if k, ok := f.(FlagKey); ok {
+               DBQueryRow("SELECT MIN(time) FROM flag_found WHERE id_team = ? AND id_flag = ?", t.Id, k.Id).Scan(&tm)
+       } else if m, ok := f.(MCQ); ok {
+               DBQueryRow("SELECT MIN(time) FROM mcq_found WHERE id_team = ? AND id_mcq = ?", t.Id, m.Id).Scan(&tm)
+       } else {
+               log.Fatal("Unknown flag type")
+       }
        return
 }
index f13c351..acb2a7b 100644 (file)
@@ -174,7 +174,7 @@ func MyJSONTeam(t *Team, started bool) (interface{}, error) {
                                justifiedMCQ := map[int64]FlagLabel{}
                                exercice.Flags = map[int64]myTeamFlag{}
 
-                               if flags, err := e.GetFlags(); err != nil {
+                               if flags, err := e.GetFlagKeys(); err != nil {
                                        return nil, err
                                } else {
                                        for _, k := range flags {
@@ -240,7 +240,7 @@ func MyJSONTeam(t *Team, started bool) (interface{}, error) {
                                                soluce := ""
                                                fullySolved := true
                                                if t != nil {
-                                                       m.PSolved = t.HasPartiallyRespond(mcq)
+                                                       m.PSolved = t.HasPartiallySolved(mcq)
                                                }
 
                                                for _, e := range mcq.Entries {