2016-01-13 19:25:25 +00:00
package fic
2016-01-07 17:43:02 +00:00
2016-01-18 18:40:11 +00:00
import (
2018-09-07 18:53:08 +00:00
"log"
2019-01-17 11:03:18 +00:00
"math"
2016-01-18 18:40:11 +00:00
"time"
2021-09-09 09:20:45 +00:00
"golang.org/x/crypto/bcrypt"
2016-01-18 18:40:11 +00:00
)
2016-01-07 17:43:02 +00:00
2019-01-17 11:03:56 +00:00
// UnlockedChallengeDepth is the number of challenges to unlock ahead (0: only the next one, -1: all)
var UnlockedChallengeDepth int
2016-12-04 17:58:53 +00:00
2022-06-08 14:37:25 +00:00
// UnlockedChallengeUpTo is the number of level to unlock
var UnlockedChallengeUpTo int
2019-01-17 11:03:18 +00:00
// WchoiceCoefficient is the current coefficient applied on the cost of changing flag into choices
var WChoiceCoefficient = 1.0
2018-03-09 18:07:08 +00:00
// Team represents a group of players, come to solve our challenges.
2016-01-07 17:43:02 +00:00
type Team struct {
2021-09-09 09:20:45 +00:00
Id int64 ` json:"id" `
Name string ` json:"name" `
Color uint32 ` json:"color" `
Active bool ` json:"active" `
ExternalId string ` json:"external_id" `
Password * string ` json:"password" `
2016-01-07 17:43:02 +00:00
}
2021-11-22 14:35:07 +00:00
func getTeams ( filter string ) ( [ ] * Team , error ) {
2021-09-09 09:20:45 +00:00
if rows , err := DBQuery ( "SELECT id_team, name, color, active, external_id, password FROM teams " + filter ) ; err != nil {
2016-01-07 17:43:02 +00:00
return nil , err
} else {
defer rows . Close ( )
2021-11-22 14:35:07 +00:00
var teams [ ] * Team
2016-01-07 17:43:02 +00:00
for rows . Next ( ) {
2021-11-22 14:35:07 +00:00
t := & Team { }
2021-09-09 09:20:45 +00:00
if err := rows . Scan ( & t . Id , & t . Name , & t . Color , & t . Active , & t . ExternalId , & t . Password ) ; err != nil {
2016-01-07 17:43:02 +00:00
return nil , err
}
teams = append ( teams , t )
}
if err := rows . Err ( ) ; err != nil {
return nil , err
}
return teams , nil
}
}
2019-01-21 01:35:03 +00:00
// GetTeams returns a list of registered Team from the database.
2021-11-22 14:35:07 +00:00
func GetTeams ( ) ( [ ] * Team , error ) {
2019-01-21 01:35:03 +00:00
return getTeams ( "" )
}
// GetActiveTeams returns a list of registered Team from the database, limited to team to generate.
2021-11-22 14:35:07 +00:00
func GetActiveTeams ( ) ( [ ] * Team , error ) {
2019-01-21 01:35:03 +00:00
return getTeams ( "WHERE active = 1" )
}
2018-03-09 18:07:08 +00:00
// GetTeam retrieves a Team from its identifier.
2021-11-22 14:35:07 +00:00
func GetTeam ( id int64 ) ( * Team , error ) {
t := & Team { }
2021-09-09 09:20:45 +00:00
if err := DBQueryRow ( "SELECT id_team, name, color, active, external_id, password FROM teams WHERE id_team = ?" , id ) . Scan ( & t . Id , & t . Name , & t . Color , & t . Active , & t . ExternalId , & t . Password ) ; err != nil {
2016-04-28 14:11:19 +00:00
return t , err
}
return t , nil
}
2018-03-09 18:07:08 +00:00
// GetTeamBySerial retrieves a Team from one of its associated certificates.
2021-11-22 14:35:07 +00:00
func GetTeamBySerial ( serial int64 ) ( * Team , error ) {
t := & Team { }
2021-09-09 09:20:45 +00:00
if err := DBQueryRow ( "SELECT T.id_team, T.name, T.color, T.active, T.external_id, T.password FROM certificates C INNER JOIN teams T ON T.id_team = C.id_team WHERE id_cert = ?" , serial ) . Scan ( & t . Id , & t . Name , & t . Color , & t . Active , & t . ExternalId , & t . Password ) ; err != nil {
2016-01-07 17:43:02 +00:00
return t , err
}
return t , nil
}
2018-03-09 18:07:08 +00:00
// CreateTeam creates and fills a new struct Team and registers it into the database.
2021-11-22 14:35:07 +00:00
func CreateTeam ( name string , color uint32 , externalId string ) ( * Team , error ) {
2021-09-03 15:23:00 +00:00
if res , err := DBExec ( "INSERT INTO teams (name, color, external_id) VALUES (?, ?, ?)" , name , color , externalId ) ; err != nil {
2021-11-22 14:35:07 +00:00
return nil , err
2016-01-07 17:43:02 +00:00
} else if tid , err := res . LastInsertId ( ) ; err != nil {
2021-11-22 14:35:07 +00:00
return nil , err
2016-01-07 17:43:02 +00:00
} else {
2021-11-22 14:35:07 +00:00
return & Team { tid , name , color , true , "" , nil } , nil
2016-01-07 17:43:02 +00:00
}
}
2018-03-09 18:07:08 +00:00
// Update applies modifications back to the database.
2021-11-22 14:35:07 +00:00
func ( t * Team ) Update ( ) ( int64 , error ) {
2021-09-09 09:20:45 +00:00
if res , err := DBExec ( "UPDATE teams SET name = ?, color = ?, active = ?, external_id = ?, password = ? WHERE id_team = ?" , t . Name , t . Color , t . Active , t . ExternalId , t . Password , t . Id ) ; err != nil {
2016-01-07 17:43:02 +00:00
return 0 , err
} else if nb , err := res . RowsAffected ( ) ; err != nil {
return 0 , err
} else {
return nb , err
}
}
2018-03-09 18:07:08 +00:00
// Delete the challenge from the database
2021-11-22 14:35:07 +00:00
func ( t * Team ) Delete ( ) ( int64 , error ) {
2017-11-10 19:23:45 +00:00
if _ , err := DBExec ( "DELETE FROM team_members WHERE id_team = ?" , t . Id ) ; err != nil {
return 0 , err
} else if res , err := DBExec ( "DELETE FROM teams WHERE id_team = ?" , t . Id ) ; err != nil {
2016-01-07 17:43:02 +00:00
return 0 , err
} else if nb , err := res . RowsAffected ( ) ; err != nil {
return 0 , err
} else {
return nb , err
}
}
2016-01-15 11:57:35 +00:00
2018-03-09 18:07:08 +00:00
// HasAccess checks if the Team has access to the given challenge.
2021-11-22 14:35:07 +00:00
func ( t * Team ) HasAccess ( e * Exercice ) bool {
2024-03-16 10:28:59 +00:00
// Case of standalone exercices
if e . IdTheme == nil || * e . IdTheme == 0 {
ord , err := e . GetOrdinal ( )
if err != nil {
return false
}
if ord < UnlockedStandaloneExercices {
return true
}
2024-03-17 09:17:39 +00:00
nbsteps , nbexos , err := t . SolvedCount ( )
if nbsteps == nil || nbexos == nil || err != nil {
2024-03-16 10:28:59 +00:00
return false
}
2024-03-17 09:17:39 +00:00
if ord < UnlockedStandaloneExercices + int ( math . Floor ( UnlockedStandaloneExercicesByThemeStepValidation * float64 ( * nbsteps ) ) ) + int ( math . Floor ( UnlockedStandaloneExercicesByStandaloneExerciceValidation * float64 ( * nbexos ) ) ) {
2024-03-16 10:28:59 +00:00
return true
}
return false
}
2019-01-17 11:03:56 +00:00
if UnlockedChallengeDepth < 0 {
2016-01-18 17:24:46 +00:00
return true
2019-01-17 11:03:56 +00:00
}
2022-06-08 14:37:25 +00:00
if UnlockedChallengeUpTo > 1 {
lvl , err := e . GetLevel ( )
if err == nil && lvl <= UnlockedChallengeUpTo {
return true
}
}
2019-01-17 11:03:56 +00:00
for i := UnlockedChallengeDepth ; i >= 0 ; i -- {
// An exercice without dependency is accessible
if e . Depend == nil {
return true
}
2021-11-22 14:35:07 +00:00
ed := & Exercice { }
2016-01-18 17:24:46 +00:00
ed . Id = * e . Depend
2021-11-22 14:35:07 +00:00
s := t . HasSolved ( ed )
if s != nil {
return true
2019-01-17 11:03:56 +00:00
}
// Prepare next iteration
var err error
e , err = GetExercice ( ed . Id )
if err != nil {
return false
}
2022-11-05 14:26:57 +00:00
// If our previous exercice is WIP, unlock
if i == UnlockedChallengeDepth && e . WIP {
return true
}
2016-01-18 17:24:46 +00:00
}
2019-01-17 11:03:56 +00:00
return false
2016-01-18 17:24:46 +00:00
}
2018-09-07 18:53:08 +00:00
// CanDownload checks if the Team has access to the given file.
2021-11-22 14:35:07 +00:00
func ( t * Team ) CanDownload ( f * EFile ) bool {
2022-10-31 17:52:29 +00:00
if ! f . Published {
return false
} else if deps , err := f . GetDepends ( ) ; err != nil {
2018-09-07 18:53:08 +00:00
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
}
}
2019-10-26 09:33:30 +00:00
// CanSeeHint checks if the Team has access to the given hint.
2021-11-22 14:35:07 +00:00
func ( t * Team ) CanSeeHint ( h * EHint ) bool {
2019-10-26 09:33:30 +00:00
if deps , err := h . 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
}
}
2018-12-02 18:21:07 +00:00
// CanSeeFlag checks if the Team has access to the given flag.
2021-11-22 14:35:07 +00:00
func ( t * Team ) CanSeeFlag ( k Flag ) bool {
2018-12-02 18:21:07 +00:00
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
}
}
2018-03-09 18:07:08 +00:00
// NbTry retrieves the number of attempts made by the Team to the given challenge.
2021-11-22 14:35:07 +00:00
func NbTry ( t * Team , e * Exercice ) int {
2018-12-06 21:18:08 +00:00
tries_table := "exercice_tries"
if SubmissionUniqueness {
tries_table = "exercice_distinct_tries"
}
2022-05-27 15:11:51 +00:00
if CountOnlyNotGoodTries {
tries_table += "_notgood"
}
2018-12-06 21:18:08 +00:00
2016-10-13 17:52:13 +00:00
var cnt * int
if t != nil {
2021-09-03 15:23:00 +00:00
DBQueryRow ( "SELECT COUNT(*) FROM " + tries_table + " WHERE id_team = ? AND id_exercice = ?" , t . Id , e . Id ) . Scan ( & cnt )
2016-10-13 17:52:13 +00:00
} else {
2021-09-03 15:23:00 +00:00
DBQueryRow ( "SELECT COUNT(*) FROM " + tries_table + " WHERE id_exercice = ?" , e . Id ) . Scan ( & cnt )
2016-10-13 17:52:13 +00:00
}
if cnt == nil {
return 0
} else {
return * cnt
}
}
2018-03-09 18:07:08 +00:00
// HasHint checks if the Team has revealed the given Hint.
2021-11-22 14:35:07 +00:00
func ( t * Team ) HasHint ( h * EHint ) bool {
2016-12-04 18:15:39 +00:00
var tm * time . Time
DBQueryRow ( "SELECT MIN(time) FROM team_hints WHERE id_team = ? AND id_hint = ?" , t . Id , h . Id ) . Scan ( & tm )
return tm != nil
}
2018-03-09 18:07:08 +00:00
// OpenHint registers to the database that the Team has now revealed.
2021-11-22 14:35:07 +00:00
func ( t * Team ) OpenHint ( h * EHint ) error {
2019-01-17 11:03:18 +00:00
_ , err := DBExec ( "INSERT INTO team_hints (id_team, id_hint, time, coefficient) VALUES (?, ?, ?, ?)" , t . Id , h . Id , time . Now ( ) , math . Max ( HintCoefficient , 0.0 ) )
2016-12-09 10:49:29 +00:00
return err
}
2018-12-02 22:18:32 +00:00
// SeeChoices checks if the Team has revealed the given choices.
2021-11-22 14:35:07 +00:00
func ( t * Team ) SeeChoices ( k * FlagKey ) bool {
2018-12-02 22:18:32 +00:00
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.
2021-11-22 14:35:07 +00:00
func ( t * Team ) DisplayChoices ( k * FlagKey ) error {
2019-01-17 11:03:18 +00:00
_ , err := DBExec ( "INSERT INTO team_wchoices (id_team, id_flag, time, coefficient) VALUES (?, ?, ?, ?)" , t . Id , k . Id , time . Now ( ) , math . Max ( WChoiceCoefficient , 0.0 ) )
2018-12-02 22:18:32 +00:00
return err
}
2018-03-09 18:07:08 +00:00
// CountTries gets the amount of attempts made by the Team and retrieves the time of the latest attempt.
2022-02-04 16:33:11 +00:00
func ( t * Team ) CountTries ( e * Exercice ) ( nb int64 , tm * time . Time ) {
2021-09-08 01:34:48 +00:00
table := "exercice_tries"
2022-02-04 16:33:11 +00:00
if SubmissionUniqueness {
table = "exercice_distinct_tries"
}
2021-09-08 01:34:48 +00:00
if CountOnlyNotGoodTries {
table += "_notgood"
}
2022-02-04 16:33:11 +00:00
DBQueryRow ( "SELECT COUNT(id_exercice), MAX(time) FROM " + table + " WHERE id_team = ? AND id_exercice = ?" , t . Id , e . Id ) . Scan ( & nb , & tm )
// time is not accurate in distinct nor _notgood tables as it only considers notgood or distincts answers, so the last try is not count
if SubmissionUniqueness || CountOnlyNotGoodTries {
DBQueryRow ( "SELECT MAX(time) FROM exercice_tries WHERE id_team = ? AND id_exercice = ?" , t . Id , e . Id ) . Scan ( & tm )
2017-01-16 12:09:31 +00:00
}
2022-02-04 16:33:11 +00:00
return
2017-01-16 12:09:31 +00:00
}
2018-03-09 18:07:08 +00:00
// LastTryDist retrieves the distance to the correct answers, for the given challenge.
// The distance is the number of bad responses given in differents MCQs.
2021-11-22 14:35:07 +00:00
func ( t * Team ) LastTryDist ( e * Exercice ) int64 {
2017-12-17 01:48:02 +00:00
var nb * int64
if DBQueryRow ( "SELECT nbdiff FROM exercice_tries WHERE id_team = ? AND id_exercice = ? ORDER BY time DESC LIMIT 1" , t . Id , e . Id ) . Scan ( & nb ) ; nb == nil {
return 0
} else {
return * nb
}
}
2024-03-16 10:28:59 +00:00
// SolvedCount returns the number of solved exercices.
2024-03-17 09:17:39 +00:00
func ( t * Team ) SolvedCount ( ) ( nbsteps * int64 , nbex * int64 , err error ) {
err = DBQueryRow ( "SELECT COUNT(S.id_exercice) FROM exercice_solved S INNER JOIN exercices E ON E.id_exercice = S.id_exercice WHERE S.id_team = ? AND E.id_theme IS NOT NULL" , t . Id ) . Scan ( & nbsteps )
if err != nil {
return
}
err = DBQueryRow ( "SELECT COUNT(S.id_exercice) FROM exercice_solved S INNER JOIN exercices E ON E.id_exercice = S.id_exercice WHERE S.id_team = ? AND E.id_theme IS NULL" , t . Id ) . Scan ( & nbex )
2024-03-16 10:28:59 +00:00
return
}
2018-03-09 18:07:08 +00:00
// HasSolved checks if the Team already has validated the given challenge.
// Note that the function also returns the effective validation timestamp.
2021-11-22 14:35:07 +00:00
func ( t * Team ) HasSolved ( e * Exercice ) ( tm * time . Time ) {
2023-04-02 15:24:25 +00:00
DBQueryRow ( "SELECT time FROM exercice_solved WHERE id_team = ? AND id_exercice = ? ORDER BY time ASC LIMIT 1" , t . Id , e . Id ) . Scan ( & tm )
2021-11-22 14:35:07 +00:00
return
2016-01-15 11:57:35 +00:00
}
2016-03-06 17:57:08 +00:00
2018-03-09 18:07:08 +00:00
// GetSolvedRank returns the number of teams that solved the challenge before the Team.
2021-11-22 14:35:07 +00:00
func ( t * Team ) GetSolvedRank ( e * Exercice ) ( nb int64 , err error ) {
2020-01-18 22:45:28 +00:00
if rows , errr := DBQuery ( "SELECT id_team FROM exercice_solved WHERE id_exercice = ? ORDER BY time ASC" , e . Id ) ; errr != nil {
2018-03-09 18:07:08 +00:00
return nb , errr
2016-10-13 17:52:40 +00:00
} else {
2019-01-17 15:41:37 +00:00
defer rows . Close ( )
2018-03-09 18:07:08 +00:00
for rows . Next ( ) {
var tid int64
if err = rows . Scan ( & tid ) ; err != nil {
return
}
nb += 1
if t . Id == tid {
break
}
}
return
2016-10-13 17:52:40 +00:00
}
}
2018-09-24 08:00:17 +00:00
// HasPartiallySolved checks if the Team already has unlocked the given flag and returns the validation's timestamp.
2021-11-22 14:35:07 +00:00
func ( t * Team ) HasPartiallySolved ( f Flag ) ( tm * time . Time ) {
2022-01-21 12:06:37 +00:00
if _ , ok := f . ( * FlagLabel ) ; ok {
now := time . Now ( )
return & now
} else if k , ok := f . ( * FlagKey ) ; ok {
2019-01-02 20:51:09 +00:00
DBQueryRow ( "SELECT MIN(time) FROM flag_found WHERE id_team = ? AND id_flag = ?" , t . Id , k . Id ) . Scan ( & tm )
2021-11-22 14:35:07 +00:00
} else if m , ok := f . ( * MCQ ) ; ok {
2019-01-02 20:51:09 +00:00
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" )
}
2018-03-09 18:07:08 +00:00
return
2017-12-16 00:16:30 +00:00
}
2021-09-09 09:20:45 +00:00
// HashedPassword compute a bcrypt version of the team's password.
2021-11-22 14:35:07 +00:00
func ( t * Team ) HashedPassword ( ) ( string , error ) {
2021-09-09 09:20:45 +00:00
if t . Password == nil {
if passwd , err := GeneratePassword ( ) ; err != nil {
return "" , err
} else {
h , err := bcrypt . GenerateFromPassword ( [ ] byte ( passwd ) , bcrypt . DefaultCost )
return string ( h ) , err
}
}
h , err := bcrypt . GenerateFromPassword ( [ ] byte ( * t . Password ) , bcrypt . DefaultCost )
return string ( h ) , err
}