From a9a06ad51d25a05a868f8f338eb7ea23e1397378 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Fri, 19 Sep 2014 17:37:18 +0200 Subject: [PATCH] Working on collaborators --- grafana | 2 +- pkg/api/api.go | 16 +++-------- pkg/api/api_account.go | 28 +++++++++++-------- pkg/api/api_auth.go | 48 ++++++++++++++++++++++++++++++++ pkg/api/api_dashboard.go | 30 +++++++++----------- pkg/api/api_login.go | 23 ++------------- pkg/api/api_routing.go | 36 ++++++++++++++++++++++++ pkg/models/account.go | 43 ++++++++++++++++++++++++++++ pkg/models/dashboards.go | 34 ---------------------- pkg/stores/rethinkdb_accounts.go | 1 + views/index.html | 11 ++++++-- 11 files changed, 174 insertions(+), 98 deletions(-) create mode 100644 pkg/api/api_auth.go create mode 100644 pkg/api/api_routing.go create mode 100644 pkg/models/account.go diff --git a/grafana b/grafana index e5fd35db343..c65b7d15918 160000 --- a/grafana +++ b/grafana @@ -1 +1 @@ -Subproject commit e5fd35db343109feec09d339d5d770dd1de1808a +Subproject commit c65b7d159189f81b0d87ecc5b64be3ffbe332393 diff --git a/pkg/api/api.go b/pkg/api/api.go index 65ee23b88a4..3167ebff74f 100644 --- a/pkg/api/api.go +++ b/pkg/api/api.go @@ -7,6 +7,7 @@ import ( "github.com/gin-gonic/gin" "github.com/gorilla/sessions" "github.com/torkelo/grafana-pro/pkg/components" + "github.com/torkelo/grafana-pro/pkg/models" "github.com/torkelo/grafana-pro/pkg/stores" ) @@ -61,9 +62,9 @@ func (self *HttpServer) ListenAndServe() { func (self *HttpServer) index(c *gin.Context) { viewModel := &IndexDto{} - login, _ := c.Get("login") - if login != nil { - viewModel.User.Login = login.(string) + userAccount, _ := c.Get("userAccount") + if userAccount != nil { + viewModel.User.Login = userAccount.(*models.UserAccount).Login } c.HTML(200, "index.html", viewModel) @@ -74,12 +75,3 @@ func CacheHeadersMiddleware() gin.HandlerFunc { c.Writer.Header().Add("Cache-Control", "max-age=0, public, must-revalidate, proxy-revalidate") } } - -// Api Handler Registration -var routeHandlers = make([]routeHandlerRegisterFn, 0) - -type routeHandlerRegisterFn func(self *HttpServer) - -func addRoutes(fn routeHandlerRegisterFn) { - routeHandlers = append(routeHandlers, fn) -} diff --git a/pkg/api/api_account.go b/pkg/api/api_account.go index 22f2b85b70e..c4cf1aa94b7 100644 --- a/pkg/api/api_account.go +++ b/pkg/api/api_account.go @@ -4,7 +4,7 @@ import "github.com/gin-gonic/gin" func init() { addRoutes(func(self *HttpServer) { - self.router.POST("/api/account/collaborators/add", self.auth(), self.addCollaborator) + self.addRoute("POST", "/api/account/collaborators/add", self.addCollaborator) }) } @@ -12,28 +12,34 @@ type addCollaboratorDto struct { Email string `json:"email" binding:"required"` } -func (self *HttpServer) addCollaborator(c *gin.Context) { +func (self *HttpServer) addCollaborator(c *gin.Context, auth *authContext) { var model addCollaboratorDto if !c.EnsureBody(&model) { - c.JSON(400, gin.H{"status": "bad request"}) + c.JSON(400, gin.H{"status": "Collaborator not found"}) return } - accountId, _ := c.Get("accountId") - account, err := self.store.GetAccount(accountId.(int)) - if err != nil { - c.JSON(401, gin.H{"status": "Authentication error"}) - } - collaborator, err := self.store.GetUserAccountLogin(model.Email) if err != nil { c.JSON(404, gin.H{"status": "Collaborator not found"}) + return } - account.AddCollaborator(collaborator.Id) + userAccount := auth.userAccount - self.store.SaveUserAccount(account) + if collaborator.Id == userAccount.Id { + c.JSON(400, gin.H{"status": "Cannot add yourself as collaborator"}) + return + } + + err = userAccount.AddCollaborator(collaborator.Id) + if err != nil { + c.JSON(400, gin.H{"status": err.Error()}) + return + } + + self.store.SaveUserAccount(userAccount) c.JSON(200, gin.H{"status": "Collaborator added"}) } diff --git a/pkg/api/api_auth.go b/pkg/api/api_auth.go new file mode 100644 index 00000000000..5d5a65aaada --- /dev/null +++ b/pkg/api/api_auth.go @@ -0,0 +1,48 @@ +package api + +import ( + "github.com/gin-gonic/gin" + "github.com/torkelo/grafana-pro/pkg/models" +) + +type authContext struct { + account *models.UserAccount + userAccount *models.UserAccount +} + +func (auth *authContext) getAccountId() int { + return auth.account.Id +} + +func (self *HttpServer) authDenied(c *gin.Context) { + c.Writer.Header().Set("Location", "/login") + c.Abort(302) +} + +func (self *HttpServer) auth() gin.HandlerFunc { + return func(c *gin.Context) { + session, _ := sessionStore.Get(c.Request, "grafana-session") + + if c.Request.URL.Path != "/login" && session.Values["userAccountId"] == nil { + self.authDenied(c) + return + } + + account, err := self.store.GetAccount(session.Values["userAccountId"].(int)) + if err != nil { + self.authDenied(c) + return + } + + usingAccount, err := self.store.GetAccount(session.Values["usingAccountId"].(int)) + if err != nil { + self.authDenied(c) + return + } + + c.Set("userAccount", account) + c.Set("usingAccount", usingAccount) + + session.Save(c.Request, c.Writer) + } +} diff --git a/pkg/api/api_dashboard.go b/pkg/api/api_dashboard.go index 77b50bdf202..36448aa7ecb 100644 --- a/pkg/api/api_dashboard.go +++ b/pkg/api/api_dashboard.go @@ -8,18 +8,17 @@ import ( func init() { addRoutes(func(self *HttpServer) { - self.router.GET("/api/dashboards/:slug", self.auth(), self.getDashboard) - self.router.GET("/api/search/", self.auth(), self.search) - self.router.POST("/api/dashboard", self.auth(), self.postDashboard) - self.router.DELETE("/api/dashboard/:slug", self.auth(), self.deleteDashboard) + self.addRoute("GET", "/api/dashboards/:slug", self.getDashboard) + self.addRoute("GET", "/api/search/", self.search) + self.addRoute("POST", "/api/dashboard/", self.postDashboard) + self.addRoute("DELETE", "/api/dashboard/:slug", self.deleteDashboard) }) } -func (self *HttpServer) getDashboard(c *gin.Context) { +func (self *HttpServer) getDashboard(c *gin.Context, auth *authContext) { slug := c.Params.ByName("slug") - accountId, err := c.Get("accountId") - dash, err := self.store.GetDashboard(slug, accountId.(int)) + dash, err := self.store.GetDashboard(slug, auth.getAccountId()) if err != nil { c.JSON(404, newErrorResponse("Dashboard not found")) return @@ -30,17 +29,16 @@ func (self *HttpServer) getDashboard(c *gin.Context) { c.JSON(200, dash.Data) } -func (self *HttpServer) deleteDashboard(c *gin.Context) { +func (self *HttpServer) deleteDashboard(c *gin.Context, auth *authContext) { slug := c.Params.ByName("slug") - accountId, err := c.Get("accountId") - dash, err := self.store.GetDashboard(slug, accountId.(int)) + dash, err := self.store.GetDashboard(slug, auth.getAccountId()) if err != nil { c.JSON(404, newErrorResponse("Dashboard not found")) return } - err = self.store.DeleteDashboard(slug, accountId.(int)) + err = self.store.DeleteDashboard(slug, auth.getAccountId()) if err != nil { c.JSON(500, newErrorResponse("Failed to delete dashboard: "+err.Error())) return @@ -51,11 +49,10 @@ func (self *HttpServer) deleteDashboard(c *gin.Context) { c.JSON(200, resp) } -func (self *HttpServer) search(c *gin.Context) { +func (self *HttpServer) search(c *gin.Context, auth *authContext) { query := c.Params.ByName("q") - accountId, err := c.Get("accountId") - results, err := self.store.Query(query, accountId.(int)) + results, err := self.store.Query(query, auth.getAccountId()) if err != nil { log.Error("Store query error: %v", err) c.JSON(500, newErrorResponse("Failed")) @@ -65,15 +62,14 @@ func (self *HttpServer) search(c *gin.Context) { c.JSON(200, results) } -func (self *HttpServer) postDashboard(c *gin.Context) { +func (self *HttpServer) postDashboard(c *gin.Context, auth *authContext) { var command saveDashboardCommand - accountId, _ := c.Get("accountId") if c.EnsureBody(&command) { dashboard := models.NewDashboard("test") dashboard.Data = command.Dashboard dashboard.Title = dashboard.Data["title"].(string) - dashboard.AccountId = accountId.(int) + dashboard.AccountId = auth.getAccountId() dashboard.UpdateSlug() if dashboard.Data["id"] != nil { diff --git a/pkg/api/api_login.go b/pkg/api/api_login.go index 1651535e53e..487f1d237b1 100644 --- a/pkg/api/api_login.go +++ b/pkg/api/api_login.go @@ -35,8 +35,8 @@ func (self *HttpServer) loginPost(c *gin.Context) { } session, _ := sessionStore.Get(c.Request, "grafana-session") - session.Values["login"] = loginModel.Email - session.Values["accountId"] = account.Id + session.Values["userAccountId"] = account.Id + session.Values["usingAccountId"] = account.UsingAccountId session.Save(c.Request, c.Writer) var resp = &LoginResultDto{} @@ -48,25 +48,8 @@ func (self *HttpServer) loginPost(c *gin.Context) { func (self *HttpServer) logoutPost(c *gin.Context) { session, _ := sessionStore.Get(c.Request, "grafana-session") - session.Values["login"] = nil + session.Values = nil session.Save(c.Request, c.Writer) c.JSON(200, gin.H{"status": "logged out"}) } - -func (self *HttpServer) auth() gin.HandlerFunc { - return func(c *gin.Context) { - session, _ := sessionStore.Get(c.Request, "grafana-session") - - if c.Request.URL.Path != "/login" && session.Values["login"] == nil { - c.Writer.Header().Set("Location", "/login") - c.Abort(302) - return - } - - c.Set("accountId", session.Values["accountId"]) - c.Set("login", session.Values["login"]) - - session.Save(c.Request, c.Writer) - } -} diff --git a/pkg/api/api_routing.go b/pkg/api/api_routing.go new file mode 100644 index 00000000000..5fb703a6e54 --- /dev/null +++ b/pkg/api/api_routing.go @@ -0,0 +1,36 @@ +package api + +import ( + "github.com/gin-gonic/gin" + "github.com/torkelo/grafana-pro/pkg/models" +) + +type routeHandlerRegisterFn func(self *HttpServer) +type routeHandlerFn func(c *gin.Context, auth *authContext) + +var routeHandlers = make([]routeHandlerRegisterFn, 0) + +func getRouteHandlerWrapper(handler routeHandlerFn) gin.HandlerFunc { + return func(c *gin.Context) { + authContext := authContext{ + account: c.MustGet("usingAccount").(*models.UserAccount), + userAccount: c.MustGet("userAccount").(*models.UserAccount), + } + handler(c, &authContext) + } +} + +func (self *HttpServer) addRoute(method string, path string, handler routeHandlerFn) { + switch method { + case "GET": + self.router.GET(path, self.auth(), getRouteHandlerWrapper(handler)) + case "POST": + self.router.POST(path, self.auth(), getRouteHandlerWrapper(handler)) + case "DELETE": + self.router.DELETE(path, self.auth(), getRouteHandlerWrapper(handler)) + } +} + +func addRoutes(fn routeHandlerRegisterFn) { + routeHandlers = append(routeHandlers, fn) +} diff --git a/pkg/models/account.go b/pkg/models/account.go new file mode 100644 index 00000000000..73d9cb2280c --- /dev/null +++ b/pkg/models/account.go @@ -0,0 +1,43 @@ +package models + +import ( + "errors" + "time" +) + +type CollaboratorLink struct { + AccountId int + Role string + ModifiedOn time.Time + CreatedOn time.Time +} + +type UserAccount struct { + Id int `gorethink:"id"` + UserName string + Login string + Email string + Password string + NextDashboardId int + UsingAccountId int + Collaborators []CollaboratorLink + CreatedOn time.Time + ModifiedOn time.Time +} + +func (account *UserAccount) AddCollaborator(accountId int) error { + for _, collaborator := range account.Collaborators { + if collaborator.AccountId == accountId { + return errors.New("Collaborator already exists") + } + } + + account.Collaborators = append(account.Collaborators, CollaboratorLink{ + AccountId: accountId, + Role: "admin", + CreatedOn: time.Now(), + ModifiedOn: time.Now(), + }) + + return nil +} diff --git a/pkg/models/dashboards.go b/pkg/models/dashboards.go index 27066076a76..2a507564a45 100644 --- a/pkg/models/dashboards.go +++ b/pkg/models/dashboards.go @@ -21,31 +21,6 @@ type Dashboard struct { Data map[string]interface{} } -type CollaboratorLink struct { - AccountId int - Role string - ModifiedOn time.Time - CreatedOn time.Time -} - -type UserAccount struct { - Id int `gorethink:"id"` - UserName string - Login string - Email string - Password string - NextDashboardId int - UsingAccountId int - Collaborators []CollaboratorLink - CreatedOn time.Time - ModifiedOn time.Time -} - -type UserContext struct { - UserId string - AccountId string -} - type SearchResult struct { Id string `json:"id"` Title string `json:"title"` @@ -87,12 +62,3 @@ func (dash *Dashboard) UpdateSlug() { re2 := regexp.MustCompile("\\s") dash.Slug = re2.ReplaceAllString(re.ReplaceAllString(title, ""), "-") } - -func (account *UserAccount) AddCollaborator(accountId int) { - account.Collaborators = append(account.Collaborators, CollaboratorLink{ - AccountId: accountId, - Role: "admin", - CreatedOn: time.Now(), - ModifiedOn: time.Now(), - }) -} diff --git a/pkg/stores/rethinkdb_accounts.go b/pkg/stores/rethinkdb_accounts.go index 0492f5c729c..4b034b58804 100644 --- a/pkg/stores/rethinkdb_accounts.go +++ b/pkg/stores/rethinkdb_accounts.go @@ -32,6 +32,7 @@ func (self *rethinkStore) SaveUserAccount(account *models.UserAccount) error { } account.Id = accountId + account.UsingAccountId = accountId resp, err := r.Table("accounts").Insert(account).RunWrite(self.session) if err != nil { diff --git a/views/index.html b/views/index.html index 9f5f83dbae5..49159dcb8ee 100644 --- a/views/index.html +++ b/views/index.html @@ -28,9 +28,14 @@
-
- - {{alert.title}}
{{$index + 1}} alert(s)
+
+
+ +
{{alert.title}}
+
+