diff --git a/admin/.gitignore b/admin/.gitignore index e2c3cd5d..0ccbc66d 100644 --- a/admin/.gitignore +++ b/admin/.gitignore @@ -2,3 +2,4 @@ admin fic.db PKI/ FILES/ +static/full_import_report.json diff --git a/admin/api.go b/admin/api.go deleted file mode 100644 index ec8e80a8..00000000 --- a/admin/api.go +++ /dev/null @@ -1,102 +0,0 @@ -package main - -import ( - "encoding/json" - "errors" - "fmt" - "io" - "log" - "net/http" - "strings" -) - -type DispatchFunction func([]string, []byte) (interface{}, error) - -var apiRoutes = map[string]*(map[string]DispatchFunction){ - "version": &ApiVersionRouting, - "ca": &ApiCARouting, - "events": &ApiEventsRouting, - "exercices": &ApiExercicesRouting, - "themes": &ApiThemesRouting, - "teams": &ApiTeamsRouting, -} - -type apiRouting struct{} - -func ApiHandler() http.Handler { - return apiRouting{} -} - -func (a apiRouting) ServeHTTP(w http.ResponseWriter, r *http.Request) { - log.Printf("Handling %s request from %s: %s [%s]\n", r.Method, r.RemoteAddr, r.URL.Path, r.UserAgent()) - - // Extract URL arguments - var sURL = strings.Split(r.URL.Path, "/")[1:] - if len(sURL) > 1 && sURL[len(sURL)-1] == "" { - // Remove trailing / - sURL = sURL[:len(sURL)-1] - } - - w.Header().Set("Content-Type", "application/json") - - var ret interface{} - var err error = nil - - // Read the body - if r.ContentLength < 0 || r.ContentLength > 6553600 { - http.Error(w, fmt.Sprintf("{errmsg:\"Request too large or request size unknown\"}", err), http.StatusRequestEntityTooLarge) - return - } - var body []byte - if r.ContentLength > 0 { - tmp := make([]byte, 1024) - for { - n, err := r.Body.Read(tmp) - for j := 0; j < n; j++ { - body = append(body, tmp[j]) - } - if err != nil || n <= 0 { - break - } - } - } - - // Route request - if len(sURL) > 0 { - if h, ok := apiRoutes[sURL[0]]; ok { - if f, ok := (*h)[r.Method]; ok { - ret, err = f(sURL[1:], body) - } else { - err = errors.New(fmt.Sprintf("Invalid action (%s) provided for %s.", r.Method, sURL[0])) - } - } - } else { - err = errors.New("No action provided.") - } - - // Format response - resStatus := http.StatusOK - if err != nil { - ret = map[string]string{"errmsg": err.Error()} - resStatus = http.StatusBadRequest - log.Println(r.RemoteAddr, resStatus, err.Error()) - } - - if ret == nil { - ret = map[string]string{"errmsg": "Page not found"} - resStatus = http.StatusNotFound - } - - if str, found := ret.(string); found { - w.WriteHeader(resStatus) - io.WriteString(w, str) - } else if bts, found := ret.([]byte); found { - w.WriteHeader(resStatus) - w.Write(bts) - } else if j, err := json.Marshal(ret); err != nil { - http.Error(w, fmt.Sprintf("{\"errmsg\":\"%q\"}", err), http.StatusInternalServerError) - } else { - w.WriteHeader(resStatus) - w.Write(j) - } -} diff --git a/admin/api/certificate.go b/admin/api/certificate.go new file mode 100644 index 00000000..1b6e701f --- /dev/null +++ b/admin/api/certificate.go @@ -0,0 +1,60 @@ +package api + +import ( + "errors" + "io/ioutil" + "os" + + "srs.epita.fr/fic-server/libfic" + + "github.com/julienschmidt/httprouter" +) + +func init() { + router.GET("/api/ca.pem", apiHandler(GetCAPEM)) + router.POST("/api/ca/new", apiHandler( + func(_ httprouter.Params, _ []byte) (interface{}, error) { return fic.GenerateCA() })) + router.GET("/api/ca/crl", apiHandler(GetCRL)) + router.POST("/api/ca/crl", apiHandler( + func(_ httprouter.Params, _ []byte) (interface{}, error) { return fic.GenerateCRL() })) + + + router.HEAD("/api/teams/:tid/certificate.p12", apiHandler(teamHandler(GetTeamCertificate))) + router.GET("/api/teams/:tid/certificate.p12", apiHandler(teamHandler(GetTeamCertificate))) + router.DELETE("/api/teams/:tid/certificate.p12", apiHandler(teamHandler( + func(team fic.Team, _ []byte) (interface{}, error) { return team.RevokeCert() }))) + router.GET("/api/teams/:tid/certificate/generate", apiHandler(teamHandler( + func(team fic.Team, _ []byte) (interface{}, error) { return team.GenerateCert() }))) +} + + +func GetCAPEM(_ httprouter.Params, _ []byte) (interface{}, error) { + if _, err := os.Stat("../PKI/shared/cacert.crt"); os.IsNotExist(err) { + return nil, errors.New("Unable to locate the CA root certificate. Have you generated it?") + } else if fd, err := os.Open("../PKI/shared/cacert.crt"); err == nil { + return ioutil.ReadAll(fd) + } else { + return nil, err + } +} + +func GetCRL(_ httprouter.Params, _ []byte) (interface{}, error) { + if _, err := os.Stat("../PKI/shared/crl.pem"); os.IsNotExist(err) { + return nil, errors.New("Unable to locate the CRL. Have you generated it?") + } else if fd, err := os.Open("../PKI/shared/crl.pem"); err == nil { + return ioutil.ReadAll(fd) + } else { + return nil, err + } +} + + +func GetTeamCertificate(team fic.Team, _ []byte) (interface{}, error) { + if _, err := os.Stat("../PKI/pkcs/" + team.InitialName + ".p12"); os.IsNotExist(err) { + return nil, errors.New("Unable to locate the p12. Have you generated it?") + } else if fd, err := os.Open("../PKI/pkcs/" + team.InitialName + ".p12"); err == nil { + return ioutil.ReadAll(fd) + } else { + return nil, err + } +} diff --git a/admin/api/events.go b/admin/api/events.go new file mode 100644 index 00000000..a9fd5977 --- /dev/null +++ b/admin/api/events.go @@ -0,0 +1,76 @@ +package api + +import ( + "encoding/json" + + "srs.epita.fr/fic-server/libfic" + + "github.com/julienschmidt/httprouter" +) + +func init() { + router.GET("/api/events/", apiHandler(getEvents)) + router.GET("/api/events.json", apiHandler(getLastEvents)) + router.POST("/api/events/", apiHandler(newEvent)) + router.DELETE("/api/events/", apiHandler(clearEvents)) + + router.GET("/api/events/:evid", apiHandler(eventHandler(showEvent))) + router.PUT("/api/events/:evid", apiHandler(eventHandler(updateEvent))) + router.DELETE("/api/events/:evid", apiHandler(eventHandler(deleteEvent))) +} + +func getEvents(_ httprouter.Params, _ []byte) (interface{}, error) { + if evts, err := fic.GetEvents(); err != nil { + return nil, err + } else { + return evts, nil + } +} + +func getLastEvents(_ httprouter.Params, _ []byte) (interface{}, error) { + if evts, err := fic.GetLastEvents(); err != nil { + return nil, err + } else { + return evts, nil + } +} + +func showEvent(event fic.Event, _ []byte) (interface{}, error) { + return event, nil +} + +func newEvent(_ httprouter.Params, body []byte) (interface{}, error) { + var ue fic.Event + if err := json.Unmarshal(body, &ue); err != nil { + return nil, err + } + + if event, err := fic.NewEvent(ue.Text, ue.Kind); err != nil { + return nil, err + } else { + return event, nil + } +} + +func clearEvents(_ httprouter.Params, _ []byte) (interface{}, error) { + return fic.ClearEvents() +} + +func updateEvent(event fic.Event, body []byte) (interface{}, error) { + var ue fic.Event + if err := json.Unmarshal(body, &ue); err != nil { + return nil, err + } + + ue.Id = event.Id + + if _, err := ue.Update(); err != nil { + return nil, err + } else { + return ue, nil + } +} + +func deleteEvent(event fic.Event, _ []byte) (interface{}, error) { + return event.Delete() +} diff --git a/admin/api/exercice.go b/admin/api/exercice.go new file mode 100644 index 00000000..681e1e3a --- /dev/null +++ b/admin/api/exercice.go @@ -0,0 +1,258 @@ +package api + +import ( + "encoding/json" + "encoding/hex" + "errors" + "strings" + + "srs.epita.fr/fic-server/libfic" + "srs.epita.fr/fic-server/admin/sync" + + "github.com/julienschmidt/httprouter" +) + +func init() { + router.GET("/api/exercices/", apiHandler(listExercices)) + + router.GET("/api/exercices/:eid", apiHandler(exerciceHandler(showExercice))) + router.PUT("/api/exercices/:eid", apiHandler(exerciceHandler(updateExercice))) + router.DELETE("/api/exercices/:eid", apiHandler(exerciceHandler(deleteExercice))) + + router.GET("/api/exercices/:eid/files", apiHandler(exerciceHandler(listExerciceFiles))) + router.POST("/api/exercices/:eid/files", apiHandler(exerciceHandler(createExerciceFile))) + router.GET("/api/exercices/:eid/files/:fid", apiHandler(fileHandler(showExerciceFile))) + router.DELETE("/api/exercices/:eid/files/:fid", apiHandler(fileHandler(deleteExerciceFile))) + + router.GET("/api/exercices/:eid/hints", apiHandler(exerciceHandler(listExerciceHints))) + router.POST("/api/exercices/:eid/hints", apiHandler(exerciceHandler(createExerciceHint))) + router.GET("/api/exercices/:eid/hints/:hid", apiHandler(hintHandler(showExerciceHint))) + router.PUT("/api/exercices/:eid/hints/:hid", apiHandler(hintHandler(updateExerciceHint))) + router.DELETE("/api/exercices/:eid/hints/:hid", apiHandler(hintHandler(deleteExerciceHint))) + + router.GET("/api/exercices/:eid/keys", apiHandler(exerciceHandler(listExerciceKeys))) + router.POST("/api/exercices/:eid/keys", apiHandler(exerciceHandler(createExerciceKey))) + router.GET("/api/exercices/:eid/keys/:kid", apiHandler(keyHandler(showExerciceKey))) + router.PUT("/api/exercices/:eid/keys/:kid", apiHandler(keyHandler(updateExerciceKey))) + router.DELETE("/api/exercices/:eid/keys/:kid", apiHandler(keyHandler(deleteExerciceKey))) + + router.GET("/api/exercices/:eid/quiz", apiHandler(exerciceHandler(listExerciceQuiz))) + router.GET("/api/exercices/:eid/quiz/:qid", apiHandler(quizHandler(showExerciceQuiz))) + router.DELETE("/api/exercices/:eid/quiz/:qid", apiHandler(quizHandler(deleteExerciceQuiz))) + + + // Synchronize + router.GET("/api/sync/exercices/:eid/files", apiHandler(exerciceHandler( + func(exercice fic.Exercice, _ []byte) (interface{}, error) { return sync.SyncExerciceFiles(sync.GlobalImporter, exercice), nil }))) + router.GET("/api/sync/exercices/:eid/hints", apiHandler(exerciceHandler( + func(exercice fic.Exercice, _ []byte) (interface{}, error) { return sync.SyncExerciceHints(sync.GlobalImporter, exercice), nil }))) + router.GET("/api/sync/exercices/:eid/keys", apiHandler(exerciceHandler( + func(exercice fic.Exercice, _ []byte) (interface{}, error) { return sync.SyncExerciceKeys(sync.GlobalImporter, exercice), nil }))) + router.GET("/api/sync/exercices/:eid/quiz", apiHandler(exerciceHandler( + func(exercice fic.Exercice, _ []byte) (interface{}, error) { return sync.SyncExerciceMCQ(sync.GlobalImporter, exercice), nil }))) +} + +func listExercices(_ httprouter.Params, body []byte) (interface{}, error) { + // List all exercices + return fic.GetExercices() +} + +func listExerciceFiles(exercice fic.Exercice, body []byte) (interface{}, error) { + return exercice.GetFiles() +} + +func listExerciceHints(exercice fic.Exercice, body []byte) (interface{}, error) { + return exercice.GetHints() +} + +func listExerciceKeys(exercice fic.Exercice, body []byte) (interface{}, error) { + return exercice.GetKeys() +} + +func listExerciceQuiz(exercice fic.Exercice, body []byte) (interface{}, error) { + return exercice.GetMCQ() +} + +func showExercice(exercice fic.Exercice, body []byte) (interface{}, error) { + return exercice, nil +} + +func deleteExercice(exercice fic.Exercice, _ []byte) (interface{}, error) { + return exercice.Delete() +} + +func updateExercice(exercice fic.Exercice, body []byte) (interface{}, error) { + var ue fic.Exercice + if err := json.Unmarshal(body, &ue); err != nil { + return nil, err + } + + ue.Id = exercice.Id + + if len(ue.Title) == 0 { + return nil, errors.New("Exercice's title not filled") + } + + if _, err := ue.Update(); err != nil { + return nil, err + } + + return ue, nil +} + +func createExercice(theme fic.Theme, body []byte) (interface{}, error) { + // Create a new exercice + var ue fic.Exercice + if err := json.Unmarshal(body, &ue); err != nil { + return nil, err + } + + if len(ue.Title) == 0 { + return nil, errors.New("Title not filled") + } + + var depend *fic.Exercice = nil + if ue.Depend != nil { + if d, err := fic.GetExercice(*ue.Depend); err != nil { + return nil, err + } else { + depend = &d + } + } + + return theme.AddExercice(ue.Title, ue.Path, ue.Statement, ue.Overview, depend, ue.Gain, ue.VideoURI) +} + + +type uploadedHint struct { + Title string + Path string + Content string + Cost int64 + URI string +} + +func createExerciceHint(exercice fic.Exercice, body []byte) (interface{}, error) { + var uh uploadedHint + if err := json.Unmarshal(body, &uh); err != nil { + return nil, err + } + + if len(uh.Content) != 0 { + return exercice.AddHint(uh.Title, uh.Content, uh.Cost) + } else if len(uh.URI) != 0 { + return sync.ImportFile(sync.GlobalImporter, uh.URI, + func(filePath string, origin string) (interface{}, error) { + return exercice.AddHint(uh.Title, "$FILES" + strings.TrimPrefix(filePath, fic.FilesDir), uh.Cost) + }) + } else { + return nil, errors.New("Hint's content not filled") + } +} + +func showExerciceHint(hint fic.EHint, body []byte) (interface{}, error) { + return hint, nil +} + +func updateExerciceHint(hint fic.EHint, body []byte) (interface{}, error) { + var uh fic.EHint + if err := json.Unmarshal(body, &uh); err != nil { + return nil, err + } + + uh.Id = hint.Id + + if len(uh.Title) == 0 { + return nil, errors.New("Hint's title not filled") + } + + if _, err := uh.Update(); err != nil { + return nil, err + } + + return uh, nil +} + +func deleteExerciceHint(hint fic.EHint, _ []byte) (interface{}, error) { + return hint.Delete() +} + + +type uploadedKey struct { + Label string + Key string + Hash []byte +} + +func createExerciceKey(exercice fic.Exercice, body []byte) (interface{}, error) { + var uk uploadedKey + if err := json.Unmarshal(body, &uk); err != nil { + return nil, err + } + + if len(uk.Key) == 0 { + return nil, errors.New("Key not filled") + } + + return exercice.AddRawKey(uk.Label, uk.Key) +} + +func showExerciceKey(key fic.Key, _ fic.Exercice, body []byte) (interface{}, error) { + return key, nil +} + +func updateExerciceKey(key fic.Key, exercice fic.Exercice, body []byte) (interface{}, error) { + var uk uploadedKey + if err := json.Unmarshal(body, &uk); err != nil { + return nil, err + } + + if len(uk.Label) == 0 { + key.Label = "Flag" + } else { + key.Label = uk.Label + } + + if _, err := key.Update(); err != nil { + return nil, err + } + + return key, nil +} + +func deleteExerciceKey(key fic.Key, _ fic.Exercice, _ []byte) (interface{}, error) { + return key.Delete() +} + + +func showExerciceQuiz(quiz fic.MCQ, _ fic.Exercice, body []byte) (interface{}, error) { + return quiz, nil +} + +func deleteExerciceQuiz(quiz fic.MCQ, _ fic.Exercice, _ []byte) (interface{}, error) { + return quiz.Delete() +} + +func createExerciceFile(exercice fic.Exercice, body []byte) (interface{}, error) { + var uf uploadedFile + if err := json.Unmarshal(body, &uf); err != nil { + return nil, err + } + + return sync.ImportFile(sync.GlobalImporter, uf.URI, + func(filePath string, origin string) (interface{}, error) { + if digest, err := hex.DecodeString(uf.Digest); err != nil { + return nil, err + } else { + return exercice.ImportFile(filePath, origin, digest) + } + }) +} + +func showExerciceFile(file fic.EFile, body []byte) (interface{}, error) { + return file, nil +} + +func deleteExerciceFile(file fic.EFile, _ []byte) (interface{}, error) { + return file.Delete() +} diff --git a/admin/api/file.go b/admin/api/file.go new file mode 100644 index 00000000..e2179371 --- /dev/null +++ b/admin/api/file.go @@ -0,0 +1,8 @@ +package api + +import () + +type uploadedFile struct { + URI string + Digest string +} diff --git a/admin/api/handlers.go b/admin/api/handlers.go new file mode 100644 index 00000000..228970bd --- /dev/null +++ b/admin/api/handlers.go @@ -0,0 +1,253 @@ +package api + +import ( + "encoding/json" + "errors" + "fmt" + "io" + "log" + "net/http" + "strconv" + + "srs.epita.fr/fic-server/libfic" + + "github.com/julienschmidt/httprouter" +) + + +type DispatchFunction func(httprouter.Params, []byte) (interface{}, error) + +func apiHandler(f DispatchFunction) func(http.ResponseWriter, *http.Request, httprouter.Params) { + return func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { + if addr := r.Header.Get("X-Forwarded-For"); addr != "" { + r.RemoteAddr = addr + } + log.Printf("%s \"%s %s\" [%s]\n", r.RemoteAddr, r.Method, r.URL.Path, r.UserAgent()) + + w.Header().Set("Content-Type", "application/json") + + var ret interface{} + var err error = nil + + // Read the body + if r.ContentLength < 0 || r.ContentLength > 6553600 { + http.Error(w, fmt.Sprintf("{errmsg:\"Request too large or request size unknown\"}", err), http.StatusRequestEntityTooLarge) + return + } + var body []byte + if r.ContentLength > 0 { + tmp := make([]byte, 1024) + for { + n, err := r.Body.Read(tmp) + for j := 0; j < n; j++ { + body = append(body, tmp[j]) + } + if err != nil || n <= 0 { + break + } + } + } + + ret, err = f(ps, body) + + // Format response + resStatus := http.StatusOK + if err != nil { + ret = map[string]string{"errmsg": err.Error()} + resStatus = http.StatusBadRequest + log.Println(r.RemoteAddr, resStatus, err.Error()) + } + + if ret == nil { + ret = map[string]string{"errmsg": "Page not found"} + resStatus = http.StatusNotFound + } + + if str, found := ret.(string); found { + w.WriteHeader(resStatus) + io.WriteString(w, str) + } else if bts, found := ret.([]byte); found { + w.WriteHeader(resStatus) + w.Write(bts) + } else if j, err := json.Marshal(ret); err != nil { + http.Error(w, fmt.Sprintf("{\"errmsg\":%q}", err), http.StatusInternalServerError) + } else { + w.WriteHeader(resStatus) + w.Write(j) + } + } +} + +func teamPublicHandler(f func(*fic.Team,[]byte) (interface{}, error)) func (httprouter.Params,[]byte) (interface{}, error) { + return func (ps httprouter.Params, body []byte) (interface{}, error) { + if tid, err := strconv.Atoi(string(ps.ByName("tid"))); err != nil { + if team, err := fic.GetTeamByInitialName(ps.ByName("tid")); err != nil { + return nil, err + } else { + return f(&team, body) + } + } else if tid == 0 { + return f(nil, body) + } else if team, err := fic.GetTeam(tid); err != nil { + return nil, err + } else { + return f(&team, body) + } + } +} + +func teamHandler(f func(fic.Team,[]byte) (interface{}, error)) func (httprouter.Params,[]byte) (interface{}, error) { + return func (ps httprouter.Params, body []byte) (interface{}, error) { + if tid, err := strconv.Atoi(string(ps.ByName("tid"))); err != nil { + if team, err := fic.GetTeamByInitialName(ps.ByName("tid")); err != nil { + return nil, err + } else { + return f(team, body) + } + } else if team, err := fic.GetTeam(tid); err != nil { + return nil, err + } else { + return f(team, body) + } + } +} + +func themeHandler(f func(fic.Theme,[]byte) (interface{}, error)) func (httprouter.Params,[]byte) (interface{}, error) { + return func (ps httprouter.Params, body []byte) (interface{}, error) { + if thid, err := strconv.Atoi(string(ps.ByName("thid"))); err != nil { + return nil, err + } else if theme, err := fic.GetTheme(thid); err != nil { + return nil, err + } else { + return f(theme, body) + } + } +} + +func exerciceHandler(f func(fic.Exercice,[]byte) (interface{}, error)) func (httprouter.Params,[]byte) (interface{}, error) { + return func (ps httprouter.Params, body []byte) (interface{}, error) { + if eid, err := strconv.Atoi(string(ps.ByName("eid"))); err != nil { + return nil, err + } else if exercice, err := fic.GetExercice(int64(eid)); err != nil { + return nil, err + } else { + return f(exercice, body) + } + } +} + +func themedExerciceHandler(f func(fic.Theme,fic.Exercice,[]byte) (interface{}, error)) func (httprouter.Params,[]byte) (interface{}, error) { + return func (ps httprouter.Params, body []byte) (interface{}, error) { + var theme fic.Theme + var exercice fic.Exercice + + themeHandler(func (th fic.Theme, _[]byte) (interface{}, error) { + theme = th + return nil,nil + })(ps, body) + + exerciceHandler(func (ex fic.Exercice, _[]byte) (interface{}, error) { + exercice = ex + return nil,nil + })(ps, body) + + return f(theme, exercice, body) + } +} + +func hintHandler(f func(fic.EHint,[]byte) (interface{}, error)) func (httprouter.Params,[]byte) (interface{}, error) { + return func (ps httprouter.Params, body []byte) (interface{}, error) { + if hid, err := strconv.Atoi(string(ps.ByName("hid"))); err != nil { + return nil, err + } else if hint, err := fic.GetHint(int64(hid)); err != nil { + return nil, err + } else { + return f(hint, body) + } + } +} + +func keyHandler(f func(fic.Key,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) { + exercice = ex + return nil,nil + })(ps, body) + + if kid, err := strconv.Atoi(string(ps.ByName("kid"))); err != nil { + return nil, err + } else if keys, err := exercice.GetKeys(); err != nil { + return nil, err + } else { + for _, key := range keys { + if (key.Id == int64(kid)) { + return f(key, exercice, body) + } + } + return nil, errors.New("Unable to find the requested key") + } + } +} + +func quizHandler(f func(fic.MCQ,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) { + exercice = ex + return nil,nil + })(ps, body) + + if qid, err := strconv.Atoi(string(ps.ByName("qid"))); err != nil { + return nil, err + } else if mcqs, err := exercice.GetMCQ(); err != nil { + return nil, err + } else { + for _, mcq := range mcqs { + if (mcq.Id == int64(qid)) { + return f(mcq, exercice, body) + } + } + return nil, errors.New("Unable to find the requested key") + } + } +} + +func fileHandler(f func(fic.EFile,[]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) { + exercice = ex + return nil,nil + })(ps, body) + + if fid, err := strconv.Atoi(string(ps.ByName("fid"))); err != nil { + return nil, err + } else if files, err := exercice.GetFiles(); err != nil { + return nil, err + } else { + for _, file := range files { + if (file.Id == int64(fid)) { + return f(file, body) + } + } + return nil, errors.New("Unable to find the requested file") + } + } +} + +func eventHandler(f func(fic.Event,[]byte) (interface{}, error)) func (httprouter.Params,[]byte) (interface{}, error) { + return func (ps httprouter.Params, body []byte) (interface{}, error) { + if evid, err := strconv.Atoi(string(ps.ByName("evid"))); err != nil { + return nil, err + } else if event, err := fic.GetEvent(evid); err != nil { + return nil, err + } else { + return f(event, body) + } + } +} + +func notFound(ps httprouter.Params, _ []byte) (interface{}, error) { + return nil, nil +} diff --git a/admin/api/public.go b/admin/api/public.go new file mode 100644 index 00000000..17d2d983 --- /dev/null +++ b/admin/api/public.go @@ -0,0 +1,89 @@ +package api + +import ( + "encoding/json" + "fmt" + "os" + "path" + + "github.com/julienschmidt/httprouter" +) + +var TeamsDir string + +func init() { + router.GET("/api/public/:sid", apiHandler(getPublic)) + router.DELETE("/api/public/:sid", apiHandler(deletePublic)) + router.PUT("/api/public/:sid", apiHandler(savePublic)) +} + +type FICPublicScene struct { + Type string `json:"type"` + Params map[string]interface{} `json:"params"` +} + +func readPublic(path string) ([]FICPublicScene, error) { + var s []FICPublicScene + if fd, err := os.Open(path); err != nil { + return s, err + } else { + defer fd.Close() + jdec := json.NewDecoder(fd) + + if err := jdec.Decode(&s); err != nil { + return s, err + } + + return s, nil + } +} + +func savePublicTo(path string, s []FICPublicScene) error { + if fd, err := os.Create(path); err != nil { + return err + } else { + defer fd.Close() + jenc := json.NewEncoder(fd) + + if err := jenc.Encode(s); err != nil { + return err + } + + return nil + } +} + +func getPublic(ps httprouter.Params, body []byte) (interface{}, error) { + if _, err := os.Stat(path.Join(TeamsDir, "_public", fmt.Sprintf("public%s.json", ps.ByName("sid")))); !os.IsNotExist(err) { + return readPublic(path.Join(TeamsDir, "_public", fmt.Sprintf("public%s.json", ps.ByName("sid")))) + } else { + return []FICPublicScene{}, nil + } +} + +func deletePublic(ps httprouter.Params, body []byte) (interface{}, error) { + if err := savePublicTo(path.Join(TeamsDir, "_public", fmt.Sprintf("public%s.json", ps.ByName("sid"))), []FICPublicScene{}); err != nil { + return nil, err + } else { + return []FICPublicScene{}, err + } +} + +func savePublic(ps httprouter.Params, body []byte) (interface{}, error) { + var scenes []FICPublicScene + if err := json.Unmarshal(body, &scenes); err != nil { + return nil, err + } + + if _, err := os.Stat(path.Join(TeamsDir, "_public")); os.IsNotExist(err) { + if err := os.Mkdir(path.Join(TeamsDir, "_public"), 0750); err != nil { + return nil, err + } + } + + if err := savePublicTo(path.Join(TeamsDir, "_public", fmt.Sprintf("public%s.json", ps.ByName("sid"))), scenes); err != nil { + return nil, err + } else { + return scenes, err + } +} diff --git a/admin/api/router.go b/admin/api/router.go new file mode 100644 index 00000000..a6bd873b --- /dev/null +++ b/admin/api/router.go @@ -0,0 +1,11 @@ +package api + +import ( + "github.com/julienschmidt/httprouter" +) + +var router = httprouter.New() + +func Router() *httprouter.Router { + return router +} diff --git a/admin/api/settings.go b/admin/api/settings.go new file mode 100644 index 00000000..c0bb0fca --- /dev/null +++ b/admin/api/settings.go @@ -0,0 +1,73 @@ +package api + +import ( + "encoding/json" + "errors" + "path" + "time" + + "srs.epita.fr/fic-server/admin/sync" + "srs.epita.fr/fic-server/libfic" + "srs.epita.fr/fic-server/settings" + + "github.com/julienschmidt/httprouter" +) + +func init() { + router.GET("/api/settings-ro.json", apiHandler(getROSettings)) + router.GET("/api/settings.json", apiHandler(getSettings)) + router.PUT("/api/settings.json", apiHandler(saveSettings)) + + router.POST("/api/reset", apiHandler(reset)) +} + +func getROSettings(_ httprouter.Params, body []byte) (interface{}, error) { + syncMtd := "Disabled" + if sync.GlobalImporter != nil { + syncMtd = sync.GlobalImporter.Kind() + } + + return map[string]interface{}{ + "sync": syncMtd, + }, nil +} + +func getSettings(_ httprouter.Params, body []byte) (interface{}, error) { + if settings.ExistsSettings(path.Join(settings.SettingsDir, settings.SettingsFile)) { + return settings.ReadSettings(path.Join(settings.SettingsDir, settings.SettingsFile)) + } else { + return settings.FICSettings{"Challenge FIC", "Laboratoire SRS, ÉPITA", time.Unix(0,0), time.Unix(0,0), time.Unix(0,0), fic.FirstBlood, fic.SubmissionCostBase, false, false, false, true, true}, nil + } +} + +func saveSettings(_ httprouter.Params, body []byte) (interface{}, error) { + var config settings.FICSettings + if err := json.Unmarshal(body, &config); err != nil { + return nil, err + } + + if err := settings.SaveSettings(path.Join(settings.SettingsDir, settings.SettingsFile), config); err != nil { + return nil, err + } else { + return config, err + } +} + +func reset(_ httprouter.Params, body []byte) (interface{}, error) { + var m map[string]string + if err := json.Unmarshal(body, &m); err != nil { + return nil, err + } + + if t, ok := m["type"]; !ok { + return nil, errors.New("Field type not found") + } else if t == "teams" { + return true, fic.ResetTeams() + } else if t == "challenges" { + return true, fic.ResetExercices() + } else if t == "game" { + return true, fic.ResetGame() + } else { + return nil, errors.New("Unknown reset type") + } +} diff --git a/admin/api_stats.go b/admin/api/stats.go similarity index 95% rename from admin/api_stats.go rename to admin/api/stats.go index 887d3b56..36addb45 100644 --- a/admin/api_stats.go +++ b/admin/api/stats.go @@ -1,4 +1,4 @@ -package main +package api import ( "fmt" @@ -30,6 +30,7 @@ func genStats() (interface{}, error) { exos[fmt.Sprintf("%d", exercice.Id)] = fic.ExportedExercice{ exercice.Title, exercice.Gain, + exercice.Coefficient, exercice.SolvedCount(), exercice.TriedTeamCount(), } diff --git a/admin/api/team.go b/admin/api/team.go new file mode 100644 index 00000000..3dd1c017 --- /dev/null +++ b/admin/api/team.go @@ -0,0 +1,213 @@ +package api + +import ( + "encoding/json" + "fmt" + "strconv" + "strings" + + "srs.epita.fr/fic-server/libfic" + + "github.com/julienschmidt/httprouter" +) + +func init() { + router.GET("/api/teams.json", apiHandler( + func(httprouter.Params,[]byte) (interface{}, error) { + return fic.ExportTeams() })) + router.GET("/api/teams-binding", apiHandler( + func(httprouter.Params,[]byte) (interface{}, error) { + return bindingTeams() })) + router.GET("/api/teams-nginx", apiHandler( + func(httprouter.Params,[]byte) (interface{}, error) { + return nginxGenTeam() })) + router.GET("/api/teams-nginx-members", apiHandler( + func(httprouter.Params,[]byte) (interface{}, error) { + return nginxGenMember() })) + router.GET("/api/teams-tries.json", apiHandler( + func(httprouter.Params,[]byte) (interface{}, error) { + return fic.GetTries(nil, nil) })) + + router.GET("/api/teams/", apiHandler( + func(httprouter.Params,[]byte) (interface{}, error) { + return fic.GetTeams() })) + router.POST("/api/teams/", apiHandler(createTeam)) + + router.GET("/api/teams/:tid/", apiHandler(teamHandler( + func(team fic.Team, _ []byte) (interface{}, error) { + return team, nil }))) + router.PUT("/api/teams/:tid/", apiHandler(teamHandler(updateTeam))) + router.POST("/api/teams/:tid/", apiHandler(teamHandler(addTeamMember))) + router.DELETE("/api/teams/:tid/", apiHandler(teamHandler( + func(team fic.Team, _ []byte) (interface{}, error) { + return team.Delete() }))) + router.GET("/api/teams/:tid/my.json", apiHandler(teamPublicHandler( + func(team *fic.Team, _ []byte) (interface{}, error) { + return fic.MyJSONTeam(team, true) }))) + router.GET("/api/teams/:tid/wait.json", apiHandler(teamPublicHandler( + func(team *fic.Team, _ []byte) (interface{}, error) { + return fic.MyJSONTeam(team, false) }))) + router.GET("/api/teams/:tid/stats.json", apiHandler(teamPublicHandler( + func(team *fic.Team, _ []byte) (interface{}, error) { + if team != nil { + return team.GetStats() + } else { + return fic.GetTeamsStats(nil) + } + }))) + router.GET("/api/teams/:tid/history.json", apiHandler(teamPublicHandler( + func(team *fic.Team, _ []byte) (interface{}, error) { + if team != nil { + return team.GetHistory() + } else { + return fic.GetTeamsStats(nil) + } + }))) + router.GET("/api/teams/:tid/tries", apiHandler(teamPublicHandler( + func(team *fic.Team, _ []byte) (interface{}, error) { + return fic.GetTries(team, nil) }))) + router.GET("/api/teams/:tid/members", apiHandler(teamHandler( + func(team fic.Team, _ []byte) (interface{}, error) { + return team.GetMembers() }))) + router.POST("/api/teams/:tid/members", apiHandler(teamHandler(addTeamMember))) + router.PUT("/api/teams/:tid/members", apiHandler(teamHandler(setTeamMember))) + router.GET("/api/teams/:tid/name", apiHandler(teamHandler( + func(team fic.Team, _ []byte) (interface{}, error) { + return team.InitialName, nil }))) + + router.GET("/api/members/:mid/team", apiHandler(dispMemberTeam)) + router.GET("/api/members/:mid/team/name", apiHandler(dispMemberTeamName)) +} + +func nginxGenMember() (string, error) { + if teams, err := fic.GetTeams(); err != nil { + return "", err + } else { + ret := "" + for _, team := range teams { + if members, err := team.GetMembers(); err == nil { + for _, member := range members { + ret += fmt.Sprintf(" if ($remote_user = \"%s\") { set $team \"%s\"; }\n", member.Nickname, team.InitialName) + } + } else { + return "", err + } + } + + return ret, nil + } +} + +func nginxGenTeam() (string, error) { + if teams, err := fic.GetTeams(); err != nil { + return "", err + } else { + ret := "" + for _, team := range teams { + ret += fmt.Sprintf(" if ($ssl_client_s_dn ~ \"/C=FR/ST=France/O=Epita/OU=SRS/CN=%s\") { set $team \"%s\"; }\n", team.InitialName, team.InitialName) + } + + return ret, nil + } +} + +func bindingTeams() (string, error) { + if teams, err := fic.GetTeams(); err != nil { + return "", err + } else { + ret := "" + for _, team := range teams { + if members, err := team.GetMembers(); err != nil { + return "", err + } else { + var mbs []string + for _, member := range members { + mbs = append(mbs, fmt.Sprintf("%s %s", member.Firstname, member.Lastname)) + } + ret += fmt.Sprintf("%d;%s;%s\n", team.Id, team.Name, strings.Join(mbs, ";")) + } + } + return ret, nil + } +} + +type uploadedTeam struct { + Name string + Color uint32 +} + +type uploadedMember struct { + Firstname string + Lastname string + Nickname string + Company string +} + +func createTeam(_ httprouter.Params, body []byte) (interface{}, error) { + var ut uploadedTeam + if err := json.Unmarshal(body, &ut); err != nil { + return nil, err + } + + return fic.CreateTeam(strings.TrimSpace(ut.Name), ut.Color) +} + +func updateTeam(team fic.Team, body []byte) (interface{}, error) { + var ut fic.Team + if err := json.Unmarshal(body, &ut); err != nil { + return nil, err + } + + ut.Id = team.Id + + if _, err := ut.Update(); err != nil { + return nil, err + } + + return ut, nil +} + +func addTeamMember(team fic.Team, body []byte) (interface{}, error) { + var members []uploadedMember + if err := json.Unmarshal(body, &members); err != nil { + return nil, err + } + + for _, member := range members { + team.AddMember(strings.TrimSpace(member.Firstname), strings.TrimSpace(member.Lastname), strings.TrimSpace(member.Nickname), strings.TrimSpace(member.Company)) + } + + return team.GetMembers() +} + +func setTeamMember(team fic.Team, body []byte) (interface{}, error) { + var members []uploadedMember + if err := json.Unmarshal(body, &members); err != nil { + return nil, err + } + + team.ClearMembers() + for _, member := range members { + team.AddMember(strings.TrimSpace(member.Firstname), strings.TrimSpace(member.Lastname), strings.TrimSpace(member.Nickname), strings.TrimSpace(member.Company)) + } + + return team.GetMembers() +} + +func dispMemberTeam(ps httprouter.Params, body []byte) (interface{}, error) { + if mid, err := strconv.Atoi(string(ps.ByName("mid"))); err != nil { + return fic.Team{}, err + } else { + return fic.GetMember(mid) + } +} + +func dispMemberTeamName(ps httprouter.Params, body []byte) (interface{}, error) { + if mid, err := strconv.Atoi(string(ps.ByName("mid"))); err != nil { + return nil, err + } else if team, err := fic.GetMember(mid); err != nil { + return nil, err + } else { + return team.InitialName, nil + } +} diff --git a/admin/api/theme.go b/admin/api/theme.go new file mode 100644 index 00000000..6e6ec069 --- /dev/null +++ b/admin/api/theme.go @@ -0,0 +1,148 @@ +package api + +import ( + "encoding/json" + "errors" + "fmt" + "strconv" + + "srs.epita.fr/fic-server/admin/sync" + "srs.epita.fr/fic-server/libfic" + + "github.com/julienschmidt/httprouter" +) + +func init() { + router.GET("/api/themes", apiHandler(listThemes)) + router.POST("/api/themes", apiHandler(createTheme)) + router.GET("/api/themes.json", apiHandler(exportThemes)) + router.GET("/api/files-bindings", apiHandler(bindingFiles)) + + router.GET("/api/themes/:thid", apiHandler(themeHandler(showTheme))) + router.PUT("/api/themes/:thid", apiHandler(themeHandler(updateTheme))) + router.DELETE("/api/themes/:thid", apiHandler(themeHandler(deleteTheme))) + + router.GET("/api/themes/:thid/exercices", apiHandler(themeHandler(listThemedExercices))) + router.POST("/api/themes/:thid/exercices", apiHandler(themeHandler(createExercice))) + + router.GET("/api/themes/:thid/exercices/:eid", apiHandler(exerciceHandler(showExercice))) + router.PUT("/api/themes/:thid/exercices/:eid", apiHandler(exerciceHandler(updateExercice))) + router.DELETE("/api/themes/:thid/exercices/:eid", apiHandler(exerciceHandler(deleteExercice))) + + + router.GET("/api/themes/:thid/exercices/:eid/files", apiHandler(exerciceHandler(listExerciceFiles))) + router.POST("/api/themes/:thid/exercices/:eid/files", apiHandler(exerciceHandler(createExerciceFile))) + + router.GET("/api/themes/:thid/exercices/:eid/hints", apiHandler(exerciceHandler(listExerciceHints))) + router.POST("/api/themes/:thid/exercices/:eid/hints", apiHandler(exerciceHandler(createExerciceHint))) + + router.GET("/api/themes/:thid/exercices/:eid/keys", apiHandler(exerciceHandler(listExerciceKeys))) + router.POST("/api/themes/:thid/exercices/:eid/keys", apiHandler(exerciceHandler(createExerciceKey))) + + // Remote + router.GET("/api/remote/themes", apiHandler(sync.ApiListRemoteThemes)) + router.GET("/api/remote/themes/:thid", apiHandler(sync.ApiGetRemoteTheme)) + router.GET("/api/remote/themes/:thid/exercices", apiHandler(themeHandler(sync.ApiListRemoteExercices))) + + // Synchronize + router.GET("/api/sync/deep", apiHandler( + func(_ httprouter.Params, _ []byte) (interface{}, error) { return sync.SyncDeep(sync.GlobalImporter), nil })) + router.GET("/api/sync/themes", apiHandler( + func(_ httprouter.Params, _ []byte) (interface{}, error) { return sync.SyncThemes(sync.GlobalImporter), nil })) + router.GET("/api/sync/themes/:thid/exercices", apiHandler(themeHandler( + func(theme fic.Theme, _ []byte) (interface{}, error) { return sync.SyncExercices(sync.GlobalImporter, theme), nil }))) + router.GET("/api/sync/themes/:thid/exercices/:eid/files", apiHandler(exerciceHandler( + func(exercice fic.Exercice, _ []byte) (interface{}, error) { return sync.SyncExerciceFiles(sync.GlobalImporter, exercice), nil }))) + router.GET("/api/sync/themes/:thid/exercices/:eid/hints", apiHandler(exerciceHandler( + func(exercice fic.Exercice, _ []byte) (interface{}, error) { return sync.SyncExerciceHints(sync.GlobalImporter, exercice), nil }))) + router.GET("/api/sync/themes/:thid/exercices/:eid/keys", apiHandler(exerciceHandler( + func(exercice fic.Exercice, _ []byte) (interface{}, error) { return sync.SyncExerciceKeys(sync.GlobalImporter, exercice), nil }))) + router.GET("/api/sync/themes/:thid/exercices/:eid/quiz", apiHandler(exerciceHandler( + func(exercice fic.Exercice, _ []byte) (interface{}, error) { return sync.SyncExerciceMCQ(sync.GlobalImporter, exercice), nil }))) +} + +func bindingFiles(_ httprouter.Params, body []byte) (interface{}, error) { + if files, err := fic.GetFiles(); err != nil { + return "", err + } else { + ret := "" + for _, file := range files { + ret += fmt.Sprintf("%s;%s\n", file.GetOrigin(), file.Path) + } + return ret, nil + } +} + +func getExercice(args []string) (fic.Exercice, error) { + if tid, err := strconv.Atoi(string(args[0])); err != nil { + return fic.Exercice{}, err + } else if theme, err := fic.GetTheme(tid); err != nil { + return fic.Exercice{}, err + } else if eid, err := strconv.Atoi(string(args[1])); err != nil { + return fic.Exercice{}, err + } else { + return theme.GetExercice(eid) + } +} + +func listThemes(_ httprouter.Params, _ []byte) (interface{}, error) { + return fic.GetThemes() +} + +func exportThemes(_ httprouter.Params, _ []byte) (interface{}, error) { + return fic.ExportThemes() +} + +func showTheme(theme fic.Theme, _ []byte) (interface{}, error) { + return theme, nil +} + +func listThemedExercices(theme fic.Theme, _ []byte) (interface{}, error) { + return theme.GetExercices() +} + +func showThemedExercice(theme fic.Theme, exercice fic.Exercice, body []byte) (interface{}, error) { + return exercice, nil +} + + +type uploadedTheme struct { + Name string + Authors string +} + +func createTheme(_ httprouter.Params, body []byte) (interface{}, error) { + var ut uploadedTheme + if err := json.Unmarshal(body, &ut); err != nil { + return nil, err + } + + if len(ut.Name) == 0 { + return nil, errors.New("Theme's name not filled") + } + + return fic.CreateTheme(ut.Name, ut.Authors) +} + +func updateTheme(theme fic.Theme, body []byte) (interface{}, error) { + var ut fic.Theme + if err := json.Unmarshal(body, &ut); err != nil { + return nil, err + } + + ut.Id = theme.Id + + if len(ut.Name) == 0 { + return nil, errors.New("Theme's name not filled") + } + + if _, err := ut.Update(); err != nil { + return nil, err + } else { + return ut, nil + } +} + +func deleteTheme(theme fic.Theme, _ []byte) (interface{}, error) { + return theme.Delete() +} diff --git a/admin/api/version.go b/admin/api/version.go new file mode 100644 index 00000000..d99314f8 --- /dev/null +++ b/admin/api/version.go @@ -0,0 +1,13 @@ +package api + +import ( + "github.com/julienschmidt/httprouter" +) + +func init() { + router.GET("/api/version", apiHandler(showVersion)) +} + +func showVersion(_ httprouter.Params, body []byte) (interface{}, error) { + return map[string]interface{}{"version": 0.4}, nil +} diff --git a/admin/api_certificate.go b/admin/api_certificate.go deleted file mode 100644 index 68f6af93..00000000 --- a/admin/api_certificate.go +++ /dev/null @@ -1,32 +0,0 @@ -package main - -import ( - "io/ioutil" - "os" - - "srs.epita.fr/fic-server/libfic" -) - -func CertificateAPI(team fic.Team, args []string) (interface{}, error) { - if len(args) == 1 { - if args[0] == "generate" { - return team.GenerateCert(), nil - } else if args[0] == "revoke" { - return team.RevokeCert(), nil - } else { - return nil, nil - } - } else if fd, err := os.Open("../PKI/pkcs/" + team.Name + ".p12"); err == nil { - return ioutil.ReadAll(fd) - } else { - return nil, err - } -} - -var ApiCARouting = map[string]DispatchFunction{ - "GET": genCA, -} - -func genCA(args []string, body []byte) (interface{}, error) { - return fic.GenerateCA(), nil -} diff --git a/admin/api_events.go b/admin/api_events.go deleted file mode 100644 index 66958376..00000000 --- a/admin/api_events.go +++ /dev/null @@ -1,17 +0,0 @@ -package main - -import ( - "srs.epita.fr/fic-server/libfic" -) - -var ApiEventsRouting = map[string]DispatchFunction{ - "GET": getEvents, -} - -func getEvents(args []string, body []byte) (interface{}, error) { - if evts, err := fic.GetEvents(); err != nil { - return nil, err - } else { - return evts, nil - } -} diff --git a/admin/api_exercice.go b/admin/api_exercice.go deleted file mode 100644 index 5dadccc2..00000000 --- a/admin/api_exercice.go +++ /dev/null @@ -1,144 +0,0 @@ -package main - -import ( - "encoding/json" - "errors" - "strconv" - - "srs.epita.fr/fic-server/libfic" -) - -var ApiExercicesRouting = map[string]DispatchFunction{ - "GET": listExercice, - "PATCH": updateExercice, - "DELETE": deletionExercice, -} - -func listExercice(args []string, body []byte) (interface{}, error) { - if len(args) == 1 { - if eid, err := strconv.Atoi(string(args[0])); err != nil { - return nil, err - } else { - return fic.GetExercice(int64(eid)) - } - } else { - // List all exercices - return fic.GetExercices() - } -} - -func deletionExercice(args []string, body []byte) (interface{}, error) { - if len(args) == 1 { - if eid, err := strconv.Atoi(string(args[0])); err != nil { - return nil, err - } else if exercice, err := fic.GetExercice(int64(eid)); err != nil { - return nil, err - } else { - return exercice.Delete() - } - } else { - return nil, nil - } -} - -type uploadedExercice struct { - Title string - Statement string - Hint string - Depend *int64 - Gain int - VideoURI string -} - -func updateExercice(args []string, body []byte) (interface{}, error) { - if len(args) == 1 { - if eid, err := strconv.Atoi(string(args[0])); err != nil { - return nil, err - } else if exercice, err := fic.GetExercice(int64(eid)); err != nil { - return nil, err - } else { - // Update an exercice - var ue uploadedExercice - if err := json.Unmarshal(body, &ue); err != nil { - return nil, err - } - - if len(ue.Title) == 0 { - return nil, errors.New("Exercice's title not filled") - } - - if ue.Depend != nil { - if _, err := fic.GetExercice(*ue.Depend); err != nil { - return nil, err - } - } - - exercice.Title = ue.Title - exercice.Statement = ue.Statement - exercice.Hint = ue.Hint - exercice.Depend = ue.Depend - exercice.Gain = int64(ue.Gain) - exercice.VideoURI = ue.VideoURI - - return exercice.Update() - } - } else { - return nil, nil - } -} - -func createExercice(theme fic.Theme, args []string, body []byte) (interface{}, error) { - if len(args) >= 1 { - if eid, err := strconv.Atoi(args[0]); err != nil { - return nil, err - } else if exercice, err := theme.GetExercice(eid); err != nil { - return nil, err - } else { - if args[1] == "files" { - return createExerciceFile(theme, exercice, args[2:], body) - } else if args[1] == "keys" { - return createExerciceKey(theme, exercice, args[2:], body) - } - } - return nil, nil - } else { - // Create a new exercice - var ue uploadedExercice - if err := json.Unmarshal(body, &ue); err != nil { - return nil, err - } - - if len(ue.Title) == 0 { - return nil, errors.New("Title not filled") - } - - var depend *fic.Exercice = nil - if ue.Depend != nil { - if d, err := fic.GetExercice(*ue.Depend); err != nil { - return nil, err - } else { - depend = &d - } - } - - return theme.AddExercice(ue.Title, ue.Statement, ue.Hint, depend, ue.Gain, ue.VideoURI) - } -} - -type uploadedKey struct { - Name string - Key string -} - -func createExerciceKey(theme fic.Theme, exercice fic.Exercice, args []string, body []byte) (interface{}, error) { - var uk uploadedKey - if err := json.Unmarshal(body, &uk); err != nil { - return nil, err - } - - if len(uk.Key) == 0 { - return nil, errors.New("Key not filled") - } - - return exercice.AddRawKey(uk.Name, uk.Key) -} diff --git a/admin/api_file.go b/admin/api_file.go deleted file mode 100644 index f2bb8129..00000000 --- a/admin/api_file.go +++ /dev/null @@ -1,85 +0,0 @@ -package main - -import ( - "bufio" - "crypto/sha512" - "encoding/base32" - "encoding/json" - "errors" - "log" - "net/http" - "os" - "path" - "strings" - - "srs.epita.fr/fic-server/libfic" -) - -func createExerciceFile(theme fic.Theme, exercice fic.Exercice, args []string, body []byte) (interface{}, error) { - var uf map[string]string - if err := json.Unmarshal(body, &uf); err != nil { - return nil, err - } - - var hash [sha512.Size]byte - var logStr string - var fromURI string - var getFile func(string) error - - if URI, ok := uf["URI"]; ok { - hash = sha512.Sum512([]byte(URI)) - logStr = "Import file from Cloud: " + URI + " =>" - fromURI = URI - getFile = func(dest string) error { return getCloudFile(URI, dest) } - } else if path, ok := uf["path"]; ok { - hash = sha512.Sum512([]byte(path)) - logStr = "Import file from local FS: " + path + " =>" - fromURI = path - getFile = func(dest string) error { return os.Symlink(path, dest) } - } else { - return nil, errors.New("URI or path not filled") - } - - pathname := path.Join(fic.FilesDir, strings.ToLower(base32.StdEncoding.EncodeToString(hash[:])), path.Base(fromURI)) - - if _, err := os.Stat(pathname); os.IsNotExist(err) { - log.Println(logStr, pathname) - if err := os.MkdirAll(path.Dir(pathname), 0777); err != nil { - return nil, err - } else if err := getFile(pathname); err != nil { - return nil, err - } - } - - return exercice.ImportFile(pathname, fromURI) -} - -func getCloudFile(pathname string, dest string) error { - client := http.Client{} - if req, err := http.NewRequest("GET", CloudDAVBase+pathname, nil); err != nil { - return err - } else { - req.SetBasicAuth(CloudUsername, CloudPassword) - if resp, err := client.Do(req); err != nil { - return err - } else { - defer resp.Body.Close() - - if fd, err := os.Create(dest); err != nil { - return err - } else { - defer fd.Close() - - if resp.StatusCode != http.StatusOK { - return errors.New(resp.Status) - } else { - writer := bufio.NewWriter(fd) - reader := bufio.NewReader(resp.Body) - reader.WriteTo(writer) - writer.Flush() - } - } - } - } - return nil -} diff --git a/admin/api_team.go b/admin/api_team.go deleted file mode 100644 index 95e78838..00000000 --- a/admin/api_team.go +++ /dev/null @@ -1,231 +0,0 @@ -package main - -import ( - "encoding/json" - "fmt" - "strconv" - "strings" - - "srs.epita.fr/fic-server/libfic" -) - -var ApiTeamsRouting = map[string]DispatchFunction{ - "GET": listTeam, - "PUT": creationTeamMembers, - "POST": creationTeam, - "DELETE": deletionTeam, -} - -func nginxGenMember() (string, error) { - if teams, err := fic.GetTeams(); err != nil { - return "", err - } else { - ret := "" - for _, team := range teams { - if members, err := team.GetMembers(); err == nil { - for _, member := range members { - ret += fmt.Sprintf(" if ($remote_user = \"%s\") { set $team \"%s\"; }\n", member.Nickname, team.InitialName) - } - } else { - return "", err - } - } - - return ret, nil - } -} - -func nginxGenTeam() (string, error) { - if teams, err := fic.GetTeams(); err != nil { - return "", err - } else { - ret := "" - for _, team := range teams { - ret += fmt.Sprintf(" if ($ssl_client_s_dn ~ \"/C=FR/ST=France/O=Epita/OU=SRS/CN=%s\") { set $team \"%s\"; }\n", team.InitialName, team.InitialName) - } - - return ret, nil - } -} - -func bindingTeams() (string, error) { - if teams, err := fic.GetTeams(); err != nil { - return "", err - } else { - ret := "" - for _, team := range teams { - if members, err := team.GetMembers(); err != nil { - return "", err - } else { - var mbs []string - for _, member := range members { - mbs = append(mbs, fmt.Sprintf("%s %s", member.Firstname, member.Lastname)) - } - ret += fmt.Sprintf("%d;%s;%s\n", team.Id, team.Name, strings.Join(mbs, ";")) - } - } - return ret, nil - } -} - -type uploadedTeam struct { - Name string - Color uint32 -} - -type uploadedMember struct { - Firstname string - Lastname string - Nickname string - Company string -} - -func listTeam(args []string, body []byte) (interface{}, error) { - if len(args) >= 2 { - var team *fic.Team - if tid, err := strconv.Atoi(args[0]); err != nil { - if t, err := fic.GetTeamByInitialName(args[0]); err != nil { - return nil, err - } else { - team = &t - } - } else { - if tid == 0 { - team = nil - } else if t, err := fic.GetTeam(tid); err != nil { - return nil, err - } else { - team = &t - } - - } - - if args[1] == "my.json" { - return fic.MyJSONTeam(team, true) - } else if args[1] == "wait.json" { - return fic.MyJSONTeam(team, false) - } else if args[1] == "stats.json" { - if team != nil { - return team.GetStats() - } else { - return fic.GetTeamsStats(nil) - } - } else if args[1] == "tries" { - return fic.GetTries(team, nil) - } else if team != nil && args[1] == "members" { - return team.GetMembers() - } else if args[1] == "certificate" && team != nil { - return CertificateAPI(*team, args[2:]) - } else if team != nil && args[1] == "name" { - return team.Name, nil - } - } else if len(args) == 1 { - if args[0] == "teams.json" { - return fic.ExportTeams() - } else if args[0] == "tries" { - return fic.GetTries(nil, nil) - } else if args[0] == "nginx" { - return nginxGenTeam() - } else if args[0] == "nginx-members" { - return nginxGenMember() - } else if args[0] == "binding" { - return bindingTeams() - } else if tid, err := strconv.Atoi(string(args[0])); err != nil { - return fic.GetTeamByInitialName(args[0]) - } else if team, err := fic.GetTeam(tid); err != nil { - return nil, err - } else { - return team, nil - } - } else if len(args) == 0 { - // List all teams - return fic.GetTeams() - } - return nil, nil -} - -func creationTeam(args []string, body []byte) (interface{}, error) { - if len(args) == 1 { - // List given team - if tid, err := strconv.Atoi(string(args[0])); err != nil { - return nil, err - } else if team, err := fic.GetTeam(tid); err != nil { - return nil, err - } else { - var members []uploadedMember - if err := json.Unmarshal(body, &members); err != nil { - return nil, err - } - - for _, member := range members { - team.AddMember(member.Firstname, member.Lastname, member.Nickname, member.Company) - } - - return team.GetMembers() - } - } else if len(args) == 0 { - // Create a new team - var ut uploadedTeam - if err := json.Unmarshal(body, &ut); err != nil { - return nil, err - } - - return fic.CreateTeam(ut.Name, ut.Color) - } else { - return nil, nil - } -} - -func creationTeamMembers(args []string, body []byte) (interface{}, error) { - if len(args) == 1 { - // List given team - if tid, err := strconv.Atoi(string(args[0])); err != nil { - return nil, err - } else if team, err := fic.GetTeam(tid); err != nil { - return nil, err - } else { - var member uploadedMember - if err := json.Unmarshal(body, &member); err != nil { - return nil, err - } - - team.AddMember(member.Firstname, member.Lastname, member.Nickname, member.Company) - - return team.GetMembers() - } - } else if len(args) == 0 { - // Create a new team - var members []uploadedMember - if err := json.Unmarshal(body, &members); err != nil { - return nil, err - } - - if team, err := fic.CreateTeam("", 0); err != nil { - return nil, err - } else { - for _, member := range members { - if _, err := team.AddMember(member.Firstname, member.Lastname, member.Nickname, member.Company); err != nil { - return nil, err - } - } - - return team, nil - } - } else { - return nil, nil - } -} - -func deletionTeam(args []string, body []byte) (interface{}, error) { - if len(args) == 1 { - if tid, err := strconv.Atoi(string(args[0])); err != nil { - return nil, err - } else if team, err := fic.GetTeam(tid); err != nil { - return nil, err - } else { - return team.Delete() - } - } else { - return nil, nil - } -} diff --git a/admin/api_theme.go b/admin/api_theme.go deleted file mode 100644 index a023b217..00000000 --- a/admin/api_theme.go +++ /dev/null @@ -1,164 +0,0 @@ -package main - -import ( - "encoding/json" - "errors" - "fmt" - "strconv" - - "srs.epita.fr/fic-server/libfic" -) - -var ApiThemesRouting = map[string]DispatchFunction{ - "GET": listTheme, - "PATCH": updateTheme, - "POST": creationTheme, - "DELETE": deletionTheme, -} - -func bindingFiles() (string, error) { - if files, err := fic.GetFiles(); err != nil { - return "", err - } else { - ret := "" - for _, file := range files { - ret += fmt.Sprintf("%s;%s\n", file.GetOrigin(), file.Path) - } - return ret, nil - } -} - -func getTheme(args []string) (fic.Theme, error) { - if tid, err := strconv.Atoi(string(args[0])); err != nil { - return fic.Theme{}, err - } else { - return fic.GetTheme(tid) - } -} - -func getExercice(args []string) (fic.Exercice, error) { - if theme, err := getTheme(args); err != nil { - return fic.Exercice{}, err - } else if eid, err := strconv.Atoi(string(args[1])); err != nil { - return fic.Exercice{}, err - } else { - return theme.GetExercice(eid) - } -} - -func listTheme(args []string, body []byte) (interface{}, error) { - if len(args) == 3 { - if e, err := getExercice(args); err != nil { - return nil, err - } else { - if args[2] == "files" { - return e.GetFiles() - } else if args[2] == "keys" { - return e.GetKeys() - } - } - } else if len(args) == 2 { - if args[1] == "exercices" { - if theme, err := getTheme(args); err != nil { - return nil, err - } else { - return theme.GetExercices() - } - } else { - return getExercice(args) - } - } else if len(args) == 1 { - if args[0] == "files-bindings" { - return bindingFiles() - } else if args[0] == "themes.json" { - return fic.ExportThemes() - } else { - return getTheme(args) - } - } else if len(args) == 0 { - // List all themes - return fic.GetThemes() - } - return nil, nil -} - -type uploadedTheme struct { - Name string - Authors string -} - -func creationTheme(args []string, body []byte) (interface{}, error) { - if len(args) >= 1 { - if theme, err := getTheme(args); err != nil { - return nil, err - } else { - return createExercice(theme, args[1:], body) - } - } else if len(args) == 0 { - // Create a new theme - var ut uploadedTheme - if err := json.Unmarshal(body, &ut); err != nil { - return nil, err - } - - if len(ut.Name) == 0 { - return nil, errors.New("Theme's name not filled") - } - - return fic.CreateTheme(ut.Name, ut.Authors) - } else { - return nil, nil - } -} - -func updateTheme(args []string, body []byte) (interface{}, error) { - if len(args) == 2 { - // Update an exercice - var ue fic.Exercice - if err := json.Unmarshal(body, &ue); err != nil { - return nil, err - } - - if len(ue.Title) == 0 { - return nil, errors.New("Exercice's title not filled") - } - - if _, err := ue.Update(); err != nil { - return nil, err - } - - return ue, nil - } else if len(args) == 1 { - // Update a theme - var ut fic.Theme - if err := json.Unmarshal(body, &ut); err != nil { - return nil, err - } - - if len(ut.Name) == 0 { - return nil, errors.New("Theme's name not filled") - } - - return ut.Update() - } else { - return nil, nil - } -} - -func deletionTheme(args []string, body []byte) (interface{}, error) { - if len(args) == 2 { - if exercice, err := getExercice(args); err != nil { - return nil, err - } else { - return exercice.Delete() - } - } else if len(args) == 1 { - if theme, err := getTheme(args); err != nil { - return nil, err - } else { - return theme.Delete() - } - } else { - return nil, nil - } -} diff --git a/admin/api_version.go b/admin/api_version.go deleted file mode 100644 index 9b4cbbfb..00000000 --- a/admin/api_version.go +++ /dev/null @@ -1,11 +0,0 @@ -package main - -import () - -var ApiVersionRouting = map[string]DispatchFunction{ - "GET": showVersion, -} - -func showVersion(args []string, body []byte) (interface{}, error) { - return map[string]interface{}{"version": 0.1}, nil -} diff --git a/admin/fill_exercices.sh b/admin/fill_exercices.sh index 797c1352..e2ce2141 100755 --- a/admin/fill_exercices.sh +++ b/admin/fill_exercices.sh @@ -1,14 +1,14 @@ #!/bin/bash -BASEURL="http://localhost:8081/admin" -BASEURI="https://owncloud.srs.epita.fr/remote.php/webdav/FIC 2017" +BASEURL="http://localhost:8081" +BASEURI="https://owncloud.srs.epita.fr/remote.php/webdav/FIC 2018" BASEFILE="/mnt/fic/" -CLOUDPASS=fic:'f>t\nV33R|(+?$i*' +CLOUDPASS="$CLOUD_USER:$CLOUD_PASS" new_theme() { NAME=`echo $1 | sed 's/"/\\\\"/g'` AUTHORS=`echo $2 | sed 's/"/\\\\"/g'` - curl -f -s -d "{\"name\": \"$NAME\", \"authors\": \"$AUTHORS\"}" "${BASEURL}/api/themes/" | + curl -f -s -d "{\"name\": \"$NAME\", \"authors\": \"$AUTHORS\"}" "${BASEURL}/api/themes" | grep -Eo '"id":[0-9]+,' | grep -Eo "[0-9]+" } @@ -16,32 +16,65 @@ new_exercice() { THEME="$1" TITLE=`echo "$2" | sed 's/"/\\\\"/g'` STATEMENT=`echo "$3" | sed 's/"/\\\\"/g' | sed ':a;N;$!ba;s/\n/
/g'` - HINT=`echo "$4" | sed 's/"/\\\\"/g' | sed ':a;N;$!ba;s/\n/
/g'` - DEPEND="$5" - GAIN="$6" - VIDEO="$7" + DEPEND="$4" + GAIN="$5" + VIDEO="$6" - curl -f -s -d "{\"title\": \"$TITLE\", \"statement\": \"$STATEMENT\", \"hint\": \"$HINT\", \"depend\": $DEPEND, \"gain\": $GAIN, \"videoURI\": \"$VIDEO\"}" "${BASEURL}/api/themes/$THEME" | + curl -f -s -d "{\"title\": \"$TITLE\", \"statement\": \"$STATEMENT\", \"depend\": $DEPEND, \"gain\": $GAIN, \"videoURI\": \"$VIDEO\"}" "${BASEURL}/api/themes/$THEME/exercices" | grep -Eo '"id":[0-9]+,' | grep -Eo "[0-9]+" } -new_file() { +new_file() ( THEME="$1" EXERCICE="$2" URI="$3" + DIGEST="$4" + ARGS="$5" + + FIRST= + PARTS=$(echo "$ARGS" | while read arg + do + [ -n "$arg" ] && { + [ -z "${FIRST}" ] || echo -n "," + echo "\"$arg\"" + } + FIRST=1 + done) + + [ -n "${DIGEST}" ] && DIGEST=", \"digest\": \"${DIGEST}\"" + + cat <&2 +{"path": "${BASEFILE}${URI}"${DIGEST}, "parts": [${PARTS}]} +EOF # curl -f -s -d "{\"URI\": \"${BASEFILE}${URI}\"}" "${BASEURL}/api/themes/$THEME/$EXERCICE/files" | - curl -f -s -d "{\"path\": \"${BASEFILE}${URI}\"}" "${BASEURL}/api/themes/$THEME/$EXERCICE/files" | + curl -f -s -d @- "${BASEURL}/api/themes/$THEME/exercices/$EXERCICE/files" </g'` + COST="$5" + URI="$6" + + [ -n "${CONTENT}" ] && CONTENT=", \"content\": \"${CONTENT}\"" + [ -n "${URI}" ] && URI=", \"path\": \"${BASEFILE}${URI}\"" + + curl -f -s -d "{\"title\": \"$TITLE\"$CONTENT$URI, \"cost\": $COST}" "${BASEURL}/api/themes/$THEME/exercices/$EXERCICE/hints" | grep -Eo '"id":[0-9]+,' | grep -Eo "[0-9]+" } new_key() { THEME="$1" EXERCICE="$2" - NAME="$3" - KEY=`echo $4 | sed 's/"/\\\\"/g' | sed 's#\\\\#\\\\\\\\#g'` + TYPE="$3" + KEY=`echo $4 | sed 's#\\\\#\\\\\\\\#g' | sed 's/"/\\\\"/g'` - curl -f -s -d "{\"name\": \"$NAME\", \"key\": \"$KEY\"}" "${BASEURL}/api/themes/$THEME/$EXERCICE/keys" | + curl -f -s -d "{\"type\": \"$TYPE\", \"key\": \"$KEY\"}" "${BASEURL}/api/themes/$THEME/exercices/$EXERCICE/keys" | grep -Eo '"id":[0-9]+,' | grep -Eo "[0-9]+" } @@ -49,7 +82,7 @@ get_dir_from_cloud() { curl -f -s -X PROPFIND -u "${CLOUDPASS}" "${BASEURI}$1" | xmllint --format - | grep 'd:href' | sed -E 's/^.*>(.*)<.*$/\1/' } get_dir() { - ls "${BASEFILE}$1" + ls "${BASEFILE}$1" 2> /dev/null } #alias get_dir=get_dir_from_cloud @@ -57,7 +90,8 @@ get_file_from_cloud() { curl -f -s -u "${CLOUDPASS}" "${BASEURI}$1" | tr -d '\r' } get_file() { - cat "${BASEFILE}$1" | tr -d '\r' + cat "${BASEFILE}$1" 2> /dev/null | tr -d '\r' + echo } #alias get_file=get_file_from_cloud @@ -66,11 +100,17 @@ unhtmlentities() { } # Theme -get_dir "" | while read f; do basename "$f"; done | while read THEME_URI +{ + if [ $# -ge 1 ]; then + echo $1 + else + get_dir "" + fi +} | while read f; do basename "$f"; done | while read THEME_URI do THM_BASEURI="/${THEME_URI}/" THEME_NAME=$(echo "${THEME_URI#*-}" | unhtmlentities) - THEME_AUTHORS=$(get_file "${THM_BASEURI}/AUTHORS.txt" | sed 's/$/, /' | tr -d '\n' | sed 's/, $//') + THEME_AUTHORS=$(get_file "${THM_BASEURI}/AUTHORS.txt" | sed '/^$/d;s/$/, /' | tr -d '\n' | sed 's/, $//') THEME_ID=`new_theme "$THEME_NAME" "$THEME_AUTHORS"` if [ -z "$THEME_ID" ]; then echo -e "\e[31;01m!!! An error occured during theme add\e[00m" @@ -81,7 +121,13 @@ do LAST=null EXO_NUM=0 - get_dir "${THM_BASEURI}" | sed 1d | while read f; do basename "$f"; done | while read EXO_URI + { + if [ $# -ge 2 ]; then + echo "$2" + else + get_dir "${THM_BASEURI}" + fi + } | while read f; do basename "$f"; done | while read EXO_URI do case ${EXO_URI} in [0-9]-*) @@ -98,7 +144,8 @@ do EXO_BASEURI="${EXO_URI}/" - EXO_VIDEO=$(get_dir "${THM_BASEURI}${EXO_BASEURI}/resolution/" | grep -E "\.(mov|mkv|mp4|avi|flv|ogv|webm)$" | while read f; do basename $f; done | tail -1) + EXO_VIDEO=$(get_dir "${THM_BASEURI}${EXO_BASEURI}/resolution/" | grep -E "\.(mov|mkv|mp4|avi|flv|ogv|webm)$" | while read f; do basename "$f"; done | tail -1) + [ -n "$EXO_VIDEO" ] && EXO_VIDEO="/resolution${THM_BASEURI}${EXO_BASEURI}resolution/${EXO_VIDEO}" if [ "${LAST}" = "null" ]; then echo ">>> Assuming this exercice has no dependency" @@ -107,12 +154,12 @@ do fi EXO_GAIN=$((3 * (2 ** $EXO_NUM) - 1)) + HINT_COST=$(($EXO_GAIN / 4)) echo ">>> Using default gain: ${EXO_GAIN} points" EXO_SCENARIO=$(get_file "${THM_BASEURI}${EXO_BASEURI}/scenario.txt") - EXO_HINT=$(get_file "${THM_BASEURI}${EXO_BASEURI}/hint.txt") - EXO_ID=`new_exercice "${THEME_ID}" "${EXO_NAME}" "${EXO_SCENARIO}" "${EXO_HINT}" "${LAST}" "${EXO_GAIN}" "/resolution${THM_BASEURI}${EXO_BASEURI}resolution/${EXO_VIDEO}"` + EXO_ID=`new_exercice "${THEME_ID}" "${EXO_NAME}" "${EXO_SCENARIO}" "${LAST}" "${EXO_GAIN}" "${EXO_VIDEO}"` if [ -z "$EXO_ID" ]; then echo -e "\e[31;01m!!! An error occured during exercice add.\e[00m" continue @@ -124,8 +171,15 @@ do # Keys get_file "${THM_BASEURI}${EXO_BASEURI}/flags.txt" | while read KEYLINE do - KEY_NAME=$(echo "$KEYLINE" | cut -d : -f 1) - KEY_RAW=$(echo "$KEYLINE" | cut -d : -f 2-) + [ -z "${KEYLINE}" ] && continue + + KEY_NAME=$(echo "$KEYLINE" | cut -d$'\t' -f 1) + KEY_RAW=$(echo "$KEYLINE" | cut -d$'\t' -f 2-) + + if [ -z "${KEY_RAW}" ] || [ "${KEY_NAME}" = "${KEY_RAW}" ]; then + KEY_NAME=$(echo "$KEYLINE" | cut -d : -f 1) + KEY_RAW=$(echo "$KEYLINE" | cut -d : -f 2-) + fi if [ -z "${KEY_NAME}" ]; then KEY_NAME="Flag" @@ -140,11 +194,60 @@ do done - # Files - get_dir "${THM_BASEURI}${EXO_BASEURI}files/" | grep -v DIGESTS.txt | while read f; do basename "$f"; done | while read FILE_URI + # Hints + HINTS=$(get_dir "${THM_BASEURI}${EXO_BASEURI}/hints/" | sed -E 's#(.*)#hints/\1#') + [ -z "${HINTS}" ] && HINTS=$(get_dir "${THM_BASEURI}${EXO_BASEURI}/" | grep ^hint.) + [ -z "${HINTS}" ] && HINTS="hint.txt" + HINT_COUNT=1 + echo "${HINTS}" | while read HINT do + EXO_HINT=$(get_file "${THM_BASEURI}${EXO_BASEURI}/${HINT}") + if [ -n "$EXO_HINT" ]; then + EXO_HINT_TYPE=$(echo "${EXO_HINT}" | file --mime-type -b -) + if echo "${EXO_HINT_TYPE}" | grep text/ && [ $(echo "${EXO_HINT}" | wc -l) -lt 25 ]; then + HINT_ID=`new_hint "${THEME_ID}" "${EXO_ID}" "Astuce #${HINT_COUNT}" "${EXO_HINT}" "${HINT_COST}"` + else + HINT_ID=`new_hint "${THEME_ID}" "${EXO_ID}" "Astuce #${HINT_COUNT}" "" "${HINT_COST}" "${THM_BASEURI}${EXO_BASEURI}/${HINT}"` + fi + + if [ -z "$HINT_ID" ]; then + echo -e "\e[31;01m!!! An error occured during hint import!\e[00m (title=Astuce #${HINT_COUNT};content::${EXO_HINT_TYPE};cost=${HINT_COST})" + else + echo -e "\e[32m>>> New hint added:\e[00m $HINT_ID - Astuce #${HINT_COUNT}" + fi + fi + HINT_COUNT=$(($HINT_COUNT + 1)) + done + + + # Files: splited + get_dir "${THM_BASEURI}${EXO_BASEURI}files/" | grep -v DIGESTS.txt | grep '[0-9][0-9]$' | sed -E 's/\.?([0-9][0-9])$//' | sort | uniq | while read f; do basename "$f"; done | while read FILE_URI + do + DIGEST=$(get_file "${THM_BASEURI}${EXO_BASEURI}files/DIGESTS.txt" | grep "${FILE_URI}\$" | awk '{ print $1; }') + + PARTS= + for part in $(get_dir "${THM_BASEURI}${EXO_BASEURI}files/" | grep "${FILE_URI}" | sort) + do + PARTS="${PARTS}${BASEFILE}${THM_BASEURI}${EXO_BASEURI}files/${part} +" + done + echo -e "\e[35mImport splited file ${THM_BASEURI}${EXO_BASEURI}files/${FILE_URI} from\e[00m `echo ${PARTS} | tr '\n' ' '`" + + FILE_ID=`new_file "${THEME_ID}" "${EXO_ID}" "${THM_BASEURI}${EXO_BASEURI}files/${FILE_URI}" "${DIGEST}" "${PARTS}"` + if [ -z "$FILE_ID" ]; then + echo -e "\e[31;01m!!! An error occured during file import! Please check path.\e[00m" + else + echo -e "\e[32m>>> New file added:\e[00m $FILE_ID - $FILE_URI" + fi + done + + # Files: entire + get_dir "${THM_BASEURI}${EXO_BASEURI}files/" | grep -v DIGESTS.txt | grep -v '[0-9][0-9]$' | while read f; do basename "$f"; done | while read FILE_URI + do + DIGEST=$(get_file "${THM_BASEURI}${EXO_BASEURI}files/DIGESTS.txt" | grep "${FILE_URI}\$" | awk '{ print $1; }') + echo "Import file ${THM_BASEURI}${EXO_BASEURI}files/${FILE_URI}" - FILE_ID=`new_file "${THEME_ID}" "${EXO_ID}" "${THM_BASEURI}${EXO_BASEURI}files/${FILE_URI}"` + FILE_ID=`new_file "${THEME_ID}" "${EXO_ID}" "${THM_BASEURI}${EXO_BASEURI}files/${FILE_URI}" "${DIGEST}"` if [ -z "$FILE_ID" ]; then echo -e "\e[31;01m!!! An error occured during file import! Please check path.\e[00m" else diff --git a/admin/fill_teams.sh b/admin/fill_teams.sh index 2ff61118..496e93ca 100755 --- a/admin/fill_teams.sh +++ b/admin/fill_teams.sh @@ -117,7 +117,7 @@ do EOF done echo "]" - ) | curl -f -s -d @- "${BASEURL}/api/teams/${TID}" + ) | curl -f -s -d @- "${BASEURL}/api/teams/${TID}/members" then echo "An error occured" elif [ "${GEN_CERTS}" -eq 1 ] && ! curl -s -f "${BASEURL}/api/teams/${TID}/certificate" > /dev/null diff --git a/admin/index.go b/admin/index.go index b3a75929..7aab5c96 100644 --- a/admin/index.go +++ b/admin/index.go @@ -5,49 +5,75 @@ const indextpl = ` Challenge Forensic - Administration - + + + -