Torkel Ödegaard 9 лет назад
Родитель
Сommit
3e657357e5

+ 27 - 21
conf/defaults.ini

@@ -9,7 +9,7 @@ app_mode = production
 # instance name, defaults to HOSTNAME environment variable value or hostname if HOSTNAME var is empty
 instance_name = ${HOSTNAME}
 
-#################################### Paths ####################################
+#################################### Paths ###############################
 [paths]
 # 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
 
-#################################### Server ####################################
+#################################### Server ##############################
 [server]
 # Protocol (http or https)
 protocol = http
@@ -57,7 +57,7 @@ enable_gzip = false
 cert_file =
 cert_key =
 
-#################################### Database ####################################
+#################################### Database ############################
 [database]
 # 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.
@@ -84,7 +84,7 @@ server_cert_name =
 # For "sqlite3" only, path relative to data_path setting
 path = grafana.db
 
-#################################### Session ####################################
+#################################### Session #############################
 [session]
 # Either "memory", "file", "redis", "mysql", "postgres", "memcache", default is "file"
 provider = file
@@ -112,7 +112,7 @@ cookie_secure = false
 session_life_time = 86400
 gc_interval_time = 86400
 
-#################################### Analytics ####################################
+#################################### Analytics ###########################
 [analytics]
 # Server reporting, sends usage counters to stats.grafana.org every 24 hours.
 # 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 =
 
-#################################### Security ####################################
+#################################### Security ############################
 [security]
 # default admin user, created on startup
 admin_user = admin
@@ -193,7 +193,7 @@ default_theme = dark
 # Allow users to sign in using username and password
 allow_user_pass_login = true
 
-#################################### Anonymous Auth ##########################
+#################################### Anonymous Auth ######################
 [auth.anonymous]
 # enable anonymous access
 enabled = false
@@ -204,7 +204,7 @@ org_name = Main Org.
 # specify role for unauthenticated users
 org_role = Viewer
 
-#################################### Github Auth ##########################
+#################################### Github Auth #########################
 [auth.github]
 enabled = false
 allow_sign_up = false
@@ -217,7 +217,7 @@ api_url = https://api.github.com/user
 team_ids =
 allowed_organizations =
 
-#################################### Google Auth ##########################
+#################################### Google Auth #########################
 [auth.google]
 enabled = 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
 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]
 enabled = false
 allow_sign_up = false
@@ -253,12 +262,12 @@ header_name = X-WEBAUTH-USER
 header_property = username
 auto_sign_up = true
 
-#################################### Auth LDAP ##########################
+#################################### Auth LDAP ###########################
 [auth.ldap]
 enabled = false
 config_file = /etc/grafana/ldap.toml
 
-#################################### SMTP / Emailing ##########################
+#################################### SMTP / Emailing #####################
 [smtp]
 enabled = false
 host = localhost:25
@@ -273,9 +282,6 @@ from_address = admin@grafana.localhost
 welcome_email_on_sign_up = false
 templates_pattern = emails/*.html
 
-[tmp.files]
-rendered_image_ttl_days = 14
-
 #################################### Logging ##########################
 [log]
 # Either "console", "file", "syslog". Default is console and  file
@@ -331,18 +337,18 @@ facility =
 tag =
 
 
-#################################### AMQP Event Publisher ##########################
+#################################### AMQP Event Publisher ################
 [event_publisher]
 enabled = false
 rabbitmq_url = amqp://localhost/
 exchange = grafana_events
 
-#################################### Dashboard JSON files ##########################
+#################################### Dashboard JSON files ################
 [dashboards.json]
 enabled = false
 path = /var/lib/grafana/dashboards
 
-#################################### Usage Quotas ##########################
+#################################### Usage Quotas ########################
 [quota]
 enabled = false
 
@@ -377,7 +383,7 @@ global_api_key = -1
 # global limit on number of logged in users.
 global_session = -1
 
-#################################### Alerting ######################################
+#################################### Alerting ############################
 # docs about alerting can be found in /docs/sources/alerting/
 #              __.-/|
 #              \`o_O'
@@ -396,7 +402,7 @@ global_session = -1
 [alerting]
 enabled = true
 
-#################################### Internal Grafana Metrics ##########################
+#################################### Internal Grafana Metrics ############
 # Metrics available at HTTP API Url /api/metrics
 [metrics]
 enabled           = true
@@ -411,7 +417,7 @@ prefix = prod.grafana.%(instance_name)s.
 [grafana_net]
 url = https://grafana.net
 
-#################################### External image storage ##########################
+#################################### External Image Storage ##############
 [external_image_storage]
 # You can choose between (s3, webdav)
 provider = s3

+ 10 - 1
conf/sample.ini

@@ -116,7 +116,7 @@
 # in some UI views to notify that grafana or plugin update exists
 # This option does not cause any auto updates, nor send any information
 # 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_ua_id =
@@ -224,6 +224,15 @@ check_for_updates = true
 ;team_ids =
 ;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]
 ;enabled = false

+ 6 - 4
pkg/api/login.go

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

+ 5 - 4
pkg/api/login_oauth.go

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

+ 1 - 0
pkg/models/models.go

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

+ 10 - 9
pkg/models/user.go

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

+ 5 - 1
pkg/services/sqlstore/user.go

@@ -128,7 +128,11 @@ func CreateUser(cmd *m.CreateUserCommand) error {
 			}
 
 			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 {

+ 2 - 3
pkg/setting/setting_oauth.go

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

+ 114 - 0
pkg/social/grafananet_oauth.go

@@ -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
+}

+ 27 - 13
pkg/social/social.go

@@ -15,6 +15,7 @@ type BasicUserInfo struct {
 	Email    string
 	Login    string
 	Company  string
+	Role     string
 }
 
 type SocialConnector interface {
@@ -36,7 +37,7 @@ func NewOAuthService() {
 	setting.OAuthService = &setting.OAuther{}
 	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 {
 		sec := setting.Cfg.Section("auth." + name)
@@ -50,6 +51,7 @@ func NewOAuthService() {
 			Enabled:        sec.Key("enabled").MustBool(),
 			AllowedDomains: sec.Key("allowed_domains").Strings(" "),
 			AllowSignup:    sec.Key("allow_sign_up").MustBool(),
+			Name:           sec.Key("name").MustString(name),
 		}
 
 		if !info.Enabled {
@@ -70,22 +72,18 @@ func NewOAuthService() {
 
 		// GitHub.
 		if name == "github" {
-			setting.OAuthService.GitHub = true
-			teamIds := sec.Key("team_ids").Ints(",")
-			allowedOrganizations := sec.Key("allowed_organizations").Strings(" ")
 			SocialMap["github"] = &SocialGithub{
 				Config:               &config,
 				allowedDomains:       info.AllowedDomains,
 				apiUrl:               info.ApiUrl,
 				allowSignup:          info.AllowSignup,
-				teamIds:              teamIds,
-				allowedOrganizations: allowedOrganizations,
+				teamIds:              sec.Key("team_ids").Ints(","),
+				allowedOrganizations: sec.Key("allowed_organizations").Strings(" "),
 			}
 		}
 
 		// Google.
 		if name == "google" {
-			setting.OAuthService.Google = true
 			SocialMap["google"] = &SocialGoogle{
 				Config: &config, allowedDomains: info.AllowedDomains,
 				apiUrl:      info.ApiUrl,
@@ -95,17 +93,33 @@ func NewOAuthService() {
 
 		// Generic - Uses the same scheme as Github.
 		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{
 				Config:               &config,
 				allowedDomains:       info.AllowedDomains,
 				apiUrl:               info.ApiUrl,
 				allowSignup:          info.AllowSignup,
-				teamIds:              teamIds,
-				allowedOrganizations: allowedOrganizations,
+				teamIds:              sec.Key("team_ids").Ints(","),
+				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(" "),
 			}
 		}
 	}

+ 7 - 8
public/app/core/controllers/login_ctrl.js

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

+ 8 - 5
public/app/partials/login.html

@@ -51,18 +51,21 @@
 				<div class="clearfix"></div>
 
 				<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>
 						with Google
 					</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>
 						with Github
 					</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>
-            with {{oauthProviderName || "OAuth 2"}}
-          </a>
+						with {{oauth.generic_oauth.name}}
+					</a>
 				</div>
 			</div>
 

+ 1 - 1
public/sass/components/_search.scss

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

+ 14 - 1
public/sass/pages/_login.scss

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