Browse Source

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

Torkel Ödegaard 11 years ago
parent
commit
fdfcc3ab2a

+ 6 - 0
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 =

+ 1 - 1
grafana

@@ -1 +1 @@
-Subproject commit 961ebbde6b6540f03d3fb5a1741722614166099f
+Subproject commit cf344abff2cdf7638d1748aa698caf23c3848715

+ 2 - 0
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
 }

+ 2 - 4
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)

+ 9 - 7
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 {

+ 12 - 4
pkg/stores/sqlstore/accounts.go → 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
 }

+ 0 - 0
pkg/stores/sqlstore/accounts_test.go → pkg/services/sqlstore/accounts_test.go


+ 0 - 0
pkg/stores/sqlstore/dashboards.go → pkg/services/sqlstore/dashboards.go


+ 0 - 0
pkg/stores/sqlstore/dashboards_test.go → pkg/services/sqlstore/dashboards_test.go


+ 0 - 0
pkg/stores/sqlstore/datasource.go → pkg/services/sqlstore/datasource.go


+ 0 - 0
pkg/stores/sqlstore/datasource_test.go → pkg/services/sqlstore/datasource_test.go


+ 25 - 7
pkg/stores/sqlstore/sqlstore.go → 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) {

+ 0 - 0
pkg/stores/sqlstore/tokens.go → pkg/services/sqlstore/tokens.go


+ 8 - 7
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"))
-}
 
-func initSessionService() {
+	readSessionConfig()
+}
 
+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()
-}

+ 0 - 156
pkg/stores/file_store.go

@@ -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
-// }

+ 0 - 113
pkg/stores/file_store_test.go

@@ -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
-// }