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..7469df09 --- /dev/null +++ b/admin/api/exercice.go @@ -0,0 +1,117 @@ +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) +} diff --git a/admin/api_file.go b/admin/api/file.go similarity index 93% rename from admin/api_file.go rename to admin/api/file.go index 3c78cdf7..03681e60 100644 --- a/admin/api_file.go +++ b/admin/api/file.go @@ -1,4 +1,4 @@ -package main +package api import ( "bufio" @@ -15,7 +15,11 @@ import ( "srs.epita.fr/fic-server/libfic" ) -func createExerciceFile(theme fic.Theme, exercice fic.Exercice, args []string, body []byte) (interface{}, error) { +var CloudDAVBase string +var CloudUsername string +var CloudPassword string + +func createExerciceFile(theme fic.Theme, exercice fic.Exercice, args map[string]string, body []byte) (interface{}, error) { var uf map[string]string if err := json.Unmarshal(body, &uf); err != nil { return nil, err 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..dfa2b014 --- /dev/null +++ b/admin/api/theme.go @@ -0,0 +1,153 @@ +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)))) + + 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 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_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/main.go b/admin/main.go index be427b40..8e7c47ef 100644 --- a/admin/main.go +++ b/admin/main.go @@ -10,29 +10,26 @@ import ( "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 var StaticDir string func main() { 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", "/", "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(&StaticDir, "static", "./htdocs-admin/", "Directory containing static files") 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() log.SetPrefix("[admin] ") @@ -78,15 +75,8 @@ func main() { os.Chdir(PKIDir) - log.Println("Registering handlers...") - mux := http.NewServeMux() - mux.Handle(path.Join(BaseURL, "api") + "/", http.StripPrefix(path.Join(BaseURL, "api"), ApiHandler())) - mux.Handle(path.Join(BaseURL, "teams") + "/", http.StripPrefix(BaseURL, StaticHandler(staticDir))) - mux.Handle(path.Join(BaseURL, "themes") + "/", http.StripPrefix(BaseURL, StaticHandler(staticDir))) - mux.Handle(BaseURL, http.StripPrefix(BaseURL, 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) } }