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..eb9de979 --- /dev/null +++ b/admin/api/certificate.go @@ -0,0 +1,31 @@ +package api + +import ( + "io/ioutil" + "os" + + "srs.epita.fr/fic-server/libfic" +) + +func init() { + router.Path("/ca").Methods("GET").HandlerFunc(apiHandler(genCA)) + + rt := router.PathPrefix("/teams/{tid}/certificate").Subrouter() + rt.Path("/").Methods("GET").HandlerFunc(apiHandler(teamHandler(GetTeamCertificate))) + rt.Path("/generate").Methods("GET").HandlerFunc(apiHandler(teamHandler( + func(team fic.Team, args map[string]string, body []byte) (interface{}, error) { return team.GenerateCert(), nil }))) + rt.Path("/revoke").Methods("GET").HandlerFunc(apiHandler(teamHandler( + func(team fic.Team, args map[string]string, body []byte) (interface{}, error) { return team.RevokeCert(), nil }))) +} + +func genCA(args map[string]string, body []byte) (interface{}, error) { + return fic.GenerateCA(), nil +} + +func GetTeamCertificate(team fic.Team, args map[string]string, body []byte) (interface{}, error) { +if fd, err := os.Open("../PKI/pkcs/" + team.Name + ".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..0a12d588 --- /dev/null +++ b/admin/api/events.go @@ -0,0 +1,17 @@ +package api + +import ( + "srs.epita.fr/fic-server/libfic" +) + +func init() { + router.Path("/events/").Methods("GET").HandlerFunc(apiHandler(getEvents)) +} + +func getEvents(args map[string]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 new file mode 100644 index 00000000..c4eeba7d --- /dev/null +++ b/admin/api/exercice.go @@ -0,0 +1,136 @@ +package api + +import ( + "encoding/json" + "errors" + "strconv" + + "srs.epita.fr/fic-server/libfic" +) + +func init() { + router.Path("/exercices/").Methods("GET").HandlerFunc(apiHandler(listExercices)) + re := router.Path("/exercices/{eid:[0-9]+}").Subrouter() + re.Methods("GET").HandlerFunc(apiHandler(exerciceHandler(showExercice))) + re.Methods("PUT").HandlerFunc(apiHandler(updateExercice)) + re.Methods("DELETE").HandlerFunc(apiHandler(deleteExercice)) +} + +func listExercices(args map[string]string, body []byte) (interface{}, error) { + // List all exercices + return fic.GetExercices() +} + +func showExercice(exercice fic.Exercice, args map[string]string, body []byte) (interface{}, error) { + return exercice, nil +} + +func deleteExercice(args map[string]string, body []byte) (interface{}, error) { + if eid, err := strconv.Atoi(args["eid"]); err != nil { + return nil, err + } else if exercice, err := fic.GetExercice(int64(eid)); err != nil { + return nil, err + } else { + return exercice.Delete() + } +} + +type uploadedExercice struct { + Title string + Statement string + Depend *int64 + Gain int + VideoURI string +} + +func updateExercice(args map[string]string, body []byte) (interface{}, error) { + if eid, err := strconv.Atoi(args["eid"]); 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.Depend = ue.Depend + exercice.Gain = int64(ue.Gain) + exercice.VideoURI = ue.VideoURI + + return exercice.Update() + } +} + +func createExercice(theme fic.Theme, args map[string]string, body []byte) (interface{}, error) { + // 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, depend, ue.Gain, ue.VideoURI) +} + +type uploadedKey struct { + Name string + Key string +} + +func createExerciceKey(theme fic.Theme, exercice fic.Exercice, args map[string]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) +} + +type uploadedHint struct { + Title string + Content string + Cost int64 +} + +func createExerciceHint(theme fic.Theme, exercice fic.Exercice, args map[string]string, body []byte) (interface{}, error) { + var uh uploadedHint + if err := json.Unmarshal(body, &uh); err != nil { + return nil, err + } + + if len(uh.Content) == 0 { + return nil, errors.New("Hint's content not filled") + } + + return exercice.AddHint(uh.Title, uh.Content, uh.Cost) +} diff --git a/admin/api/file.go b/admin/api/file.go new file mode 100644 index 00000000..1678f8b7 --- /dev/null +++ b/admin/api/file.go @@ -0,0 +1,120 @@ +package api + +import ( + "bufio" + "crypto/sha512" + "encoding/base32" + "encoding/json" + "errors" + "fmt" + "log" + "net/http" + "os" + "path" + "strings" + + "srs.epita.fr/fic-server/libfic" +) + +var CloudDAVBase string +var CloudUsername string +var CloudPassword string + +type uploadedFile struct { + URI string + Digest []byte + Path string + Parts []string +} + +func createExerciceFile(theme fic.Theme, exercice fic.Exercice, args map[string]string, body []byte) (interface{}, error) { + var uf uploadedFile + 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 uf.URI != "" { + hash = sha512.Sum512([]byte(uf.URI)) + logStr = "Import file from Cloud: " + uf.URI + " =>" + fromURI = uf.URI + getFile = func(dest string) error { return getCloudFile(uf.URI, dest); } + } else if uf.Path != "" && len(uf.Parts) > 0 { + hash = sha512.Sum512([]byte(uf.Path)) + logStr = fmt.Sprintf("Import file from local FS: %s =>", uf.Parts) + fromURI = uf.Path + getFile = func(dest string) error { + if fdto, err := os.Create(dest); err != nil { + return err + } else { + writer := bufio.NewWriter(fdto) + for _, partname := range uf.Parts { + if fdfrm, err := os.Open(partname); err != nil { + return err + } else { + reader := bufio.NewReader(fdfrm) + reader.WriteTo(writer) + writer.Flush() + fdfrm.Close() + } + } + fdto.Close() + } + return nil + } + } else if uf.Path != "" { + hash = sha512.Sum512([]byte(uf.Path)) + logStr = "Import file from local FS: " + uf.Path + " =>" + fromURI = uf.Path + getFile = func(dest string) error { return os.Symlink(uf.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/handlers.go b/admin/api/handlers.go new file mode 100644 index 00000000..649c5b32 --- /dev/null +++ b/admin/api/handlers.go @@ -0,0 +1,131 @@ +package api + +import ( + "encoding/json" + "fmt" + "io" + "log" + "net/http" + "strconv" + + "srs.epita.fr/fic-server/libfic" + + "github.com/gorilla/mux" +) + + +type DispatchFunction func([]string, []byte) (interface{}, error) + +func apiHandler(f func (map[string]string,[]byte) (interface{}, error)) func(http.ResponseWriter, *http.Request) { + return func(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()) + + 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(mux.Vars(r), 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 teamHandler(f func(fic.Team,map[string]string,[]byte) (interface{}, error)) func (map[string]string,[]byte) (interface{}, error) { + return func (args map[string]string, body []byte) (interface{}, error) { + if tid, err := strconv.Atoi(string(args["tid"])); err != nil { + if team, err := fic.GetTeamByInitialName(args["tid"]); err != nil { + return nil, err + } else { + return f(team, args, body) + } + } else if team, err := fic.GetTeam(tid); err != nil { + return nil, err + } else { + return f(team, args, body) + } + } +} + +func themeHandler(f func(fic.Theme,map[string]string,[]byte) (interface{}, error)) func (map[string]string,[]byte) (interface{}, error) { + return func (args map[string]string, body []byte) (interface{}, error) { + if tid, err := strconv.Atoi(string(args["tid"])); err != nil { + return nil, err + } else if theme, err := fic.GetTheme(tid); err != nil { + return nil, err + } else { + return f(theme, args, body) + } + } +} + +func exerciceHandler(f func(fic.Exercice,map[string]string,[]byte) (interface{}, error)) func (map[string]string,[]byte) (interface{}, error) { + return func (args map[string]string, body []byte) (interface{}, error) { + if eid, err := strconv.Atoi(string(args["eid"])); err != nil { + return nil, err + } else if exercice, err := fic.GetExercice(int64(eid)); err != nil { + return nil, err + } else { + return f(exercice, args, body) + } + } +} + +func themedExerciceHandler(f func(fic.Theme,fic.Exercice,map[string]string,[]byte) (interface{}, error)) func (fic.Theme,map[string]string,[]byte) (interface{}, error) { + return func (theme fic.Theme, args map[string]string, body []byte) (interface{}, error) { + if eid, err := strconv.Atoi(string(args["eid"])); err != nil { + return nil, err + } else if exercice, err := fic.GetExercice(int64(eid)); err != nil { + return nil, err + } else { + return f(theme, exercice, args, body) + } + } +} + +func notFound(args map[string]string, body []byte) (interface{}, error) { + return nil, nil +} diff --git a/admin/api/router.go b/admin/api/router.go new file mode 100644 index 00000000..3c6be0ec --- /dev/null +++ b/admin/api/router.go @@ -0,0 +1,14 @@ +package api + +import ( + "github.com/gorilla/mux" +) + +var api_router = mux.NewRouter().StrictSlash(true) + +var router = api_router.PathPrefix("/api/").Subrouter() + + +func Router() *mux.Router { + return api_router +} diff --git a/admin/api_stats.go b/admin/api/stats.go similarity index 98% rename from admin/api_stats.go rename to admin/api/stats.go index 887d3b56..85a4b5b6 100644 --- a/admin/api_stats.go +++ b/admin/api/stats.go @@ -1,4 +1,4 @@ -package main +package api import ( "fmt" diff --git a/admin/api/team.go b/admin/api/team.go new file mode 100644 index 00000000..4671f6ec --- /dev/null +++ b/admin/api/team.go @@ -0,0 +1,156 @@ +package api + +import ( + "encoding/json" + "fmt" + "strings" + + "srs.epita.fr/fic-server/libfic" +) + +func init() { + rts := router.PathPrefix("/teams").Subrouter() + router.Path("/teams.json").Methods("GET").HandlerFunc(apiHandler( + func(map[string]string,[]byte) (interface{}, error) { + return fic.ExportTeams() })) + rts.Path("/").Methods("GET").HandlerFunc(apiHandler( + func(map[string]string,[]byte) (interface{}, error) { + return fic.GetTeams() })) + rts.Path("/binding").Methods("GET").HandlerFunc(apiHandler( + func(map[string]string,[]byte) (interface{}, error) { + return bindingTeams() })) + rts.Path("/nginx").Methods("GET").HandlerFunc(apiHandler( + func(map[string]string,[]byte) (interface{}, error) { + return nginxGenTeam() })) + rts.Path("/nginx-members").Methods("GET").HandlerFunc(apiHandler( + func(map[string]string,[]byte) (interface{}, error) { + return nginxGenMember() })) + rts.Path("/binding").Methods("GET").HandlerFunc(apiHandler( + func(map[string]string,[]byte) (interface{}, error) { + return fic.GetTries(nil, nil) })) + + rts.Path("/0/my.json").Methods("GET").HandlerFunc(apiHandler( + func(map[string]string,[]byte) (interface{}, error) { + return fic.MyJSONTeam(nil, true) })) + rts.Path("/0/wait.json").Methods("GET").HandlerFunc(apiHandler( + func(map[string]string,[]byte) (interface{}, error) { + return fic.MyJSONTeam(nil, false) })) + rts.Path("/0/stats.json").Methods("GET").HandlerFunc(apiHandler( + func(map[string]string,[]byte) (interface{}, error) { + return fic.GetTeamsStats(nil) })) + rts.Path("/0/tries").Methods("GET").HandlerFunc(apiHandler( + func(map[string]string,[]byte) (interface{}, error) { + return fic.GetTries(nil, nil) })) + + rt := rts.PathPrefix("/{tid}").Subrouter() + rt.Path("/").Methods("GET").HandlerFunc(apiHandler(teamHandler( + func(team fic.Team, args map[string]string, body []byte) (interface{}, error) { + return team, nil }))) + rt.Path("/").Methods("DELETE").HandlerFunc(apiHandler(teamHandler( + func(team fic.Team, args map[string]string, body []byte) (interface{}, error) { + return team.Delete() }))) + rt.Path("/my.json").Methods("GET").HandlerFunc(apiHandler(teamHandler( + func(team fic.Team, args map[string]string, body []byte) (interface{}, error) { + return fic.MyJSONTeam(&team, true) }))) + rt.Path("/wait.json").Methods("GET").HandlerFunc(apiHandler(teamHandler( + func(team fic.Team, args map[string]string, body []byte) (interface{}, error) { + return fic.MyJSONTeam(&team, false) }))) + rt.Path("/stats.json").Methods("GET").HandlerFunc(apiHandler(teamHandler( + func(team fic.Team, args map[string]string, body []byte) (interface{}, error) { + return team.GetStats() }))) + rt.Path("/tries").Methods("GET").HandlerFunc(apiHandler(teamHandler( + func(team fic.Team, args map[string]string, body []byte) (interface{}, error) { + return fic.GetTries(&team, nil) }))) + rt.Path("/members").Methods("GET").HandlerFunc(apiHandler(teamHandler( + func(team fic.Team, args map[string]string, body []byte) (interface{}, error) { + return team.GetMembers() }))) + rt.Path("/name").Methods("GET").HandlerFunc(apiHandler(teamHandler( + func(team fic.Team, args map[string]string, body []byte) (interface{}, error) { + return team.Name, nil }))) +} + +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(args map[string]string, body []byte) (interface{}, error) { + var ut uploadedTeam + if err := json.Unmarshal(body, &ut); err != nil { + return nil, err + } + + return fic.CreateTeam(ut.Name, ut.Color) +} + +func addTeamMember(team fic.Team, args map[string]string, body []byte) (interface{}, error) { + 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() +} diff --git a/admin/api/theme.go b/admin/api/theme.go new file mode 100644 index 00000000..7223861f --- /dev/null +++ b/admin/api/theme.go @@ -0,0 +1,161 @@ +package api + +import ( + "encoding/json" + "errors" + "fmt" + "strconv" + + "srs.epita.fr/fic-server/libfic" +) + +func init() { + router.Path("/themes/").Methods("GET").HandlerFunc(apiHandler(listThemes)) + router.Path("/themes/").Methods("POST").HandlerFunc(apiHandler(createTheme)) + router.Path("/themes.json").Methods("GET").HandlerFunc(apiHandler(exportThemes)) + router.Path("/themes/files-bindings").Methods("GET").HandlerFunc(apiHandler(bindingFiles)) + + rt := router.PathPrefix("/themes/{tid:[0-9]+}").Subrouter() + rt.Path("/").Methods("GET").HandlerFunc(apiHandler(themeHandler(showTheme))) + rt.Path("/").Methods("PUT").HandlerFunc(apiHandler(themeHandler(updateTheme))) + rt.Path("/").Methods("DELETE").HandlerFunc(apiHandler(themeHandler(deleteTheme))) + + rtes := rt.PathPrefix("/exercices").Subrouter() + rtes.Path("/").Methods("GET").HandlerFunc(apiHandler(themeHandler(listThemedExercices))) + rtes.Path("/").Methods("POST").HandlerFunc(apiHandler(themeHandler(createExercice))) + + rte := rtes.PathPrefix("/{eid:[0-9]+}").Subrouter() + rte.Path("/").Methods("GET").HandlerFunc(apiHandler(themeHandler(themedExerciceHandler(showThemedExercice)))) + rte.Path("/").Methods("PUT").HandlerFunc(apiHandler(themeHandler(themedExerciceHandler(updateThemedExercice)))) + rte.Path("/").Methods("DELETE").HandlerFunc(apiHandler(themeHandler(themedExerciceHandler(deleteThemedExercice)))) + + rtef := rte.Path("/files").Subrouter() + rtef.Methods("GET").HandlerFunc(apiHandler(themeHandler(themedExerciceHandler(listThemedExerciceFiles)))) + rtef.Methods("POST").HandlerFunc(apiHandler(themeHandler(themedExerciceHandler(createExerciceFile)))) + + rteh := rte.Path("/hints").Subrouter() + rteh.Methods("GET").HandlerFunc(apiHandler(themeHandler(themedExerciceHandler(listThemedExerciceHints)))) + rteh.Methods("POST").HandlerFunc(apiHandler(themeHandler(themedExerciceHandler(createExerciceHint)))) + + rtek := rte.Path("/keys").Subrouter() + rtek.Methods("GET").HandlerFunc(apiHandler(themeHandler(themedExerciceHandler(listThemedExerciceKeys)))) + rtek.Methods("POST").HandlerFunc(apiHandler(themeHandler(themedExerciceHandler(createExerciceKey)))) +} + +func bindingFiles(args map[string]string, 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(args map[string]string, body []byte) (interface{}, error) { + return fic.GetThemes() +} + +func exportThemes(args map[string]string, body []byte) (interface{}, error) { + return fic.ExportThemes() +} + +func showTheme(theme fic.Theme, args map[string]string, body []byte) (interface{}, error) { + return theme, nil +} + +func listThemedExercices(theme fic.Theme, args map[string]string, body []byte) (interface{}, error) { + return theme.GetExercices() +} + +func showThemedExercice(theme fic.Theme, exercice fic.Exercice, args map[string]string, body []byte) (interface{}, error) { + return exercice, nil +} + +func listThemedExerciceFiles(theme fic.Theme, exercice fic.Exercice, args map[string]string, body []byte) (interface{}, error) { + return exercice.GetFiles() +} + +func listThemedExerciceHints(theme fic.Theme, exercice fic.Exercice, args map[string]string, body []byte) (interface{}, error) { + return exercice.GetHints() +} + +func listThemedExerciceKeys(theme fic.Theme, exercice fic.Exercice, args map[string]string, body []byte) (interface{}, error) { + return exercice.GetKeys() +} + + +type uploadedTheme struct { + Name string + Authors string +} + +func createTheme(args map[string]string, 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, args map[string]string, 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") + } + + return ut.Update() +} + +func updateThemedExercice(theme fic.Theme, exercice fic.Exercice, args map[string]string, body []byte) (interface{}, error) { + // Update an exercice + 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 deleteTheme(theme fic.Theme, args map[string]string, body []byte) (interface{}, error) { + return theme.Delete() +} + +func deleteThemedExercice(theme fic.Theme, exercice fic.Exercice, args map[string]string, body []byte) (interface{}, error) { + return exercice.Delete() +} diff --git a/admin/api/version.go b/admin/api/version.go new file mode 100644 index 00000000..eb5a883a --- /dev/null +++ b/admin/api/version.go @@ -0,0 +1,11 @@ +package api + +import () + +func init() { + router.Path("/version").Methods("GET").HandlerFunc(apiHandler(showVersion)) +} + +func showVersion(args map[string]string, body []byte) (interface{}, error) { + return map[string]interface{}{"version": 0.1}, 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 f77e6d9a..00000000 --- a/admin/api_file.go +++ /dev/null @@ -1,75 +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" -) - -type uploadedFile struct { - URI string -} - -func createExerciceFile(theme fic.Theme, exercice fic.Exercice, args []string, body []byte) (interface{}, error) { - var uf uploadedFile - if err := json.Unmarshal(body, &uf); err != nil { - return nil, err - } - - if len(uf.URI) == 0 { - return nil, errors.New("URI not filled") - } - - hash := sha512.Sum512([]byte(uf.URI)) - pathname := path.Join(fic.FilesDir, strings.ToLower(base32.StdEncoding.EncodeToString(hash[:])), path.Base(uf.URI)) - - if _, err := os.Stat(pathname); os.IsNotExist(err) { - log.Println("Import file from Cloud:", uf.URI, "=>", pathname) - if err := os.MkdirAll(path.Dir(pathname), 0777); err != nil { - return nil, err - } else if err := getCloudFile(uf.URI, pathname); err != nil { - return nil, err - } - } - - return exercice.ImportFile(pathname, uf.URI) -} - -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 da32dee4..00000000 --- a/admin/api_team.go +++ /dev/null @@ -1,200 +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 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 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] == "nginx" { - return nginxGenTeam() - } 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 9d66f680..3b65e3fa 100755 --- a/admin/fill_exercices.sh +++ b/admin/fill_exercices.sh @@ -1,9 +1,9 @@ -#!/bin/sh +#!/bin/bash BASEURL="http://localhost:8081" -BASEURI="https://srs.epita.fr/owncloud/remote.php/webdav/FIC 2016" -BASEFILE="/files" -CLOUDPASS=fic:'f>t\nV33R|(+?$i*' +BASEURI="https://owncloud.srs.epita.fr/remote.php/webdav/FIC 2017" +BASEFILE="/mnt/fic/" +CLOUDPASS=nemunaire:'p0WJ$&I#OWEtC5UI4@dD' new_theme() { NAME=`echo $1 | sed 's/"/\\\\"/g'` @@ -16,12 +16,11 @@ 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]+" } @@ -29,8 +28,28 @@ new_file() { THEME="$1" EXERCICE="$2" URI="$3" + ARGS="$4" - curl -f -s -d "{\"URI\": \"${BASEFILE}${URI}\"}" "${BASEURL}/api/themes/$THEME/$EXERCICE/files" | + echo "$ARGS" | while read arg + do + [ -z "${PARTS}" ] || PARTS="${PARTS}," + PARTS="${PARTS}\"$arg\"" + done + +# curl -f -s -d "{\"URI\": \"${BASEFILE}${URI}\"}" "${BASEURL}/api/themes/$THEME/$EXERCICE/files" | + curl -f -s -d @- "${BASEURL}/api/themes/$THEME/exercices/$EXERCICE/files" </g'` + COST="$5" + + curl -f -s -d "{\"title\": \"$TITLE\", \"content\": \"$CONTENT\", \"cost\": $COST}" "${BASEURL}/api/themes/$THEME/exercices/$EXERCICE/hints" | grep -Eo '"id":[0-9]+,' | grep -Eo "[0-9]+" } @@ -40,42 +59,65 @@ new_key() { NAME="$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 "{\"name\": \"$NAME\", \"key\": \"$KEY\"}" "${BASEURL}/api/themes/$THEME/exercices/$EXERCICE/keys" | grep -Eo '"id":[0-9]+,' | grep -Eo "[0-9]+" } +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" 2> /dev/null +} +#alias get_dir=get_dir_from_cloud + +get_file_from_cloud() { + curl -f -s -u "${CLOUDPASS}" "${BASEURI}$1" | tr -d '\r' +} +get_file() { + cat "${BASEFILE}$1" 2> /dev/null | tr -d '\r' +} +#alias get_file=get_file_from_cloud + +unhtmlentities() { + cat | sed -E 's/%20/ /g' | sed -E "s/%27/'/g" | sed -E 's/%c3%a9/é/g' | sed -E 's/%c3%a8/è/g' +} + # Theme -curl -f -s -X PROPFIND -u "${CLOUDPASS}" "${BASEURI}" | xmllint --format - | grep 'd:href' | sed -E 's/^.*>(.*)<.*$/\1/' | sed 1d | tac | while read f; do basename "$f"; done | while read THEME_URI +get_dir "" | while read f; do basename "$f"; done | while read THEME_URI do THM_BASEURI="/${THEME_URI}/" - THEME_NAME=$(echo "${THEME_URI}" | sed -E 's/%20/ /g' | sed -E 's/%c3%a9/é/g' | sed -E 's/%c3%a8/è/g') - THEME_AUTHORS=$(curl -f -s -u "${CLOUDPASS}" "${BASEURI}${THM_BASEURI}/AUTHORS.txt" | sed 's/$/,/' | xargs) + THEME_NAME=$(echo "${THEME_URI#*-}" | unhtmlentities) + THEME_AUTHORS=$(get_file "${THM_BASEURI}/AUTHORS.txt" | sed '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" - continue + continue else echo -e "\e[33m>>> New theme created:\e[00m $THEME_ID - $THEME_NAME" fi LAST=null EXO_NUM=0 - curl -f -s -X PROPFIND -u "${CLOUDPASS}" "${BASEURI}${THM_BASEURI}" | xmllint --format - | grep 'd:href' | sed -E 's/^.*>(.*)<.*$/\1/' | sed -E 's/%20/ /g' | sed -E 's/%c3%a9/é/g' | sed -E 's/%c3%a8/è/g' | sed 1d | while read f; do basename "$f"; done | while read EXO_NAME + get_dir "${THM_BASEURI}" | while read f; do basename "$f"; done | while read EXO_URI do - if ! echo $EXO_NAME | grep Exercice > /dev/null - then - continue - fi + case ${EXO_URI} in + [0-9]-*) + ;; + *) + continue;; + esac - EXO_NUM=$((EXO_NUM + 1)) + #EXO_NUM=$((EXO_NUM + 1)) + EXO_NUM=${EXO_URI%-*} + EXO_NAME=$(echo "${EXO_URI#*-}" | unhtmlentities) echo echo -e "\e[36m--- Filling exercice ${EXO_NUM} in theme ${THEME_NAME}\e[00m" - EXO_BASEURI="${EXO_NAME}/" + EXO_BASEURI="${EXO_URI}/" - FILES=$(curl -f -s -X PROPFIND -u "${CLOUDPASS}" "${BASEURI}${THM_BASEURI}${EXO_BASEURI}" | xmllint --format - | grep 'd:href' | sed -E 's/^.*>(.*)<.*$/\1/' | sed 1d | while read f; do basename $f; done) - - EXO_VIDEO=$(echo "$FILES" | grep -E "\.(mov|mkv|mp4|avi|flv|ogv|webm)$" | 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" @@ -86,10 +128,9 @@ do EXO_GAIN=$((3 * (2 ** $EXO_NUM) - 1)) echo ">>> Using default gain: ${EXO_GAIN} points" - EXO_DESC=$(curl -f -s -u "${CLOUDPASS}" "${BASEURI}${THM_BASEURI}${EXO_BASEURI}/description.txt") - EXO_HINT=$(curl -f -s -u "${CLOUDPASS}" "${BASEURI}${THM_BASEURI}${EXO_BASEURI}/hint.txt") + EXO_SCENARIO=$(get_file "${THM_BASEURI}${EXO_BASEURI}/scenario.txt") - EXO_ID=`new_exercice "${THEME_ID}" "${EXO_NAME}" "${EXO_DESC}" "${EXO_HINT}" "${LAST}" "${EXO_GAIN}" "${THM_BASEURI}${EXO_BASEURI}${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 @@ -99,7 +140,7 @@ do # Keys - curl -f -s -u "${CLOUDPASS}" "${BASEURI}${THM_BASEURI}${EXO_BASEURI}/keys.txt" | while read KEYLINE + 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-) @@ -117,16 +158,45 @@ do done - # Files - for f in $FILES; do echo $f; done | grep -vEi "(ressources|readme|description.txt|hint.txt|keys.txt|${EXO_VIDEO})" | - while read FBASE + # Hints + EXO_HINT=$(get_file "${THM_BASEURI}${EXO_BASEURI}/hint.txt") + if [ -n "$EXO_HINT" ]; then + HINT_ID=`new_hint "${THEME_ID}" "${EXO_ID}" "Astuce #1" "${EXO_HINT}" "1"` + if [ -z "$HINT_ID" ]; then + echo -e "\e[31;01m!!! An error occured during hint import!\e[00m (title=Astuce $1;content=${EXO_HINT};cost=1)" + else + echo -e "\e[32m>>> New key added:\e[00m $KEY_ID - $KEY_NAME" + fi + fi + + + # 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 - echo "Import file ${BASEURI}${THM_BASEURI}${EXO_BASEURI}${FBASE}" - FILE_ID=`new_file "${THEME_ID}" "${EXO_ID}" "${THM_BASEURI}${EXO_BASEURI}${FBASE}"` + 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}\n" + done + echo "Import splited file ${THM_BASEURI}${EXO_BASEURI}files/${FILE_URI} from `echo ${PART} | tr '\n' ' '`" + + FILE_ID=`new_file "${THEME_ID}" "${EXO_ID}" "${THM_BASEURI}${EXO_BASEURI}files/${FILE_URI}" "${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 - $FBASE" + 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 + 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}"` + 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 diff --git a/admin/fill_teams.sh b/admin/fill_teams.sh index 92931112..2ff61118 100755 --- a/admin/fill_teams.sh +++ b/admin/fill_teams.sh @@ -1,7 +1,48 @@ -#!/bin/sh +#!/bin/bash -BASEURL="http://localhost:8081" -PART_FILE="Challenge_Liste des participants.csv" +BASEURL="http://127.0.0.1:8081/admin" +GEN_CERTS=0 +EXTRA_TEAMS=0 +CSV_SPLITER="," +CSV_COL_LASTNAME=1 +CSV_COL_FIRSTNAME=2 +CSV_COL_NICKNAME=3 +CSV_COL_COMPANY=7 +CSV_COL_TEAM=7 + +usage() { + echo "$0 [options] csv_file" + echo " -B -baseurl BASEURL URL to administration endpoint (default: $BASEURL)" + echo " -S -csv-spliter SEP CSV separator (default: $CSV_SPLITER)" + echo " -e -extra-teams NBS Number of extra teams to generate (default: ${EXTRA_TEAMS})" + echo " -c -generate-certificate Should team certificates be generated? (default: no)" +} + +# Parse options +while [ "${1:0:1}" = "-" ] +do + case "$1" in + -B|-baseurl) + BASEURL=$2 + shift;; + -S|-csv-spliter) + CSV_SPLITER=$2 + shift;; + -e|-extra-teams) + EXTRA_TEAMS=$2 + shift;; + -c|-generate-certificates) + GEN_CERTS=1;; + *) + echo "Unknown option '$1'" + usage + exit 1;; + esac + shift +done + +[ "$#" -lt 1 ] && { usage; exit 1; } +PART_FILE="$1" new_team() { head -n "$1" team-names.txt | tail -1 | sed -E 's/^.*\|\[\[([^|]+\|)?([^|]+)\]\][^|]*\|([A-Fa-f0-9]{1,2})\|([A-Fa-f0-9]{1,2})\|([A-Fa-f0-9]{1,2})\|([0-9]{1,3})\|([0-9]{1,3})\|([0-9]{1,3})\|.*$/\6 \7 \8 \2/' | @@ -10,7 +51,11 @@ new_team() { R=`echo $line | cut -d " " -f 1` G=`echo $line | cut -d " " -f 2` B=`echo $line | cut -d " " -f 3` - N=`echo $line | cut -d " " -f 4` + if [ -z "$2" ]; then + N=`echo $line | cut -d " " -f 4` + else + N=`echo -n $2 | tr -d '\r\n'` + fi COLOR=$((($R*256 + $G) * 256 + $B)) @@ -20,7 +65,7 @@ new_team() { TNUM=0 -for i in `seq 12` +for i in $(seq $EXTRA_TEAMS) do TNUM=$(($TNUM + 1)) @@ -28,31 +73,32 @@ do TID=`new_team $TNUM` - if ! curl -s -f "${BASEURL}/api/teams/${TID}/certificate" > /dev/null + if [ "${GEN_CERTS}" -eq 1 ] && ! curl -s -f "${BASEURL}/api/teams/${TID}/certificate" > /dev/null then curl -s -f "${BASEURL}/api/teams/${TID}/certificate/generate" fi echo done -TMAX=`sed "1d" "$PART_FILE" | cut -d \; -f 15 | sort | uniq | wc -l` +TMAX=`cat "$PART_FILE" | cut -d "${CSV_SPLITER}" -f $CSV_COL_TEAM | sort | uniq | wc -l` TMAX=$(($TMAX + $TNUM)) -sed "1d" "$PART_FILE" | cut -d \; -f 15 | sort | uniq | while read TEAMID +cat "$PART_FILE" | cut -d "${CSV_SPLITER}" -f $CSV_COL_TEAM | sort | uniq | while read TEAMID do TNUM=$(($TNUM + 1)) echo "Doing team $TNUM/$TMAX ("$(($TNUM*100/$TMAX))"%)..." - TID=`new_team $TNUM` + TID=`new_team "${TNUM}" "${TEAMID}"` - ( + if ! ( echo -n "[" HAS_MEMBER=1 - grep ";$TEAMID\$" "$PART_FILE" | while read MEMBER + grep "${CSV_SPLITER}${TEAMID}\$" "$PART_FILE" | while read MEMBER do - LASTNAME=`echo $MEMBER | cut -d ";" -f 2` - FIRSTNAME=`echo $MEMBER | cut -d ";" -f 3` - COMPANY=`echo $MEMBER | cut -d ";" -f 4` + LASTNAME=`echo $MEMBER | cut -d "${CSV_SPLITER}" -f $CSV_COL_LASTNAME | tr -d "\r\n"` + FIRSTNAME=`echo $MEMBER | cut -d "${CSV_SPLITER}" -f $CSV_COL_FIRSTNAME | tr -d "\r\n"` + NICKNAME=`echo $MEMBER | cut -d "${CSV_SPLITER}" -f $CSV_COL_NICKNAME | tr -d "\r\n"` + COMPANY=`echo $MEMBER | cut -d "${CSV_SPLITER}" -f $CSV_COL_COMPANY | tr -d "\r\n"` if [ $HAS_MEMBER = 0 ] then @@ -65,15 +111,16 @@ do { "firstname": "$FIRSTNAME", "lastname": "$LASTNAME", - "nickname": "", + "nickname": "$NICKNAME", "company": "$COMPANY" } EOF done echo "]" - ) | curl -s -d @- "${BASEURL}/api/teams/${TID}" > /dev/null - - if ! curl -s -f "${BASEURL}/api/teams/${TID}/certificate" > /dev/null + ) | curl -f -s -d @- "${BASEURL}/api/teams/${TID}" + then + echo "An error occured" + elif [ "${GEN_CERTS}" -eq 1 ] && ! curl -s -f "${BASEURL}/api/teams/${TID}/certificate" > /dev/null then curl -s -f "${BASEURL}/api/teams/${TID}/certificate/generate" fi diff --git a/admin/index.go b/admin/index.go new file mode 100644 index 00000000..b3a75929 --- /dev/null +++ b/admin/index.go @@ -0,0 +1,54 @@ +package main + +const indextpl = ` + + + + Challenge Forensic - Administration + + + + + + + +
+
+
+
+
+ + + + + + + + + +` diff --git a/admin/main.go b/admin/main.go index 95932e57..a3efc500 100644 --- a/admin/main.go +++ b/admin/main.go @@ -6,35 +6,36 @@ import ( "log" "net/http" "os" + "path" "path/filepath" + "text/template" + "srs.epita.fr/fic-server/admin/api" "srs.epita.fr/fic-server/libfic" ) var PKIDir string var SubmissionDir string -var BaseURL string -var CloudDAVBase string -var CloudUsername string -var CloudPassword string func main() { - var bind = flag.String("bind", "0.0.0.0:8081", "Bind port/socket") + var staticDir string + var bind = flag.String("bind", "127.0.0.1:8081", "Bind port/socket") var dsn = flag.String("dsn", "fic:fic@/fic", "DSN to connect to the MySQL server") - flag.StringVar(&BaseURL, "baseurl", "http://fic.srs.epita.fr/", "URL prepended to each URL") + var baseURL = flag.String("baseurl", "/", "URL prepended to each URL") flag.StringVar(&SubmissionDir, "submission", "./submissions/", "Base directory where save submissions") flag.StringVar(&PKIDir, "pki", "./pki/", "Base directory where found PKI scripts") flag.StringVar(&fic.FilesDir, "files", "./FILES/", "Base directory where found challenges files, local part") - flag.StringVar(&CloudDAVBase, "clouddav", "https://srs.epita.fr/owncloud/remote.php/webdav/FIC 2016", + flag.StringVar(&api.CloudDAVBase, "clouddav", "https://srs.epita.fr/owncloud/remote.php/webdav/FIC 2016", "Base directory where found challenges files, cloud part") - flag.StringVar(&CloudUsername, "clouduser", "fic", "Username used to sync") - flag.StringVar(&CloudPassword, "cloudpass", "", "Password used to sync") + flag.StringVar(&api.CloudUsername, "clouduser", "fic", "Username used to sync") + flag.StringVar(&api.CloudPassword, "cloudpass", "", "Password used to sync") flag.Parse() - var staticDir string + log.SetPrefix("[admin] ") + var err error log.Println("Checking paths...") - if staticDir, err = filepath.Abs("./static/"); err != nil { + if staticDir, err = filepath.Abs(staticDir); err != nil { log.Fatal(err) } if fic.FilesDir, err = filepath.Abs(fic.FilesDir); err != nil { @@ -61,17 +62,20 @@ func main() { log.Fatal("Cannot create database: ", err) } + log.Println("Changing base url...") + if file, err := os.OpenFile(path.Join(staticDir, "index.html"), os.O_CREATE|os.O_WRONLY|os.O_TRUNC, os.FileMode(0644)); err != nil { + log.Println("Unable to open index.html: ", err) + } else if indexTmpl, err := template.New("index").Parse(indextpl); err != nil { + log.Println("Cannot create template: ", err) + } else if err := indexTmpl.Execute(file, map[string]string{"urlbase": path.Clean(path.Join(*baseURL, "nuke"))[:len(path.Clean(path.Join(*baseURL, "nuke"))) - 4]}); err != nil { + log.Println("An error occurs during template execution: ", err) + } + + os.Chdir(PKIDir) - log.Println("Registering handlers...") - mux := http.NewServeMux() - mux.Handle("/api/", http.StripPrefix("/api", ApiHandler())) - mux.Handle("/teams/", StaticHandler(staticDir)) - mux.Handle("/themes/", StaticHandler(staticDir)) - mux.Handle("/", http.FileServer(http.Dir(staticDir))) - log.Println(fmt.Sprintf("Ready, listening on %s", *bind)) - if err := http.ListenAndServe(*bind, mux); err != nil { + if err := http.ListenAndServe(*bind, http.StripPrefix(*baseURL, api.Router())); err != nil { log.Fatal("Unable to listen and serve: ", err) } } diff --git a/admin/static/img/epita.png b/admin/static/img/epita.png new file mode 100644 index 00000000..e89f7181 Binary files /dev/null and b/admin/static/img/epita.png differ diff --git a/admin/static/img/fic.png b/admin/static/img/fic.png new file mode 100644 index 00000000..7956bd4d Binary files /dev/null and b/admin/static/img/fic.png differ diff --git a/admin/static/img/srs.png b/admin/static/img/srs.png new file mode 100644 index 00000000..82294f52 Binary files /dev/null and b/admin/static/img/srs.png differ diff --git a/admin/static/index.html b/admin/static/index.html index e2142455..67c1f462 100644 --- a/admin/static/index.html +++ b/admin/static/index.html @@ -2,15 +2,40 @@ - Challenge Forensic FIC 2016 - Administration + Challenge Forensic - Administration + -
-