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
-
+
+
+
-