Datasource proxy & session timeout fix (casued 401 Unauthorized error after a while), Fixes #1667

This commit is contained in:
Torkel Ödegaard
2015-04-07 09:25:00 +02:00
parent 382f7066d9
commit 22adf0d06e
7 changed files with 109 additions and 55 deletions

View File

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

View File

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

View File

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

View File

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

View File

@ -28,6 +28,11 @@ function (angular, _, config) {
};
this._handleError = function(err) {
return function() {
if (err.isHandled) {
return;
}
if (err.status === 422) {
alertSrv.set("Validation failed", "", "warning", 4000);
throw err.data;
@ -51,20 +56,18 @@ function (angular, _, config) {
throw data;
};
this.request = function(options) {
var httpOptions = {
url: options.url,
method: options.method,
data: options.data,
params: options.params,
};
if (httpOptions.url.indexOf('/') === 0) {
httpOptions.url = config.appSubUrl + httpOptions.url;
this.request = 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(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);
};

View File

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

View File

@ -68,6 +68,7 @@ define([
self.templateSrv = new TemplateSrvStub();
self.timeSrv = new TimeSrvStub();
self.datasourceSrv = {};
self.backendSrv = {};
self.$routeParams = {};
this.providePhase = function(mocks) {