mirror of
https://github.com/grafana/grafana.git
synced 2025-07-30 17:02:15 +08:00
Datasource proxy & session timeout fix (casued 401 Unauthorized error after a while), Fixes #1667
This commit is contained in:
@ -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
|
- [Issue #1660](https://github.com/grafana/grafana/issues/1660). OAuth: Specify allowed email address domains for google or and github oauth logins
|
||||||
|
|
||||||
**Fixes**
|
**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 #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 #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
|
- [Issue #1675](https://github.com/grafana/grafana/issues/1675). Data source proxy: Fixed issue with Gzip enabled and data source proxy
|
||||||
|
@ -48,6 +48,9 @@ func Register(r *macaron.Macaron) {
|
|||||||
r.Get("/api/snapshots/:key", GetDashboardSnapshot)
|
r.Get("/api/snapshots/:key", GetDashboardSnapshot)
|
||||||
r.Get("/api/snapshots-delete/:key", DeleteDashboardSnapshot)
|
r.Get("/api/snapshots-delete/:key", DeleteDashboardSnapshot)
|
||||||
|
|
||||||
|
// api renew session based on remember cookie
|
||||||
|
r.Get("/api/login/ping", LoginApiPing)
|
||||||
|
|
||||||
// authed api
|
// authed api
|
||||||
r.Group("/api", func() {
|
r.Group("/api", func() {
|
||||||
// user
|
// user
|
||||||
|
@ -28,11 +28,25 @@ func LoginView(c *middleware.Context) {
|
|||||||
settings["githubAuthEnabled"] = setting.OAuthService.GitHub
|
settings["githubAuthEnabled"] = setting.OAuthService.GitHub
|
||||||
settings["disableUserSignUp"] = !setting.AllowUserSignUp
|
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.
|
// Check auto-login.
|
||||||
uname := c.GetCookie(setting.CookieUserName)
|
uname := c.GetCookie(setting.CookieUserName)
|
||||||
if len(uname) == 0 {
|
if len(uname) == 0 {
|
||||||
c.HTML(200, VIEW_INDEX)
|
return false
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
isSucceed := false
|
isSucceed := false
|
||||||
@ -47,32 +61,29 @@ func LoginView(c *middleware.Context) {
|
|||||||
|
|
||||||
userQuery := m.GetUserByLoginQuery{LoginOrEmail: uname}
|
userQuery := m.GetUserByLoginQuery{LoginOrEmail: uname}
|
||||||
if err := bus.Dispatch(&userQuery); err != nil {
|
if err := bus.Dispatch(&userQuery); err != nil {
|
||||||
if err != m.ErrUserNotFound {
|
return false
|
||||||
c.Handle(500, "GetUserByLoginQuery", err)
|
|
||||||
} else {
|
|
||||||
c.HTML(200, VIEW_INDEX)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
user := userQuery.Result
|
user := userQuery.Result
|
||||||
|
|
||||||
|
// validate remember me cookie
|
||||||
if val, _ := c.GetSuperSecureCookie(
|
if val, _ := c.GetSuperSecureCookie(
|
||||||
util.EncodeMd5(user.Rands+user.Password), setting.CookieRememberName); val != user.Login {
|
util.EncodeMd5(user.Rands+user.Password), setting.CookieRememberName); val != user.Login {
|
||||||
c.HTML(200, VIEW_INDEX)
|
return false
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
isSucceed = true
|
isSucceed = true
|
||||||
loginUserWithUser(user, c)
|
loginUserWithUser(user, c)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
if redirectTo, _ := url.QueryUnescape(c.GetCookie("redirect_to")); len(redirectTo) > 0 {
|
func LoginApiPing(c *middleware.Context) {
|
||||||
c.SetCookie("redirect_to", "", -1, setting.AppSubUrl+"/")
|
if !tryLoginUsingRememberCookie(c) {
|
||||||
c.Redirect(redirectTo)
|
c.JsonApiErr(401, "Unauthorized", nil)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c.Redirect(setting.AppSubUrl + "/")
|
c.JsonOK("Logged in")
|
||||||
}
|
}
|
||||||
|
|
||||||
func LoginPost(c *middleware.Context, cmd dtos.LoginCommand) {
|
func LoginPost(c *middleware.Context, cmd dtos.LoginCommand) {
|
||||||
|
@ -14,7 +14,7 @@ function (angular, _, $, config, kbn, moment) {
|
|||||||
|
|
||||||
var module = angular.module('grafana.services');
|
var module = angular.module('grafana.services');
|
||||||
|
|
||||||
module.factory('GraphiteDatasource', function($q, $http, templateSrv) {
|
module.factory('GraphiteDatasource', function($q, backendSrv, templateSrv) {
|
||||||
|
|
||||||
function GraphiteDatasource(datasource) {
|
function GraphiteDatasource(datasource) {
|
||||||
this.basicAuth = datasource.basicAuth;
|
this.basicAuth = datasource.basicAuth;
|
||||||
@ -218,7 +218,7 @@ function (angular, _, $, config, kbn, moment) {
|
|||||||
options.url = this.url + options.url;
|
options.url = this.url + options.url;
|
||||||
options.inspect = { type: 'graphite' };
|
options.inspect = { type: 'graphite' };
|
||||||
|
|
||||||
return $http(options);
|
return backendSrv.datasourceRequest(options);
|
||||||
};
|
};
|
||||||
|
|
||||||
GraphiteDatasource.prototype._seriesRefLetters = [
|
GraphiteDatasource.prototype._seriesRefLetters = [
|
||||||
|
@ -28,43 +28,46 @@ function (angular, _, config) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
this._handleError = function(err) {
|
this._handleError = function(err) {
|
||||||
if (err.status === 422) {
|
return function() {
|
||||||
alertSrv.set("Validation failed", "", "warning", 4000);
|
if (err.isHandled) {
|
||||||
throw err.data;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var data = err.data || { message: 'Unexpected error' };
|
if (err.status === 422) {
|
||||||
|
alertSrv.set("Validation failed", "", "warning", 4000);
|
||||||
|
throw err.data;
|
||||||
|
}
|
||||||
|
|
||||||
if (_.isString(data)) {
|
var data = err.data || { message: 'Unexpected error' };
|
||||||
data = { message: data };
|
|
||||||
}
|
|
||||||
|
|
||||||
data.severity = 'error';
|
if (_.isString(data)) {
|
||||||
|
data = { message: data };
|
||||||
|
}
|
||||||
|
|
||||||
if (err.status < 500) {
|
data.severity = 'error';
|
||||||
data.severity = "warning";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (data.message) {
|
if (err.status < 500) {
|
||||||
alertSrv.set("Problem!", data.message, data.severity, 10000);
|
data.severity = "warning";
|
||||||
}
|
}
|
||||||
|
|
||||||
throw data;
|
if (data.message) {
|
||||||
|
alertSrv.set("Problem!", data.message, data.severity, 10000);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw data;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
this.request = function(options) {
|
this.request = function(options) {
|
||||||
var httpOptions = {
|
options.retry = options.retry || 0;
|
||||||
url: options.url,
|
var requestIsLocal = options.url.indexOf('/') === 0;
|
||||||
method: options.method,
|
var firstAttempt = options.retry === 0;
|
||||||
data: options.data,
|
|
||||||
params: options.params,
|
|
||||||
};
|
|
||||||
|
|
||||||
if (httpOptions.url.indexOf('/') === 0) {
|
if (requestIsLocal && firstAttempt) {
|
||||||
httpOptions.url = config.appSubUrl + httpOptions.url;
|
options.url = config.appSubUrl + options.url;
|
||||||
}
|
}
|
||||||
|
|
||||||
return $http(httpOptions).then(function(results) {
|
return $http(options).then(function(results) {
|
||||||
if (options.method !== 'GET') {
|
if (options.method !== 'GET') {
|
||||||
if (results && results.data.message) {
|
if (results && results.data.message) {
|
||||||
alertSrv.set(results.data.message, '', 'success', 3000);
|
alertSrv.set(results.data.message, '', 'success', 3000);
|
||||||
@ -72,15 +75,45 @@ function (angular, _, config) {
|
|||||||
}
|
}
|
||||||
return results.data;
|
return results.data;
|
||||||
}, function(err) {
|
}, function(err) {
|
||||||
$timeout(function() {
|
// handle unauthorized
|
||||||
if (err.isHandled) { return; }
|
if (err.status === 401 && firstAttempt) {
|
||||||
self._handleError(err);
|
return self.loginPing().then(function() {
|
||||||
}, 50);
|
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;
|
throw err;
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
this.loginPing = function() {
|
||||||
|
return this.request({url: '/api/login/ping', method: 'GET', retry: 1 });
|
||||||
|
};
|
||||||
|
|
||||||
this.search = function(query) {
|
this.search = function(query) {
|
||||||
return this.get('/api/search', query);
|
return this.get('/api/search', query);
|
||||||
};
|
};
|
||||||
|
@ -8,7 +8,8 @@ define([
|
|||||||
var ctx = new helpers.ServiceTestContext();
|
var ctx = new helpers.ServiceTestContext();
|
||||||
|
|
||||||
beforeEach(module('grafana.services'));
|
beforeEach(module('grafana.services'));
|
||||||
beforeEach(ctx.providePhase());
|
beforeEach(ctx.providePhase(['backendSrv']));
|
||||||
|
|
||||||
beforeEach(ctx.createService('GraphiteDatasource'));
|
beforeEach(ctx.createService('GraphiteDatasource'));
|
||||||
beforeEach(function() {
|
beforeEach(function() {
|
||||||
ctx.ds = new ctx.service({ url: [''] });
|
ctx.ds = new ctx.service({ url: [''] });
|
||||||
@ -21,25 +22,25 @@ define([
|
|||||||
maxDataPoints: 500,
|
maxDataPoints: 500,
|
||||||
};
|
};
|
||||||
|
|
||||||
var response = [{ target: 'prod1.count', datapoints: [[10, 1], [12,1]], }];
|
|
||||||
var results;
|
var results;
|
||||||
var request;
|
var requestOptions;
|
||||||
|
|
||||||
beforeEach(function() {
|
beforeEach(function() {
|
||||||
|
ctx.backendSrv.datasourceRequest = function(options) {
|
||||||
ctx.$httpBackend.expectPOST('/render', function(body) { request = body; return true; })
|
requestOptions = options;
|
||||||
.respond(response);
|
return ctx.$q.when({data: [{ target: 'prod1.count', datapoints: [[10, 1], [12,1]] }]});
|
||||||
|
};
|
||||||
|
|
||||||
ctx.ds.query(query).then(function(data) { results = data; });
|
ctx.ds.query(query).then(function(data) { results = data; });
|
||||||
ctx.$httpBackend.flush();
|
ctx.$rootScope.$apply();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should generate the correct query', function() {
|
it('should generate the correct query', function() {
|
||||||
ctx.$httpBackend.verifyNoOutstandingExpectation();
|
expect(requestOptions.url).to.be('/render');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should query correctly', function() {
|
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=prod1.count');
|
||||||
expect(params).to.contain('target=prod2.count');
|
expect(params).to.contain('target=prod2.count');
|
||||||
expect(params).to.contain('from=-1h');
|
expect(params).to.contain('from=-1h');
|
||||||
@ -47,7 +48,7 @@ define([
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should exclude undefined params', function() {
|
it('should exclude undefined params', function() {
|
||||||
var params = request.split('&');
|
var params = requestOptions.data.split('&');
|
||||||
expect(params).to.not.contain('cacheTimeout=undefined');
|
expect(params).to.not.contain('cacheTimeout=undefined');
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -56,6 +57,10 @@ define([
|
|||||||
expect(results.data[0].target).to.be('prod1.count');
|
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() {
|
describe('building graphite params', function() {
|
||||||
|
@ -68,6 +68,7 @@ define([
|
|||||||
self.templateSrv = new TemplateSrvStub();
|
self.templateSrv = new TemplateSrvStub();
|
||||||
self.timeSrv = new TimeSrvStub();
|
self.timeSrv = new TimeSrvStub();
|
||||||
self.datasourceSrv = {};
|
self.datasourceSrv = {};
|
||||||
|
self.backendSrv = {};
|
||||||
self.$routeParams = {};
|
self.$routeParams = {};
|
||||||
|
|
||||||
this.providePhase = function(mocks) {
|
this.providePhase = function(mocks) {
|
||||||
|
Reference in New Issue
Block a user