Commit eb575685 by Torkel Ödegaard

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

parent 7a954512
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
**Enhancements** **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 #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** **Fixes**
- [Issue #1707](https://github.com/grafana/grafana/issues/1707). Unsaved changes: Do not show for snapshots, scripted and file based dashboards - [Issue #1707](https://github.com/grafana/grafana/issues/1707). Unsaved changes: Do not show for snapshots, scripted and file based dashboards
......
...@@ -95,6 +95,8 @@ client_secret = some_secret ...@@ -95,6 +95,8 @@ client_secret = some_secret
scopes = user:email scopes = user:email
auth_url = https://github.com/login/oauth/authorize auth_url = https://github.com/login/oauth/authorize
token_url = https://github.com/login/oauth/access_token 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] [auth.google]
enabled = false enabled = false
...@@ -103,6 +105,8 @@ client_secret = some_client_secret ...@@ -103,6 +105,8 @@ client_secret = some_client_secret
scopes = https://www.googleapis.com/auth/userinfo.profile https://www.googleapis.com/auth/userinfo.email scopes = https://www.googleapis.com/auth/userinfo.profile https://www.googleapis.com/auth/userinfo.email
auth_url = https://accounts.google.com/o/oauth2/auth auth_url = https://accounts.google.com/o/oauth2/auth
token_url = https://accounts.google.com/o/oauth2/token token_url = https://accounts.google.com/o/oauth2/token
; uncomment bellow to only allow specific email domains
; allowed_domains = mycompany.com othercompany.com
[log] [log]
root_path = data/log root_path = data/log
......
...@@ -33,7 +33,6 @@ func OAuthLogin(ctx *middleware.Context) { ...@@ -33,7 +33,6 @@ func OAuthLogin(ctx *middleware.Context) {
ctx.Redirect(connect.AuthCodeURL("", oauth2.AccessTypeOnline)) ctx.Redirect(connect.AuthCodeURL("", oauth2.AccessTypeOnline))
return return
} }
log.Info("code: %v", code)
// handle call back // handle call back
token, err := connect.Exchange(oauth2.NoContext, code) token, err := connect.Exchange(oauth2.NoContext, code)
...@@ -50,7 +49,14 @@ func OAuthLogin(ctx *middleware.Context) { ...@@ -50,7 +49,14 @@ func OAuthLogin(ctx *middleware.Context) {
return 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} userQuery := m.GetUserByLoginQuery{LoginOrEmail: userInfo.Email}
err = bus.Dispatch(&userQuery) err = bus.Dispatch(&userQuery)
......
...@@ -179,6 +179,7 @@ func NewConfigContext(config string) { ...@@ -179,6 +179,7 @@ func NewConfigContext(config string) {
for i, file := range configFiles { for i, file := range configFiles {
if i == 0 { if i == 0 {
Cfg, err = ini.Load(configFiles[i]) Cfg, err = ini.Load(configFiles[i])
Cfg.BlockMode = false
} else { } else {
err = Cfg.Append(configFiles[i]) err = Cfg.Append(configFiles[i])
} }
......
...@@ -5,6 +5,7 @@ type OAuthInfo struct { ...@@ -5,6 +5,7 @@ type OAuthInfo struct {
Scopes []string Scopes []string
AuthUrl, TokenUrl string AuthUrl, TokenUrl string
Enabled bool Enabled bool
AllowedDomains []string
} }
type OAuther struct { type OAuther struct {
......
...@@ -2,6 +2,7 @@ package social ...@@ -2,6 +2,7 @@ package social
import ( import (
"encoding/json" "encoding/json"
"fmt"
"strconv" "strconv"
"strings" "strings"
...@@ -23,6 +24,7 @@ type BasicUserInfo struct { ...@@ -23,6 +24,7 @@ type BasicUserInfo struct {
type SocialConnector interface { type SocialConnector interface {
Type() int Type() int
UserInfo(token *oauth2.Token) (*BasicUserInfo, error) UserInfo(token *oauth2.Token) (*BasicUserInfo, error)
IsEmailAllowed(email string) bool
AuthCodeURL(state string, opts ...oauth2.AuthCodeOption) string AuthCodeURL(state string, opts ...oauth2.AuthCodeOption) string
Exchange(ctx context.Context, code string) (*oauth2.Token, error) Exchange(ctx context.Context, code string) (*oauth2.Token, error)
...@@ -48,6 +50,7 @@ func NewOAuthService() { ...@@ -48,6 +50,7 @@ func NewOAuthService() {
AuthUrl: sec.Key("auth_url").String(), AuthUrl: sec.Key("auth_url").String(),
TokenUrl: sec.Key("token_url").String(), TokenUrl: sec.Key("token_url").String(),
Enabled: sec.Key("enabled").MustBool(), Enabled: sec.Key("enabled").MustBool(),
AllowedDomains: sec.Key("allowed_domains").Strings(" "),
} }
if !info.Enabled { if !info.Enabled {
...@@ -69,25 +72,44 @@ func NewOAuthService() { ...@@ -69,25 +72,44 @@ func NewOAuthService() {
// GitHub. // GitHub.
if name == "github" { if name == "github" {
setting.OAuthService.GitHub = true setting.OAuthService.GitHub = true
SocialMap["github"] = &SocialGithub{Config: &config} SocialMap["github"] = &SocialGithub{Config: &config, allowedDomains: info.AllowedDomains}
} }
// Google. // Google.
if name == "google" { if name == "google" {
setting.OAuthService.Google = true 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 { type SocialGithub struct {
*oauth2.Config *oauth2.Config
allowedDomains []string
} }
func (s *SocialGithub) Type() int { func (s *SocialGithub) Type() int {
return int(models.GITHUB) 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) { func (s *SocialGithub) UserInfo(token *oauth2.Token) (*BasicUserInfo, error) {
var data struct { var data struct {
Id int `json:"id"` Id int `json:"id"`
...@@ -124,12 +146,17 @@ func (s *SocialGithub) UserInfo(token *oauth2.Token) (*BasicUserInfo, error) { ...@@ -124,12 +146,17 @@ func (s *SocialGithub) UserInfo(token *oauth2.Token) (*BasicUserInfo, error) {
type SocialGoogle struct { type SocialGoogle struct {
*oauth2.Config *oauth2.Config
allowedDomains []string
} }
func (s *SocialGoogle) Type() int { func (s *SocialGoogle) Type() int {
return int(models.GOOGLE) 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) { func (s *SocialGoogle) UserInfo(token *oauth2.Token) (*BasicUserInfo, error) {
var data struct { var data struct {
Id string `json:"id"` Id string `json:"id"`
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment