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 = ${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

View File

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

View File

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

View File

@ -86,6 +86,7 @@ func OAuthLogin(ctx *middleware.Context) {
Email: userInfo.Email,
Name: userInfo.Name,
Company: userInfo.Company,
DefaultOrgRole: userInfo.Role,
}
if err = bus.Dispatch(&cmd); err != nil {

View File

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

View File

@ -53,6 +53,7 @@ type CreateUserCommand struct {
EmailVerified bool
IsAdmin bool
SkipOrgSetup bool
DefaultOrgRole string
Result User
}

View File

@ -128,8 +128,12 @@ func CreateUser(cmd *m.CreateUserCommand) error {
}
if setting.AutoAssignOrg && !user.IsAdmin {
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 {
return err

View File

@ -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
}
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
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(" "),
}
}
}

View File

@ -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;

View File

@ -51,17 +51,20 @@
<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"}}
with {{oauth.generic_oauth.name}}
</a>
</div>
</div>

View File

@ -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;

View File

@ -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 {