Merge branch 'gnet-oauth'

This commit is contained in:
Torkel Ödegaard 2016-09-28 15:11:03 +02:00
commit 3e657357e5
14 changed files with 237 additions and 71 deletions

View File

@ -9,7 +9,7 @@ app_mode = production
# instance name, defaults to HOSTNAME environment variable value or hostname if HOSTNAME var is empty # instance name, defaults to HOSTNAME environment variable value or hostname if HOSTNAME var is empty
instance_name = ${HOSTNAME} instance_name = ${HOSTNAME}
#################################### Paths #################################### #################################### Paths ###############################
[paths] [paths]
# Path to where grafana can store temp files, sessions, and the sqlite3 db (if that is used) # Path to where grafana can store temp files, sessions, and the sqlite3 db (if that is used)
# #
@ -23,7 +23,7 @@ logs = data/log
# #
plugins = data/plugins plugins = data/plugins
#################################### Server #################################### #################################### Server ##############################
[server] [server]
# Protocol (http or https) # Protocol (http or https)
protocol = http protocol = http
@ -57,7 +57,7 @@ enable_gzip = false
cert_file = cert_file =
cert_key = cert_key =
#################################### Database #################################### #################################### Database ############################
[database] [database]
# You can configure the database connection by specifying type, host, name, user and password # You can configure the database connection by specifying type, host, name, user and password
# as seperate properties or as on string using the url propertie. # as seperate properties or as on string using the url propertie.
@ -84,7 +84,7 @@ server_cert_name =
# For "sqlite3" only, path relative to data_path setting # For "sqlite3" only, path relative to data_path setting
path = grafana.db path = grafana.db
#################################### Session #################################### #################################### Session #############################
[session] [session]
# Either "memory", "file", "redis", "mysql", "postgres", "memcache", default is "file" # Either "memory", "file", "redis", "mysql", "postgres", "memcache", default is "file"
provider = file provider = file
@ -112,7 +112,7 @@ cookie_secure = false
session_life_time = 86400 session_life_time = 86400
gc_interval_time = 86400 gc_interval_time = 86400
#################################### Analytics #################################### #################################### Analytics ###########################
[analytics] [analytics]
# Server reporting, sends usage counters to stats.grafana.org every 24 hours. # Server reporting, sends usage counters to stats.grafana.org every 24 hours.
# No ip addresses are being tracked, only simple counters to track # No ip addresses are being tracked, only simple counters to track
@ -133,7 +133,7 @@ google_analytics_ua_id =
# Google Tag Manager ID, only enabled if you specify an id here # Google Tag Manager ID, only enabled if you specify an id here
google_tag_manager_id = google_tag_manager_id =
#################################### Security #################################### #################################### Security ############################
[security] [security]
# default admin user, created on startup # default admin user, created on startup
admin_user = admin admin_user = admin
@ -193,7 +193,7 @@ default_theme = dark
# Allow users to sign in using username and password # Allow users to sign in using username and password
allow_user_pass_login = true allow_user_pass_login = true
#################################### Anonymous Auth ########################## #################################### Anonymous Auth ######################
[auth.anonymous] [auth.anonymous]
# enable anonymous access # enable anonymous access
enabled = false enabled = false
@ -204,7 +204,7 @@ org_name = Main Org.
# specify role for unauthenticated users # specify role for unauthenticated users
org_role = Viewer org_role = Viewer
#################################### Github Auth ########################## #################################### Github Auth #########################
[auth.github] [auth.github]
enabled = false enabled = false
allow_sign_up = false allow_sign_up = false
@ -217,7 +217,7 @@ api_url = https://api.github.com/user
team_ids = team_ids =
allowed_organizations = allowed_organizations =
#################################### Google Auth ########################## #################################### Google Auth #########################
[auth.google] [auth.google]
enabled = false enabled = false
allow_sign_up = false allow_sign_up = false
@ -229,7 +229,16 @@ token_url = https://accounts.google.com/o/oauth2/token
api_url = https://www.googleapis.com/oauth2/v1/userinfo api_url = https://www.googleapis.com/oauth2/v1/userinfo
allowed_domains = allowed_domains =
#################################### Generic OAuth ########################## #################################### Grafana.net Auth ####################
[auth.grafananet]
enabled = false
allow_sign_up = false
client_id = some_id
client_secret = some_secret
scopes = user:email
allowed_organizations =
#################################### Generic OAuth #######################
[auth.generic_oauth] [auth.generic_oauth]
enabled = false enabled = false
allow_sign_up = false allow_sign_up = false
@ -253,12 +262,12 @@ header_name = X-WEBAUTH-USER
header_property = username header_property = username
auto_sign_up = true auto_sign_up = true
#################################### Auth LDAP ########################## #################################### Auth LDAP ###########################
[auth.ldap] [auth.ldap]
enabled = false enabled = false
config_file = /etc/grafana/ldap.toml config_file = /etc/grafana/ldap.toml
#################################### SMTP / Emailing ########################## #################################### SMTP / Emailing #####################
[smtp] [smtp]
enabled = false enabled = false
host = localhost:25 host = localhost:25
@ -273,9 +282,6 @@ from_address = admin@grafana.localhost
welcome_email_on_sign_up = false welcome_email_on_sign_up = false
templates_pattern = emails/*.html templates_pattern = emails/*.html
[tmp.files]
rendered_image_ttl_days = 14
#################################### Logging ########################## #################################### Logging ##########################
[log] [log]
# Either "console", "file", "syslog". Default is console and file # Either "console", "file", "syslog". Default is console and file
@ -331,18 +337,18 @@ facility =
tag = tag =
#################################### AMQP Event Publisher ########################## #################################### AMQP Event Publisher ################
[event_publisher] [event_publisher]
enabled = false enabled = false
rabbitmq_url = amqp://localhost/ rabbitmq_url = amqp://localhost/
exchange = grafana_events exchange = grafana_events
#################################### Dashboard JSON files ########################## #################################### Dashboard JSON files ################
[dashboards.json] [dashboards.json]
enabled = false enabled = false
path = /var/lib/grafana/dashboards path = /var/lib/grafana/dashboards
#################################### Usage Quotas ########################## #################################### Usage Quotas ########################
[quota] [quota]
enabled = false enabled = false
@ -377,7 +383,7 @@ global_api_key = -1
# global limit on number of logged in users. # global limit on number of logged in users.
global_session = -1 global_session = -1
#################################### Alerting ###################################### #################################### Alerting ############################
# docs about alerting can be found in /docs/sources/alerting/ # docs about alerting can be found in /docs/sources/alerting/
# __.-/| # __.-/|
# \`o_O' # \`o_O'
@ -396,7 +402,7 @@ global_session = -1
[alerting] [alerting]
enabled = true enabled = true
#################################### Internal Grafana Metrics ########################## #################################### Internal Grafana Metrics ############
# Metrics available at HTTP API Url /api/metrics # Metrics available at HTTP API Url /api/metrics
[metrics] [metrics]
enabled = true enabled = true
@ -411,7 +417,7 @@ prefix = prod.grafana.%(instance_name)s.
[grafana_net] [grafana_net]
url = https://grafana.net url = https://grafana.net
#################################### External image storage ########################## #################################### External Image Storage ##############
[external_image_storage] [external_image_storage]
# You can choose between (s3, webdav) # You can choose between (s3, webdav)
provider = s3 provider = s3

View File

@ -116,7 +116,7 @@
# in some UI views to notify that grafana or plugin update exists # in some UI views to notify that grafana or plugin update exists
# This option does not cause any auto updates, nor send any information # This option does not cause any auto updates, nor send any information
# only a GET request to http://grafana.net to get latest versions # only a GET request to http://grafana.net to get latest versions
check_for_updates = true ;check_for_updates = true
# Google Analytics universal tracking code, only enabled if you specify an id here # Google Analytics universal tracking code, only enabled if you specify an id here
;google_analytics_ua_id = ;google_analytics_ua_id =
@ -224,6 +224,15 @@ check_for_updates = true
;team_ids = ;team_ids =
;allowed_organizations = ;allowed_organizations =
#################################### Grafana.net Auth ####################
[auth.grafananet]
;enabled = false
;allow_sign_up = false
;client_id = some_id
;client_secret = some_secret
;scopes = user:email
;allowed_organizations =
#################################### Auth Proxy ########################## #################################### Auth Proxy ##########################
[auth.proxy] [auth.proxy]
;enabled = false ;enabled = false

View File

@ -25,10 +25,12 @@ func LoginView(c *middleware.Context) {
return return
} }
viewData.Settings["googleAuthEnabled"] = setting.OAuthService.Google enabledOAuths := make(map[string]interface{})
viewData.Settings["githubAuthEnabled"] = setting.OAuthService.GitHub for key, oauth := range setting.OAuthService.OAuthInfos {
viewData.Settings["genericOAuthEnabled"] = setting.OAuthService.Generic enabledOAuths[key] = map[string]string{"name": oauth.Name}
viewData.Settings["oauthProviderName"] = setting.OAuthService.OAuthProviderName }
viewData.Settings["oauth"] = enabledOAuths
viewData.Settings["disableUserSignUp"] = !setting.AllowUserSignUp viewData.Settings["disableUserSignUp"] = !setting.AllowUserSignUp
viewData.Settings["loginHint"] = setting.LoginHint viewData.Settings["loginHint"] = setting.LoginHint
viewData.Settings["allowUserPassLogin"] = setting.AllowUserPassLogin viewData.Settings["allowUserPassLogin"] = setting.AllowUserPassLogin

View File

@ -82,10 +82,11 @@ func OAuthLogin(ctx *middleware.Context) {
return return
} }
cmd := m.CreateUserCommand{ cmd := m.CreateUserCommand{
Login: userInfo.Email, Login: userInfo.Email,
Email: userInfo.Email, Email: userInfo.Email,
Name: userInfo.Name, Name: userInfo.Name,
Company: userInfo.Company, Company: userInfo.Company,
DefaultOrgRole: userInfo.Role,
} }
if err = bus.Dispatch(&cmd); err != nil { if err = bus.Dispatch(&cmd); err != nil {

View File

@ -7,4 +7,5 @@ const (
GOOGLE GOOGLE
TWITTER TWITTER
GENERIC GENERIC
GRAFANANET
) )

View File

@ -44,15 +44,16 @@ func (u *User) NameOrFallback() string {
// COMMANDS // COMMANDS
type CreateUserCommand struct { type CreateUserCommand struct {
Email string Email string
Login string Login string
Name string Name string
Company string Company string
OrgName string OrgName string
Password string Password string
EmailVerified bool EmailVerified bool
IsAdmin bool IsAdmin bool
SkipOrgSetup bool SkipOrgSetup bool
DefaultOrgRole string
Result User Result User
} }

View File

@ -128,7 +128,11 @@ func CreateUser(cmd *m.CreateUserCommand) error {
} }
if setting.AutoAssignOrg && !user.IsAdmin { if setting.AutoAssignOrg && !user.IsAdmin {
orgUser.Role = m.RoleType(setting.AutoAssignOrgRole) if len(cmd.DefaultOrgRole) > 0 {
orgUser.Role = m.RoleType(cmd.DefaultOrgRole)
} else {
orgUser.Role = m.RoleType(setting.AutoAssignOrgRole)
}
} }
if _, err = sess.Insert(&orgUser); err != nil { if _, err = sess.Insert(&orgUser); err != nil {

View File

@ -8,12 +8,11 @@ type OAuthInfo struct {
AllowedDomains []string AllowedDomains []string
ApiUrl string ApiUrl string
AllowSignup bool AllowSignup bool
Name string
} }
type OAuther struct { type OAuther struct {
GitHub, Google, Twitter, Generic bool OAuthInfos map[string]*OAuthInfo
OAuthInfos map[string]*OAuthInfo
OAuthProviderName string
} }
var OAuthService *OAuther var OAuthService *OAuther

View File

@ -0,0 +1,114 @@
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"`
Role string `json:"role"`
}
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,
Role: data.Role,
}
if !s.IsOrganizationMember(client) {
return nil, ErrMissingOrganizationMembership
}
return userInfo, nil
}

View File

@ -15,6 +15,7 @@ type BasicUserInfo struct {
Email string Email string
Login string Login string
Company string Company string
Role string
} }
type SocialConnector interface { type SocialConnector interface {
@ -36,7 +37,7 @@ func NewOAuthService() {
setting.OAuthService = &setting.OAuther{} setting.OAuthService = &setting.OAuther{}
setting.OAuthService.OAuthInfos = make(map[string]*setting.OAuthInfo) 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 { for _, name := range allOauthes {
sec := setting.Cfg.Section("auth." + name) sec := setting.Cfg.Section("auth." + name)
@ -50,6 +51,7 @@ func NewOAuthService() {
Enabled: sec.Key("enabled").MustBool(), Enabled: sec.Key("enabled").MustBool(),
AllowedDomains: sec.Key("allowed_domains").Strings(" "), AllowedDomains: sec.Key("allowed_domains").Strings(" "),
AllowSignup: sec.Key("allow_sign_up").MustBool(), AllowSignup: sec.Key("allow_sign_up").MustBool(),
Name: sec.Key("name").MustString(name),
} }
if !info.Enabled { if !info.Enabled {
@ -70,22 +72,18 @@ func NewOAuthService() {
// GitHub. // GitHub.
if name == "github" { if name == "github" {
setting.OAuthService.GitHub = true
teamIds := sec.Key("team_ids").Ints(",")
allowedOrganizations := sec.Key("allowed_organizations").Strings(" ")
SocialMap["github"] = &SocialGithub{ SocialMap["github"] = &SocialGithub{
Config: &config, Config: &config,
allowedDomains: info.AllowedDomains, allowedDomains: info.AllowedDomains,
apiUrl: info.ApiUrl, apiUrl: info.ApiUrl,
allowSignup: info.AllowSignup, allowSignup: info.AllowSignup,
teamIds: teamIds, teamIds: sec.Key("team_ids").Ints(","),
allowedOrganizations: allowedOrganizations, allowedOrganizations: sec.Key("allowed_organizations").Strings(" "),
} }
} }
// Google. // Google.
if name == "google" { if name == "google" {
setting.OAuthService.Google = true
SocialMap["google"] = &SocialGoogle{ SocialMap["google"] = &SocialGoogle{
Config: &config, allowedDomains: info.AllowedDomains, Config: &config, allowedDomains: info.AllowedDomains,
apiUrl: info.ApiUrl, apiUrl: info.ApiUrl,
@ -95,17 +93,33 @@ func NewOAuthService() {
// Generic - Uses the same scheme as Github. // Generic - Uses the same scheme as Github.
if name == "generic_oauth" { if name == "generic_oauth" {
setting.OAuthService.Generic = true
setting.OAuthService.OAuthProviderName = sec.Key("oauth_provider_name").String()
teamIds := sec.Key("team_ids").Ints(",")
allowedOrganizations := sec.Key("allowed_organizations").Strings(" ")
SocialMap["generic_oauth"] = &GenericOAuth{ SocialMap["generic_oauth"] = &GenericOAuth{
Config: &config, Config: &config,
allowedDomains: info.AllowedDomains, allowedDomains: info.AllowedDomains,
apiUrl: info.ApiUrl, apiUrl: info.ApiUrl,
allowSignup: info.AllowSignup, allowSignup: info.AllowSignup,
teamIds: teamIds, teamIds: sec.Key("team_ids").Ints(","),
allowedOrganizations: allowedOrganizations, allowedOrganizations: sec.Key("allowed_organizations").Strings(" "),
}
}
if name == "grafananet" {
config := oauth2.Config{
ClientID: info.ClientId,
ClientSecret: info.ClientSecret,
Endpoint: oauth2.Endpoint{
AuthURL: setting.GrafanaNetUrl + "/oauth2/authorize",
TokenURL: setting.GrafanaNetUrl + "/api/oauth2/token",
},
RedirectURL: strings.TrimSuffix(setting.AppUrl, "/") + SocialBaseUrl + name,
Scopes: info.Scopes,
}
SocialMap["grafananet"] = &SocialGrafanaNet{
Config: &config,
url: setting.GrafanaNetUrl,
allowSignup: info.AllowSignup,
allowedOrganizations: sec.Key("allowed_organizations").Strings(" "),
} }
} }
} }

View File

@ -1,14 +1,15 @@
define([ define([
'angular', 'angular',
'lodash',
'../core_module', '../core_module',
'app/core/config', 'app/core/config',
], ],
function (angular, coreModule, config) { function (angular, _, coreModule, config) {
'use strict'; 'use strict';
var failCodes = { var failCodes = {
"1000": "Required Github team membership not fulfilled", "1000": "Required team membership not fulfilled",
"1001": "Required Github organization membership not fulfilled", "1001": "Required organization membership not fulfilled",
"1002": "Required email domain not fulfilled", "1002": "Required email domain not fulfilled",
}; };
@ -21,12 +22,10 @@ function (angular, coreModule, config) {
contextSrv.sidemenu = false; contextSrv.sidemenu = false;
$scope.googleAuthEnabled = config.googleAuthEnabled; $scope.oauth = config.oauth;
$scope.githubAuthEnabled = config.githubAuthEnabled; $scope.oauthEnabled = _.keys(config.oauth).length > 0;
$scope.oauthEnabled = config.githubAuthEnabled || config.googleAuthEnabled || config.genericOAuthEnabled;
$scope.allowUserPassLogin = config.allowUserPassLogin; $scope.allowUserPassLogin = config.allowUserPassLogin;
$scope.genericOAuthEnabled = config.genericOAuthEnabled;
$scope.oauthProviderName = config.oauthProviderName;
$scope.disableUserSignUp = config.disableUserSignUp; $scope.disableUserSignUp = config.disableUserSignUp;
$scope.loginHint = config.loginHint; $scope.loginHint = config.loginHint;

View File

@ -51,18 +51,21 @@
<div class="clearfix"></div> <div class="clearfix"></div>
<div class="login-oauth text-center" ng-show="oauthEnabled"> <div class="login-oauth text-center" ng-show="oauthEnabled">
<a class="btn btn-large btn-google" href="login/google" target="_self" ng-if="googleAuthEnabled"> <a class="btn btn-large btn-google" href="login/google" target="_self" ng-if="oauth.google">
<i class="fa fa-google"></i> <i class="fa fa-google"></i>
with Google with Google
</a> </a>
<a class="btn btn-large btn-github" href="login/github" target="_self" ng-if="githubAuthEnabled"> <a class="btn btn-large btn-github" href="login/github" target="_self" ng-if="oauth.github">
<i class="fa fa-github"></i> <i class="fa fa-github"></i>
with Github with Github
</a> </a>
<a class="btn btn-large btn-generic-oauth" href="login/generic_oauth" target="_self" ng-if="genericOAuthEnabled"> <a class="btn btn-large btn-grafana-net" href="login/grafananet" target="_self" ng-if="oauth.grafananet">
with <span>Grafana.net</span>
</a>
<a class="btn btn-large btn-generic-oauth" href="login/generic_oauth" target="_self" ng-if="oauth.generic_oauth">
<i class="fa fa-gear"></i> <i class="fa fa-gear"></i>
with {{oauthProviderName || "OAuth 2"}} with {{oauth.generic_oauth.name}}
</a> </a>
</div> </div>
</div> </div>

View File

@ -111,7 +111,7 @@
font-size: $font-size-sm; font-size: $font-size-sm;
padding-right: 7rem; padding-right: 7rem;
background: url(../img/grafana_net_logo.svg); background: url(../img/grafana_net_logo.svg);
background-size: 6.5rem 3rem; background-size: 6.5rem;
background-repeat: no-repeat; background-repeat: no-repeat;
background-position: right; background-position: right;
position: relative; position: relative;

View File

@ -112,6 +112,19 @@
background: #555; background: #555;
color: white; 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 { .password-recovery {
@ -157,7 +170,7 @@
.invite-box { .invite-box {
text-align: center; text-align: center;
border: 1px solid $tight-form-func-bg; border: 1px solid $tight-form-func-bg;
background-color: $panel-bg; background-color: $panel-bg;
max-width: 800px; max-width: 800px;
margin-left: auto; margin-left: auto;
margin-right: auto; margin-right: auto;