diff --git a/admin/sync/exercice_defines.go b/admin/sync/exercice_defines.go index 01f82fc3..33ce3949 100644 --- a/admin/sync/exercice_defines.go +++ b/admin/sync/exercice_defines.go @@ -20,18 +20,18 @@ type ExerciceDependency struct { Theme string `toml:",omitempty"` } +// ExerciceUnlockFile holds parameters related to a locked file. +type ExerciceUnlockFile struct { + Filename string `toml:",omitempty"` +} + // ExerciceFlag holds informations about a "classic" flag. type ExerciceFlag struct { Label string `toml:",omitempty"` Raw string - IgnoreCase bool `toml:",omitempty"` - Help string `toml:",omitempty"` - LockedFile []ExerciceFlag `toml:unlock_file",omitempty"` -} - -// ExerciceUnlockFile holds parameters related to a locked file. -type ExerciceFlag struct { - Filename string `toml:",omitempty"` + IgnoreCase bool `toml:",omitempty"` + Help string `toml:",omitempty"` + LockedFile []ExerciceUnlockFile `toml:"unlock_file,omitempty"` } // ExerciceFlagMCQChoice holds a choice for an MCQ flag. @@ -60,7 +60,7 @@ type ExerciceFlagUCQ struct { DisplayAs string `toml:",omitempty"` Choices_Cost int64 `toml:",omitempty"` Choice []ExerciceFlagUCQChoice - LockedFile []ExerciceFlag `toml:unlock_file",omitempty"` + LockedFile []ExerciceUnlockFile `toml:"unlock_file,omitempty"` } // ExerciceParams contains values parsed from defines.txt. diff --git a/admin/sync/exercice_keys.go b/admin/sync/exercice_keys.go index 8ccadf9d..56f644a7 100644 --- a/admin/sync/exercice_keys.go +++ b/admin/sync/exercice_keys.go @@ -40,9 +40,20 @@ func SyncExerciceKeys(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 _, err := exercice.AddRawKey(flag.Label, flag.Raw); err != nil { + if k, err := exercice.AddRawKey(flag.Label, flag.Raw); err != nil { errs = append(errs, fmt.Sprintf("%q: error flag #%d: %s", path.Base(exercice.Path), nline + 1, err)) continue + } else { + // Import dependency to file + for _, lf := range flag.LockedFile { + if rf, err := exercice.GetFileByFilename(lf.Filename); err != nil { + errs = append(errs, fmt.Sprintf("%q: error flag #%d dependency to %s: %s", path.Base(exercice.Path), nline + 1, lf.Filename, err)) + continue + } else if err := rf.AddDepend(k); err != nil { + errs = append(errs, fmt.Sprintf("%q: error flag #%d dependency to %s: %s", path.Base(exercice.Path), nline + 1, lf.Filename, err)) + continue + } + } } } @@ -56,9 +67,20 @@ func SyncExerciceKeys(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 _, err := exercice.AddRawKey(flag.Label, flag.Raw); err != nil { + if k, err := exercice.AddRawKey(flag.Label, flag.Raw); err != nil { errs = append(errs, fmt.Sprintf("%q: error flag UCQ #%d: %s", path.Base(exercice.Path), nline + 1, err)) continue + } else { + // Import dependency to file + for _, lf := range flag.LockedFile { + if rf, err := exercice.GetFileByFilename(lf.Filename); err != nil { + errs = append(errs, fmt.Sprintf("%q: error flag UCQ #%d dependency to %s: %s", path.Base(exercice.Path), nline + 1, lf.Filename, err)) + continue + } else if err := rf.AddDepend(k); err != nil { + errs = append(errs, fmt.Sprintf("%q: error flag UCQ #%d dependency to %s: %s", path.Base(exercice.Path), nline + 1, lf.Filename, err)) + continue + } + } } } diff --git a/admin/sync/full.go b/admin/sync/full.go index 0b5fb6ae..9d4c3128 100644 --- a/admin/sync/full.go +++ b/admin/sync/full.go @@ -34,9 +34,9 @@ func SyncDeep(i Importer) (errs map[string][]string) { if exercices, err := theme.GetExercices(); err == nil { for _, exercice := range exercices { + errs[theme.Name] = append(errs[theme.Name], SyncExerciceKeys(i, exercice)...) errs[theme.Name] = append(errs[theme.Name], SyncExerciceFiles(i, exercice)...) errs[theme.Name] = append(errs[theme.Name], SyncExerciceHints(i, exercice)...) - errs[theme.Name] = append(errs[theme.Name], SyncExerciceKeys(i, exercice)...) } } } diff --git a/libfic/db.go b/libfic/db.go index 98eb202b..caebf16a 100644 --- a/libfic/db.go +++ b/libfic/db.go @@ -163,6 +163,16 @@ CREATE TABLE IF NOT EXISTS exercice_keys( 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_files_deps( + id_file INTEGER NOT NULL, + id_key INTEGER NOT NULL, + FOREIGN KEY(id_file) REFERENCES exercice_files(id_file), + FOREIGN KEY(id_key) REFERENCES exercice_keys(id_key) +) DEFAULT CHARACTER SET = utf8 COLLATE = utf8_bin; `); err != nil { return err } diff --git a/libfic/file.go b/libfic/file.go index 35d6a983..a122fcb6 100644 --- a/libfic/file.go +++ b/libfic/file.go @@ -84,6 +84,15 @@ func GetFileByPath(path string) (EFile, error) { return f, nil } +// GetFileByFilename retrieves the file that should be called so. +func (e Exercice) GetFileByFilename(filename string) (f EFile, err error) { + filename = path.Base(filename) + + err = DBQueryRow("SELECT id_file, origin, path, id_exercice, name, cksum, size FROM exercice_files WHERE id_exercice = ? AND origin LIKE ?", e.Id, "%/" + filename).Scan(&f.Id, &f.origin, &f.Path, &f.IdExercice, &f.Name, &f.Checksum, &f.Size) + + return +} + // GetFiles returns a list of files coming with the challenge. func (e Exercice) GetFiles() ([]EFile, error) { if rows, err := DBQuery("SELECT id_file, origin, path, name, cksum, size FROM exercice_files WHERE id_exercice = ?", e.Id); err != nil { @@ -262,6 +271,35 @@ func (f EFile) GetOrigin() string { return f.origin } +// AddDepend insert a new dependency to a given flag. +func (f EFile) AddDepend(k Key) (err error) { + _, err = DBExec("INSERT INTO exercice_files_deps (id_file, id_key) VALUES (?, ?)", f.Id, k.Id) + return +} + +// GetDepends retrieve the flag's dependency list. +func (f EFile) GetDepends() ([]Key, error) { + if rows, err := DBQuery("SELECT id_key FROM exercice_files_deps WHERE id_file = ?", f.Id); err != nil { + return nil, err + } else { + defer rows.Close() + + var deps = make([]Key, 0) + for rows.Next() { + var d int64 + if err := rows.Scan(&d); err != nil { + return nil, err + } + deps = append(deps, Key{d, f.IdExercice, "", []byte{}}) + } + if err := rows.Err(); err != nil { + return nil, err + } + + return deps, nil + } +} + // CheckFileOnDisk recalculates the hash of the file on disk. func (f EFile) CheckFileOnDisk() error { if _, size, err := checkFileHash(path.Join(FilesDir, f.Path), f.Checksum); err != nil { diff --git a/libfic/key.go b/libfic/key.go index 1ca9f470..ba7c52a4 100644 --- a/libfic/key.go +++ b/libfic/key.go @@ -79,7 +79,9 @@ func (k Key) Update() (int64, error) { // Delete the flag from the database. func (k Key) Delete() (int64, error) { - if res, err := DBExec("DELETE FROM exercice_keys WHERE id_key = ?", k.Id); err != nil { + if _, err := DBExec("DELETE FROM exercice_files_deps WHERE id_key = ?", k.Id); err != nil { + return 0, err + } else if res, err := DBExec("DELETE FROM exercice_keys WHERE id_key = ?", k.Id); err != nil { return 0, err } else if nb, err := res.RowsAffected(); err != nil { return 0, err @@ -90,7 +92,9 @@ func (k Key) Delete() (int64, error) { // WipeKeys deletes keys coming with the challenge. func (e Exercice) WipeKeys() (int64, error) { - if res, err := DBExec("DELETE FROM exercice_keys WHERE id_exercice = ?", e.Id); err != nil { + if _, err := DBExec("DELETE FROM exercice_files_deps WHERE id_key IN (SELECT id_key FROM exercice_keys WHERE id_exercice = ?)", e.Id); err != nil { + return 0, err + } else if res, err := DBExec("DELETE FROM exercice_keys WHERE id_exercice = ?", e.Id); err != nil { return 0, err } else if nb, err := res.RowsAffected(); err != nil { return 0, err diff --git a/libfic/reset.go b/libfic/reset.go index cd1625e8..892b4c84 100644 --- a/libfic/reset.go +++ b/libfic/reset.go @@ -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", "key_found", "exercice_keys", "exercice_solved", "exercice_tries", "exercice_hints", "mcq_found", "mcq_entries", "exercice_mcq", "exercices", "themes") + return truncateTable("team_hints", "exercice_files_deps", "exercice_files", "key_found", "exercice_keys", "exercice_solved", "exercice_tries", "exercice_hints", "mcq_found", "mcq_entries", "exercice_mcq", "exercices", "themes") } // ResetTeams wipes out all teams, incluings members and attempts. diff --git a/libfic/team.go b/libfic/team.go index d8568087..b7735e54 100644 --- a/libfic/team.go +++ b/libfic/team.go @@ -1,6 +1,7 @@ package fic import ( + "log" "time" ) @@ -104,6 +105,25 @@ func (t Team) HasAccess(e Exercice) bool { } } +// CanDownload checks if the Team has access to the given file. +func (t Team) CanDownload(f EFile) bool { + if deps, err := f.GetDepends(); err != nil { + log.Printf("Unable to retrieve file 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 diff --git a/libfic/team_my.go b/libfic/team_my.go index 4fc61fcd..60264ada 100644 --- a/libfic/team_my.go +++ b/libfic/team_my.go @@ -115,7 +115,9 @@ func MyJSONTeam(t *Team, started bool) (interface{}, error) { return nil, err } else { for _, f := range files { - exercice.Files = append(exercice.Files, myTeamFile{path.Join(FilesDir, f.Path), f.Name, hex.EncodeToString(f.Checksum), f.Size}) + if t == nil || t.CanDownload(f) { + exercice.Files = append(exercice.Files, myTeamFile{path.Join(FilesDir, f.Path), f.Name, hex.EncodeToString(f.Checksum), f.Size}) + } } }