浏览代码

working on oauth

Torkel Ödegaard 11 年之前
父节点
当前提交
450d242d5f

+ 2 - 0
.gitignore

@@ -13,3 +13,5 @@ config.js
 *.sublime-workspace
 *.sublime-workspace
 *.swp
 *.swp
 .idea/
 .idea/
+
+data/sessions

二进制
grafana-pro


+ 1 - 1
pkg/middleware/middleware.go

@@ -55,7 +55,7 @@ func (ctx *Context) Handle(status int, title string, err error) {
 	ctx.HTML(status, "index")
 	ctx.HTML(status, "index")
 }
 }
 
 
-func (ctx *Context) ApiError(status int, message string, err error) {
+func (ctx *Context) JsonApiErr(status int, message string, err error) {
 	resp := make(map[string]interface{})
 	resp := make(map[string]interface{})
 
 
 	if err != nil {
 	if err != nil {

+ 9 - 0
pkg/models/models.go

@@ -0,0 +1,9 @@
+package models
+
+type OAuthType int
+
+const (
+	GITHUB OAuthType = iota + 1
+	GOOGLE
+	TWITTER
+)

+ 6 - 6
pkg/routes/api/api_dashboard.go

@@ -13,7 +13,7 @@ func GetDashboard(c *middleware.Context) {
 
 
 	dash, err := models.GetDashboard(slug, c.GetAccountId())
 	dash, err := models.GetDashboard(slug, c.GetAccountId())
 	if err != nil {
 	if err != nil {
-		c.ApiError(404, "Dashboard not found", nil)
+		c.JsonApiErr(404, "Dashboard not found", nil)
 		return
 		return
 	}
 	}
 
 
@@ -27,13 +27,13 @@ func DeleteDashboard(c *middleware.Context) {
 
 
 	dash, err := models.GetDashboard(slug, c.GetAccountId())
 	dash, err := models.GetDashboard(slug, c.GetAccountId())
 	if err != nil {
 	if err != nil {
-		c.ApiError(404, "Dashboard not found", nil)
+		c.JsonApiErr(404, "Dashboard not found", nil)
 		return
 		return
 	}
 	}
 
 
 	err = models.DeleteDashboard(slug, c.GetAccountId())
 	err = models.DeleteDashboard(slug, c.GetAccountId())
 	if err != nil {
 	if err != nil {
-		c.ApiError(500, "Failed to delete dashboard", err)
+		c.JsonApiErr(500, "Failed to delete dashboard", err)
 		return
 		return
 	}
 	}
 
 
@@ -47,7 +47,7 @@ func Search(c *middleware.Context) {
 
 
 	results, err := models.SearchQuery(query, c.GetAccountId())
 	results, err := models.SearchQuery(query, c.GetAccountId())
 	if err != nil {
 	if err != nil {
-		c.ApiError(500, "Search failed", err)
+		c.JsonApiErr(500, "Search failed", err)
 		return
 		return
 	}
 	}
 
 
@@ -58,7 +58,7 @@ func PostDashboard(c *middleware.Context) {
 	var command apimodel.SaveDashboardCommand
 	var command apimodel.SaveDashboardCommand
 
 
 	if !c.JsonBody(&command) {
 	if !c.JsonBody(&command) {
-		c.ApiError(400, "bad request", nil)
+		c.JsonApiErr(400, "bad request", nil)
 		return
 		return
 	}
 	}
 
 
@@ -74,7 +74,7 @@ func PostDashboard(c *middleware.Context) {
 
 
 	err := models.SaveDashboard(dashboard)
 	err := models.SaveDashboard(dashboard)
 	if err != nil {
 	if err != nil {
-		c.ApiError(500, "Failed to save dashboard", err)
+		c.JsonApiErr(500, "Failed to save dashboard", err)
 		return
 		return
 	}
 	}
 
 

+ 2 - 1
pkg/routes/index.go

@@ -16,8 +16,9 @@ func Register(m *macaron.Macaron) {
 	m.Post("/logout", login.LogoutPost)
 	m.Post("/logout", login.LogoutPost)
 	m.Post("/login", login.LoginPost)
 	m.Post("/login", login.LoginPost)
 
 
-	// no auth
+	// login
 	m.Get("/login", Index)
 	m.Get("/login", Index)
+	m.Get("/login/:name", login.OAuthLogin)
 
 
 	// dashboards
 	// dashboards
 	m.Get("/dashboard/*", auth, Index)
 	m.Get("/dashboard/*", auth, Index)

+ 71 - 0
pkg/routes/login/login_oauth.go

@@ -0,0 +1,71 @@
+package login
+
+import (
+	"errors"
+	"fmt"
+
+	"github.com/torkelo/grafana-pro/pkg/log"
+	"github.com/torkelo/grafana-pro/pkg/middleware"
+	"github.com/torkelo/grafana-pro/pkg/models"
+	"github.com/torkelo/grafana-pro/pkg/setting"
+	"github.com/torkelo/grafana-pro/pkg/social"
+)
+
+func OAuthLogin(ctx *middleware.Context) {
+	if setting.OAuthService == nil {
+		ctx.Handle(404, "social.SocialSignIn(oauth service not enabled)", nil)
+		return
+	}
+
+	name := ctx.Params(":name")
+	connect, ok := social.SocialMap[name]
+	if !ok {
+		ctx.Handle(404, "social.SocialSignIn(social login not enabled)", errors.New(name))
+		return
+	}
+
+	code := ctx.Query("code")
+	if code == "" {
+		ctx.Redirect(connect.AuthCodeURL("", "online", "auto"))
+		return
+	}
+
+	// handle call back
+	transport, err := connect.NewTransportWithCode(code)
+	if err != nil {
+		ctx.Handle(500, "social.SocialSignIn(NewTransportWithCode)", err)
+		return
+	}
+
+	log.Trace("social.SocialSignIn(Got token)")
+
+	userInfo, err := connect.UserInfo(transport)
+	if err != nil {
+		ctx.Handle(500, fmt.Sprintf("social.SocialSignIn(get info from %s)", name), err)
+		return
+	}
+
+	log.Info("social.SocialSignIn(social login): %s", userInfo)
+
+	account, err := models.GetAccountByLogin(userInfo.Email)
+
+	// create account if missing
+	if err == models.ErrAccountNotFound {
+		account = &models.Account{
+			Login:   userInfo.Login,
+			Email:   userInfo.Email,
+			Name:    userInfo.Name,
+			Company: userInfo.Company,
+		}
+
+		if err = models.CreateAccount(account); err != nil {
+			ctx.Handle(500, "Failed to create account", err)
+			return
+		}
+	}
+
+	// login
+	loginUserWithAccount(account, ctx)
+
+	ctx.Redirect("/")
+}

+ 14 - 0
pkg/setting/setting_oauth.go

@@ -0,0 +1,14 @@
+package setting
+
+type OAuthInfo struct {
+	ClientId, ClientSecret string
+	Scopes                 []string
+	AuthUrl, TokenUrl      string
+}
+
+type OAuther struct {
+	GitHub, Google, Twitter bool
+	OAuthInfos              map[string]*OAuthInfo
+}
+
+var OAuthService *OAuther

+ 175 - 0
pkg/social/social.go

@@ -0,0 +1,175 @@
+package social
+
+import (
+	"encoding/json"
+	"net/http"
+	"strconv"
+	"strings"
+
+	"github.com/gogits/gogs/models"
+	"github.com/golang/oauth2"
+	"github.com/torkelo/grafana-pro/pkg/log"
+	"github.com/torkelo/grafana-pro/pkg/setting"
+)
+
+type BasicUserInfo struct {
+	Identity string
+	Name     string
+	Email    string
+	Login    string
+	Company  string
+}
+
+type SocialConnector interface {
+	Type() int
+	UserInfo(transport *oauth2.Transport) (*BasicUserInfo, error)
+
+	AuthCodeURL(state, accessType, prompt string) string
+	NewTransportWithCode(code string) (*oauth2.Transport, error)
+}
+
+var (
+	SocialBaseUrl = "/login"
+	SocialMap     = make(map[string]SocialConnector)
+)
+
+func NewOauthService() {
+	if !setting.Cfg.MustBool("oauth", "enabled") {
+		return
+	}
+
+	var err error
+	setting.OAuthService = &setting.OAuther{}
+	setting.OAuthService.OAuthInfos = make(map[string]*setting.OAuthInfo)
+
+	socialConfigs := make(map[string]*oauth2.Config)
+
+	allOauthes := []string{"github", "google", "twitter"}
+
+	// Load all OAuth config data.
+	for _, name := range allOauthes {
+		info := &setting.OAuthInfo{
+			ClientId:     setting.Cfg.MustValue("oauth."+name, "client_id"),
+			ClientSecret: setting.Cfg.MustValue("oauth."+name, "client_secrect"),
+			Scopes:       setting.Cfg.MustValueArray("oauth."+name, "scopes", " "),
+			AuthUrl:      setting.Cfg.MustValue("oauth."+name, "auth_url"),
+			TokenUrl:     setting.Cfg.MustValue("oauth."+name, "token_url"),
+		}
+
+		opts := &oauth2.Options{
+			ClientID:     info.ClientId,
+			ClientSecret: info.ClientSecret,
+			RedirectURL:  strings.TrimSuffix(setting.AppUrl, "/") + SocialBaseUrl + name,
+			Scopes:       info.Scopes,
+		}
+
+		setting.OAuthService.OAuthInfos[name] = info
+		socialConfigs[name], err = oauth2.NewConfig(opts, info.AuthUrl, info.TokenUrl)
+		if err != nil {
+			log.Error(4, "Failed to init oauth service", err)
+		}
+	}
+
+	enabledOauths := make([]string, 0, 10)
+
+	// GitHub.
+	if setting.Cfg.MustBool("oauth.github", "enabled") {
+		setting.OAuthService.GitHub = true
+		newGitHubOAuth(socialConfigs["github"])
+		enabledOauths = append(enabledOauths, "GitHub")
+	}
+
+	// Google.
+	if setting.Cfg.MustBool("oauth.google", "enabled") {
+		setting.OAuthService.Google = true
+		newGoogleOAuth(socialConfigs["google"])
+		enabledOauths = append(enabledOauths, "Google")
+	}
+}
+
+type SocialGithub struct {
+	*oauth2.Config
+}
+
+func (s *SocialGithub) Type() int {
+	return int(models.GITHUB)
+}
+
+func newGitHubOAuth(config *oauth2.Config) {
+	SocialMap["github"] = &SocialGithub{
+		Config: config,
+	}
+}
+
+func (s *SocialGithub) UserInfo(transport *oauth2.Transport) (*BasicUserInfo, error) {
+	var data struct {
+		Id    int    `json:"id"`
+		Name  string `json:"login"`
+		Email string `json:"email"`
+	}
+
+	var err error
+	client := http.Client{Transport: transport}
+	r, err := client.Get("https://api.github.com/user")
+	if err != nil {
+		return nil, err
+	}
+
+	defer r.Body.Close()
+
+	if err = json.NewDecoder(r.Body).Decode(&data); err != nil {
+		return nil, err
+	}
+
+	return &BasicUserInfo{
+		Identity: strconv.Itoa(data.Id),
+		Name:     data.Name,
+		Email:    data.Email,
+	}, nil
+}
+
+//   ________                     .__
+//  /  _____/  ____   ____   ____ |  |   ____
+// /   \  ___ /  _ \ /  _ \ / ___\|  | _/ __ \
+// \    \_\  (  <_> |  <_> ) /_/  >  |_\  ___/
+//  \______  /\____/ \____/\___  /|____/\___  >
+//         \/             /_____/           \/
+
+type SocialGoogle struct {
+	*oauth2.Config
+}
+
+func (s *SocialGoogle) Type() int {
+	return int(models.GOOGLE)
+}
+
+func newGoogleOAuth(config *oauth2.Config) {
+	SocialMap["google"] = &SocialGoogle{
+		Config: config,
+	}
+}
+
+func (s *SocialGoogle) UserInfo(transport *oauth2.Transport) (*BasicUserInfo, error) {
+	var data struct {
+		Id    string `json:"id"`
+		Name  string `json:"name"`
+		Email string `json:"email"`
+	}
+	var err error
+
+	reqUrl := "https://www.googleapis.com/oauth2/v1/userinfo"
+	client := http.Client{Transport: transport}
+	r, err := client.Get(reqUrl)
+	if err != nil {
+		return nil, err
+	}
+	defer r.Body.Close()
+	if err = json.NewDecoder(r.Body).Decode(&data); err != nil {
+		return nil, err
+	}
+	return &BasicUserInfo{
+		Identity: data.Id,
+		Name:     data.Name,
+		Email:    data.Email,
+	}, nil
+}

+ 0 - 0
views/404.html