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 @@
**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
......
......@@ -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
......
......@@ -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)
......
......@@ -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])
}
......
......@@ -5,6 +5,7 @@ type OAuthInfo struct {
Scopes []string
AuthUrl, TokenUrl string
Enabled bool
AllowedDomains []string
}
type OAuther struct {
......
......@@ -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"`
......
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