Commit fdfcc3ab by Torkel Ödegaard

Admin flagged users, create a default admin user on startup if missing

parent 5ec07db1
...@@ -35,6 +35,12 @@ session_id_hashfunc = sha1 ...@@ -35,6 +35,12 @@ session_id_hashfunc = sha1
; Session hash key, default is use random string ; Session hash key, default is use random string
session_id_hashkey = 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] [auth]
anonymous = false anonymous = false
anonymous_account_id = anonymous_account_id =
......
Subproject commit 961ebbde6b6540f03d3fb5a1741722614166099f Subproject commit cf344abff2cdf7638d1748aa698caf23c3848715
...@@ -16,6 +16,7 @@ type LoginResult struct { ...@@ -16,6 +16,7 @@ type LoginResult struct {
type CurrentUser struct { type CurrentUser struct {
Login string `json:"login"` Login string `json:"login"`
Email string `json:"email"` Email string `json:"email"`
IsAdmin bool `json:"isAdmin"`
GravatarUrl string `json:"gravatarUrl"` GravatarUrl string `json:"gravatarUrl"`
} }
...@@ -48,6 +49,7 @@ func NewCurrentUser(account *models.Account) *CurrentUser { ...@@ -48,6 +49,7 @@ func NewCurrentUser(account *models.Account) *CurrentUser {
model.Login = account.Login model.Login = account.Login
model.Email = account.Email model.Email = account.Email
model.GravatarUrl = getGravatarUrl(account.Email) model.GravatarUrl = getGravatarUrl(account.Email)
model.IsAdmin = account.IsAdmin
} }
return model return model
} }
......
...@@ -16,9 +16,9 @@ import ( ...@@ -16,9 +16,9 @@ import (
"github.com/torkelo/grafana-pro/pkg/api" "github.com/torkelo/grafana-pro/pkg/api"
"github.com/torkelo/grafana-pro/pkg/log" "github.com/torkelo/grafana-pro/pkg/log"
"github.com/torkelo/grafana-pro/pkg/middleware" "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/setting"
"github.com/torkelo/grafana-pro/pkg/social" "github.com/torkelo/grafana-pro/pkg/social"
"github.com/torkelo/grafana-pro/pkg/stores/sqlstore"
) )
var CmdWeb = cli.Command{ var CmdWeb = cli.Command{
...@@ -76,11 +76,9 @@ func runWeb(c *cli.Context) { ...@@ -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)) log.Info("Version: %v, Commit: %v, Build date: %v", setting.BuildVersion, setting.BuildCommit, time.Unix(setting.BuildStamp, 0))
setting.NewConfigContext() setting.NewConfigContext()
setting.InitServices()
social.NewOAuthService() social.NewOAuthService()
sqlstore.Init()
sqlstore.NewEngine() sqlstore.NewEngine()
sqlstore.EnsureAdminUser()
m := newMacaron() m := newMacaron()
api.Register(m) api.Register(m)
......
...@@ -37,6 +37,8 @@ type CreateAccountCommand struct { ...@@ -37,6 +37,8 @@ type CreateAccountCommand struct {
Name string `json:"name"` Name string `json:"name"`
Company string `json:"company"` Company string `json:"company"`
Salt string `json:"-"` Salt string `json:"-"`
IsAdmin bool `json:"-"`
Result Account `json:"-"` Result Account `json:"-"`
} }
......
package sqlstore package sqlstore
import ( import (
"strings"
"time" "time"
"github.com/go-xorm/xorm" "github.com/go-xorm/xorm"
...@@ -30,10 +31,13 @@ func CreateAccount(cmd *m.CreateAccountCommand) error { ...@@ -30,10 +31,13 @@ func CreateAccount(cmd *m.CreateAccountCommand) error {
Login: cmd.Login, Login: cmd.Login,
Password: cmd.Password, Password: cmd.Password,
Salt: cmd.Salt, Salt: cmd.Salt,
IsAdmin: cmd.IsAdmin,
Created: time.Now(), Created: time.Now(),
Updated: time.Now(), Updated: time.Now(),
} }
sess.UseBool("is_admin")
_, err := sess.Insert(&account) _, err := sess.Insert(&account)
cmd.Result = account cmd.Result = account
return err return err
...@@ -137,10 +141,14 @@ func GetAccountByToken(query *m.GetAccountByTokenQuery) error { ...@@ -137,10 +141,14 @@ func GetAccountByToken(query *m.GetAccountByTokenQuery) error {
} }
func GetAccountByLogin(query *m.GetAccountByLoginQuery) 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 { if err != nil {
return err return err
...@@ -152,7 +160,7 @@ func GetAccountByLogin(query *m.GetAccountByLoginQuery) error { ...@@ -152,7 +160,7 @@ func GetAccountByLogin(query *m.GetAccountByLoginQuery) error {
account.UsingAccountId = account.Id account.UsingAccountId = account.Id
} }
query.Result = &account query.Result = account
return nil return nil
} }
......
...@@ -6,9 +6,11 @@ import ( ...@@ -6,9 +6,11 @@ import (
"path" "path"
"strings" "strings"
"github.com/torkelo/grafana-pro/pkg/bus"
"github.com/torkelo/grafana-pro/pkg/log" "github.com/torkelo/grafana-pro/pkg/log"
m "github.com/torkelo/grafana-pro/pkg/models" m "github.com/torkelo/grafana-pro/pkg/models"
"github.com/torkelo/grafana-pro/pkg/setting" "github.com/torkelo/grafana-pro/pkg/setting"
"github.com/torkelo/grafana-pro/pkg/util"
_ "github.com/go-sql-driver/mysql" _ "github.com/go-sql-driver/mysql"
"github.com/go-xorm/xorm" "github.com/go-xorm/xorm"
...@@ -42,23 +44,39 @@ func init() { ...@@ -42,23 +44,39 @@ func init() {
new(m.Token)) 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) { func NewEngine() {
x, err = getEngine() x, err := getEngine()
if err != nil { 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) err = SetEngine(x, true)
if err != nil { 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) { func SetEngine(engine *xorm.Engine, enableLog bool) (err error) {
......
...@@ -59,6 +59,8 @@ var ( ...@@ -59,6 +59,8 @@ var (
EnableGzip bool EnableGzip bool
// Http auth // Http auth
AdminUser string
AdminPassword string
Anonymous bool Anonymous bool
AnonymousAccountId int64 AnonymousAccountId int64
...@@ -119,7 +121,7 @@ func findConfigFiles() []string { ...@@ -119,7 +121,7 @@ func findConfigFiles() []string {
func NewConfigContext() { func NewConfigContext() {
configFiles := findConfigFiles() configFiles := findConfigFiles()
log.Info("Loading config files: %v", configFiles) //log.Info("Loading config files: %v", configFiles)
var err error var err error
Cfg, err = goconfig.LoadConfigFile(configFiles[0]) Cfg, err = goconfig.LoadConfigFile(configFiles[0])
...@@ -168,6 +170,8 @@ func NewConfigContext() { ...@@ -168,6 +170,8 @@ func NewConfigContext() {
EnableGzip = Cfg.MustBool("server", "enable_gzip") EnableGzip = Cfg.MustBool("server", "enable_gzip")
// Http auth // Http auth
AdminUser = Cfg.MustValue("admin", "user", "admin")
AdminPassword = Cfg.MustValue("admin", "password", "admin")
Anonymous = Cfg.MustBool("auth", "anonymous", false) Anonymous = Cfg.MustBool("auth", "anonymous", false)
AnonymousAccountId = Cfg.MustInt64("auth", "anonymous_account_id", 0) AnonymousAccountId = Cfg.MustInt64("auth", "anonymous_account_id", 0)
...@@ -180,10 +184,11 @@ func NewConfigContext() { ...@@ -180,10 +184,11 @@ func NewConfigContext() {
PhantomDir = "_vendor/phantomjs" PhantomDir = "_vendor/phantomjs"
LogRootPath = Cfg.MustValue("log", "root_path", path.Join(WorkDir, "/data/log")) LogRootPath = Cfg.MustValue("log", "root_path", path.Join(WorkDir, "/data/log"))
}
func initSessionService() { readSessionConfig()
}
func readSessionConfig() {
SessionOptions = session.Options{} SessionOptions = session.Options{}
SessionOptions.Provider = Cfg.MustValueRange("session", "provider", "memory", []string{"memory", "file"}) SessionOptions.Provider = Cfg.MustValueRange("session", "provider", "memory", []string{"memory", "file"})
SessionOptions.ProviderConfig = strings.Trim(Cfg.MustValue("session", "provider_config"), "\" ") SessionOptions.ProviderConfig = strings.Trim(Cfg.MustValue("session", "provider_config"), "\" ")
...@@ -199,7 +204,3 @@ func initSessionService() { ...@@ -199,7 +204,3 @@ func initSessionService() {
log.Info("Session Service Enabled") log.Info("Session Service Enabled")
} }
func InitServices() {
initSessionService()
}
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
// }
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
// }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment