diff --git a/CHANGELOG.md b/CHANGELOG.md index b9133bf8601..70aaa67ab22 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,10 @@ -# 2.0.3 (unreleased) +# 2.1.0 (unreleased - master branch) + +**Backend** +- [Issue #1905](https://github.com/grafana/grafana/issues/1905). Github OAuth: You can now configure a Github team membership requirement, thx @dewski + + +# 2.0.3 (unreleased - 2.0.x branch) **Fixes** - [Issue #1872](https://github.com/grafana/grafana/issues/1872). Firefox/IE issue, invisible text in dashboard search fixed diff --git a/conf/defaults.ini b/conf/defaults.ini index 6bb3fb80857..4fef290b908 100644 --- a/conf/defaults.ini +++ b/conf/defaults.ini @@ -137,18 +137,20 @@ org_role = Viewer #################################### Github Auth ########################## [auth.github] enabled = false +allow_sign_up = false client_id = some_id client_secret = some_secret scopes = user:email auth_url = https://github.com/login/oauth/authorize token_url = https://github.com/login/oauth/access_token api_url = https://api.github.com/user +team_ids = allowed_domains = -allow_sign_up = false #################################### Google Auth ########################## [auth.google] enabled = false +allow_sign_up = false client_id = some_client_id client_secret = some_client_secret scopes = https://www.googleapis.com/auth/userinfo.profile https://www.googleapis.com/auth/userinfo.email @@ -156,7 +158,6 @@ auth_url = https://accounts.google.com/o/oauth2/auth token_url = https://accounts.google.com/o/oauth2/token api_url = https://www.googleapis.com/oauth2/v1/userinfo allowed_domains = -allow_sign_up = false #################################### Logging ########################## [log] diff --git a/conf/sample.ini b/conf/sample.ini index 68bd3eb3a1d..62d5c2a09ce 100644 --- a/conf/sample.ini +++ b/conf/sample.ini @@ -136,26 +136,27 @@ #################################### Github Auth ########################## [auth.github] ;enabled = false +;allow_sign_up = false ;client_id = some_id ;client_secret = some_secret ;scopes = user:email ;auth_url = https://github.com/login/oauth/authorize ;token_url = https://github.com/login/oauth/access_token ;api_url = https://api.github.com/user -# Uncomment bellow to only allow specific email domains -; allowed_domains = mycompany.com othercompany.com +;team_ids = +;allowed_domains = #################################### Google Auth ########################## [auth.google] ;enabled = false +;allow_sign_up = false ;client_id = some_client_id ;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 ;api_url = https://www.googleapis.com/oauth2/v1/userinfo -# Uncomment bellow to only allow specific email domains -; allowed_domains = mycompany.com othercompany.com +;allowed_domains = #################################### Logging ########################## [log] diff --git a/docs/Makefile b/docs/Makefile index d44bc545e2c..fcb1708f916 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -44,7 +44,7 @@ docs-test: docs-build $(DOCKER_RUN_DOCS) "$(DOCKER_DOCS_IMAGE)" ./test.sh docs-build: - git fetch https://github.com/grafana/grafana.git docs-2.0 && git diff --name-status FETCH_HEAD...HEAD -- . > changed-files + git fetch https://github.com/grafana/grafana.git docs-1.x && git diff --name-status FETCH_HEAD...HEAD -- . > changed-files echo "$(GIT_BRANCH)" > GIT_BRANCH echo "$(GITCOMMIT)" > GITCOMMIT docker build -t "$(DOCKER_DOCS_IMAGE)" . diff --git a/docs/sources/guides/screencasts.md b/docs/sources/guides/screencasts.md index 6c09e5b26af..d8c605a4245 100644 --- a/docs/sources/guides/screencasts.md +++ b/docs/sources/guides/screencasts.md @@ -15,10 +15,9 @@ no_toc: true

Episode 2 - Templated Graphite Queries

- +
-
@@ -34,7 +33,6 @@ no_toc: true
-
@@ -50,7 +48,6 @@ no_toc: true
-
diff --git a/docs/sources/installation/configuration.md b/docs/sources/installation/configuration.md index a79a8494d73..ace8c57184f 100644 --- a/docs/sources/installation/configuration.md +++ b/docs/sources/installation/configuration.md @@ -182,6 +182,7 @@ Client ID and a Client Secret. Specify these in the grafana config file. Example auth_url = https://github.com/login/oauth/authorize token_url = https://github.com/login/oauth/access_token allow_sign_up = false + team_ids = Restart the grafana backend. You should now see a github login button on the login page. You can now login or signup with your github accounts. @@ -189,6 +190,21 @@ now login or signup with your github accounts. You may allow users to sign-up via github auth by setting allow_sign_up to true. When this option is set to true, any user successfully authenticating via github auth will be automatically signed up. +### team_ids +Require an active team membership for at least one of the given teams on GitHub. +If the authenticated user isn't a member of at least one the teams they will not +be able to register or authenticate with your Grafana instance. Example: + + [auth.github] + enabled = true + client_id = YOUR_GITHUB_APP_CLIENT_ID + client_secret = YOUR_GITHUB_APP_CLIENT_SECRET + scopes = user:email + team_ids = 150,300 + auth_url = https://github.com/login/oauth/authorize + token_url = https://github.com/login/oauth/access_token + allow_sign_up = false + ## [auth.google] You need to create a google project. You can do this in the [Google Developer Console](https://console.developers.google.com/project). When you create the project you will need to specify a callback URL. Specify this as callback: @@ -257,5 +273,3 @@ enabled. Counters are sent every 24 hours. Default value is `true`. ### google_analytics_ua_id If you want to track Grafana usage via Google analytics specify *your* Univeral Analytics ID here. By defualt this feature is disabled. - - diff --git a/docs/sources/installation/debian.md b/docs/sources/installation/debian.md index 4bba51ec530..c2594e38445 100644 --- a/docs/sources/installation/debian.md +++ b/docs/sources/installation/debian.md @@ -16,7 +16,7 @@ Description | Download $ wget https://grafanarel.s3.amazonaws.com/builds/grafana_2.0.2_amd64.deb $ sudo apt-get install -y adduser libfontconfig - $ sudo dpkg -i grafana_2.0.1_amd64.deb + $ sudo dpkg -i grafana_2.0.2_amd64.deb ## APT Repository Add the following line to your `/etc/apt/sources.list` diff --git a/pkg/api/dtos/models.go b/pkg/api/dtos/models.go index 0057f78ff0d..fea88f07550 100644 --- a/pkg/api/dtos/models.go +++ b/pkg/api/dtos/models.go @@ -21,8 +21,9 @@ type CurrentUser struct { Email string `json:"email"` Name string `json:"name"` LightTheme bool `json:"lightTheme"` - OrgRole m.RoleType `json:"orgRole"` + OrgId int64 `json:"orgId"` OrgName string `json:"orgName"` + OrgRole m.RoleType `json:"orgRole"` IsGrafanaAdmin bool `json:"isGrafanaAdmin"` GravatarUrl string `json:"gravatarUrl"` } diff --git a/pkg/api/index.go b/pkg/api/index.go index d9ecf65b699..86a5e3f1882 100644 --- a/pkg/api/index.go +++ b/pkg/api/index.go @@ -18,6 +18,7 @@ func setIndexViewData(c *middleware.Context) error { Email: c.Email, Name: c.Name, LightTheme: c.Theme == "light", + OrgId: c.OrgId, OrgName: c.OrgName, OrgRole: c.OrgRole, GravatarUrl: dtos.GetGravatarUrl(c.Email), diff --git a/pkg/api/login_oauth.go b/pkg/api/login_oauth.go index 11d62754a18..505c17ddde8 100644 --- a/pkg/api/login_oauth.go +++ b/pkg/api/login_oauth.go @@ -3,6 +3,7 @@ package api import ( "errors" "fmt" + "net/url" "golang.org/x/oauth2" @@ -45,7 +46,11 @@ func OAuthLogin(ctx *middleware.Context) { userInfo, err := connect.UserInfo(token) if err != nil { - ctx.Handle(500, fmt.Sprintf("login.OAuthLogin(get info from %s)", name), err) + if err == social.ErrMissingTeamMembership { + ctx.Redirect(setting.AppSubUrl + "/login?failedMsg=" + url.QueryEscape("Required Github team membership not fulfilled")) + } else { + ctx.Handle(500, fmt.Sprintf("login.OAuthLogin(get info from %s)", name), err) + } return } @@ -54,7 +59,7 @@ func OAuthLogin(ctx *middleware.Context) { // 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") + ctx.Redirect(setting.AppSubUrl + "/login?failedMsg=" + url.QueryEscape("Required email domain not fulfilled")) return } diff --git a/pkg/social/social.go b/pkg/social/social.go index 47c7ea5dc38..355f85b54b6 100644 --- a/pkg/social/social.go +++ b/pkg/social/social.go @@ -2,7 +2,9 @@ package social import ( "encoding/json" + "errors" "fmt" + "net/http" "strconv" "strings" @@ -75,13 +77,24 @@ func NewOAuthService() { // GitHub. if name == "github" { setting.OAuthService.GitHub = true - SocialMap["github"] = &SocialGithub{Config: &config, allowedDomains: info.AllowedDomains, ApiUrl: info.ApiUrl, allowSignup: info.AllowSignup} + teamIds := sec.Key("team_ids").Ints(",") + SocialMap["github"] = &SocialGithub{ + Config: &config, + allowedDomains: info.AllowedDomains, + apiUrl: info.ApiUrl, + allowSignup: info.AllowSignup, + teamIds: teamIds, + } } // Google. if name == "google" { setting.OAuthService.Google = true - SocialMap["google"] = &SocialGoogle{Config: &config, allowedDomains: info.AllowedDomains, ApiUrl: info.ApiUrl, allowSignup: info.AllowSignup} + SocialMap["google"] = &SocialGoogle{ + Config: &config, allowedDomains: info.AllowedDomains, + apiUrl: info.ApiUrl, + allowSignup: info.AllowSignup, + } } } } @@ -103,10 +116,15 @@ func isEmailAllowed(email string, allowedDomains []string) bool { type SocialGithub struct { *oauth2.Config allowedDomains []string - ApiUrl string + apiUrl string allowSignup bool + teamIds []int } +var ( + ErrMissingTeamMembership = errors.New("User not a member of one of the required teams") +) + func (s *SocialGithub) Type() int { return int(models.GITHUB) } @@ -119,6 +137,28 @@ func (s *SocialGithub) IsSignupAllowed() bool { return s.allowSignup } +func (s *SocialGithub) IsTeamMember(client *http.Client, username string, teamId int) bool { + var data struct { + Url string `json:"url"` + State string `json:"state"` + } + + membershipUrl := fmt.Sprintf("https://api.github.com/teams/%d/memberships/%s", teamId, username) + r, err := client.Get(membershipUrl) + if err != nil { + return false + } + + defer r.Body.Close() + + if err = json.NewDecoder(r.Body).Decode(&data); err != nil { + return false + } + + active := data.State == "active" + return active +} + func (s *SocialGithub) UserInfo(token *oauth2.Token) (*BasicUserInfo, error) { var data struct { Id int `json:"id"` @@ -128,7 +168,7 @@ func (s *SocialGithub) UserInfo(token *oauth2.Token) (*BasicUserInfo, error) { var err error client := s.Client(oauth2.NoContext, token) - r, err := client.Get(s.ApiUrl) + r, err := client.Get(s.apiUrl) if err != nil { return nil, err } @@ -139,11 +179,23 @@ func (s *SocialGithub) UserInfo(token *oauth2.Token) (*BasicUserInfo, error) { return nil, err } - return &BasicUserInfo{ + userInfo := &BasicUserInfo{ Identity: strconv.Itoa(data.Id), Name: data.Name, Email: data.Email, - }, nil + } + + if len(s.teamIds) > 0 { + for _, teamId := range s.teamIds { + if s.IsTeamMember(client, data.Name, teamId) { + return userInfo, nil + } + } + + return nil, ErrMissingTeamMembership + } else { + return userInfo, nil + } } // ________ .__ @@ -156,7 +208,7 @@ func (s *SocialGithub) UserInfo(token *oauth2.Token) (*BasicUserInfo, error) { type SocialGoogle struct { *oauth2.Config allowedDomains []string - ApiUrl string + apiUrl string allowSignup bool } @@ -181,7 +233,7 @@ func (s *SocialGoogle) UserInfo(token *oauth2.Token) (*BasicUserInfo, error) { var err error client := s.Client(oauth2.NoContext, token) - r, err := client.Get(s.ApiUrl) + r, err := client.Get(s.apiUrl) if err != nil { return nil, err } diff --git a/public/app/components/kbn.js b/public/app/components/kbn.js index fa2b8214873..652b7d6c923 100644 --- a/public/app/components/kbn.js +++ b/public/app/components/kbn.js @@ -376,6 +376,7 @@ function($, _, moment) { kbn.valueFormats.bytes = kbn.formatFuncCreator(1024, [' B', ' KiB', ' MiB', ' GiB', ' TiB', ' PiB', ' EiB', ' ZiB', ' YiB']); kbn.valueFormats.kbytes = kbn.formatFuncCreator(1024, [' KiB', ' MiB', ' GiB', ' TiB', ' PiB', ' EiB', ' ZiB', ' YiB']); kbn.valueFormats.mbytes = kbn.formatFuncCreator(1024, [' MiB', ' GiB', ' TiB', ' PiB', ' EiB', ' ZiB', ' YiB']); + kbn.valueFormats.gbytes = kbn.formatFuncCreator(1024, [' GiB', ' TiB', ' PiB', ' EiB', ' ZiB', ' YiB']); kbn.valueFormats.bps = kbn.formatFuncCreator(1000, [' bps', ' Kbps', ' Mbps', ' Gbps', ' Tbps', ' Pbps', ' Ebps', ' Zbps', ' Ybps']); kbn.valueFormats.Bps = kbn.formatFuncCreator(1000, [' Bps', ' KBps', ' MBps', ' GBps', ' TBps', ' PBps', ' EBps', ' ZBps', ' YBps']); kbn.valueFormats.short = kbn.formatFuncCreator(1000, ['', ' K', ' Mil', ' Bil', ' Tri', ' Qaudr', ' Quint', ' Sext', ' Sept']); @@ -547,6 +548,7 @@ function($, _, moment) { {text: 'bytes', value: 'bytes'}, {text: 'kilobytes', value: 'kbytes'}, {text: 'megabytes', value: 'mbytes'}, + {text: 'gigabytes', value: 'gbytes'}, ] }, { diff --git a/public/app/controllers/loginCtrl.js b/public/app/controllers/loginCtrl.js index 5de773842f8..c8856df0690 100644 --- a/public/app/controllers/loginCtrl.js +++ b/public/app/controllers/loginCtrl.js @@ -7,7 +7,7 @@ function (angular, config) { var module = angular.module('grafana.controllers'); - module.controller('LoginCtrl', function($scope, backendSrv, contextSrv) { + module.controller('LoginCtrl', function($scope, backendSrv, contextSrv, $location) { $scope.formModel = { user: '', email: '', @@ -28,6 +28,13 @@ function (angular, config) { $scope.init = function() { $scope.$watch("loginMode", $scope.loginModeChanged); $scope.passwordChanged(); + + var params = $location.search(); + if (params.failedMsg) { + $scope.appEvent('alert-warning', ['Login Failed', params.failedMsg]); + delete params.failedMsg; + $location.search(params); + } }; // build info view model diff --git a/public/app/features/templating/templateValuesSrv.js b/public/app/features/templating/templateValuesSrv.js index 4245217ebb4..9dea95fb083 100644 --- a/public/app/features/templating/templateValuesSrv.js +++ b/public/app/features/templating/templateValuesSrv.js @@ -29,13 +29,7 @@ function (angular, _, kbn) { var variable = this.variables[i]; var urlValue = queryParams['var-' + variable.name]; if (urlValue !== void 0) { - var option = _.findWhere(variable.options, { text: urlValue }); - option = option || { text: urlValue, value: urlValue }; - - var promise = this.setVariableValue(variable, option); - this.updateAutoInterval(variable); - - promises.push(promise); + promises.push(this.setVariableFromUrl(variable, urlValue)); } else if (variable.refresh) { promises.push(this.updateOptions(variable)); @@ -48,6 +42,25 @@ function (angular, _, kbn) { return $q.all(promises); }; + this.setVariableFromUrl = function(variable, urlValue) { + if (variable.refresh) { + var self = this; + //refresh the list of options before setting the value + return this.updateOptions(variable).then(function() { + var option = _.findWhere(variable.options, { text: urlValue }); + option = option || { text: urlValue, value: urlValue }; + + self.updateAutoInterval(variable); + return self.setVariableValue(variable, option); + }); + } + var option = _.findWhere(variable.options, { text: urlValue }); + option = option || { text: urlValue, value: urlValue }; + + this.updateAutoInterval(variable); + return this.setVariableValue(variable, option); + }; + this.updateAutoInterval = function(variable) { if (!variable.auto) { return; } diff --git a/public/app/plugins/datasource/influxdb/influxSeries.js b/public/app/plugins/datasource/influxdb/influxSeries.js index 9e28884547b..cca01459fc4 100644 --- a/public/app/plugins/datasource/influxdb/influxSeries.js +++ b/public/app/plugins/datasource/influxdb/influxSeries.js @@ -5,7 +5,8 @@ function (_) { 'use strict'; function InfluxSeries(options) { - this.seriesList = options.seriesList; + this.seriesList = options.seriesList && options.seriesList.results && options.seriesList.results.length > 0 + ? options.seriesList.results[0].series || [] : []; this.alias = options.alias; this.annotation = options.annotation; } @@ -17,12 +18,10 @@ function (_) { var self = this; console.log(self.seriesList); - if (!self.seriesList || !self.seriesList.results || !self.seriesList.results[0]) { + if (self.seriesList.length === 0) { return output; } - this.seriesList = self.seriesList.results[0].series; - _.each(self.seriesList, function(series) { var datapoints = []; for (var i = 0; i < series.values.length; i++) { @@ -63,19 +62,15 @@ function (_) { if (column === self.annotation.textColumn) { textCol = index; return; } }); - _.each(series.points, function (point) { + _.each(series.values, function (value) { var data = { annotation: self.annotation, - time: point[timeCol], - title: point[titleCol], - tags: point[tagsCol], - text: point[textCol] + time: + new Date(value[timeCol]), + title: value[titleCol], + tags: value[tagsCol], + text: value[textCol] }; - if (tagsCol) { - data.tags = point[tagsCol]; - } - list.push(data); }); }); diff --git a/public/app/plugins/datasource/influxdb/plugin.json b/public/app/plugins/datasource/influxdb/plugin.json index 40c5fc9cea9..c93b41747a7 100644 --- a/public/app/plugins/datasource/influxdb/plugin.json +++ b/public/app/plugins/datasource/influxdb/plugin.json @@ -13,5 +13,6 @@ "annotations": "app/plugins/datasource/influxdb/partials/annotations.editor.html" }, - "metrics": true + "metrics": true, + "annotations": true }