From fdfcc3ab2a27484ff1e775210cce9a1209a279ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Thu, 15 Jan 2015 14:44:15 +0100 Subject: [PATCH] Admin flagged users, create a default admin user on startup if missing --- conf/grafana.ini | 6 + grafana | 2 +- pkg/api/dtos/models.go | 2 + pkg/cmd/web.go | 6 +- pkg/models/account.go | 16 +- pkg/{stores => services}/sqlstore/accounts.go | 16 +- .../sqlstore/accounts_test.go | 0 .../sqlstore/dashboards.go | 0 .../sqlstore/dashboards_test.go | 0 .../sqlstore/datasource.go | 0 .../sqlstore/datasource_test.go | 0 pkg/{stores => services}/sqlstore/sqlstore.go | 32 +++- pkg/{stores => services}/sqlstore/tokens.go | 0 pkg/setting/setting.go | 15 +- pkg/stores/file_store.go | 156 ------------------ pkg/stores/file_store_test.go | 113 ------------- 16 files changed, 65 insertions(+), 299 deletions(-) rename pkg/{stores => services}/sqlstore/accounts.go (93%) rename pkg/{stores => services}/sqlstore/accounts_test.go (100%) rename pkg/{stores => services}/sqlstore/dashboards.go (100%) rename pkg/{stores => services}/sqlstore/dashboards_test.go (100%) rename pkg/{stores => services}/sqlstore/datasource.go (100%) rename pkg/{stores => services}/sqlstore/datasource_test.go (100%) rename pkg/{stores => services}/sqlstore/sqlstore.go (77%) rename pkg/{stores => services}/sqlstore/tokens.go (100%) delete mode 100644 pkg/stores/file_store.go delete mode 100644 pkg/stores/file_store_test.go diff --git a/conf/grafana.ini b/conf/grafana.ini index f0210e89b36..26931572eb3 100644 --- a/conf/grafana.ini +++ b/conf/grafana.ini @@ -35,6 +35,12 @@ session_id_hashfunc = sha1 ; Session hash key, default is use random string session_id_hashkey = +[admin] +; default admin user, created on startup +user = admin +; default admin password, can be changed before first start of grafana, or in profile settings +password = admin + [auth] anonymous = false anonymous_account_id = diff --git a/grafana b/grafana index 961ebbde6b6..cf344abff2c 160000 --- a/grafana +++ b/grafana @@ -1 +1 @@ -Subproject commit 961ebbde6b6540f03d3fb5a1741722614166099f +Subproject commit cf344abff2cdf7638d1748aa698caf23c3848715 diff --git a/pkg/api/dtos/models.go b/pkg/api/dtos/models.go index ea5fc7f4c85..05a4967ed30 100644 --- a/pkg/api/dtos/models.go +++ b/pkg/api/dtos/models.go @@ -16,6 +16,7 @@ type LoginResult struct { type CurrentUser struct { Login string `json:"login"` Email string `json:"email"` + IsAdmin bool `json:"isAdmin"` GravatarUrl string `json:"gravatarUrl"` } @@ -48,6 +49,7 @@ func NewCurrentUser(account *models.Account) *CurrentUser { model.Login = account.Login model.Email = account.Email model.GravatarUrl = getGravatarUrl(account.Email) + model.IsAdmin = account.IsAdmin } return model } diff --git a/pkg/cmd/web.go b/pkg/cmd/web.go index 786c5304534..8870816466d 100644 --- a/pkg/cmd/web.go +++ b/pkg/cmd/web.go @@ -16,9 +16,9 @@ import ( "github.com/torkelo/grafana-pro/pkg/api" "github.com/torkelo/grafana-pro/pkg/log" "github.com/torkelo/grafana-pro/pkg/middleware" + "github.com/torkelo/grafana-pro/pkg/services/sqlstore" "github.com/torkelo/grafana-pro/pkg/setting" "github.com/torkelo/grafana-pro/pkg/social" - "github.com/torkelo/grafana-pro/pkg/stores/sqlstore" ) var CmdWeb = cli.Command{ @@ -76,11 +76,9 @@ func runWeb(c *cli.Context) { log.Info("Version: %v, Commit: %v, Build date: %v", setting.BuildVersion, setting.BuildCommit, time.Unix(setting.BuildStamp, 0)) setting.NewConfigContext() - setting.InitServices() social.NewOAuthService() - - sqlstore.Init() sqlstore.NewEngine() + sqlstore.EnsureAdminUser() m := newMacaron() api.Register(m) diff --git a/pkg/models/account.go b/pkg/models/account.go index 8e4a897a58b..5e9f507c578 100644 --- a/pkg/models/account.go +++ b/pkg/models/account.go @@ -31,13 +31,15 @@ type Account struct { // COMMANDS type CreateAccountCommand struct { - Email string `json:"email" binding:"required"` - Login string `json:"login"` - Password string `json:"password" binding:"required"` - Name string `json:"name"` - Company string `json:"company"` - Salt string `json:"-"` - Result Account `json:"-"` + Email string `json:"email" binding:"required"` + Login string `json:"login"` + Password string `json:"password" binding:"required"` + Name string `json:"name"` + Company string `json:"company"` + Salt string `json:"-"` + IsAdmin bool `json:"-"` + + Result Account `json:"-"` } type SetUsingAccountCommand struct { diff --git a/pkg/stores/sqlstore/accounts.go b/pkg/services/sqlstore/accounts.go similarity index 93% rename from pkg/stores/sqlstore/accounts.go rename to pkg/services/sqlstore/accounts.go index 76fbb288d81..f0e91dc2169 100644 --- a/pkg/stores/sqlstore/accounts.go +++ b/pkg/services/sqlstore/accounts.go @@ -1,6 +1,7 @@ package sqlstore import ( + "strings" "time" "github.com/go-xorm/xorm" @@ -30,10 +31,13 @@ func CreateAccount(cmd *m.CreateAccountCommand) error { Login: cmd.Login, Password: cmd.Password, Salt: cmd.Salt, + IsAdmin: cmd.IsAdmin, Created: time.Now(), Updated: time.Now(), } + sess.UseBool("is_admin") + _, err := sess.Insert(&account) cmd.Result = account return err @@ -137,10 +141,14 @@ func GetAccountByToken(query *m.GetAccountByTokenQuery) error { } func GetAccountByLogin(query *m.GetAccountByLoginQuery) error { - var err error + account := new(m.Account) + if strings.Contains(query.Login, "@") { + account = &m.Account{Email: query.Login} + } else { + account = &m.Account{Login: strings.ToLower(query.Login)} + } - account := m.Account{Login: query.Login} - has, err := x.Get(&account) + has, err := x.Get(account) if err != nil { return err @@ -152,7 +160,7 @@ func GetAccountByLogin(query *m.GetAccountByLoginQuery) error { account.UsingAccountId = account.Id } - query.Result = &account + query.Result = account return nil } diff --git a/pkg/stores/sqlstore/accounts_test.go b/pkg/services/sqlstore/accounts_test.go similarity index 100% rename from pkg/stores/sqlstore/accounts_test.go rename to pkg/services/sqlstore/accounts_test.go diff --git a/pkg/stores/sqlstore/dashboards.go b/pkg/services/sqlstore/dashboards.go similarity index 100% rename from pkg/stores/sqlstore/dashboards.go rename to pkg/services/sqlstore/dashboards.go diff --git a/pkg/stores/sqlstore/dashboards_test.go b/pkg/services/sqlstore/dashboards_test.go similarity index 100% rename from pkg/stores/sqlstore/dashboards_test.go rename to pkg/services/sqlstore/dashboards_test.go diff --git a/pkg/stores/sqlstore/datasource.go b/pkg/services/sqlstore/datasource.go similarity index 100% rename from pkg/stores/sqlstore/datasource.go rename to pkg/services/sqlstore/datasource.go diff --git a/pkg/stores/sqlstore/datasource_test.go b/pkg/services/sqlstore/datasource_test.go similarity index 100% rename from pkg/stores/sqlstore/datasource_test.go rename to pkg/services/sqlstore/datasource_test.go diff --git a/pkg/stores/sqlstore/sqlstore.go b/pkg/services/sqlstore/sqlstore.go similarity index 77% rename from pkg/stores/sqlstore/sqlstore.go rename to pkg/services/sqlstore/sqlstore.go index e21a9c2e6e2..c06b7124b50 100644 --- a/pkg/stores/sqlstore/sqlstore.go +++ b/pkg/services/sqlstore/sqlstore.go @@ -6,9 +6,11 @@ import ( "path" "strings" + "github.com/torkelo/grafana-pro/pkg/bus" "github.com/torkelo/grafana-pro/pkg/log" m "github.com/torkelo/grafana-pro/pkg/models" "github.com/torkelo/grafana-pro/pkg/setting" + "github.com/torkelo/grafana-pro/pkg/util" _ "github.com/go-sql-driver/mysql" "github.com/go-xorm/xorm" @@ -42,23 +44,39 @@ func init() { new(m.Token)) } -func Init() { +func EnsureAdminUser() { + adminQuery := m.GetAccountByLoginQuery{Login: setting.AdminUser} + + if err := bus.Dispatch(&adminQuery); err == m.ErrAccountNotFound { + cmd := m.CreateAccountCommand{} + cmd.Login = setting.AdminUser + cmd.Email = setting.AdminUser + "@localhost" + cmd.Salt = util.GetRandomString(10) + cmd.Password = util.EncodePassword(setting.AdminPassword, cmd.Salt) + cmd.IsAdmin = true + + if err = bus.Dispatch(&cmd); err != nil { + log.Fatal(3, "Failed to create default admin user", err) + } + + log.Info("Created default admin user: %v", setting.AdminUser) + } else if err != nil { + log.Fatal(3, "Could not determine if admin user exists: %v", err) + } } -func NewEngine() (err error) { - x, err = getEngine() +func NewEngine() { + x, err := getEngine() if err != nil { - return fmt.Errorf("sqlstore.init(fail to connect to database): %v", err) + log.Fatal(3, "Sqlstore: Fail to connect to database: %v", err) } err = SetEngine(x, true) if err != nil { - log.Fatal(4, "fail to initialize orm engine: %v", err) + log.Fatal(3, "fail to initialize orm engine: %v", err) } - - return nil } func SetEngine(engine *xorm.Engine, enableLog bool) (err error) { diff --git a/pkg/stores/sqlstore/tokens.go b/pkg/services/sqlstore/tokens.go similarity index 100% rename from pkg/stores/sqlstore/tokens.go rename to pkg/services/sqlstore/tokens.go diff --git a/pkg/setting/setting.go b/pkg/setting/setting.go index 4805949eb33..1e79fe8fc6b 100644 --- a/pkg/setting/setting.go +++ b/pkg/setting/setting.go @@ -59,6 +59,8 @@ var ( EnableGzip bool // Http auth + AdminUser string + AdminPassword string Anonymous bool AnonymousAccountId int64 @@ -119,7 +121,7 @@ func findConfigFiles() []string { func NewConfigContext() { configFiles := findConfigFiles() - log.Info("Loading config files: %v", configFiles) + //log.Info("Loading config files: %v", configFiles) var err error Cfg, err = goconfig.LoadConfigFile(configFiles[0]) @@ -168,6 +170,8 @@ func NewConfigContext() { EnableGzip = Cfg.MustBool("server", "enable_gzip") // Http auth + AdminUser = Cfg.MustValue("admin", "user", "admin") + AdminPassword = Cfg.MustValue("admin", "password", "admin") Anonymous = Cfg.MustBool("auth", "anonymous", false) AnonymousAccountId = Cfg.MustInt64("auth", "anonymous_account_id", 0) @@ -180,10 +184,11 @@ func NewConfigContext() { PhantomDir = "_vendor/phantomjs" LogRootPath = Cfg.MustValue("log", "root_path", path.Join(WorkDir, "/data/log")) + + readSessionConfig() } -func initSessionService() { - +func readSessionConfig() { SessionOptions = session.Options{} SessionOptions.Provider = Cfg.MustValueRange("session", "provider", "memory", []string{"memory", "file"}) SessionOptions.ProviderConfig = strings.Trim(Cfg.MustValue("session", "provider_config"), "\" ") @@ -199,7 +204,3 @@ func initSessionService() { log.Info("Session Service Enabled") } - -func InitServices() { - initSessionService() -} diff --git a/pkg/stores/file_store.go b/pkg/stores/file_store.go deleted file mode 100644 index bf1c3f4f77e..00000000000 --- a/pkg/stores/file_store.go +++ /dev/null @@ -1,156 +0,0 @@ -package stores - -// -// import ( -// "encoding/json" -// "io" -// "os" -// "path/filepath" -// "strings" -// -// log "github.com/alecthomas/log4go" -// "github.com/torkelo/grafana-pro/pkg/models" -// ) -// -// type fileStore struct { -// dataDir string -// dashDir string -// cache map[string]*models.Dashboard -// } -// -// func NewFileStore(dataDir string) *fileStore { -// -// if dirDoesNotExist(dataDir) { -// log.Crashf("FileStore failed to initialize, dataDir does not exist %v", dataDir) -// } -// -// dashDir := filepath.Join(dataDir, "dashboards") -// -// if dirDoesNotExist(dashDir) { -// log.Debug("Did not find dashboard dir, creating...") -// err := os.Mkdir(dashDir, 0777) -// if err != nil { -// log.Crashf("FileStore failed to initialize, could not create directory %v, error: %v", dashDir, err) -// } -// } -// -// store := &fileStore{} -// store.dataDir = dataDir -// store.dashDir = dashDir -// store.cache = make(map[string]*models.Dashboard) -// store.scanFiles() -// -// return store -// } -// -// func (store *fileStore) scanFiles() { -// visitor := func(path string, f os.FileInfo, err error) error { -// if err != nil { -// return err -// } -// if f.IsDir() { -// return nil -// } -// if strings.HasSuffix(f.Name(), ".json") { -// err = store.loadDashboardIntoCache(path) -// if err != nil { -// return err -// } -// } -// return nil -// } -// -// err := filepath.Walk(store.dashDir, visitor) -// if err != nil { -// log.Error("FileStore::updateCache failed %v", err) -// } -// } -// -// func (store fileStore) loadDashboardIntoCache(filename string) error { -// log.Info("Loading dashboard file %v into cache", filename) -// dash, err := loadDashboardFromFile(filename) -// if err != nil { -// return err -// } -// -// store.cache[dash.Title] = dash -// -// return nil -// } -// -// func (store *fileStore) Close() { -// -// } -// -// func (store *fileStore) GetById(id string) (*models.Dashboard, error) { -// log.Debug("FileStore::GetById id = %v", id) -// filename := store.getFilePathForDashboard(id) -// -// return loadDashboardFromFile(filename) -// } -// -// func (store *fileStore) Save(dash *models.Dashboard) error { -// filename := store.getFilePathForDashboard(dash.Title) -// -// log.Debug("Saving dashboard %v to %v", dash.Title, filename) -// -// var err error -// var data []byte -// if data, err = json.Marshal(dash.Data); err != nil { -// return err -// } -// -// return writeFile(filename, data) -// } -// -// func (store *fileStore) Query(query string) ([]*models.SearchResult, error) { -// results := make([]*models.SearchResult, 0, 50) -// -// for _, dash := range store.cache { -// item := &models.SearchResult{ -// Id: dash.Title, -// Type: "dashboard", -// } -// results = append(results, item) -// } -// -// return results, nil -// } -// -// func loadDashboardFromFile(filename string) (*models.Dashboard, error) { -// log.Debug("FileStore::loading dashboard from file %v", filename) -// -// configFile, err := os.Open(filename) -// if err != nil { -// return nil, err -// } -// -// return models.NewFromJson(configFile) -// } -// -// func (store *fileStore) getFilePathForDashboard(id string) string { -// id = strings.ToLower(id) -// id = strings.Replace(id, " ", "-", -1) -// return filepath.Join(store.dashDir, id) + ".json" -// } -// -// func dirDoesNotExist(dir string) bool { -// _, err := os.Stat(dir) -// return os.IsNotExist(err) -// } -// -// func writeFile(filename string, data []byte) error { -// f, err := os.OpenFile(filename, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666) -// if err != nil { -// return err -// } -// n, err := f.Write(data) -// if err == nil && n < len(data) { -// err = io.ErrShortWrite -// } -// if err1 := f.Close(); err == nil { -// err = err1 -// } -// -// return err -// } diff --git a/pkg/stores/file_store_test.go b/pkg/stores/file_store_test.go deleted file mode 100644 index aff4673c7f5..00000000000 --- a/pkg/stores/file_store_test.go +++ /dev/null @@ -1,113 +0,0 @@ -package stores - -// -// import ( -// "fmt" -// "io" -// "io/ioutil" -// "os" -// "path/filepath" -// "testing" -// -// . "github.com/smartystreets/goconvey/convey" -// "github.com/torkelo/grafana-pro/pkg/models" -// ) -// -// func TestFileStore(t *testing.T) { -// -// GivenFileStore("When saving a dashboard", t, func(store *fileStore) { -// dashboard := models.NewDashboard("hello") -// -// err := store.Save(dashboard) -// -// Convey("should be saved to disk", func() { -// So(err, ShouldBeNil) -// -// _, err = os.Stat(store.getFilePathForDashboard("hello")) -// So(err, ShouldBeNil) -// }) -// }) -// -// GivenFileStore("When getting a saved dashboard", t, func(store *fileStore) { -// copyDashboardToTempData("default.json", "", store.dashDir) -// dash, err := store.GetById("default") -// -// Convey("should be read from disk", func() { -// So(err, ShouldBeNil) -// So(dash, ShouldNotBeNil) -// -// So(dash.Title, ShouldEqual, "Grafana Play Home") -// }) -// }) -// -// GivenFileStore("when getting dashboard with capital letters", t, func(store *fileStore) { -// copyDashboardToTempData("annotations.json", "", store.dashDir) -// dash, err := store.GetById("AnnoTations") -// -// Convey("should be read from disk", func() { -// So(err, ShouldBeNil) -// So(dash, ShouldNotBeNil) -// -// So(dash.Title, ShouldEqual, "Annotations") -// }) -// }) -// -// GivenFileStore("When copying dashboards into data dir", t, func(store *fileStore) { -// copyDashboardToTempData("annotations.json", "", store.dashDir) -// copyDashboardToTempData("default.json", "", store.dashDir) -// copyDashboardToTempData("graph-styles.json", "", store.dashDir) -// store.scanFiles() -// -// Convey("scan should generate index of all dashboards", func() { -// -// result, err := store.Query("*") -// So(err, ShouldBeNil) -// So(len(result), ShouldEqual, 3) -// }) -// }) -// } -// -// func copyDashboardToTempData(name string, destName string, dir string) { -// if destName == "" { -// destName = name -// } -// source, _ := filepath.Abs("../../data/dashboards/" + name) -// dest := filepath.Join(dir, destName) -// err := copyFile(dest, source) -// if err != nil { -// panic(fmt.Sprintf("failed to copy file %v", name)) -// } -// } -// -// func GivenFileStore(desc string, t *testing.T, f func(store *fileStore)) { -// Convey(desc, t, func() { -// tempDir, _ := ioutil.TempDir("", "store") -// -// store := NewFileStore(tempDir) -// -// f(store) -// -// Reset(func() { -// os.RemoveAll(tempDir) -// }) -// }) -// } -// -// func copyFile(dst, src string) error { -// in, err := os.Open(src) -// if err != nil { -// return err -// } -// defer in.Close() -// out, err := os.Create(dst) -// if err != nil { -// return err -// } -// defer out.Close() -// _, err = io.Copy(out, in) -// cerr := out.Close() -// if err != nil { -// return err -// } -// return cerr -// }