Quellcode durchsuchen

OAuth: Specify allowed email address domains for google or and github oauth logins, Closes #1660

Torkel Ödegaard vor 10 Jahren
Ursprung
Commit
eb575685aa
6 geänderte Dateien mit 50 neuen und 10 gelöschten Zeilen
  1. 1 0
      CHANGELOG.md
  2. 4 0
      conf/defaults.ini
  3. 8 2
      pkg/api/login_oauth.go
  4. 1 0
      pkg/setting/setting.go
  5. 1 0
      pkg/setting/setting_oauth.go
  6. 35 8
      pkg/social/social.go

+ 1 - 0
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

+ 4 - 0
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

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

+ 1 - 0
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])
 		}

+ 1 - 0
pkg/setting/setting_oauth.go

@@ -5,6 +5,7 @@ type OAuthInfo struct {
 	Scopes                 []string
 	AuthUrl, TokenUrl      string
 	Enabled                bool
+	AllowedDomains         []string
 }
 
 type OAuther struct {

+ 35 - 8
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"`