From eb575685aa606ebecab1cb50d41fd29f5cc43e7d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Mon, 6 Apr 2015 14:16:22 +0200 Subject: [PATCH] OAuth: Specify allowed email address domains for google or and github oauth logins, Closes #1660 --- CHANGELOG.md | 1 + conf/defaults.ini | 4 ++++ pkg/api/login_oauth.go | 10 +++++++-- pkg/setting/setting.go | 1 + pkg/setting/setting_oauth.go | 1 + pkg/social/social.go | 43 +++++++++++++++++++++++++++++------- 6 files changed, 50 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0fc4e886b64..0623c149c2f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ **Enhancements** - [Issue #1701](https://github.com/grafana/grafana/issues/1701). Share modal: Override UI theme via URL param for Share link, rendered panel, or embedded panel +- [Issue #1660](https://github.com/grafana/grafana/issues/1660). OAuth: Specify allowed email address domains for google or and github oauth logins **Fixes** - [Issue #1707](https://github.com/grafana/grafana/issues/1707). Unsaved changes: Do not show for snapshots, scripted and file based dashboards diff --git a/conf/defaults.ini b/conf/defaults.ini index ff159dc8531..182af44ac5f 100644 --- a/conf/defaults.ini +++ b/conf/defaults.ini @@ -95,6 +95,8 @@ client_secret = some_secret scopes = user:email auth_url = https://github.com/login/oauth/authorize token_url = https://github.com/login/oauth/access_token +; uncomment bellow to only allow specific email domains +; allowed_domains = mycompany.com othercompany.com [auth.google] enabled = false @@ -103,6 +105,8 @@ client_secret = some_client_secret scopes = https://www.googleapis.com/auth/userinfo.profile https://www.googleapis.com/auth/userinfo.email auth_url = https://accounts.google.com/o/oauth2/auth token_url = https://accounts.google.com/o/oauth2/token +; uncomment bellow to only allow specific email domains +; allowed_domains = mycompany.com othercompany.com [log] root_path = data/log diff --git a/pkg/api/login_oauth.go b/pkg/api/login_oauth.go index a234ef02bf3..c960d16e368 100644 --- a/pkg/api/login_oauth.go +++ b/pkg/api/login_oauth.go @@ -33,7 +33,6 @@ func OAuthLogin(ctx *middleware.Context) { ctx.Redirect(connect.AuthCodeURL("", oauth2.AccessTypeOnline)) return } - log.Info("code: %v", code) // handle call back token, err := connect.Exchange(oauth2.NoContext, code) @@ -50,7 +49,14 @@ func OAuthLogin(ctx *middleware.Context) { return } - log.Info("login.OAuthLogin(social login): %s", userInfo) + log.Trace("login.OAuthLogin(social login): %s", userInfo) + + // validate that the email is allowed to login to grafana + if !connect.IsEmailAllowed(userInfo.Email) { + log.Info("OAuth login attempt with unallowed email, %s", userInfo.Email) + ctx.Redirect(setting.AppSubUrl + "/login?email_not_allowed=1") + return + } userQuery := m.GetUserByLoginQuery{LoginOrEmail: userInfo.Email} err = bus.Dispatch(&userQuery) diff --git a/pkg/setting/setting.go b/pkg/setting/setting.go index 24701c48d75..36b76e20de8 100644 --- a/pkg/setting/setting.go +++ b/pkg/setting/setting.go @@ -179,6 +179,7 @@ func NewConfigContext(config string) { for i, file := range configFiles { if i == 0 { Cfg, err = ini.Load(configFiles[i]) + Cfg.BlockMode = false } else { err = Cfg.Append(configFiles[i]) } diff --git a/pkg/setting/setting_oauth.go b/pkg/setting/setting_oauth.go index 43a022079b7..e833e534ff6 100644 --- a/pkg/setting/setting_oauth.go +++ b/pkg/setting/setting_oauth.go @@ -5,6 +5,7 @@ type OAuthInfo struct { Scopes []string AuthUrl, TokenUrl string Enabled bool + AllowedDomains []string } type OAuther struct { diff --git a/pkg/social/social.go b/pkg/social/social.go index 4c65dbd347c..d5ff8a4ee75 100644 --- a/pkg/social/social.go +++ b/pkg/social/social.go @@ -2,6 +2,7 @@ package social import ( "encoding/json" + "fmt" "strconv" "strings" @@ -23,6 +24,7 @@ type BasicUserInfo struct { type SocialConnector interface { Type() int UserInfo(token *oauth2.Token) (*BasicUserInfo, error) + IsEmailAllowed(email string) bool AuthCodeURL(state string, opts ...oauth2.AuthCodeOption) string Exchange(ctx context.Context, code string) (*oauth2.Token, error) @@ -42,12 +44,13 @@ func NewOAuthService() { for _, name := range allOauthes { sec := setting.Cfg.Section("auth." + name) info := &setting.OAuthInfo{ - ClientId: sec.Key("client_id").String(), - ClientSecret: sec.Key("client_secret").String(), - Scopes: sec.Key("scopes").Strings(" "), - AuthUrl: sec.Key("auth_url").String(), - TokenUrl: sec.Key("token_url").String(), - Enabled: sec.Key("enabled").MustBool(), + ClientId: sec.Key("client_id").String(), + ClientSecret: sec.Key("client_secret").String(), + Scopes: sec.Key("scopes").Strings(" "), + AuthUrl: sec.Key("auth_url").String(), + TokenUrl: sec.Key("token_url").String(), + Enabled: sec.Key("enabled").MustBool(), + AllowedDomains: sec.Key("allowed_domains").Strings(" "), } if !info.Enabled { @@ -69,25 +72,44 @@ func NewOAuthService() { // GitHub. if name == "github" { setting.OAuthService.GitHub = true - SocialMap["github"] = &SocialGithub{Config: &config} + SocialMap["github"] = &SocialGithub{Config: &config, allowedDomains: info.AllowedDomains} } // Google. if name == "google" { setting.OAuthService.Google = true - SocialMap["google"] = &SocialGoogle{Config: &config} + SocialMap["google"] = &SocialGoogle{Config: &config, allowedDomains: info.AllowedDomains} } } } +func isEmailAllowed(email string, allowedDomains []string) bool { + if len(allowedDomains) == 0 { + return true + } + + valid := false + for _, domain := range allowedDomains { + emailSuffix := fmt.Sprintf("@%s", domain) + valid = valid || strings.HasSuffix(email, emailSuffix) + } + + return valid +} + type SocialGithub struct { *oauth2.Config + allowedDomains []string } func (s *SocialGithub) Type() int { return int(models.GITHUB) } +func (s *SocialGithub) IsEmailAllowed(email string) bool { + return isEmailAllowed(email, s.allowedDomains) +} + func (s *SocialGithub) UserInfo(token *oauth2.Token) (*BasicUserInfo, error) { var data struct { Id int `json:"id"` @@ -124,12 +146,17 @@ func (s *SocialGithub) UserInfo(token *oauth2.Token) (*BasicUserInfo, error) { type SocialGoogle struct { *oauth2.Config + allowedDomains []string } func (s *SocialGoogle) Type() int { return int(models.GOOGLE) } +func (s *SocialGoogle) IsEmailAllowed(email string) bool { + return isEmailAllowed(email, s.allowedDomains) +} + func (s *SocialGoogle) UserInfo(token *oauth2.Token) (*BasicUserInfo, error) { var data struct { Id string `json:"id"`