Handle optionnal flags

This commit is contained in:
nemunaire 2022-05-31 22:03:51 +02:00
parent e581630d5e
commit a414cd22c8
18 changed files with 68 additions and 22 deletions

View file

@ -735,7 +735,8 @@ type uploadedFlag struct {
SortReGroups bool `json:"sort_re_grps"`
Flag string
Value []byte
ChoicesCost int64 `json:"choices_cost"`
ChoicesCost int32 `json:"choices_cost"`
BonusGain int32 `json:"bonus_gain"`
}
func createExerciceFlag(c *gin.Context) {
@ -758,7 +759,7 @@ func createExerciceFlag(c *gin.Context) {
exercice := c.MustGet("exercice").(*fic.Exercice)
flag, err := exercice.AddRawFlagKey(uk.Label, uk.Type, uk.Placeholder, uk.IgnoreCase, uk.NoTrim, uk.Multiline, vre, uk.SortReGroups, []byte(uk.Flag), uk.ChoicesCost)
flag, err := exercice.AddRawFlagKey(uk.Label, uk.Type, uk.Placeholder, uk.IgnoreCase, uk.NoTrim, uk.Multiline, vre, uk.SortReGroups, []byte(uk.Flag), uk.ChoicesCost, uk.BonusGain)
if err != nil {
log.Println("Unable to createExerciceFlag:", err.Error())
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs when trying to create flag."})
@ -839,6 +840,7 @@ func updateExerciceFlag(c *gin.Context) {
flag.Checksum = uk.Value
}
flag.ChoicesCost = uk.ChoicesCost
flag.BonusGain = uk.BonusGain
if uk.ValidatorRe != nil && len(*uk.ValidatorRe) > 0 {
flag.ValidatorRegexp = uk.ValidatorRe

View file

@ -62,6 +62,9 @@
<div class="col-2">
<input type="text" id="kccost{{flag.id}}" ng-model="flag.choices_cost" class="form-control form-control-sm" placeholder="Choices cost" title="Choices cost" integer>
</div>
<div class="col-2">
<input type="text" id="kbgain{{flag.id}}" ng-model="flag.bonus_gain" class="form-control form-control-sm" placeholder="Bonus gain" title="Bonus gain" integer>
</div>
<div class="col-2">
<input type="text" id="korder{{flag.id}}" ng-model="flag.order" class="form-control form-control-sm" placeholder="Order" title="Order" integer>
</div>

View file

@ -13,7 +13,7 @@
<th>Date</th>
</thead>
<tbody>
<tr ng-repeat="row in scores | filter: query | orderBy:'time'" ng-class="{'bg-ffound': row.reason == 'First blood', 'bg-wchoices': row.reason == 'Display choices', 'bg-success': row.reason == 'Validation', 'bg-info': row.reason == 'Hint', 'bg-warning': row.reason == 'Tries'}">
<tr ng-repeat="row in scores | filter: query | orderBy:'time'" ng-class="{'bg-danger': row.reason == 'Bonus flag', 'bg-ffound': row.reason == 'First blood', 'bg-wchoices': row.reason == 'Display choices', 'bg-success': row.reason == 'Validation', 'bg-info': row.reason == 'Hint', 'bg-warning': row.reason == 'Tries'}">
<td>
<a ng-repeat="exercice in exercices" ng-if="exercice.id == row.id_exercice" href="exercices/{{ row.id_exercice }}">{{ exercice.title }}</a>
</td>

View file

@ -43,7 +43,8 @@ type ExerciceFlag struct {
SortReGroups bool `toml:"sort_validator_regexp_groups,omitempty"`
Placeholder string `toml:",omitempty"`
Help string `toml:",omitempty"`
ChoicesCost int64 `toml:"choices_cost,omitempty"`
BonusGain int32 `toml:"bonus_gain,omitempty"`
ChoicesCost int32 `toml:"choices_cost,omitempty"`
Choice []ExerciceFlagChoice
LockedFile []ExerciceUnlockFile `toml:"unlock_file,omitempty"`
NeedFlag []ExerciceDependency `toml:"need_flag,omitempty"`

View file

@ -170,6 +170,7 @@ func buildKeyFlag(exercice *fic.Exercice, flag ExerciceFlag, flagline int, defau
SortReGroups: flag.SortReGroups,
Checksum: hashedFlag[:],
ChoicesCost: flag.ChoicesCost,
BonusGain: flag.BonusGain,
})
f = &fl

View file

@ -82,8 +82,7 @@ func treatSubmission(pathname string, team *fic.Team, exercice_id string) {
// Ensure the team didn't already solve this exercice
tm := team.HasSolved(exercice)
if tm != nil {
log.Printf("%s [WRN] Team %d ALREADY solved exercice %d (%s : %s)\n", id, team.Id, exercice.Id, theme.Name, exercice.Title)
return
log.Printf("%s [WRN] Team %d ALREADY solved exercice %d (%s : %s), continuing for eventual bonus flags\n", id, team.Id, exercice.Id, theme.Name, exercice.Title)
}
// Handle MCQ justifications: convert to expected keyid
@ -119,7 +118,9 @@ func treatSubmission(pathname string, team *fic.Team, exercice_id string) {
log.Println(id, "[ERR] Can't remove file:", err)
}
if solved {
if tm != nil {
genTeamQueue <- team
} else if solved {
log.Printf("%s Team %d SOLVED exercice %d (%s : %s)\n", id, team.Id, exercice.Id, theme.Name, exercice.Title)
if err := exercice.Solved(team); err != nil {
log.Println(id, "[ERR] Unable to mark the challenge as solved:", err)

View file

@ -100,6 +100,11 @@
</script>
<div class="form-group mb-3">
{#if flag.bonus_gain}
<div class={'float-end badge bg-' + (flag.found?'success':'danger')} title={'Ce flag est optionnel, si vous le complétez il vous rapportera ' + flag.bonus_gain + ' points supplémentaires'}>
optionnel | {#if flag.bonus_gain > 0}+{/if}{flag.bonus_gain}&nbsp;pts
</div>
{/if}
<label for="sol_{flag.type}{flag.id}_0">{flag.label}&nbsp;:</label>
{#if flag.found && flag.value}
<span>{flag.value}</span>

View file

@ -161,7 +161,7 @@
{/if}
</Col>
<Col lg="6" class="mb-5">
{#if !$my.exercices[exercice.id].solved_rank && !solved[exercice.id]}
{#if $my.exercices[exercice.id].flags && $my.exercices[exercice.id].non_found_flags > 0 && !solved[exercice.id]}
<ExerciceFlags
{refresh_my}
{refresh_teams}
@ -169,7 +169,8 @@
bind:forcesolved={solved[exercice.id]}
flags={$my.exercices[exercice.id].flags}
/>
{:else}
{/if}
{#if $my.exercices[exercice.id].solved_rank || solved[exercice.id]}
<ExerciceSolved
{theme}
exercice={$my.exercices[exercice.id]}

View file

@ -11,6 +11,15 @@ function createMyStore() {
for (let k in my.exercices) {
my.exercices[k].id = k;
if (my.exercices[k].flags) {
let nb = 0;
for (let j in my.exercices[k].flags) {
if (!my.exercices[k].flags[j].found)
nb += 1;
}
my.exercices[k].non_found_flags = nb;
}
if (my.team_id === 0 && my.exercices[k].hints) {
for (let j in my.exercices[k].hints) {
my.exercices[k].hints[j].hidden = true;

View file

@ -195,7 +195,8 @@ CREATE TABLE IF NOT EXISTS exercice_flags(
validator_regexp VARCHAR(255) NULL,
sort_re_grps BOOLEAN NOT NULL DEFAULT 0,
cksum BINARY(64) NOT NULL,
choices_cost INTEGER NOT NULL,
choices_cost MEDIUMINT NOT NULL,
bonus_gain MEDIUMINT NOT NULL,
FOREIGN KEY(id_exercice) REFERENCES exercices(id_exercice)
) DEFAULT CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci;
`); err != nil {

View file

@ -471,10 +471,10 @@ func (e *Exercice) CheckResponse(cksum []byte, respflags map[int]string, respmcq
// Check flags
for _, flag := range flags {
if res, ok := respflags[flag.Id]; !ok && (!PartialValidation || t.HasPartiallySolved(flag) == nil) {
valid = false
valid = valid && flag.IsOptionnal()
} else if flag.Check([]byte(res)) != 0 {
if !PartialValidation || t.HasPartiallySolved(flag) == nil {
valid = false
valid = valid && flag.IsOptionnal()
}
} else {
flag.FoundBy(t)

View file

@ -338,7 +338,7 @@ func (f *EFile) GetDepends() ([]Flag, error) {
if err := rows.Scan(&d); err != nil {
return nil, err
}
deps = append(deps, &FlagKey{d, f.IdExercice, 0, "", "", "", "", "", false, false, false, nil, false, []byte{}, 0})
deps = append(deps, &FlagKey{d, f.IdExercice, 0, "", "", "", "", "", false, false, false, nil, false, []byte{}, 0, 0})
}
if err := rows.Err(); err != nil {
return nil, err

View file

@ -12,6 +12,7 @@ type Flag interface {
GetDepends() ([]Flag, error)
GetOrder() int8
Check(val interface{}) int
IsOptionnal() bool
FoundBy(t *Team)
}

View file

@ -42,12 +42,14 @@ type FlagKey struct {
// 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"`
ChoicesCost int32 `json:"choices_cost"`
// BonusGain makes the flag completion optionnal. If it is filled and correct, it gives some points.
BonusGain int32 `json:"bonus_gain"`
}
// 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 {
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, bonus_gain FROM exercice_flags WHERE id_exercice = ?", e.Id); err != nil {
return nil, err
} else {
defer rows.Close()
@ -57,7 +59,7 @@ func (e *Exercice) GetFlagKeys() ([]*FlagKey, error) {
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 {
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, &k.BonusGain); err != nil {
return nil, err
}
@ -74,21 +76,21 @@ func (e *Exercice) GetFlagKeys() ([]*FlagKey, error) {
// 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)
err = DBQueryRow("SELECT id_flag, id_exercice, ordre, label, type, placeholder, help, unit, ignorecase, notrim, multiline, validator_regexp, sort_re_grps, cksum, choices_cost, bonus_gain 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, &k.BonusGain)
return
}
// GetFlagKey returns a flag.
func (e *Exercice) 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 = ? AND id_exercice = ?", id, 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)
err = DBQueryRow("SELECT id_flag, id_exercice, ordre, label, type, placeholder, help, unit, ignorecase, notrim, multiline, validator_regexp, sort_re_grps, cksum, choices_cost, bonus_gain FROM exercice_flags WHERE id_flag = ? AND id_exercice = ?", id, 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, &k.BonusGain)
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)
err = DBQueryRow("SELECT id_flag, id_exercice, ordre, label, type, placeholder, help, unit, ignorecase, notrim, multiline, validator_regexp, sort_re_grps, cksum, choices_cost, bonus_gain 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, &k.BonusGain)
return
}
@ -139,7 +141,7 @@ func ExecValidatorRegexp(vre string, val []byte, ignorecase bool, sortregroups b
}
// 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) {
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 int32, bonusgain int32) (*FlagKey, error) {
hash, err := ComputeHashedFlag(raw_value, ignorecase, notrim, validator_regexp, sortregroups)
if err != nil {
return nil, err
@ -156,6 +158,7 @@ func (e *Exercice) AddRawFlagKey(name string, t string, placeholder string, igno
SortReGroups: sortregroups,
Checksum: hash[:],
ChoicesCost: choicescost,
BonusGain: bonusgain,
}
_, err = f.Create(e)
@ -185,7 +188,7 @@ func (k *FlagKey) Create(e *Exercice) (Flag, error) {
}
}
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 {
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, bonus_gain) 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, k.BonusGain); err != nil {
return k, err
} else if kid, err := res.LastInsertId(); err != nil {
return k, err
@ -210,7 +213,7 @@ func (k *FlagKey) Update() (int64, error) {
}
}
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 {
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 = ?, bonus_gain = ? 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.BonusGain, k.Id); err != nil {
return 0, err
} else if nb, err := res.RowsAffected(); err != nil {
return 0, err
@ -299,6 +302,11 @@ func (k *FlagKey) GetDepends() ([]Flag, error) {
return deps, nil
}
// IsOptionnal to know if the flag can be omitted when validating the step.
func (k *FlagKey) IsOptionnal() bool {
return k.BonusGain != 0
}
// Check if the given val is the expected one for this flag.
func (k *FlagKey) Check(v interface{}) int {
var val []byte

View file

@ -167,6 +167,11 @@ func (k *FlagLabel) GetDepends() ([]Flag, error) {
return deps, nil
}
// IsOptionnal to know if the flag can be omitted when validating the step.
func (k *FlagLabel) IsOptionnal() bool {
return true
}
// Check if the given val is the expected one for this flag.
func (k *FlagLabel) Check(v interface{}) int {
return 0

View file

@ -314,6 +314,11 @@ func (c *MCQ_entry) GetJustifiedFlag(e *Exercice) (*FlagKey, error) {
return e.GetFlagKeyByLabel(fmt.Sprintf("\\%%%d\\%%%%", c.Id))
}
// IsOptionnal to know if the flag can be omitted when validating the step.
func (m *MCQ) IsOptionnal() bool {
return false
}
// Check if the given vals are the expected ones to validate this flag.
func (m *MCQ) Check(v interface{}) int {
var vals map[int]bool

View file

@ -32,6 +32,7 @@ func exoptsQuery(whereExo string) string {
SELECT id_team, id_exercice, MIN(time) AS time, ` + fmt.Sprintf("%f", FirstBlood) + ` AS coeff, "First blood" AS reason FROM exercice_solved GROUP BY id_exercice UNION
SELECT id_team, id_exercice, time, coefficient AS coeff, "Validation" AS reason FROM exercice_solved
) S INNER JOIN exercices E ON S.id_exercice = E.id_exercice ` + whereExo + ` UNION ALL
SELECT B.id_team, B.time, F.bonus_gain AS points, 1 AS coeff, "Bonus flag" AS reason, F.id_exercice FROM flag_found B INNER JOIN exercice_flags F ON F.id_flag = B.id_flag WHERE F.bonus_gain != 0 HAVING points != 0 UNION ALL
SELECT id_team, MAX(time) AS time, (FLOOR(COUNT(*)/10 - 1) * (FLOOR(COUNT(*)/10)))/0.2 + (FLOOR(COUNT(*)/10) * (COUNT(*)%10)) AS points, ` + fmt.Sprintf("%f", SubmissionCostBase*-1) + ` AS coeff, "Tries" AS reason, id_exercice FROM ` + tries_table + ` S ` + whereExo + ` GROUP BY id_exercice, id_team`
}

View file

@ -48,6 +48,7 @@ type myTeamFlag struct {
PSolved *time.Time `json:"part_solved,omitempty"`
Soluce string `json:"soluce,omitempty"`
Justify bool `json:"justify,omitempty"`
BonusGain int64 `json:"bonus_gain,omitempty"`
Choices map[string]interface{} `json:"choices,omitempty"`
ChoicesCost int64 `json:"choices_cost,omitempty"`
Variant string `json:"variant,omitempty"`
@ -275,6 +276,7 @@ func MyJSONTeam(t *Team, started bool) (interface{}, error) {
}
flag.Multiline = k.Multiline
flag.BonusGain = int64(float64(k.BonusGain) * GlobalScoreCoefficient)
var fl FlagMCQLabel
if fl, err = k.GetMCQJustification(); err == nil {