diff --git a/grafana b/grafana index 071ac0dc85e..d584076b93b 160000 --- a/grafana +++ b/grafana @@ -1 +1 @@ -Subproject commit 071ac0dc85e48be546315dde196f90f01ad7b274 +Subproject commit d584076b93b4ebfb33e5a5f375feb6d6ff7f9bfc diff --git a/pkg/api/api_oauth.go b/pkg/api/api_oauth.go new file mode 100644 index 00000000000..778f64ec17c --- /dev/null +++ b/pkg/api/api_oauth.go @@ -0,0 +1 @@ +package api diff --git a/pkg/api/api_oauth_github.go b/pkg/api/api_oauth_github.go new file mode 100644 index 00000000000..7e6a96babc8 --- /dev/null +++ b/pkg/api/api_oauth_github.go @@ -0,0 +1,112 @@ +package api + +import ( + "encoding/json" + "net/http" + + log "github.com/alecthomas/log4go" + "github.com/gin-gonic/gin" + "github.com/golang/oauth2" + "github.com/torkelo/grafana-pro/pkg/models" + "github.com/torkelo/grafana-pro/pkg/stores" +) + +var ( + githubOAuthConfig *oauth2.Config + githubRedirectUrl string = "http://localhost:3000/oauth2/github/callback" + githubAuthUrl string = "https://github.com/login/oauth/authorize" + githubTokenUrl string = "https://github.com/login/oauth/access_token" +) + +func init() { + addRoutes(func(self *HttpServer) { + if !self.cfg.Http.GithubOAuth.Enabled { + return + } + + self.router.GET("/oauth2/github", self.oauthGithub) + self.router.GET("/oauth2/github/callback", self.oauthGithubCallback) + + options := &oauth2.Options{ + ClientID: self.cfg.Http.GithubOAuth.ClientId, + ClientSecret: self.cfg.Http.GithubOAuth.ClientSecret, + RedirectURL: githubRedirectUrl, + Scopes: []string{"user:email"}, + } + + cfg, err := oauth2.NewConfig(options, githubAuthUrl, githubTokenUrl) + + if err != nil { + log.Error("Failed to init github auth %v", err) + } + + githubOAuthConfig = cfg + }) +} + +func (self *HttpServer) oauthGithub(c *gin.Context) { + url := githubOAuthConfig.AuthCodeURL("", "online", "auto") + c.Redirect(302, url) +} + +type githubUserInfoDto struct { + Login string `json:"login"` + Name string `json:"name"` + Email string `json:"email"` + Company string `json:"company"` +} + +func (self *HttpServer) oauthGithubCallback(c *gin.Context) { + code := c.Request.URL.Query()["code"][0] + log.Info("OAuth code: %v", code) + + transport, err := githubOAuthConfig.NewTransportWithCode(code) + if err != nil { + c.String(500, "Failed to exchange oauth token: "+err.Error()) + return + } + + client := http.Client{Transport: transport} + resp, err := client.Get("https://api.github.com/user") + if err != nil { + c.String(500, err.Error()) + return + } + + var userInfo githubUserInfoDto + decoder := json.NewDecoder(resp.Body) + err = decoder.Decode(&userInfo) + if err != nil { + c.String(500, err.Error()) + return + } + + if len(userInfo.Email) < 5 { + c.String(500, "Invalid email") + return + } + + // try find existing account + account, err := self.store.GetAccountByLogin(userInfo.Email) + + // create account if missing + if err == stores.ErrAccountNotFound { + account = &models.Account{ + Login: userInfo.Login, + Email: userInfo.Email, + Name: userInfo.Name, + Company: userInfo.Company, + } + + if err = self.store.CreateAccount(account); err != nil { + log.Error("Failed to create account %v", err) + c.String(500, "Failed to create account") + return + } + } + + // login + loginUserWithAccount(account, c) + + c.Redirect(302, "/") +} diff --git a/pkg/api/api_google_oauth.go b/pkg/api/api_oauth_google.go similarity index 65% rename from pkg/api/api_google_oauth.go rename to pkg/api/api_oauth_google.go index 5f833a23a54..efa27e2d1f8 100644 --- a/pkg/api/api_google_oauth.go +++ b/pkg/api/api_oauth_google.go @@ -11,7 +11,14 @@ import ( "github.com/torkelo/grafana-pro/pkg/stores" ) -var oauthCfg *oauth2.Config +var ( + googleOAuthConfig *oauth2.Config + googleRedirectUrl string = "http://localhost:3000/oauth2/google/callback" + googleAuthUrl string = "https://accounts.google.com/o/oauth2/auth" + googleTokenUrl string = "https://accounts.google.com/o/oauth2/token" + googleScopeProfile string = "https://www.googleapis.com/auth/userinfo.profile" + googleScopeEmail string = "https://www.googleapis.com/auth/userinfo.email" +) func init() { addRoutes(func(self *HttpServer) { @@ -19,33 +26,28 @@ func init() { return } - self.router.GET("/login/google", self.loginGoogle) - self.router.GET("/oauth2callback", self.oauthCallback) + self.router.GET("/oauth2/google", self.oauthGoogle) + self.router.GET("/oauth2/google/callback", self.oauthGoogleCallback) options := &oauth2.Options{ ClientID: self.cfg.Http.GoogleOAuth.ClientId, ClientSecret: self.cfg.Http.GoogleOAuth.ClientSecret, - RedirectURL: "http://localhost:3000/oauth2callback", - Scopes: []string{ - "https://www.googleapis.com/auth/userinfo.profile", - "https://www.googleapis.com/auth/userinfo.email", - }, + RedirectURL: googleRedirectUrl, + Scopes: []string{googleScopeEmail, googleScopeProfile}, } - cfg, err := oauth2.NewConfig(options, - "https://accounts.google.com/o/oauth2/auth", - "https://accounts.google.com/o/oauth2/token") + cfg, err := oauth2.NewConfig(options, googleAuthUrl, googleTokenUrl) if err != nil { log.Error("Failed to init google auth %v", err) } - oauthCfg = cfg + googleOAuthConfig = cfg }) } -func (self *HttpServer) loginGoogle(c *gin.Context) { - url := oauthCfg.AuthCodeURL("", "online", "auto") +func (self *HttpServer) oauthGoogle(c *gin.Context) { + url := googleOAuthConfig.AuthCodeURL("", "online", "auto") c.Redirect(302, url) } @@ -56,11 +58,11 @@ type googleUserInfoDto struct { Name string `json:"name"` } -func (self *HttpServer) oauthCallback(c *gin.Context) { +func (self *HttpServer) oauthGoogleCallback(c *gin.Context) { code := c.Request.URL.Query()["code"][0] log.Info("OAuth code: %v", code) - transport, err := oauthCfg.NewTransportWithCode(code) + transport, err := googleOAuthConfig.NewTransportWithCode(code) if err != nil { c.String(500, "Failed to exchange oauth token: "+err.Error()) return diff --git a/pkg/configuration/configuration.go b/pkg/configuration/configuration.go index 3f74b7a4896..78cf32ede5a 100644 --- a/pkg/configuration/configuration.go +++ b/pkg/configuration/configuration.go @@ -6,10 +6,11 @@ type Cfg struct { type HttpCfg struct { Port string - GoogleOAuth GoogleOAuthCfg + GoogleOAuth OAuthCfg + GithubOAuth OAuthCfg } -type GoogleOAuthCfg struct { +type OAuthCfg struct { Enabled bool ClientId string ClientSecret string @@ -24,11 +25,16 @@ func NewCfg(port string) *Cfg { return &Cfg{ Http: HttpCfg{ Port: port, - GoogleOAuth: GoogleOAuthCfg{ + GoogleOAuth: OAuthCfg{ Enabled: true, ClientId: "106011922963-4pvl05e9urtrm8bbqr0vouosj3e8p8kb.apps.googleusercontent.com", ClientSecret: "K2evIa4QhfbhhAm3SO72t2Zv", }, + GithubOAuth: OAuthCfg{ + Enabled: true, + ClientId: "de054205006b9baa2e17", + ClientSecret: "72b7ea52d9f1096fdf36cea95e95362a307e0322", + }, }, } } diff --git a/pkg/models/account.go b/pkg/models/account.go index cd4e8469fdc..7d198e72d28 100644 --- a/pkg/models/account.go +++ b/pkg/models/account.go @@ -27,6 +27,7 @@ type Account struct { AccountName string Password string Name string + Company string NextDashboardId int UsingAccountId int Collaborators []CollaboratorLink