diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000000000000000000000000000000000000..f36e43750f66aa7e9921645912501ac74e65203e --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,22 @@ +{ + "workbench.colorCustomizations": { + "activityBar.activeBackground": "#65f4ff", + "activityBar.activeBorder": "#ff40f1", + "activityBar.background": "#65f4ff", + "activityBar.foreground": "#15202b", + "activityBar.inactiveForeground": "#15202b99", + "activityBarBadge.background": "#ff40f1", + "activityBarBadge.foreground": "#15202b", + "sash.hoverBorder": "#65f4ff", + "statusBar.background": "#32f0ff", + "statusBar.foreground": "#15202b", + "statusBarItem.hoverBackground": "#00ebfe", + "statusBarItem.remoteBackground": "#32f0ff", + "statusBarItem.remoteForeground": "#15202b", + "titleBar.activeBackground": "#32f0ff", + "titleBar.activeForeground": "#15202b", + "titleBar.inactiveBackground": "#32f0ff99", + "titleBar.inactiveForeground": "#15202b99" + }, + "peacock.color": "#32f0ff" +} \ No newline at end of file diff --git a/internal/auth/oauth2.go b/internal/auth/oauth2.go index 870b2dee6acb81fd25308d0aac196b7f88b825cf..01657b0056414750a326ddf1227a51dc9d591988 100644 --- a/internal/auth/oauth2.go +++ b/internal/auth/oauth2.go @@ -125,6 +125,17 @@ func (m Manager) HandleOAuth2Callback() http.Handler { for key, role := range user.Roles { user.Roles[key] = strings.TrimPrefix(strings.Split(role, ",")[0], "CN=") } + + // Check if user has the correct role + err = checkUserHasRole(TokenData{User: user}, []string{AdminRole}) + + if err != nil { + // Log the connexion attempt + log.Printf("| %v (%v %v) | Login failed (Unauthorized user) | %v", user.Login, user.Name, user.Surname, req.RemoteAddr) + http.Redirect(w, r, "/", http.StatusFound) + return + } + // Store the user in cookie // Generate xsrfToken, err := common.GenerateRandomString(16) diff --git a/internal/file/file.go b/internal/file/file.go index dba702c99bb34f643fa9a30221f599c7a8e200eb..846627974d257ba0e72d4d925f27feafbe6d057c 100644 --- a/internal/file/file.go +++ b/internal/file/file.go @@ -10,10 +10,12 @@ import ( "forge.grandlyon.com/web-et-numerique/llle_project/backoffice-server/internal/common" ) -var imageFolder = common.StringValueFromEnv("IMAGE_FOLDER", "") +var ( + imageFolder = common.StringValueFromEnv("IMAGE_FOLDER", "") +) func GetEcogestureImages(w http.ResponseWriter, r *http.Request) { - filenames, err := fileNamesFromFolder(imageFolder) + filenames, err := fileNamesFromFolder(imageFolder + "/ecogesture") jsondata, err := json.Marshal(filenames) if err != nil { fmt.Printf("Error: %s", err.Error()) diff --git a/internal/models/models.go b/internal/models/models.go index fc72cf87c0c4407da254bf480685d2845457d28d..d2b690f70dc0fdfbd133c5456f6f49492cf0f9f5 100644 --- a/internal/models/models.go +++ b/internal/models/models.go @@ -41,5 +41,15 @@ func NewDataHandler() *DataHandler { db.AutoMigrate(&MonthlyInfo{}) db.AutoMigrate(&MonthlyNews{}) db.AutoMigrate(&Poll{}) + db.AutoMigrate(&PartnersInfo{}) + + // Create default partner status + db.Create(&PartnersInfo{ + GRDFFailure: false, + EnedisFailure: false, + EGLFailure: false, + NotificationActivated: false, + }) + return &DataHandler{db: db} } diff --git a/internal/models/monthlyReport.go b/internal/models/monthlyReport.go index eb8816b95b15aae5e2929ee2cd8cda6ff0a0af24..fca176d2c6a44cc44ca83a1ead3d2dfd02b24aa8 100644 --- a/internal/models/monthlyReport.go +++ b/internal/models/monthlyReport.go @@ -3,6 +3,7 @@ package models import ( "encoding/json" "net/http" + "strconv" "time" "forge.grandlyon.com/web-et-numerique/llle_project/backoffice-server/internal/common" @@ -32,8 +33,16 @@ type MonthlyReport struct { func (dh *DataHandler) GetMonthlyReport(w http.ResponseWriter, r *http.Request) { year, month, err := common.YearMonthFromRequest(r) if err != nil { - http.Error(w, err.Error(), http.StatusBadRequest) - return + year, err = strconv.Atoi(r.URL.Query().Get("year")) + if err != nil { + http.Error(w, "year in query is not an integer", http.StatusBadRequest) + return + } + month, err = strconv.Atoi(r.URL.Query().Get("month")) + if err != nil { + http.Error(w, "month in query is not an integer", http.StatusBadRequest) + return + } } monthlyReport, err := dh.getMonthlyReport(year, month) @@ -58,7 +67,7 @@ func (dh *DataHandler) GetCurrentMonthlyReport(w http.ResponseWriter, r *http.Re year, month, _ := time.Now().Date() - monthlyReport, err := dh.getMonthlyReport(year, int(month)-1) + monthlyReport, err := dh.getMonthlyReport(year, int(month)) if err != nil { w.WriteHeader(http.StatusNotFound) return diff --git a/internal/models/partnersInfo.go b/internal/models/partnersInfo.go new file mode 100644 index 0000000000000000000000000000000000000000..35652e7cdf9ed941b72140b7f383a82b4598608a --- /dev/null +++ b/internal/models/partnersInfo.go @@ -0,0 +1,86 @@ +package models + +import ( + "encoding/json" + "errors" + "log" + "net/http" + + "gorm.io/gorm" +) + +type PartnersInfo struct { + ID uint `gorm:"<-:create"` + GRDFFailure bool `json:"grdf_failure"` + EnedisFailure bool `json:"enedis_failure"` + EGLFailure bool `json:"egl_failure"` + NotificationActivated bool `json:"notification_activated"` +} + +// GetPartnersInfo godoc +// @Summary Give status of partners' services +// @Description Give status of partners' services +// @Tags partnersInfo +// @Produce json +// @Success 200 {object} PartnersInfo +// @Failure 404 {string} string "Not found" +// @Router /api/common/partnersInfo [get] +func (dh *DataHandler) GetPartnersInfo(w http.ResponseWriter, r *http.Request) { + var partnersInfo PartnersInfo + err := dh.db.First(&partnersInfo).Error + + if errors.Is(err, gorm.ErrRecordNotFound) { + http.Error(w, "partners status not found", http.StatusNotFound) + return + } + + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(partnersInfo) + log.Printf("| get partnersInfo | %v", r.RemoteAddr) +} + +// SavePartnersInfo godoc +// @Summary Update partnersInfo' content +// @Description Update partnersInfo' content +// @Tags partnersInfo +// @Accept json +// @Produce json +// @Success 200 {object} PartnersInfo "Updated successfully" +// @Failure 400 {string} string "Bad Request" +// @Failure 500 {string} string "Internal server error" +// @Param partnersInfo body PartnersInfo true "PartnersInfo to create/update with new content" +// @Router /api/admin/partnersInfo [put] +func (dh *DataHandler) SavePartnersInfo(w http.ResponseWriter, r *http.Request) { + if r.Body == http.NoBody { + http.Error(w, "request body is empty", http.StatusBadRequest) + return + } + + decoder := json.NewDecoder(r.Body) + var partnersInfo PartnersInfo + err := decoder.Decode(&partnersInfo) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + return + } + + var updatedPartnersInfo PartnersInfo + + err = dh.db.First(&updatedPartnersInfo).Error + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + return + } + + updatedPartnersInfo.GRDFFailure = partnersInfo.GRDFFailure + updatedPartnersInfo.EnedisFailure = partnersInfo.EnedisFailure + updatedPartnersInfo.EGLFailure = partnersInfo.EGLFailure + updatedPartnersInfo.NotificationActivated = partnersInfo.NotificationActivated + + dh.db.Save(&updatedPartnersInfo) + + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(partnersInfo) + log.Printf("| updated partnersInfo | %v", r.RemoteAddr) + +} diff --git a/internal/rootmux/rootmux.go b/internal/rootmux/rootmux.go index 84307853fc0f8bb6e6803b2f4674d3625708fabd..6264fbf04e703022ae8694e927a9773b14816869 100644 --- a/internal/rootmux/rootmux.go +++ b/internal/rootmux/rootmux.go @@ -5,12 +5,15 @@ import ( _ "forge.grandlyon.com/web-et-numerique/llle_project/backoffice-server/docs" "forge.grandlyon.com/web-et-numerique/llle_project/backoffice-server/internal/auth" + "forge.grandlyon.com/web-et-numerique/llle_project/backoffice-server/internal/common" "forge.grandlyon.com/web-et-numerique/llle_project/backoffice-server/internal/file" "forge.grandlyon.com/web-et-numerique/llle_project/backoffice-server/internal/models" "github.com/gorilla/mux" httpSwagger "github.com/swaggo/http-swagger" ) +var imageFolder = common.StringValueFromEnv("IMAGE_FOLDER", "") + type RootMux struct { Router *mux.Router Manager *auth.Manager @@ -35,9 +38,11 @@ func CreateRootMux() RootMux { r.Handle("/OAuth2Callback", m.HandleOAuth2Callback()) r.HandleFunc("/Logout", m.HandleLogout) r.Handle("/api/common/WhoAmI", auth.ValidateAuthMiddleware(auth.WhoAmI(), []string{"*"}, false)) + r.PathPrefix("/assets/").Handler(http.StripPrefix("/assets/", http.FileServer(http.Dir("./"+imageFolder+"/")))) - r.HandleFunc("/api/common/monthlyReport", dh.GetCurrentMonthlyReport).Methods(http.MethodGet) + r.HandleFunc("/api/common/monthlyReport", dh.GetMonthlyReport).Methods(http.MethodGet) r.HandleFunc("/api/common/monthlyReport/{year}/{month}", dh.GetMonthlyReport).Methods(http.MethodGet) + r.HandleFunc("/api/common/partnersInfo", dh.GetPartnersInfo).Methods(http.MethodGet) apiAdmin := r.PathPrefix("/api/admin").Subrouter() apiAdmin.Use(auth.AdminAuthMiddleware) @@ -57,6 +62,8 @@ func CreateRootMux() RootMux { apiAdmin.HandleFunc("/poll", dh.SavePoll).Methods(http.MethodPut) apiAdmin.HandleFunc("/poll/{year}/{month}", dh.DeletePoll).Methods(http.MethodDelete) + apiAdmin.HandleFunc("/partnersInfo", dh.SavePartnersInfo).Methods(http.MethodPut) + apiAdmin.HandleFunc("/imageNames", file.GetEcogestureImages).Methods(http.MethodGet) // Swagger diff --git a/internal/rootmux/rootmux_test.go b/internal/rootmux/rootmux_test.go index 9fa492dc35d827089db7d6e0023fa7ff99e3260b..c52e2a1deacf81322268e73e611134be9d9afa3a 100644 --- a/internal/rootmux/rootmux_test.go +++ b/internal/rootmux/rootmux_test.go @@ -17,14 +17,16 @@ import ( ) var ( - oAuth2Server *httptest.Server - monthlyInfo = models.MonthlyInfo{Year: 2021, Month: 0, Info: "Informations du mois", Image: "imagebase64"} - monthlyInfoStr string - monthlyNews = models.MonthlyNews{Year: 2021, Month: 0, Title: "", Content: "Nouvelles fonctionnalités"} - monthlyNewsStr string - newPoll = models.Poll{Year: 2021, Month: 0, Question: "pollQuestion", Link: "pollLink"} - newPollStr string - noH map[string]string + oAuth2Server *httptest.Server + monthlyInfo = models.MonthlyInfo{Year: 2021, Month: 0, Info: "Informations du mois", Image: "imagebase64"} + monthlyInfoStr string + monthlyNews = models.MonthlyNews{Year: 2021, Month: 0, Title: "", Content: "Nouvelles fonctionnalités"} + monthlyNewsStr string + newPoll = models.Poll{Year: 2021, Month: 0, Question: "pollQuestion", Link: "pollLink"} + newPollStr string + partnersInfo = models.PartnersInfo{ID: 1, GRDFFailure: false, EnedisFailure: false, EGLFailure: true, NotificationActivated: true} + partnersInfoStr string + noH map[string]string ) func TestMain(m *testing.M) { @@ -51,6 +53,8 @@ func TestMain(m *testing.M) { monthlyNewsStr = string(monthlyNewsBytes) monthlyInfoBytes, _ := json.Marshal(monthlyInfo) monthlyInfoStr = string(monthlyInfoBytes) + partnersInfoBytes, _ := json.Marshal(partnersInfo) + partnersInfoStr = string(partnersInfoBytes) newPollBytes, _ := json.Marshal(newPoll) newPollStr = string(newPollBytes) @@ -94,8 +98,12 @@ func unloggedTests(t *testing.T) { // Try to create a monthlyNews (must fail) do("PUT", "/api/admin/monthlyNews", noH, monthlyNewsStr, http.StatusUnauthorized, "error extracting token") - // Try to get the most recent monthlyReport (must fail because not found) - do("GET", "/api/common/monthlyReport", noH, "", http.StatusNotFound, "") + // Try to get a monthlyReport (must fail because no parameters are given) + do("GET", "/api/common/monthlyReport", noH, "", http.StatusBadRequest, "") + // Try to get a monthlyReport (must fail because not found) + do("GET", "/api/common/monthlyReport?year=2021&month=12", noH, "", http.StatusNotFound, "") + // Try to get partnersInfo (must pass) + do("GET", "/api/common/partnersInfo", noH, "", http.StatusOK, `{"ID":1,"grdf_failure":false,"enedis_failure":false,"egl_failure":false,"notification_activated":false}`) } /** @@ -156,6 +164,11 @@ func adminTests(t *testing.T) { // Try to get the monthlyReport (must pass) do("GET", "/api/common/monthlyReport/2021/0", noH, "", http.StatusOK, `{"year":2021,"month":0,"info":"Informations du mois","image":"imagebase64","newsTitle":"Les nouveautés du service","newsContent":"Nouvelles fonctionnalités","question":"pollQuestion","link":"pollLink"`) + // Try to update the partnersInfo (must pass) + do("PUT", "/api/admin/partnersInfo", xsrfHeader, partnersInfoStr, http.StatusOK, partnersInfoStr) + // Try to get the monthlyInfo created (must pass) + do("GET", "/api/common/partnersInfo", xsrfHeader, "", http.StatusOK, partnersInfoStr) + // Try to delete the monthlyNews created (must pass) do("DELETE", "/api/admin/monthlyNews/2021/0", xsrfHeader, "", http.StatusOK, "successful delete") // Try to get a monthlyNews after it is deleted (must fail because not found)