From fc17ed351c859004dabf377b0e1a1b7d55362d44 Mon Sep 17 00:00:00 2001 From: Dan Cech Date: Mon, 19 Sep 2016 16:48:07 -0400 Subject: [PATCH] support logging in with grafana.net credentials --- pkg/api/login.go | 1 + pkg/models/models.go | 1 + pkg/setting/setting_oauth.go | 2 +- pkg/social/grafananet_oauth.go | 112 ++++++++++++++++++++++ pkg/social/social.go | 30 +++++- public/app/core/controllers/login_ctrl.js | 8 +- public/app/partials/login.html | 7 +- public/sass/components/_search.scss | 2 +- public/sass/pages/_login.scss | 15 ++- 9 files changed, 171 insertions(+), 7 deletions(-) create mode 100644 pkg/social/grafananet_oauth.go diff --git a/pkg/api/login.go b/pkg/api/login.go index 789765ee01e..d1c571d8e0f 100644 --- a/pkg/api/login.go +++ b/pkg/api/login.go @@ -27,6 +27,7 @@ func LoginView(c *middleware.Context) { viewData.Settings["googleAuthEnabled"] = setting.OAuthService.Google viewData.Settings["githubAuthEnabled"] = setting.OAuthService.GitHub + viewData.Settings["grafanaNetAuthEnabled"] = setting.OAuthService.GrafanaNet viewData.Settings["genericOAuthEnabled"] = setting.OAuthService.Generic viewData.Settings["oauthProviderName"] = setting.OAuthService.OAuthProviderName viewData.Settings["disableUserSignUp"] = !setting.AllowUserSignUp diff --git a/pkg/models/models.go b/pkg/models/models.go index 5a53cfdabb3..3f4b27ed6ab 100644 --- a/pkg/models/models.go +++ b/pkg/models/models.go @@ -7,4 +7,5 @@ const ( GOOGLE TWITTER GENERIC + GRAFANANET ) diff --git a/pkg/setting/setting_oauth.go b/pkg/setting/setting_oauth.go index 71c4ade1468..540b32ad83e 100644 --- a/pkg/setting/setting_oauth.go +++ b/pkg/setting/setting_oauth.go @@ -11,7 +11,7 @@ type OAuthInfo struct { } type OAuther struct { - GitHub, Google, Twitter, Generic bool + GitHub, Google, Twitter, Generic, GrafanaNet bool OAuthInfos map[string]*OAuthInfo OAuthProviderName string } diff --git a/pkg/social/grafananet_oauth.go b/pkg/social/grafananet_oauth.go new file mode 100644 index 00000000000..05cc7c1f397 --- /dev/null +++ b/pkg/social/grafananet_oauth.go @@ -0,0 +1,112 @@ +package social + +import ( + "encoding/json" + "fmt" + "net/http" + "strconv" + + "github.com/grafana/grafana/pkg/models" + + "golang.org/x/oauth2" +) + +type SocialGrafanaNet struct { + *oauth2.Config + url string + allowedOrganizations []string + allowSignup bool +} + +func (s *SocialGrafanaNet) Type() int { + return int(models.GRAFANANET) +} + +func (s *SocialGrafanaNet) IsEmailAllowed(email string) bool { + return true +} + +func (s *SocialGrafanaNet) IsSignupAllowed() bool { + return s.allowSignup +} + +func (s *SocialGrafanaNet) IsOrganizationMember(client *http.Client) bool { + if len(s.allowedOrganizations) == 0 { + return true + } + + organizations, err := s.FetchOrganizations(client) + if err != nil { + return false + } + + for _, allowedOrganization := range s.allowedOrganizations { + for _, organization := range organizations { + if organization == allowedOrganization { + return true + } + } + } + + return false +} + +func (s *SocialGrafanaNet) FetchOrganizations(client *http.Client) ([]string, error) { + type Record struct { + Login string `json:"login"` + } + + url := fmt.Sprintf(s.url + "/api/oauth2/user/orgs") + r, err := client.Get(url) + if err != nil { + return nil, err + } + + defer r.Body.Close() + + var records []Record + + if err = json.NewDecoder(r.Body).Decode(&records); err != nil { + return nil, err + } + + var logins = make([]string, len(records)) + for i, record := range records { + logins[i] = record.Login + } + + return logins, nil +} + +func (s *SocialGrafanaNet) UserInfo(token *oauth2.Token) (*BasicUserInfo, error) { + var data struct { + Id int `json:"id"` + Name string `json:"login"` + Email string `json:"email"` + } + + var err error + client := s.Client(oauth2.NoContext, token) + r, err := client.Get(s.url + "/api/oauth2/user") + if err != nil { + return nil, err + } + + defer r.Body.Close() + + if err = json.NewDecoder(r.Body).Decode(&data); err != nil { + return nil, err + } + + userInfo := &BasicUserInfo{ + Identity: strconv.Itoa(data.Id), + Name: data.Name, + Email: data.Email, + } + + if !s.IsOrganizationMember(client) { + return nil, ErrMissingOrganizationMembership + } + + return userInfo, nil +} diff --git a/pkg/social/social.go b/pkg/social/social.go index 66d0f5fa778..83e5aa19b43 100644 --- a/pkg/social/social.go +++ b/pkg/social/social.go @@ -36,7 +36,7 @@ func NewOAuthService() { setting.OAuthService = &setting.OAuther{} setting.OAuthService.OAuthInfos = make(map[string]*setting.OAuthInfo) - allOauthes := []string{"github", "google", "generic_oauth"} + allOauthes := []string{"github", "google", "generic_oauth", "grafananet"} for _, name := range allOauthes { sec := setting.Cfg.Section("auth." + name) @@ -108,5 +108,33 @@ func NewOAuthService() { allowedOrganizations: allowedOrganizations, } } + + if name == "grafananet" { + setting.OAuthService.GrafanaNet = true + allowedOrganizations := sec.Key("allowed_organizations").Strings(" ") + + url := sec.Key("url").String() + if url == "" { + url = "https://grafana.net" + } + + config := oauth2.Config{ + ClientID: info.ClientId, + ClientSecret: info.ClientSecret, + Endpoint: oauth2.Endpoint{ + AuthURL: url + "/oauth2/authorize", + TokenURL: url + "/api/oauth2/token", + }, + RedirectURL: strings.TrimSuffix(setting.AppUrl, "/") + SocialBaseUrl + name, + Scopes: info.Scopes, + } + + SocialMap["grafananet"] = &SocialGrafanaNet{ + Config: &config, + url: url, + allowSignup: info.AllowSignup, + allowedOrganizations: allowedOrganizations, + } + } } } diff --git a/public/app/core/controllers/login_ctrl.js b/public/app/core/controllers/login_ctrl.js index 3f31407f454..ee5a1832420 100644 --- a/public/app/core/controllers/login_ctrl.js +++ b/public/app/core/controllers/login_ctrl.js @@ -17,7 +17,13 @@ function (angular, coreModule, config) { $scope.googleAuthEnabled = config.googleAuthEnabled; $scope.githubAuthEnabled = config.githubAuthEnabled; - $scope.oauthEnabled = config.githubAuthEnabled || config.googleAuthEnabled || config.genericOAuthEnabled; + $scope.grafanaNetAuthEnabled = config.grafanaNetAuthEnabled; + $scope.oauthEnabled = ( + config.githubAuthEnabled + || config.googleAuthEnabled + || config.grafanaNetAuthEnabled + || config.genericOAuthEnabled + ); $scope.allowUserPassLogin = config.allowUserPassLogin; $scope.genericOAuthEnabled = config.genericOAuthEnabled; $scope.oauthProviderName = config.oauthProviderName; diff --git a/public/app/partials/login.html b/public/app/partials/login.html index f4f5fb26d7e..d9bf7ecdb18 100644 --- a/public/app/partials/login.html +++ b/public/app/partials/login.html @@ -59,10 +59,13 @@ with Github + + with Grafana.net + - with {{oauthProviderName || "OAuth 2"}} - + with {{oauthProviderName || "OAuth 2"}} + diff --git a/public/sass/components/_search.scss b/public/sass/components/_search.scss index 563f89919e5..2eeb34ec0b4 100644 --- a/public/sass/components/_search.scss +++ b/public/sass/components/_search.scss @@ -111,7 +111,7 @@ font-size: $font-size-sm; padding-right: 7rem; background: url(../img/grafana_net_logo.svg); - background-size: 6.5rem 3rem; + background-size: 6.5rem; background-repeat: no-repeat; background-position: right; position: relative; diff --git a/public/sass/pages/_login.scss b/public/sass/pages/_login.scss index 8f376dfae71..8641aabe1b1 100644 --- a/public/sass/pages/_login.scss +++ b/public/sass/pages/_login.scss @@ -112,6 +112,19 @@ background: #555; color: white; } + + .btn-grafana-net { + background: url(../img/grafana_net_logo.svg); + background-size: 10rem; + background-repeat: no-repeat; + background-position: right 35%; + overflow: hidden; + padding-right: 10.5rem; + + span { + display: none; + } + } } .password-recovery { @@ -157,7 +170,7 @@ .invite-box { text-align: center; border: 1px solid $tight-form-func-bg; - background-color: $panel-bg; + background-color: $panel-bg; max-width: 800px; margin-left: auto; margin-right: auto;