diff --git a/conf/defaults.ini b/conf/defaults.ini index eb8debc0094..01d3f378aeb 100644 --- a/conf/defaults.ini +++ b/conf/defaults.ini @@ -344,6 +344,7 @@ header_property = username auto_sign_up = true ldap_sync_ttl = 60 whitelist = +headers = #################################### Auth LDAP ########################### [auth.ldap] diff --git a/conf/sample.ini b/conf/sample.ini index e6a03718d19..c12ccbea8ec 100644 --- a/conf/sample.ini +++ b/conf/sample.ini @@ -294,6 +294,7 @@ log_queries = ;auto_sign_up = true ;ldap_sync_ttl = 60 ;whitelist = 192.168.1.1, 192.168.2.1 +;headers = Email:X-User-Email, Name:X-User-Name #################################### Basic Auth ########################## [auth.basic] diff --git a/package.json b/package.json index ac1e2a9282d..ee5f2d8b190 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "company": "Grafana Labs" }, "name": "grafana", - "version": "5.3.3", + "version": "5.3.4", "repository": { "type": "git", "url": "http://github.com/grafana/grafana.git" diff --git a/pkg/api/pluginproxy/ds_proxy.go b/pkg/api/pluginproxy/ds_proxy.go index 0c000058e4b..38a2fd187e3 100644 --- a/pkg/api/pluginproxy/ds_proxy.go +++ b/pkg/api/pluginproxy/ds_proxy.go @@ -195,6 +195,10 @@ func (proxy *DataSourceProxy) getDirector() func(req *http.Request) { req.Header.Del("X-Forwarded-Proto") req.Header.Set("User-Agent", fmt.Sprintf("Grafana/%s", setting.BuildVersion)) + // Clear Origin and Referer to avoir CORS issues + req.Header.Del("Origin") + req.Header.Del("Referer") + // set X-Forwarded-For header if req.RemoteAddr != "" { remoteAddr, _, err := net.SplitHostPort(req.RemoteAddr) diff --git a/pkg/api/pluginproxy/ds_proxy_test.go b/pkg/api/pluginproxy/ds_proxy_test.go index 7dcd187c368..c9be169565f 100644 --- a/pkg/api/pluginproxy/ds_proxy_test.go +++ b/pkg/api/pluginproxy/ds_proxy_test.go @@ -362,6 +362,32 @@ func TestDSRouteRule(t *testing.T) { }) }) + Convey("When proxying a custom datasource", func() { + plugin := &plugins.DataSourcePlugin{} + ds := &m.DataSource{ + Type: "custom-datasource", + Url: "http://host/root/", + } + ctx := &m.ReqContext{} + proxy := NewDataSourceProxy(ds, plugin, ctx, "/path/to/folder/") + req, err := http.NewRequest(http.MethodGet, "http://grafana.com/sub", nil) + req.Header.Add("Origin", "grafana.com") + req.Header.Add("Referer", "grafana.com") + req.Header.Add("X-Canary", "stillthere") + So(err, ShouldBeNil) + + proxy.getDirector()(req) + + Convey("Should keep user request (including trailing slash)", func() { + So(req.URL.String(), ShouldEqual, "http://host/root/path/to/folder/") + }) + + Convey("Origin and Referer headers should be dropped", func() { + So(req.Header.Get("Origin"), ShouldEqual, "") + So(req.Header.Get("Referer"), ShouldEqual, "") + So(req.Header.Get("X-Canary"), ShouldEqual, "stillthere") + }) + }) }) } diff --git a/public/app/features/dashboard/export/export_modal.html b/public/app/features/dashboard/export/export_modal.html index 0598c612fd6..3505e50b821 100644 --- a/public/app/features/dashboard/export/export_modal.html +++ b/public/app/features/dashboard/export/export_modal.html @@ -15,11 +15,19 @@ You can share dashboards on Grafana.com

+ + +
- - Cancel diff --git a/public/app/features/dashboard/export/export_modal.ts b/public/app/features/dashboard/export/export_modal.ts index f99946915d6..0e48041ca87 100644 --- a/public/app/features/dashboard/export/export_modal.ts +++ b/public/app/features/dashboard/export/export_modal.ts @@ -8,27 +8,47 @@ export class DashExportCtrl { dash: any; exporter: DashboardExporter; dismiss: () => void; + shareExternally: boolean; /** @ngInject */ constructor(private dashboardSrv, datasourceSrv, private $scope, private $rootScope) { this.exporter = new DashboardExporter(datasourceSrv); - this.exporter.makeExportable(this.dashboardSrv.getCurrent()).then(dash => { - this.$scope.$apply(() => { - this.dash = dash; - }); - }); + this.dash = this.dashboardSrv.getCurrent(); } - save() { - const blob = new Blob([angular.toJson(this.dash, true)], { + saveDashboardAsFile() { + if (this.shareExternally) { + this.exporter.makeExportable(this.dash).then((dashboardJson: any) => { + this.$scope.$apply(() => { + this.openSaveAsDialog(dashboardJson); + }); + }); + } else { + this.openSaveAsDialog(this.dash.getSaveModelClone()); + } + } + + viewJson() { + if (this.shareExternally) { + this.exporter.makeExportable(this.dash).then((dashboardJson: any) => { + this.$scope.$apply(() => { + this.openJsonModal(dashboardJson); + }); + }); + } else { + this.openJsonModal(this.dash.getSaveModelClone()); + } + } + + private openSaveAsDialog(dash: any) { + const blob = new Blob([angular.toJson(dash, true)], { type: 'application/json;charset=utf-8', }); - saveAs(blob, this.dash.title + '-' + new Date().getTime() + '.json'); + saveAs(blob, dash.title + '-' + new Date().getTime() + '.json'); } - saveJson() { - const clone = this.dash; + private openJsonModal(clone: any) { const editScope = this.$rootScope.$new(); editScope.object = clone; editScope.enableCopy = true; diff --git a/public/app/features/dashboard/export/exporter.ts b/public/app/features/dashboard/export/exporter.ts index d0802be72db..7aecb5c384f 100644 --- a/public/app/features/dashboard/export/exporter.ts +++ b/public/app/features/dashboard/export/exporter.ts @@ -29,19 +29,36 @@ export class DashboardExporter { } const templateizeDatasourceUsage = obj => { + let datasource = obj.datasource; + let datasourceVariable = null; + // ignore data source properties that contain a variable - if (obj.datasource && obj.datasource.indexOf('$') === 0) { - if (variableLookup[obj.datasource.substring(1)]) { - return; + if (datasource && datasource.indexOf('$') === 0) { + datasourceVariable = variableLookup[datasource.substring(1)]; + if (datasourceVariable && datasourceVariable.current) { + datasource = datasourceVariable.current.value; } } promises.push( - this.datasourceSrv.get(obj.datasource).then(ds => { + this.datasourceSrv.get(datasource).then(ds => { if (ds.meta.builtIn) { return; } + // add data source type to require list + requires['datasource' + ds.meta.id] = { + type: 'datasource', + id: ds.meta.id, + name: ds.meta.name, + version: ds.meta.info.version || '1.0.0', + }; + + // if used via variable we can skip templatizing usage + if (datasourceVariable) { + return; + } + const refName = 'DS_' + ds.name.replace(' ', '_').toUpperCase(); datasources[refName] = { name: refName, @@ -51,14 +68,8 @@ export class DashboardExporter { pluginId: ds.meta.id, pluginName: ds.meta.name, }; - obj.datasource = '${' + refName + '}'; - requires['datasource' + ds.meta.id] = { - type: 'datasource', - id: ds.meta.id, - name: ds.meta.name, - version: ds.meta.info.version || '1.0.0', - }; + obj.datasource = '${' + refName + '}'; }) ); }; diff --git a/public/app/features/dashboard/specs/exporter.test.ts b/public/app/features/dashboard/specs/exporter.test.ts index c7a232f925b..130b8ddbaea 100644 --- a/public/app/features/dashboard/specs/exporter.test.ts +++ b/public/app/features/dashboard/specs/exporter.test.ts @@ -32,8 +32,8 @@ describe('given dashboard with repeated panels', () => { { name: 'ds', type: 'datasource', - query: 'testdb', - current: { value: 'prod', text: 'prod' }, + query: 'other2', + current: { value: 'other2', text: 'other2' }, options: [], }, ], @@ -205,6 +205,11 @@ describe('given dashboard with repeated panels', () => { expect(variable.options[0].text).toBe('${VAR_PREFIX}'); expect(variable.options[0].value).toBe('${VAR_PREFIX}'); }); + + it('should add datasources only use via datasource variable to requires', () => { + const require = _.find(exported.__requires, { name: 'OtherDB_2' }); + expect(require.id).toBe('other2'); + }); }); // Stub responses @@ -219,6 +224,11 @@ stubs['other'] = { meta: { id: 'other', info: { version: '1.2.1' }, name: 'OtherDB' }, }; +stubs['other2'] = { + name: 'other2', + meta: { id: 'other2', info: { version: '1.2.1' }, name: 'OtherDB_2' }, +}; + stubs['-- Mixed --'] = { name: 'mixed', meta: { diff --git a/public/app/features/templating/template_srv.ts b/public/app/features/templating/template_srv.ts index 61326ad63ec..ef8139d8257 100644 --- a/public/app/features/templating/template_srv.ts +++ b/public/app/features/templating/template_srv.ts @@ -28,7 +28,7 @@ export class TemplateSrv { const existsOrEmpty = value => value || value === ''; this.index = this.variables.reduce((acc, currentValue) => { - if (currentValue.current && !currentValue.current.isNone && existsOrEmpty(currentValue.current.value)) { + if (currentValue.current && (currentValue.current.isNone || existsOrEmpty(currentValue.current.value))) { acc[currentValue.name] = currentValue; } return acc;