diff --git a/CHANGELOG.md b/CHANGELOG.md index 0623c149c2f..4e8348528c4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ - [Issue #1660](https://github.com/grafana/grafana/issues/1660). OAuth: Specify allowed email address domains for google or and github oauth logins **Fixes** +- [Issue #1667](https://github.com/grafana/grafana/issues/1667). Datasource proxy & session timeout fix (casued 401 Unauthorized error after a while) - [Issue #1707](https://github.com/grafana/grafana/issues/1707). Unsaved changes: Do not show for snapshots, scripted and file based dashboards - [Issue #1703](https://github.com/grafana/grafana/issues/1703). Unsaved changes: Do not show for users with role `Viewer` - [Issue #1675](https://github.com/grafana/grafana/issues/1675). Data source proxy: Fixed issue with Gzip enabled and data source proxy diff --git a/pkg/api/api.go b/pkg/api/api.go index 88f1a7a37ee..d66d6ae24ca 100644 --- a/pkg/api/api.go +++ b/pkg/api/api.go @@ -48,6 +48,9 @@ func Register(r *macaron.Macaron) { r.Get("/api/snapshots/:key", GetDashboardSnapshot) r.Get("/api/snapshots-delete/:key", DeleteDashboardSnapshot) + // api renew session based on remember cookie + r.Get("/api/login/ping", LoginApiPing) + // authed api r.Group("/api", func() { // user diff --git a/pkg/api/login.go b/pkg/api/login.go index 56a61697cb9..c9b8b598cfe 100644 --- a/pkg/api/login.go +++ b/pkg/api/login.go @@ -28,11 +28,25 @@ func LoginView(c *middleware.Context) { settings["githubAuthEnabled"] = setting.OAuthService.GitHub settings["disableUserSignUp"] = !setting.AllowUserSignUp + if !tryLoginUsingRememberCookie(c) { + c.HTML(200, VIEW_INDEX) + return + } + + if redirectTo, _ := url.QueryUnescape(c.GetCookie("redirect_to")); len(redirectTo) > 0 { + c.SetCookie("redirect_to", "", -1, setting.AppSubUrl+"/") + c.Redirect(redirectTo) + return + } + + c.Redirect(setting.AppSubUrl + "/") +} + +func tryLoginUsingRememberCookie(c *middleware.Context) bool { // Check auto-login. uname := c.GetCookie(setting.CookieUserName) if len(uname) == 0 { - c.HTML(200, VIEW_INDEX) - return + return false } isSucceed := false @@ -47,32 +61,29 @@ func LoginView(c *middleware.Context) { userQuery := m.GetUserByLoginQuery{LoginOrEmail: uname} if err := bus.Dispatch(&userQuery); err != nil { - if err != m.ErrUserNotFound { - c.Handle(500, "GetUserByLoginQuery", err) - } else { - c.HTML(200, VIEW_INDEX) - } - return + return false } user := userQuery.Result + // validate remember me cookie if val, _ := c.GetSuperSecureCookie( util.EncodeMd5(user.Rands+user.Password), setting.CookieRememberName); val != user.Login { - c.HTML(200, VIEW_INDEX) - return + return false } isSucceed = true loginUserWithUser(user, c) + return true +} - if redirectTo, _ := url.QueryUnescape(c.GetCookie("redirect_to")); len(redirectTo) > 0 { - c.SetCookie("redirect_to", "", -1, setting.AppSubUrl+"/") - c.Redirect(redirectTo) +func LoginApiPing(c *middleware.Context) { + if !tryLoginUsingRememberCookie(c) { + c.JsonApiErr(401, "Unauthorized", nil) return } - c.Redirect(setting.AppSubUrl + "/") + c.JsonOK("Logged in") } func LoginPost(c *middleware.Context, cmd dtos.LoginCommand) { diff --git a/public/app/plugins/datasource/graphite/datasource.js b/public/app/plugins/datasource/graphite/datasource.js index 37ce4356ee8..ea49de86a44 100644 --- a/public/app/plugins/datasource/graphite/datasource.js +++ b/public/app/plugins/datasource/graphite/datasource.js @@ -14,7 +14,7 @@ function (angular, _, $, config, kbn, moment) { var module = angular.module('grafana.services'); - module.factory('GraphiteDatasource', function($q, $http, templateSrv) { + module.factory('GraphiteDatasource', function($q, backendSrv, templateSrv) { function GraphiteDatasource(datasource) { this.basicAuth = datasource.basicAuth; @@ -218,7 +218,7 @@ function (angular, _, $, config, kbn, moment) { options.url = this.url + options.url; options.inspect = { type: 'graphite' }; - return $http(options); + return backendSrv.datasourceRequest(options); }; GraphiteDatasource.prototype._seriesRefLetters = [ diff --git a/public/app/services/backendSrv.js b/public/app/services/backendSrv.js index 004bf663e86..52b4753dc3d 100644 --- a/public/app/services/backendSrv.js +++ b/public/app/services/backendSrv.js @@ -28,43 +28,46 @@ function (angular, _, config) { }; this._handleError = function(err) { - if (err.status === 422) { - alertSrv.set("Validation failed", "", "warning", 4000); - throw err.data; - } + return function() { + if (err.isHandled) { + return; + } - var data = err.data || { message: 'Unexpected error' }; + if (err.status === 422) { + alertSrv.set("Validation failed", "", "warning", 4000); + throw err.data; + } - if (_.isString(data)) { - data = { message: data }; - } + var data = err.data || { message: 'Unexpected error' }; - data.severity = 'error'; + if (_.isString(data)) { + data = { message: data }; + } - if (err.status < 500) { - data.severity = "warning"; - } + data.severity = 'error'; - if (data.message) { - alertSrv.set("Problem!", data.message, data.severity, 10000); - } + if (err.status < 500) { + data.severity = "warning"; + } - throw data; + if (data.message) { + alertSrv.set("Problem!", data.message, data.severity, 10000); + } + + throw data; + }; }; this.request = function(options) { - var httpOptions = { - url: options.url, - method: options.method, - data: options.data, - params: options.params, - }; + options.retry = options.retry || 0; + var requestIsLocal = options.url.indexOf('/') === 0; + var firstAttempt = options.retry === 0; - if (httpOptions.url.indexOf('/') === 0) { - httpOptions.url = config.appSubUrl + httpOptions.url; + if (requestIsLocal && firstAttempt) { + options.url = config.appSubUrl + options.url; } - return $http(httpOptions).then(function(results) { + return $http(options).then(function(results) { if (options.method !== 'GET') { if (results && results.data.message) { alertSrv.set(results.data.message, '', 'success', 3000); @@ -72,15 +75,45 @@ function (angular, _, config) { } return results.data; }, function(err) { - $timeout(function() { - if (err.isHandled) { return; } - self._handleError(err); - }, 50); + // handle unauthorized + if (err.status === 401 && firstAttempt) { + return self.loginPing().then(function() { + options.retry = 1; + return self.request(options); + }); + } + + $timeout(self._handleError(err), 50); + throw err; + }); + }; + + this.datasourceRequest = function(options) { + options.retry = options.retry || 0; + var requestIsLocal = options.url.indexOf('/') === 0; + var firstAttempt = options.retry === 0; + + if (requestIsLocal && firstAttempt) { + options.url = config.appSubUrl + options.url; + } + + return $http(options).then(null, function(err) { + // handle unauthorized for backend requests + if (requestIsLocal && firstAttempt && err.status === 401) { + return self.loginPing().then(function() { + options.retry = 1; + return self.datasourceRequest(options); + }); + } throw err; }); }; + this.loginPing = function() { + return this.request({url: '/api/login/ping', method: 'GET', retry: 1 }); + }; + this.search = function(query) { return this.get('/api/search', query); }; diff --git a/public/test/specs/graphiteDatasource-specs.js b/public/test/specs/graphiteDatasource-specs.js index d1c983cfdbd..b82d2a4a7cd 100644 --- a/public/test/specs/graphiteDatasource-specs.js +++ b/public/test/specs/graphiteDatasource-specs.js @@ -8,7 +8,8 @@ define([ var ctx = new helpers.ServiceTestContext(); beforeEach(module('grafana.services')); - beforeEach(ctx.providePhase()); + beforeEach(ctx.providePhase(['backendSrv'])); + beforeEach(ctx.createService('GraphiteDatasource')); beforeEach(function() { ctx.ds = new ctx.service({ url: [''] }); @@ -21,25 +22,25 @@ define([ maxDataPoints: 500, }; - var response = [{ target: 'prod1.count', datapoints: [[10, 1], [12,1]], }]; var results; - var request; + var requestOptions; beforeEach(function() { - - ctx.$httpBackend.expectPOST('/render', function(body) { request = body; return true; }) - .respond(response); + ctx.backendSrv.datasourceRequest = function(options) { + requestOptions = options; + return ctx.$q.when({data: [{ target: 'prod1.count', datapoints: [[10, 1], [12,1]] }]}); + }; ctx.ds.query(query).then(function(data) { results = data; }); - ctx.$httpBackend.flush(); + ctx.$rootScope.$apply(); }); it('should generate the correct query', function() { - ctx.$httpBackend.verifyNoOutstandingExpectation(); + expect(requestOptions.url).to.be('/render'); }); it('should query correctly', function() { - var params = request.split('&'); + var params = requestOptions.data.split('&'); expect(params).to.contain('target=prod1.count'); expect(params).to.contain('target=prod2.count'); expect(params).to.contain('from=-1h'); @@ -47,7 +48,7 @@ define([ }); it('should exclude undefined params', function() { - var params = request.split('&'); + var params = requestOptions.data.split('&'); expect(params).to.not.contain('cacheTimeout=undefined'); }); @@ -56,6 +57,10 @@ define([ expect(results.data[0].target).to.be('prod1.count'); }); + it('should convert to millisecond resolution', function() { + expect(results.data[0].datapoints[0][0]).to.be(10); + }); + }); describe('building graphite params', function() { diff --git a/public/test/specs/helpers.js b/public/test/specs/helpers.js index 12a2978272f..a9d8a09bfe4 100644 --- a/public/test/specs/helpers.js +++ b/public/test/specs/helpers.js @@ -68,6 +68,7 @@ define([ self.templateSrv = new TemplateSrvStub(); self.timeSrv = new TimeSrvStub(); self.datasourceSrv = {}; + self.backendSrv = {}; self.$routeParams = {}; this.providePhase = function(mocks) {