- {thresholds.map((threshold, index) => {
- return (
-
-
this.onAddThreshold(threshold.index + 1)}>
-
-
-
-
{this.renderInput(threshold)}
+
+ {theme => {
+ return (
+
+
+ {thresholds.map((threshold, index) => {
+ return (
+
+
this.onAddThreshold(threshold.index + 1)}
+ >
+
+
+
+
{this.renderInput(threshold)}
+
+ );
+ })}
- );
- })}
-
-
+
+ );
+ }}
+
);
}
}
diff --git a/packages/grafana-ui/src/components/index.ts b/packages/grafana-ui/src/components/index.ts
index 5cd677761b0..dc435a8844d 100644
--- a/packages/grafana-ui/src/components/index.ts
+++ b/packages/grafana-ui/src/components/index.ts
@@ -14,8 +14,8 @@ export { FormLabel } from './FormLabel/FormLabel';
export { FormField } from './FormField/FormField';
export { LoadingPlaceholder } from './LoadingPlaceholder/LoadingPlaceholder';
-export { ColorPicker, SeriesColorPicker } from './ColorPicker/ColorPicker';
-export { SeriesColorPickerPopover } from './ColorPicker/SeriesColorPickerPopover';
+export { ColorPicker, SeriesColorPicker } from './ColorPicker/ColorPicker';
+export { SeriesColorPickerPopover, SeriesColorPickerPopoverWithTheme } from './ColorPicker/SeriesColorPickerPopover';
export { ThresholdsEditor } from './ThresholdsEditor/ThresholdsEditor';
export { Graph } from './Graph/Graph';
export { PanelOptionsGroup } from './PanelOptionsGroup/PanelOptionsGroup';
diff --git a/packages/grafana-ui/src/utils/namedColorsPalette.test.ts b/packages/grafana-ui/src/utils/namedColorsPalette.test.ts
index c6a1aaf0dd0..f875b9966f1 100644
--- a/packages/grafana-ui/src/utils/namedColorsPalette.test.ts
+++ b/packages/grafana-ui/src/utils/namedColorsPalette.test.ts
@@ -5,20 +5,20 @@ import {
getColorFromHexRgbOrName,
getColorDefinitionByName,
} from './namedColorsPalette';
-import { GrafanaTheme } from '../types/index';
+import { GrafanaThemeType } from '../types/index';
describe('colors', () => {
const SemiDarkBlue = getColorDefinitionByName('semi-dark-blue');
describe('getColorDefinition', () => {
it('returns undefined for unknown hex', () => {
- expect(getColorDefinition('#ff0000', GrafanaTheme.Light)).toBeUndefined();
- expect(getColorDefinition('#ff0000', GrafanaTheme.Dark)).toBeUndefined();
+ expect(getColorDefinition('#ff0000', GrafanaThemeType.Light)).toBeUndefined();
+ expect(getColorDefinition('#ff0000', GrafanaThemeType.Dark)).toBeUndefined();
});
it('returns definition for known hex', () => {
- expect(getColorDefinition(SemiDarkBlue.variants.light, GrafanaTheme.Light)).toEqual(SemiDarkBlue);
- expect(getColorDefinition(SemiDarkBlue.variants.dark, GrafanaTheme.Dark)).toEqual(SemiDarkBlue);
+ expect(getColorDefinition(SemiDarkBlue.variants.light, GrafanaThemeType.Light)).toEqual(SemiDarkBlue);
+ expect(getColorDefinition(SemiDarkBlue.variants.dark, GrafanaThemeType.Dark)).toEqual(SemiDarkBlue);
});
});
@@ -28,8 +28,8 @@ describe('colors', () => {
});
it('returns name for known hex', () => {
- expect(getColorName(SemiDarkBlue.variants.light, GrafanaTheme.Light)).toEqual(SemiDarkBlue.name);
- expect(getColorName(SemiDarkBlue.variants.dark, GrafanaTheme.Dark)).toEqual(SemiDarkBlue.name);
+ expect(getColorName(SemiDarkBlue.variants.light, GrafanaThemeType.Light)).toEqual(SemiDarkBlue.name);
+ expect(getColorName(SemiDarkBlue.variants.dark, GrafanaThemeType.Dark)).toEqual(SemiDarkBlue.name);
});
});
@@ -53,7 +53,7 @@ describe('colors', () => {
});
it("returns correct variant's hex for known color if theme specified", () => {
- expect(getColorFromHexRgbOrName(SemiDarkBlue.name, GrafanaTheme.Light)).toBe(SemiDarkBlue.variants.light);
+ expect(getColorFromHexRgbOrName(SemiDarkBlue.name, GrafanaThemeType.Light)).toBe(SemiDarkBlue.variants.light);
});
it('returns color if specified as hex or rgb/a', () => {
diff --git a/packages/grafana-ui/src/utils/namedColorsPalette.ts b/packages/grafana-ui/src/utils/namedColorsPalette.ts
index 5312b27ad26..a99a93f4207 100644
--- a/packages/grafana-ui/src/utils/namedColorsPalette.ts
+++ b/packages/grafana-ui/src/utils/namedColorsPalette.ts
@@ -1,5 +1,5 @@
import { flatten } from 'lodash';
-import { GrafanaTheme } from '../types';
+import { GrafanaThemeType } from '../types';
type Hue = 'green' | 'yellow' | 'red' | 'blue' | 'orange' | 'purple';
@@ -68,7 +68,7 @@ export const getColorDefinitionByName = (name: Color): ColorDefinition => {
return flatten(Array.from(getNamedColorPalette().values())).filter(definition => definition.name === name)[0];
};
-export const getColorDefinition = (hex: string, theme: GrafanaTheme): ColorDefinition | undefined => {
+export const getColorDefinition = (hex: string, theme: GrafanaThemeType): ColorDefinition | undefined => {
return flatten(Array.from(getNamedColorPalette().values())).filter(definition => definition.variants[theme] === hex)[0];
};
@@ -77,7 +77,7 @@ const isHex = (color: string) => {
return hexRegex.test(color);
};
-export const getColorName = (color?: string, theme?: GrafanaTheme): Color | undefined => {
+export const getColorName = (color?: string, theme?: GrafanaThemeType): Color | undefined => {
if (!color) {
return undefined;
}
@@ -86,7 +86,7 @@ export const getColorName = (color?: string, theme?: GrafanaTheme): Color | unde
return undefined;
}
if (isHex(color)) {
- const definition = getColorDefinition(color, theme || GrafanaTheme.Dark);
+ const definition = getColorDefinition(color, theme || GrafanaThemeType.Dark);
return definition ? definition.name : undefined;
}
@@ -98,7 +98,7 @@ export const getColorByName = (colorName: string) => {
return definition.length > 0 ? definition[0] : undefined;
};
-export const getColorFromHexRgbOrName = (color: string, theme?: GrafanaTheme): string => {
+export const getColorFromHexRgbOrName = (color: string, theme?: GrafanaThemeType): string => {
if (color.indexOf('rgb') > -1 || isHex(color)) {
return color;
}
@@ -112,14 +112,14 @@ export const getColorFromHexRgbOrName = (color: string, theme?: GrafanaTheme): s
return theme ? colorDefinition.variants[theme] : colorDefinition.variants.dark;
};
-export const getColorForTheme = (color: ColorDefinition, theme?: GrafanaTheme) => {
+export const getColorForTheme = (color: ColorDefinition, theme?: GrafanaThemeType) => {
return theme ? color.variants[theme] : color.variants.dark;
};
const buildNamedColorsPalette = () => {
const palette = new Map
();
- const BasicGreen = buildColorDefinition('green', 'green', ['#56A64B', '#73BF69'], true);
+ const BasicGreen = buildColorDefinition('green', 'green', ['#56A64B', '#73BF69'], true);
const DarkGreen = buildColorDefinition('green', 'dark-green', ['#19730E', '#37872D']);
const SemiDarkGreen = buildColorDefinition('green', 'semi-dark-green', ['#37872D', '#56A64B']);
const LightGreen = buildColorDefinition('green', 'light-green', ['#73BF69', '#96D98D']);
diff --git a/public/app/core/angular_wrappers.ts b/public/app/core/angular_wrappers.ts
index 4806275e87d..6db442e7470 100644
--- a/public/app/core/angular_wrappers.ts
+++ b/public/app/core/angular_wrappers.ts
@@ -9,7 +9,7 @@ import { TagFilter } from './components/TagFilter/TagFilter';
import { SideMenu } from './components/sidemenu/SideMenu';
import { MetricSelect } from './components/Select/MetricSelect';
import AppNotificationList from './components/AppNotifications/AppNotificationList';
-import { ColorPicker, SeriesColorPickerPopover } from '@grafana/ui';
+import { ColorPicker, SeriesColorPickerPopoverWithTheme } from '@grafana/ui';
export function registerAngularDirectives() {
react2AngularDirective('passwordStrength', PasswordStrength, ['password']);
@@ -27,7 +27,7 @@ export function registerAngularDirectives() {
'color',
['onChange', { watchDepth: 'reference', wrapApply: true }],
]);
- react2AngularDirective('seriesColorPickerPopover', SeriesColorPickerPopover, [
+ react2AngularDirective('seriesColorPickerPopover', SeriesColorPickerPopoverWithTheme, [
'color',
'series',
'onColorChange',
diff --git a/public/app/core/utils/ConfigProvider.tsx b/public/app/core/utils/ConfigProvider.tsx
index 6883401ad27..56b6fc3d8b9 100644
--- a/public/app/core/utils/ConfigProvider.tsx
+++ b/public/app/core/utils/ConfigProvider.tsx
@@ -1,6 +1,6 @@
import React from 'react';
import config, { Settings } from 'app/core/config';
-import { GrafanaTheme } from '@grafana/ui';
+import { GrafanaThemeType, ThemeContext, getTheme } from '@grafana/ui';
export const ConfigContext = React.createContext(config);
export const ConfigConsumer = ConfigContext.Consumer;
@@ -13,16 +13,21 @@ export const provideConfig = (component: React.ComponentType) => {
return ConfigProvider;
};
-interface ThemeProviderProps {
- children: (theme: GrafanaTheme) => JSX.Element;
-}
+export const getCurrentThemeName = () =>
+ config.bootData.user.lightTheme ? GrafanaThemeType.Light : GrafanaThemeType.Dark;
+export const getCurrentTheme = () => getTheme(getCurrentThemeName());
-export const ThemeProvider = ({ children }: ThemeProviderProps) => {
+export const ThemeProvider = ({ children }: { children: React.ReactNode }) => {
return (
- {({ bootData }) => {
- return children(bootData.user.lightTheme ? GrafanaTheme.Light : GrafanaTheme.Dark);
+ {config => {
+ const currentTheme = getCurrentThemeName();
+ return {children} ;
}}
);
};
+
+export const provideTheme = (component: React.ComponentType) => {
+ return provideConfig((props: any) => {React.createElement(component, { ...props })} );
+};
diff --git a/public/app/core/utils/react2angular.ts b/public/app/core/utils/react2angular.ts
index 1057f68fcda..eb4bccab267 100644
--- a/public/app/core/utils/react2angular.ts
+++ b/public/app/core/utils/react2angular.ts
@@ -1,11 +1,11 @@
import coreModule from 'app/core/core_module';
-import { provideConfig } from 'app/core/utils/ConfigProvider';
+import { provideTheme } from 'app/core/utils/ConfigProvider';
export function react2AngularDirective(name: string, component: any, options: any) {
coreModule.directive(name, [
'reactDirective',
reactDirective => {
- return reactDirective(provideConfig(component), options);
+ return reactDirective(provideTheme(component), options);
},
]);
}
diff --git a/public/app/plugins/panel/gauge/GaugePanel.tsx b/public/app/plugins/panel/gauge/GaugePanel.tsx
index b6f37dde94f..5cb256ee1aa 100644
--- a/public/app/plugins/panel/gauge/GaugePanel.tsx
+++ b/public/app/plugins/panel/gauge/GaugePanel.tsx
@@ -2,7 +2,7 @@
import React, { PureComponent } from 'react';
// Services & Utils
-import { processTimeSeries } from '@grafana/ui';
+import { processTimeSeries, ThemeContext } from '@grafana/ui';
// Components
import { Gauge } from '@grafana/ui';
@@ -10,7 +10,6 @@ import { Gauge } from '@grafana/ui';
// Types
import { GaugeOptions } from './types';
import { PanelProps, NullValueMode, TimeSeriesValue } from '@grafana/ui/src/types';
-import { ThemeProvider } from 'app/core/utils/ConfigProvider';
interface Props extends PanelProps {}
@@ -38,7 +37,7 @@ export class GaugePanel extends PureComponent {
}
return (
-
+
{theme => (
{
theme={theme}
/>
)}
-
+
);
}
}
diff --git a/public/app/plugins/panel/gauge/GaugePanelOptions.tsx b/public/app/plugins/panel/gauge/GaugePanelOptions.tsx
index 655c596ce84..84726ac88bf 100644
--- a/public/app/plugins/panel/gauge/GaugePanelOptions.tsx
+++ b/public/app/plugins/panel/gauge/GaugePanelOptions.tsx
@@ -11,7 +11,6 @@ import {
import ValueOptions from 'app/plugins/panel/gauge/ValueOptions';
import GaugeOptionsEditor from './GaugeOptionsEditor';
import { GaugeOptions } from './types';
-import { ThemeProvider } from 'app/core/utils/ConfigProvider';
export const defaultProps = {
options: {
@@ -46,24 +45,17 @@ export default class GaugePanelOptions extends PureComponent
- {(theme) => (
- <>
-
-
-
-
-
-
- >
- )}
-
+ return (
+ <>
+
+
+
+
+
+
+
+ >
);
}
}
diff --git a/public/app/plugins/panel/graph/Legend/LegendSeriesItem.tsx b/public/app/plugins/panel/graph/Legend/LegendSeriesItem.tsx
index d62613319b2..e3de5b067ba 100644
--- a/public/app/plugins/panel/graph/Legend/LegendSeriesItem.tsx
+++ b/public/app/plugins/panel/graph/Legend/LegendSeriesItem.tsx
@@ -2,7 +2,7 @@ import React, { PureComponent } from 'react';
import classNames from 'classnames';
import { TimeSeries } from 'app/core/core';
import { SeriesColorPicker } from '@grafana/ui';
-import { ThemeProvider } from 'app/core/utils/ConfigProvider';
+// import { ThemeProvider } from 'app/core/utils/ConfigProvider';
export const LEGEND_STATS = ['min', 'max', 'avg', 'current', 'total'];
@@ -168,24 +168,17 @@ class LegendSeriesIcon extends PureComponent
- {theme => {
- return (
-
-
-
-
-
- );
- }}
-
+
+
+
+
+
);
}
}
diff --git a/public/app/plugins/panel/graph/data_processor.ts b/public/app/plugins/panel/graph/data_processor.ts
index 4141d36e273..0d4445e1981 100644
--- a/public/app/plugins/panel/graph/data_processor.ts
+++ b/public/app/plugins/panel/graph/data_processor.ts
@@ -1,5 +1,5 @@
import _ from 'lodash';
-import { colors, GrafanaTheme, getColorFromHexRgbOrName } from '@grafana/ui';
+import { colors, GrafanaThemeType, getColorFromHexRgbOrName } from '@grafana/ui';
import TimeSeries from 'app/core/time_series2';
import config from 'app/core/config';
@@ -113,7 +113,7 @@ export class DataProcessor {
const series = new TimeSeries({
datapoints: datapoints,
alias: alias,
- color: getColorFromHexRgbOrName(color, config.bootData.user.lightTheme ? GrafanaTheme.Light : GrafanaTheme.Dark),
+ color: getColorFromHexRgbOrName(color, config.bootData.user.lightTheme ? GrafanaThemeType.Light : GrafanaThemeType.Dark),
unit: seriesData.unit,
});
diff --git a/public/app/plugins/panel/graph/graph.ts b/public/app/plugins/panel/graph/graph.ts
index aeb540551b8..3800e147d9d 100755
--- a/public/app/plugins/panel/graph/graph.ts
+++ b/public/app/plugins/panel/graph/graph.ts
@@ -25,7 +25,8 @@ import ReactDOM from 'react-dom';
import { Legend, GraphLegendProps } from './Legend/Legend';
import { GraphCtrl } from './module';
-import { GrafanaTheme, getValueFormat } from '@grafana/ui';
+import { GrafanaThemeType, getValueFormat } from '@grafana/ui';
+import { provideTheme } from 'app/core/utils/ConfigProvider';
class GraphElement {
ctrl: GraphCtrl;
@@ -53,7 +54,7 @@ class GraphElement {
this.thresholdManager = new ThresholdManager(this.ctrl);
this.timeRegionManager = new TimeRegionManager(
this.ctrl,
- config.bootData.user.lightTheme ? GrafanaTheme.Light : GrafanaTheme.Dark
+ config.bootData.user.lightTheme ? GrafanaThemeType.Light : GrafanaThemeType.Dark
);
this.tooltip = new GraphTooltip(this.elem, this.ctrl.dashboard, this.scope, () => {
return this.sortedSeries;
@@ -109,7 +110,7 @@ class GraphElement {
onToggleAxis: this.ctrl.onToggleAxis,
};
- const legendReactElem = React.createElement(Legend, legendProps);
+ const legendReactElem = React.createElement(provideTheme(Legend), legendProps);
ReactDOM.render(legendReactElem, this.legendElem, () => this.renderPanel());
}
diff --git a/public/app/plugins/panel/graph/module.ts b/public/app/plugins/panel/graph/module.ts
index 68d982eab13..cb1c0d98269 100644
--- a/public/app/plugins/panel/graph/module.ts
+++ b/public/app/plugins/panel/graph/module.ts
@@ -10,7 +10,7 @@ import { MetricsPanelCtrl } from 'app/plugins/sdk';
import { DataProcessor } from './data_processor';
import { axesEditorComponent } from './axes_editor';
import config from 'app/core/config';
-import { GrafanaTheme, getColorFromHexRgbOrName } from '@grafana/ui';
+import { GrafanaThemeType, getColorFromHexRgbOrName } from '@grafana/ui';
class GraphCtrl extends MetricsPanelCtrl {
static template = template;
@@ -244,7 +244,7 @@ class GraphCtrl extends MetricsPanelCtrl {
}
onColorChange = (series, color) => {
- series.setColor(getColorFromHexRgbOrName(color, config.bootData.user.lightTheme ? GrafanaTheme.Light : GrafanaTheme.Dark));
+ series.setColor(getColorFromHexRgbOrName(color, config.bootData.user.lightTheme ? GrafanaThemeType.Light : GrafanaThemeType.Dark));
this.panel.aliasColors[series.alias] = color;
this.render();
};
diff --git a/public/app/plugins/panel/graph/time_region_manager.ts b/public/app/plugins/panel/graph/time_region_manager.ts
index 2917583ff36..ea39927bf57 100644
--- a/public/app/plugins/panel/graph/time_region_manager.ts
+++ b/public/app/plugins/panel/graph/time_region_manager.ts
@@ -1,7 +1,7 @@
import 'vendor/flot/jquery.flot';
import _ from 'lodash';
import moment from 'moment';
-import { GrafanaTheme, getColorFromHexRgbOrName } from '@grafana/ui';
+import { GrafanaThemeType, getColorFromHexRgbOrName } from '@grafana/ui';
type TimeRegionColorDefinition = {
fill: string;
@@ -43,7 +43,7 @@ export function getColorModes() {
});
}
-function getColor(timeRegion, theme: GrafanaTheme): TimeRegionColorDefinition {
+function getColor(timeRegion, theme: GrafanaThemeType): TimeRegionColorDefinition {
if (Object.keys(colorModes).indexOf(timeRegion.colorMode) === -1) {
timeRegion.colorMode = 'red';
}
@@ -58,7 +58,7 @@ function getColor(timeRegion, theme: GrafanaTheme): TimeRegionColorDefinition {
const colorMode = colorModes[timeRegion.colorMode];
if (colorMode.themeDependent === true) {
- return theme === GrafanaTheme.Light ? colorMode.lightColor : colorMode.darkColor;
+ return theme === GrafanaThemeType.Light ? colorMode.lightColor : colorMode.darkColor;
}
return {
@@ -71,7 +71,7 @@ export class TimeRegionManager {
plot: any;
timeRegions: any;
- constructor(private panelCtrl, private theme: GrafanaTheme = GrafanaTheme.Dark) {}
+ constructor(private panelCtrl, private theme: GrafanaThemeType = GrafanaThemeType.Dark) {}
draw(plot) {
this.timeRegions = this.panelCtrl.panel.timeRegions;
diff --git a/public/app/plugins/panel/heatmap/color_legend.ts b/public/app/plugins/panel/heatmap/color_legend.ts
index 81329fe297b..dea250abf74 100644
--- a/public/app/plugins/panel/heatmap/color_legend.ts
+++ b/public/app/plugins/panel/heatmap/color_legend.ts
@@ -5,7 +5,7 @@ import { contextSrv } from 'app/core/core';
import { tickStep } from 'app/core/utils/ticks';
import { getColorScale, getOpacityScale } from './color_scale';
import coreModule from 'app/core/core_module';
-import { GrafanaTheme, getColorFromHexRgbOrName } from '@grafana/ui';
+import { GrafanaThemeType, getColorFromHexRgbOrName } from '@grafana/ui';
const LEGEND_HEIGHT_PX = 6;
const LEGEND_WIDTH_PX = 100;
@@ -250,7 +250,7 @@ function drawSimpleOpacityLegend(elem, options) {
.attr('stroke-width', 0)
.attr(
'fill',
- getColorFromHexRgbOrName(options.cardColor, contextSrv.user.lightTheme ? GrafanaTheme.Light : GrafanaTheme.Dark)
+ getColorFromHexRgbOrName(options.cardColor, contextSrv.user.lightTheme ? GrafanaThemeType.Light : GrafanaThemeType.Dark)
)
.style('opacity', d => legendOpacityScale(d));
}
diff --git a/public/app/plugins/panel/heatmap/rendering.ts b/public/app/plugins/panel/heatmap/rendering.ts
index 6489c9e9895..63604382432 100644
--- a/public/app/plugins/panel/heatmap/rendering.ts
+++ b/public/app/plugins/panel/heatmap/rendering.ts
@@ -7,7 +7,7 @@ import * as ticksUtils from 'app/core/utils/ticks';
import { HeatmapTooltip } from './heatmap_tooltip';
import { mergeZeroBuckets } from './heatmap_data_converter';
import { getColorScale, getOpacityScale } from './color_scale';
-import { GrafanaTheme, getColorFromHexRgbOrName, getValueFormat } from '@grafana/ui';
+import { GrafanaThemeType, getColorFromHexRgbOrName, getValueFormat } from '@grafana/ui';
const MIN_CARD_SIZE = 1,
CARD_PADDING = 1,
@@ -663,7 +663,7 @@ export class HeatmapRenderer {
if (this.panel.color.mode === 'opacity') {
return getColorFromHexRgbOrName(
this.panel.color.cardColor,
- contextSrv.user.lightTheme ? GrafanaTheme.Light : GrafanaTheme.Dark
+ contextSrv.user.lightTheme ? GrafanaThemeType.Light : GrafanaThemeType.Dark
);
} else {
return this.colorScale(d.count);
diff --git a/public/app/plugins/panel/singlestat/module.ts b/public/app/plugins/panel/singlestat/module.ts
index 2768951d2ba..4ea81ff8630 100644
--- a/public/app/plugins/panel/singlestat/module.ts
+++ b/public/app/plugins/panel/singlestat/module.ts
@@ -8,7 +8,7 @@ import kbn from 'app/core/utils/kbn';
import config from 'app/core/config';
import TimeSeries from 'app/core/time_series2';
import { MetricsPanelCtrl } from 'app/plugins/sdk';
-import { GrafanaTheme, getValueFormat, getColorFromHexRgbOrName } from '@grafana/ui';
+import { GrafanaThemeType, getValueFormat, getColorFromHexRgbOrName } from '@grafana/ui';
class SingleStatCtrl extends MetricsPanelCtrl {
static templateUrl = 'module.html';
@@ -590,7 +590,7 @@ class SingleStatCtrl extends MetricsPanelCtrl {
lineWidth: 1,
fillColor: getColorFromHexRgbOrName(
panel.sparkline.fillColor,
- config.bootData.user.lightTheme ? GrafanaTheme.Light : GrafanaTheme.Dark
+ config.bootData.user.lightTheme ? GrafanaThemeType.Light : GrafanaThemeType.Dark
),
},
},
@@ -610,7 +610,7 @@ class SingleStatCtrl extends MetricsPanelCtrl {
data: data.flotpairs,
color: getColorFromHexRgbOrName(
panel.sparkline.lineColor,
- config.bootData.user.lightTheme ? GrafanaTheme.Light : GrafanaTheme.Dark
+ config.bootData.user.lightTheme ? GrafanaThemeType.Light : GrafanaThemeType.Dark
),
};
@@ -630,7 +630,7 @@ class SingleStatCtrl extends MetricsPanelCtrl {
// Map panel colors to hex or rgb/a values
data.colorMap = panel.colors.map(color =>
- getColorFromHexRgbOrName(color, config.bootData.user.lightTheme ? GrafanaTheme.Light : GrafanaTheme.Dark)
+ getColorFromHexRgbOrName(color, config.bootData.user.lightTheme ? GrafanaThemeType.Light : GrafanaThemeType.Dark)
);
const body = panel.gauge.show ? '' : getBigValueHtml();
diff --git a/public/app/plugins/panel/table/module.ts b/public/app/plugins/panel/table/module.ts
index 82763e1839a..3d82dd4df68 100644
--- a/public/app/plugins/panel/table/module.ts
+++ b/public/app/plugins/panel/table/module.ts
@@ -6,7 +6,7 @@ import { transformDataToTable } from './transformers';
import { tablePanelEditor } from './editor';
import { columnOptionsTab } from './column_options';
import { TableRenderer } from './renderer';
-import { GrafanaTheme } from '@grafana/ui';
+import { GrafanaThemeType } from '@grafana/ui';
class TablePanelCtrl extends MetricsPanelCtrl {
static templateUrl = 'module.html';
@@ -131,7 +131,7 @@ class TablePanelCtrl extends MetricsPanelCtrl {
this.dashboard.isTimezoneUtc(),
this.$sanitize,
this.templateSrv,
- config.bootData.user.lightTheme ? GrafanaTheme.Light : GrafanaTheme.Dark,
+ config.bootData.user.lightTheme ? GrafanaThemeType.Light : GrafanaThemeType.Dark,
);
return super.render(this.table);
diff --git a/public/app/plugins/panel/table/renderer.ts b/public/app/plugins/panel/table/renderer.ts
index 90479a67602..e9bf89f45fe 100644
--- a/public/app/plugins/panel/table/renderer.ts
+++ b/public/app/plugins/panel/table/renderer.ts
@@ -1,7 +1,7 @@
import _ from 'lodash';
import moment from 'moment';
import kbn from 'app/core/utils/kbn';
-import { GrafanaTheme, getValueFormat, getColorFromHexRgbOrName } from '@grafana/ui';
+import { getValueFormat, getColorFromHexRgbOrName, GrafanaThemeType } from '@grafana/ui';
export class TableRenderer {
formatters: any[];
@@ -13,7 +13,7 @@ export class TableRenderer {
private isUtc,
private sanitize,
private templateSrv,
- private theme?: GrafanaTheme
+ private theme?: GrafanaThemeType
) {
this.initColumns();
}
diff --git a/public/app/routes/ReactContainer.tsx b/public/app/routes/ReactContainer.tsx
index 2cad3d828bf..985914eb067 100644
--- a/public/app/routes/ReactContainer.tsx
+++ b/public/app/routes/ReactContainer.tsx
@@ -5,6 +5,7 @@ import { Provider } from 'react-redux';
import coreModule from 'app/core/core_module';
import { store } from 'app/store/store';
import { ContextSrv } from 'app/core/services/context_srv';
+import { provideTheme } from 'app/core/utils/ConfigProvider';
function WrapInProvider(store, Component, props) {
return (
@@ -46,7 +47,7 @@ export function reactContainer(
$scope: scope,
};
- ReactDOM.render(WrapInProvider(store, component, props), elem[0]);
+ ReactDOM.render(WrapInProvider(store, provideTheme(component), props), elem[0]);
scope.$on('$destroy', () => {
ReactDOM.unmountComponentAtNode(elem[0]);
From 2196b4f1c243b4df0842e53201de238a6aa27035 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Torkel=20=C3=96degaard?=
Date: Tue, 5 Feb 2019 19:34:31 +0100
Subject: [PATCH 065/228] Delete template.html
---
.../components/DashNav/template.html | 59 -------------------
1 file changed, 59 deletions(-)
delete mode 100644 public/app/features/dashboard/components/DashNav/template.html
diff --git a/public/app/features/dashboard/components/DashNav/template.html b/public/app/features/dashboard/components/DashNav/template.html
deleted file mode 100644
index 7e53267cbfd..00000000000
--- a/public/app/features/dashboard/components/DashNav/template.html
+++ /dev/null
@@ -1,59 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
From 4d2cff41ff15fb801960f8a5a5cd17a7fc8f5136 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Torkel=20=C3=96degaard?=
Date: Tue, 5 Feb 2019 19:38:51 +0100
Subject: [PATCH 066/228] Minor code simplification
---
public/app/features/dashboard/containers/DashboardPage.tsx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/public/app/features/dashboard/containers/DashboardPage.tsx b/public/app/features/dashboard/containers/DashboardPage.tsx
index 1bd5218fd60..a620ac848b4 100644
--- a/public/app/features/dashboard/containers/DashboardPage.tsx
+++ b/public/app/features/dashboard/containers/DashboardPage.tsx
@@ -247,7 +247,7 @@ export class DashboardPage extends PureComponent {
/>
- {dashboard && editview && }
+ {editview && }
{dashboard.meta.submenuEnabled &&
}
From 1d1b617cee3cfedeb03586e0db00f5219d187761 Mon Sep 17 00:00:00 2001
From: Marcus Efraimsson
Date: Tue, 5 Feb 2019 21:08:55 +0100
Subject: [PATCH 067/228] remove unused code
---
pkg/api/common_test.go | 87 ++++--------------------------------------
1 file changed, 8 insertions(+), 79 deletions(-)
diff --git a/pkg/api/common_test.go b/pkg/api/common_test.go
index 853a04b5c11..3f3a50aae69 100644
--- a/pkg/api/common_test.go
+++ b/pkg/api/common_test.go
@@ -8,7 +8,6 @@ import (
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/middleware"
m "github.com/grafana/grafana/pkg/models"
- "github.com/grafana/grafana/pkg/services/auth"
"gopkg.in/macaron.v1"
. "github.com/smartystreets/goconvey/convey"
@@ -95,14 +94,13 @@ func (sc *scenarioContext) fakeReqWithParams(method, url string, queryParams map
}
type scenarioContext struct {
- m *macaron.Macaron
- context *m.ReqContext
- resp *httptest.ResponseRecorder
- handlerFunc handlerFunc
- defaultHandler macaron.Handler
- req *http.Request
- url string
- userAuthTokenService *fakeUserAuthTokenService
+ m *macaron.Macaron
+ context *m.ReqContext
+ resp *httptest.ResponseRecorder
+ handlerFunc handlerFunc
+ defaultHandler macaron.Handler
+ req *http.Request
+ url string
}
func (sc *scenarioContext) exec() {
@@ -124,76 +122,7 @@ func setupScenarioContext(url string) *scenarioContext {
Delims: macaron.Delims{Left: "[[", Right: "]]"},
}))
- sc.userAuthTokenService = newFakeUserAuthTokenService()
- sc.m.Use(middleware.GetContextHandler(sc.userAuthTokenService))
+ sc.m.Use(middleware.GetContextHandler(nil))
return sc
}
-
-type fakeUserToken interface {
- auth.UserToken
- SetToken(token string)
-}
-
-type userTokenImpl struct {
- userId int64
- token string
-}
-
-func (ut *userTokenImpl) GetUserId() int64 {
- return ut.userId
-}
-
-func (ut *userTokenImpl) GetToken() string {
- return ut.token
-}
-
-func (ut *userTokenImpl) SetToken(token string) {
- ut.token = token
-}
-
-type fakeUserAuthTokenService struct {
- createTokenProvider func(userId int64, clientIP, userAgent string) (auth.UserToken, error)
- tryRotateTokenProvider func(token auth.UserToken, clientIP, userAgent string) (bool, error)
- lookupTokenProvider func(unhashedToken string) (auth.UserToken, error)
- revokeTokenProvider func(token auth.UserToken) error
-}
-
-func newFakeUserAuthTokenService() *fakeUserAuthTokenService {
- return &fakeUserAuthTokenService{
- createTokenProvider: func(userId int64, clientIP, userAgent string) (auth.UserToken, error) {
- return &userTokenImpl{
- userId: 0,
- token: "",
- }, nil
- },
- tryRotateTokenProvider: func(token auth.UserToken, clientIP, userAgent string) (bool, error) {
- return false, nil
- },
- lookupTokenProvider: func(unhashedToken string) (auth.UserToken, error) {
- return &userTokenImpl{
- userId: 0,
- token: "",
- }, nil
- },
- revokeTokenProvider: func(token auth.UserToken) error {
- return nil
- },
- }
-}
-
-func (s *fakeUserAuthTokenService) CreateToken(userId int64, clientIP, userAgent string) (auth.UserToken, error) {
- return s.createTokenProvider(userId, clientIP, userAgent)
-}
-
-func (s *fakeUserAuthTokenService) LookupToken(unhashedToken string) (auth.UserToken, error) {
- return s.lookupTokenProvider(unhashedToken)
-}
-
-func (s *fakeUserAuthTokenService) TryRotateToken(token auth.UserToken, clientIP, userAgent string) (bool, error) {
- return s.tryRotateTokenProvider(token, clientIP, userAgent)
-}
-
-func (s *fakeUserAuthTokenService) RevokeToken(token auth.UserToken) error {
- return s.revokeTokenProvider(token)
-}
From 3c2fd02bc00ac631b1429533e898b848ed22dc47 Mon Sep 17 00:00:00 2001
From: Marcus Efraimsson
Date: Tue, 5 Feb 2019 21:09:55 +0100
Subject: [PATCH 068/228] refactor login/auth token configuration settings
remove login section and reuse existing sections security and auth
---
conf/defaults.ini | 41 +++++++++++----------
conf/sample.ini | 43 +++++++++++-----------
pkg/setting/setting.go | 82 ++++++++++++++++++++++++------------------
3 files changed, 89 insertions(+), 77 deletions(-)
diff --git a/conf/defaults.ini b/conf/defaults.ini
index d021d342fbf..c65cb93d426 100644
--- a/conf/defaults.ini
+++ b/conf/defaults.ini
@@ -106,25 +106,6 @@ path = grafana.db
# For "sqlite3" only. cache mode setting used for connecting to the database
cache_mode = private
-#################################### Login ###############################
-
-[login]
-
-# Login cookie name
-cookie_name = grafana_session
-
-# Login cookie same site setting. defaults to `lax`. can be set to "lax", "strict" and "none"
-cookie_samesite = lax
-
-# How many days an session can be unused before we inactivate it
-login_remember_days = 7
-
-# How often should the login token be rotated. default to '10m'
-rotate_token_minutes = 10
-
-# How long should Grafana keep expired tokens before deleting them
-delete_expired_token_after_days = 30
-
#################################### Session #############################
[session]
# Either "memory", "file", "redis", "mysql", "postgres", "memcache", default is "file"
@@ -206,8 +187,11 @@ data_source_proxy_whitelist =
# disable protection against brute force login attempts
disable_brute_force_login_protection = false
-# set cookies as https only. default is false
-https_flag_cookies = false
+# set to true if you host Grafana behind HTTPS. default is false.
+cookie_secure = false
+
+# set cookie SameSite attribute. defaults to `lax`. can be set to "lax", "strict" and "none"
+cookie_samesite = lax
#################################### Snapshots ###########################
[snapshots]
@@ -260,6 +244,21 @@ external_manage_info =
viewers_can_edit = false
[auth]
+# Login cookie name
+login_cookie_name = grafana_session
+
+# The lifetime (days) an authenticated user can be inactive before being required to login at next visit. Default is 7 days.
+login_maximum_inactive_lifetime_days = 7
+
+# The maximum lifetime (days) an autenticated user can be logged in since login time before being required to login. Default is 30 days.
+login_maximum_lifetime_days = 30
+
+# How often should auth tokens be rotated for authenticated users when being active. The default is each 10 minutes.
+token_rotation_interval_minutes = 10
+
+# How often should expired auth tokens be deleted from the database. The default is 7 days.
+expired_tokens_cleanup_interval_days = 7
+
# Set to true to disable (hide) the login form, useful if you use OAuth
disable_login_form = false
diff --git a/conf/sample.ini b/conf/sample.ini
index ef677320686..39feb31441e 100644
--- a/conf/sample.ini
+++ b/conf/sample.ini
@@ -102,25 +102,6 @@ log_queries =
# For "sqlite3" only. cache mode setting used for connecting to the database. (private, shared)
;cache_mode = private
-#################################### Login ###############################
-
-[login]
-
-# Login cookie name
-;cookie_name = grafana_session
-
-# Login cookie same site setting. defaults to `lax`. can be set to "lax", "strict" and "none"
-;cookie_samesite = lax
-
-# How many days an session can be unused before we inactivate it
-;login_remember_days = 7
-
-# How often should the login token be rotated. default to '10'
-;rotate_token_minutes = 10
-
-# How long should Grafana keep expired tokens before deleting them
-;delete_expired_token_after_days = 30
-
#################################### Session ####################################
[session]
# Either "memory", "file", "redis", "mysql", "postgres", default is "file"
@@ -193,8 +174,11 @@ log_queries =
# disable protection against brute force login attempts
;disable_brute_force_login_protection = false
-# set cookies as https only. default is false
-;https_flag_cookies = false
+# set to true if you host Grafana behind HTTPS. default is false.
+;cookie_secure = false
+
+# set cookie SameSite attribute. defaults to `lax`. can be set to "lax", "strict" and "none"
+;cookie_samesite = lax
#################################### Snapshots ###########################
[snapshots]
@@ -240,6 +224,21 @@ log_queries =
;viewers_can_edit = false
[auth]
+# Login cookie name
+;login_cookie_name = grafana_session
+
+# The lifetime (days) an authenticated user can be inactive before being required to login at next visit. Default is 7 days,
+;login_maximum_inactive_lifetime_days = 7
+
+# The maximum lifetime (days) an autenticated user can be logged in since login time before being required to login. Default is 30 days.
+;login_maximum_lifetime_days = 30
+
+# How often should auth tokens be rotated for authenticated users when being active. The default is each 10 minutes.
+;token_rotation_interval_minutes = 10
+
+# How often should expired auth tokens be deleted from the database. The default is 7 days.
+;expired_tokens_cleanup_interval_days = 7
+
# Set to true to disable (hide) the login form, useful if you use OAuth, defaults to false
;disable_login_form = false
@@ -253,7 +252,7 @@ log_queries =
# This setting is ignored if multiple OAuth providers are configured.
;oauth_auto_login = false
-#################################### Anonymous Auth ##########################
+#################################### Anonymous Auth ######################
[auth.anonymous]
# enable anonymous access
;enabled = false
diff --git a/pkg/setting/setting.go b/pkg/setting/setting.go
index c3c78d10fec..9f7d03bb472 100644
--- a/pkg/setting/setting.go
+++ b/pkg/setting/setting.go
@@ -89,6 +89,8 @@ var (
EmailCodeValidMinutes int
DataProxyWhiteList map[string]bool
DisableBruteForceLoginProtection bool
+ CookieSecure bool
+ CookieSameSite http.SameSite
// Snapshots
ExternalSnapshotUrl string
@@ -118,8 +120,10 @@ var (
ViewersCanEdit bool
// Http auth
- AdminUser string
- AdminPassword string
+ AdminUser string
+ AdminPassword string
+ LoginCookieName string
+ LoginMaxLifetimeDays int
AnonymousEnabled bool
AnonymousOrgName string
@@ -215,7 +219,11 @@ type Cfg struct {
RendererLimit int
RendererLimitAlerting int
+ // Security
DisableBruteForceLoginProtection bool
+ CookieSecure bool
+ CookieSameSite http.SameSite
+
TempDataLifetime time.Duration
MetricsEndpointEnabled bool
MetricsEndpointBasicAuthUsername string
@@ -224,13 +232,12 @@ type Cfg struct {
DisableSanitizeHtml bool
EnterpriseLicensePath string
- LoginCookieName string
- LoginCookieMaxDays int
- LoginCookieRotation int
- LoginDeleteExpiredTokensAfterDays int
- LoginCookieSameSite http.SameSite
-
- SecurityHTTPSCookies bool
+ // Auth
+ LoginCookieName string
+ LoginMaxInactiveLifetimeDays int
+ LoginMaxLifetimeDays int
+ TokenRotationIntervalMinutes int
+ ExpiredTokensCleanupIntervalDays int
}
type CommandLineArgs struct {
@@ -554,30 +561,6 @@ func (cfg *Cfg) Load(args *CommandLineArgs) error {
ApplicationName = APP_NAME_ENTERPRISE
}
- //login
- login := iniFile.Section("login")
- cfg.LoginCookieName = login.Key("cookie_name").MustString("grafana_session")
- cfg.LoginCookieMaxDays = login.Key("login_remember_days").MustInt(7)
- cfg.LoginDeleteExpiredTokensAfterDays = login.Key("delete_expired_token_after_days").MustInt(30)
-
- samesiteString := login.Key("cookie_samesite").MustString("lax")
- validSameSiteValues := map[string]http.SameSite{
- "lax": http.SameSiteLaxMode,
- "strict": http.SameSiteStrictMode,
- "none": http.SameSiteDefaultMode,
- }
-
- if samesite, ok := validSameSiteValues[samesiteString]; ok {
- cfg.LoginCookieSameSite = samesite
- } else {
- cfg.LoginCookieSameSite = http.SameSiteLaxMode
- }
-
- cfg.LoginCookieRotation = login.Key("rotate_token_minutes").MustInt(10)
- if cfg.LoginCookieRotation < 2 {
- cfg.LoginCookieRotation = 2
- }
-
Env = iniFile.Section("").Key("app_mode").MustString("development")
InstanceName = iniFile.Section("").Key("instance_name").MustString("unknown_instance_name")
PluginsPath = makeAbsolute(iniFile.Section("paths").Key("plugins").String(), HomePath)
@@ -621,9 +604,26 @@ func (cfg *Cfg) Load(args *CommandLineArgs) error {
SecretKey = security.Key("secret_key").String()
DisableGravatar = security.Key("disable_gravatar").MustBool(true)
cfg.DisableBruteForceLoginProtection = security.Key("disable_brute_force_login_protection").MustBool(false)
- cfg.SecurityHTTPSCookies = security.Key("https_flag_cookies").MustBool(false)
DisableBruteForceLoginProtection = cfg.DisableBruteForceLoginProtection
+ CookieSecure = security.Key("cookie_secure").MustBool(false)
+ cfg.CookieSecure = CookieSecure
+
+ samesiteString := security.Key("cookie_samesite").MustString("lax")
+ validSameSiteValues := map[string]http.SameSite{
+ "lax": http.SameSiteLaxMode,
+ "strict": http.SameSiteStrictMode,
+ "none": http.SameSiteDefaultMode,
+ }
+
+ if samesite, ok := validSameSiteValues[samesiteString]; ok {
+ CookieSameSite = samesite
+ cfg.CookieSameSite = CookieSameSite
+ } else {
+ CookieSameSite = http.SameSiteLaxMode
+ cfg.CookieSameSite = CookieSameSite
+ }
+
// read snapshots settings
snapshots := iniFile.Section("snapshots")
ExternalSnapshotUrl = snapshots.Key("external_snapshot_url").String()
@@ -661,6 +661,20 @@ func (cfg *Cfg) Load(args *CommandLineArgs) error {
// auth
auth := iniFile.Section("auth")
+
+ LoginCookieName = auth.Key("login_cookie_name").MustString("grafana_session")
+ cfg.LoginCookieName = LoginCookieName
+ cfg.LoginMaxInactiveLifetimeDays = auth.Key("login_maximum_inactive_lifetime_days").MustInt(7)
+
+ LoginMaxLifetimeDays = auth.Key("login_maximum_lifetime_days").MustInt(30)
+ cfg.LoginMaxLifetimeDays = LoginMaxLifetimeDays
+
+ cfg.TokenRotationIntervalMinutes = auth.Key("token_rotation_interval_minutes").MustInt(10)
+ if cfg.TokenRotationIntervalMinutes < 2 {
+ cfg.TokenRotationIntervalMinutes = 2
+ }
+ cfg.ExpiredTokensCleanupIntervalDays = auth.Key("expired_tokens_cleanup_interval_days").MustInt(7)
+
DisableLoginForm = auth.Key("disable_login_form").MustBool(false)
DisableSignoutMenu = auth.Key("disable_signout_menu").MustBool(false)
OAuthAutoLogin = auth.Key("oauth_auto_login").MustBool(false)
From 80d0943d9d1c0fb20ceb5236dad7ee672b6dc522 Mon Sep 17 00:00:00 2001
From: Marcus Efraimsson
Date: Tue, 5 Feb 2019 21:10:56 +0100
Subject: [PATCH 069/228] document login, short-lived tokens and secure cookie
configurations
---
docs/sources/auth/overview.md | 32 ++++++++++++++++++++++
docs/sources/installation/configuration.md | 8 ++++++
2 files changed, 40 insertions(+)
diff --git a/docs/sources/auth/overview.md b/docs/sources/auth/overview.md
index 0480ee88adc..e3d4c08ca5d 100644
--- a/docs/sources/auth/overview.md
+++ b/docs/sources/auth/overview.md
@@ -36,6 +36,38 @@ Grafana of course has a built in user authentication system with password authen
disable authentication by enabling anonymous access. You can also hide login form and only allow login through an auth
provider (listed above). There is also options for allowing self sign up.
+### Login and short-lived tokens
+
+> The followung applies when using Grafana's built in user authentication, LDAP (without Auth proxy) or OAuth integration.
+
+Grafana are using short-lived tokens as a mechanism for verifying authenticated users.
+These short-lived tokens are rotated each `token_rotation_interval_minutes` for an active authenticated user.
+
+An active authenticated user that gets it token rotated will extend the `login_maximum_inactive_lifetime_days` time from "now" that Grafana will remember the user.
+This means that a user can close its browser and come back before `now + login_maximum_inactive_lifetime_days` and still being authenticated.
+ This is true as long as the time since user login is less than `login_maximum_lifetime_days`.
+
+Example:
+
+```bash
+[auth]
+
+# Login cookie name
+login_cookie_name = grafana_session
+
+# The lifetime (days) an authenticated user can be inactive before being required to login at next visit. Default is 7 days.
+login_maximum_inactive_lifetime_days = 7
+
+# The maximum lifetime (days) an autenticated user can be logged in since login time before being required to login. Default is 30 days.
+login_maximum_lifetime_days = 30
+
+# How often should auth tokens be rotated for authenticated users when being active. The default is each 10 minutes.
+token_rotation_interval_minutes = 10
+
+# How often should expired auth tokens be deleted from the database. The default is 7 days.
+expired_tokens_cleanup_interval_days = 7
+```
+
### Anonymous authentication
You can make Grafana accessible without any login required by enabling anonymous access in the configuration file.
diff --git a/docs/sources/installation/configuration.md b/docs/sources/installation/configuration.md
index 46bab83654e..b4b53d7557b 100644
--- a/docs/sources/installation/configuration.md
+++ b/docs/sources/installation/configuration.md
@@ -287,6 +287,14 @@ Default is `false`.
Define a white list of allowed ips/domains to use in data sources. Format: `ip_or_domain:port` separated by spaces
+### cookie_secure
+
+Set to `true` if you host Grafana behind HTTPS. Default is `false`.
+
+### cookie_samesite
+
+Sets the `SameSite` cookie attribute and prevents the browser from sending this cookie along with cross-site requests. The main goal is mitigate the risk of cross-origin information leakage. It also provides some protection against cross-site request forgery attacks (CSRF), [read more here](https://www.owasp.org/index.php/SameSite). Valid values are `lax`, `strict` and `none`. Default is `lax`.
+
## [users]
From 0915f931ae6cff86ad04d3e531fd969b802d0b4d Mon Sep 17 00:00:00 2001
From: Marcus Efraimsson
Date: Tue, 5 Feb 2019 21:12:30 +0100
Subject: [PATCH 070/228] change configuration settings in auth package
---
pkg/services/auth/authtoken/auth_token.go | 4 ++--
pkg/services/auth/authtoken/auth_token_test.go | 8 ++++----
pkg/services/auth/authtoken/session_cleanup.go | 4 ++--
pkg/services/auth/authtoken/session_cleanup_test.go | 2 +-
4 files changed, 9 insertions(+), 9 deletions(-)
diff --git a/pkg/services/auth/authtoken/auth_token.go b/pkg/services/auth/authtoken/auth_token.go
index 4e4bd375501..1fdad4dbea5 100644
--- a/pkg/services/auth/authtoken/auth_token.go
+++ b/pkg/services/auth/authtoken/auth_token.go
@@ -81,7 +81,7 @@ func (s *UserAuthTokenServiceImpl) LookupToken(unhashedToken string) (auth.UserT
s.log.Debug("looking up token", "unhashed", unhashedToken, "hashed", hashedToken)
}
- expireBefore := getTime().Add(time.Duration(-86400*s.Cfg.LoginCookieMaxDays) * time.Second).Unix()
+ expireBefore := getTime().Add(time.Duration(-86400*s.Cfg.LoginMaxInactiveLifetimeDays) * time.Second).Unix()
var model userAuthToken
exists, err := s.SQLStore.NewSession().Where("(auth_token = ? OR prev_auth_token = ?) AND created_at > ?", hashedToken, hashedToken, expireBefore).Get(&model)
@@ -148,7 +148,7 @@ func (s *UserAuthTokenServiceImpl) TryRotateToken(token auth.UserToken, clientIP
needsRotation := false
rotatedAt := time.Unix(model.RotatedAt, 0)
if model.AuthTokenSeen {
- needsRotation = rotatedAt.Before(now.Add(-time.Duration(s.Cfg.LoginCookieRotation) * time.Minute))
+ needsRotation = rotatedAt.Before(now.Add(-time.Duration(s.Cfg.TokenRotationIntervalMinutes) * time.Minute))
} else {
needsRotation = rotatedAt.Before(now.Add(-urgentRotateTime))
}
diff --git a/pkg/services/auth/authtoken/auth_token_test.go b/pkg/services/auth/authtoken/auth_token_test.go
index 7809e235f5c..51d361a9f4f 100644
--- a/pkg/services/auth/authtoken/auth_token_test.go
+++ b/pkg/services/auth/authtoken/auth_token_test.go
@@ -341,10 +341,10 @@ func createTestContext(t *testing.T) *testContext {
tokenService := &UserAuthTokenServiceImpl{
SQLStore: sqlstore,
Cfg: &setting.Cfg{
- LoginCookieName: "grafana_session",
- LoginCookieMaxDays: 7,
- LoginDeleteExpiredTokensAfterDays: 30,
- LoginCookieRotation: 10,
+ LoginMaxInactiveLifetimeDays: 7,
+ LoginMaxLifetimeDays: 30,
+ TokenRotationIntervalMinutes: 10,
+ ExpiredTokensCleanupIntervalDays: 1,
},
log: log.New("test-logger"),
}
diff --git a/pkg/services/auth/authtoken/session_cleanup.go b/pkg/services/auth/authtoken/session_cleanup.go
index cd2b766d6c0..ecee82767e4 100644
--- a/pkg/services/auth/authtoken/session_cleanup.go
+++ b/pkg/services/auth/authtoken/session_cleanup.go
@@ -7,12 +7,12 @@ import (
func (srv *UserAuthTokenServiceImpl) Run(ctx context.Context) error {
ticker := time.NewTicker(time.Hour * 12)
- deleteSessionAfter := time.Hour * 24 * time.Duration(srv.Cfg.LoginDeleteExpiredTokensAfterDays)
+ deleteSessionAfter := time.Hour * 24 * time.Duration(srv.Cfg.ExpiredTokensCleanupIntervalDays)
for {
select {
case <-ticker.C:
- srv.ServerLockService.LockAndExecute(ctx, "delete old sessions", time.Hour*12, func() {
+ srv.ServerLockService.LockAndExecute(ctx, "delete expired auth tokens", time.Hour*12, func() {
srv.deleteOldSession(deleteSessionAfter)
})
diff --git a/pkg/services/auth/authtoken/session_cleanup_test.go b/pkg/services/auth/authtoken/session_cleanup_test.go
index 101a279c374..bca1aa824eb 100644
--- a/pkg/services/auth/authtoken/session_cleanup_test.go
+++ b/pkg/services/auth/authtoken/session_cleanup_test.go
@@ -14,7 +14,7 @@ func TestUserAuthTokenCleanup(t *testing.T) {
ctx := createTestContext(t)
insertToken := func(token string, prev string, rotatedAt int64) {
- ut := userAuthToken{AuthToken: token, PrevAuthToken: prev, RotatedAt: rotatedAt, UserAgent: "", ClientIp: ""}
+ ut := userAuthToken{AuthToken: token, PrevAuthToken: prev, CreatedAt: rotatedAt, RotatedAt: rotatedAt, UserAgent: "", ClientIp: ""}
_, err := ctx.sqlstore.NewSession().Insert(&ut)
So(err, ShouldBeNil)
}
From 871c84d195417e51839db1f1fb33e47e196e18ef Mon Sep 17 00:00:00 2001
From: Marcus Efraimsson
Date: Tue, 5 Feb 2019 21:14:23 +0100
Subject: [PATCH 071/228] changes needed for api/middleware due to
configuration settings
---
pkg/api/login.go | 5 +++--
pkg/api/login_oauth.go | 3 ++-
pkg/middleware/middleware.go | 24 +++++++++++++++---------
pkg/middleware/middleware_test.go | 16 ++++++++++++----
4 files changed, 32 insertions(+), 16 deletions(-)
diff --git a/pkg/api/login.go b/pkg/api/login.go
index d25e83d34e8..def24f983c1 100644
--- a/pkg/api/login.go
+++ b/pkg/api/login.go
@@ -137,7 +137,7 @@ func (hs *HTTPServer) loginUserWithUser(user *m.User, c *m.ReqContext) {
hs.log.Error("failed to create auth token", "error", err)
}
- middleware.WriteSessionCookie(c, userToken.GetToken(), middleware.OneYearInSeconds)
+ middleware.WriteSessionCookie(c, userToken.GetToken(), hs.Cfg.LoginMaxLifetimeDays)
}
func (hs *HTTPServer) Logout(c *m.ReqContext) {
@@ -185,7 +185,8 @@ func (hs *HTTPServer) trySetEncryptedCookie(ctx *m.ReqContext, cookieName string
Value: hex.EncodeToString(encryptedError),
HttpOnly: true,
Path: setting.AppSubUrl + "/",
- Secure: hs.Cfg.SecurityHTTPSCookies,
+ Secure: hs.Cfg.CookieSecure,
+ SameSite: hs.Cfg.CookieSameSite,
})
return nil
diff --git a/pkg/api/login_oauth.go b/pkg/api/login_oauth.go
index 4160d48733e..87a8ecc876f 100644
--- a/pkg/api/login_oauth.go
+++ b/pkg/api/login_oauth.go
@@ -214,7 +214,8 @@ func (hs *HTTPServer) writeCookie(w http.ResponseWriter, name string, value stri
Value: value,
HttpOnly: true,
Path: setting.AppSubUrl + "/",
- Secure: hs.Cfg.SecurityHTTPSCookies,
+ Secure: hs.Cfg.CookieSecure,
+ SameSite: hs.Cfg.CookieSameSite,
})
}
diff --git a/pkg/middleware/middleware.go b/pkg/middleware/middleware.go
index 6cf29340b82..9a3e5e1e01c 100644
--- a/pkg/middleware/middleware.go
+++ b/pkg/middleware/middleware.go
@@ -4,6 +4,7 @@ import (
"net/http"
"net/url"
"strconv"
+ "time"
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/components/apikeygen"
@@ -168,11 +169,8 @@ func initContextWithBasicAuth(ctx *m.ReqContext, orgId int64) bool {
return true
}
-const cookieName = "grafana_session"
-const OneYearInSeconds = 31557600 //used as default maxage for session cookies. We validate/rotate them more often.
-
func initContextWithToken(authTokenService authtoken.UserAuthTokenService, ctx *m.ReqContext, orgID int64) bool {
- rawToken := ctx.GetCookie(cookieName)
+ rawToken := ctx.GetCookie(setting.LoginCookieName)
if rawToken == "" {
return false
}
@@ -200,26 +198,34 @@ func initContextWithToken(authTokenService authtoken.UserAuthTokenService, ctx *
}
if rotated {
- WriteSessionCookie(ctx, token.GetToken(), OneYearInSeconds)
+ WriteSessionCookie(ctx, token.GetToken(), setting.LoginMaxLifetimeDays)
}
return true
}
-func WriteSessionCookie(ctx *m.ReqContext, value string, maxAge int) {
+func WriteSessionCookie(ctx *m.ReqContext, value string, maxLifetimeDays int) {
if setting.Env == setting.DEV {
ctx.Logger.Info("new token", "unhashed token", value)
}
+ var maxAge int
+ if maxLifetimeDays <= 0 {
+ maxAge = -1
+ } else {
+ maxAgeHours := (time.Duration(setting.LoginMaxLifetimeDays) * 24 * time.Hour) + time.Hour
+ maxAge = int(maxAgeHours.Seconds())
+ }
+
ctx.Resp.Header().Del("Set-Cookie")
cookie := http.Cookie{
- Name: cookieName,
+ Name: setting.LoginCookieName,
Value: url.QueryEscape(value),
HttpOnly: true,
Path: setting.AppSubUrl + "/",
- Secure: false, // TODO: use setting SecurityHTTPSCookies
+ Secure: setting.CookieSecure,
MaxAge: maxAge,
- SameSite: http.SameSiteLaxMode, // TODO: use setting LoginCookieSameSite
+ SameSite: setting.CookieSameSite,
}
http.SetCookie(ctx.Resp, &cookie)
diff --git a/pkg/middleware/middleware_test.go b/pkg/middleware/middleware_test.go
index 4e10ee39201..fdcc56da3bf 100644
--- a/pkg/middleware/middleware_test.go
+++ b/pkg/middleware/middleware_test.go
@@ -6,6 +6,7 @@ import (
"net/http/httptest"
"path/filepath"
"testing"
+ "time"
msession "github.com/go-macaron/session"
"github.com/grafana/grafana/pkg/bus"
@@ -197,13 +198,17 @@ func TestMiddlewareContext(t *testing.T) {
return true, nil
}
+ maxAgeHours := (time.Duration(setting.LoginMaxLifetimeDays) * 24 * time.Hour)
+ maxAge := (maxAgeHours + time.Hour).Seconds()
+
expectedCookie := &http.Cookie{
- Name: cookieName,
+ Name: setting.LoginCookieName,
Value: "rotated",
Path: setting.AppSubUrl + "/",
HttpOnly: true,
- MaxAge: OneYearInSeconds,
- SameSite: http.SameSiteLaxMode,
+ MaxAge: int(maxAge),
+ Secure: setting.CookieSecure,
+ SameSite: setting.CookieSameSite,
}
sc.fakeReq("GET", "/").exec()
@@ -545,6 +550,9 @@ func middlewareScenario(desc string, fn scenarioFunc) {
Convey(desc, func() {
defer bus.ClearBusHandlers()
+ setting.LoginCookieName = "grafana_session"
+ setting.LoginMaxLifetimeDays = 30
+
sc := &scenarioContext{}
viewsPath, _ := filepath.Abs("../../public/views")
@@ -655,7 +663,7 @@ func (sc *scenarioContext) exec() {
if sc.tokenSessionCookie != "" {
sc.req.AddCookie(&http.Cookie{
- Name: cookieName,
+ Name: setting.LoginCookieName,
Value: sc.tokenSessionCookie,
})
}
From 948350659094f140806c829a52b23eac172ec400 Mon Sep 17 00:00:00 2001
From: Marcus Efraimsson
Date: Tue, 5 Feb 2019 21:20:11 +0100
Subject: [PATCH 072/228] auth token clean up job now runs on schedule and
deletes all expired tokens
delete tokens having created_at <= LoginMaxLifetimeDays or
rotated_at <= LoginMaxInactiveLifetimeDays
---
.../auth/authtoken/session_cleanup.go | 34 ++++++++---
.../auth/authtoken/session_cleanup_test.go | 56 +++++++++++++++----
2 files changed, 69 insertions(+), 21 deletions(-)
diff --git a/pkg/services/auth/authtoken/session_cleanup.go b/pkg/services/auth/authtoken/session_cleanup.go
index ecee82767e4..2b8dfb7b4e2 100644
--- a/pkg/services/auth/authtoken/session_cleanup.go
+++ b/pkg/services/auth/authtoken/session_cleanup.go
@@ -6,14 +6,23 @@ import (
)
func (srv *UserAuthTokenServiceImpl) Run(ctx context.Context) error {
- ticker := time.NewTicker(time.Hour * 12)
- deleteSessionAfter := time.Hour * 24 * time.Duration(srv.Cfg.ExpiredTokensCleanupIntervalDays)
+ if srv.Cfg.ExpiredTokensCleanupIntervalDays <= 0 {
+ srv.log.Debug("cleanup of expired auth tokens are disabled")
+ return nil
+ }
+
+ jobInterval := time.Duration(srv.Cfg.ExpiredTokensCleanupIntervalDays) * 24 * time.Hour
+ srv.log.Debug("cleanup of expired auth tokens are enabled", "intervalDays", srv.Cfg.ExpiredTokensCleanupIntervalDays)
+
+ ticker := time.NewTicker(jobInterval)
+ maxInactiveLifetime := time.Duration(srv.Cfg.LoginMaxInactiveLifetimeDays) * 24 * time.Hour
+ maxLifetime := time.Duration(srv.Cfg.LoginMaxLifetimeDays) * 24 * time.Hour
for {
select {
case <-ticker.C:
- srv.ServerLockService.LockAndExecute(ctx, "delete expired auth tokens", time.Hour*12, func() {
- srv.deleteOldSession(deleteSessionAfter)
+ srv.ServerLockService.LockAndExecute(ctx, "cleanup expired auth tokens", time.Hour*12, func() {
+ srv.deleteExpiredTokens(maxInactiveLifetime, maxLifetime)
})
case <-ctx.Done():
@@ -22,17 +31,24 @@ func (srv *UserAuthTokenServiceImpl) Run(ctx context.Context) error {
}
}
-func (srv *UserAuthTokenServiceImpl) deleteOldSession(deleteSessionAfter time.Duration) (int64, error) {
- sql := `DELETE from user_auth_token WHERE rotated_at < ?`
+func (srv *UserAuthTokenServiceImpl) deleteExpiredTokens(maxInactiveLifetime, maxLifetime time.Duration) (int64, error) {
+ createdBefore := getTime().Add(-maxLifetime)
+ rotatedBefore := getTime().Add(-maxInactiveLifetime)
- deleteBefore := getTime().Add(-deleteSessionAfter)
- res, err := srv.SQLStore.NewSession().Exec(sql, deleteBefore.Unix())
+ srv.log.Debug("starting cleanup of expired auth tokens", "createdBefore", createdBefore, "rotatedBefore", rotatedBefore)
+
+ sql := `DELETE from user_auth_token WHERE created_at <= ? OR rotated_at <= ?`
+ res, err := srv.SQLStore.NewSession().Exec(sql, createdBefore.Unix(), rotatedBefore.Unix())
if err != nil {
return 0, err
}
affected, err := res.RowsAffected()
- srv.log.Info("deleted old sessions", "count", affected)
+ if err != nil {
+ srv.log.Error("failed to cleanup expired auth tokens", "error", err)
+ return 0, nil
+ }
+ srv.log.Info("cleanup of expired auth tokens done", "count", affected)
return affected, err
}
diff --git a/pkg/services/auth/authtoken/session_cleanup_test.go b/pkg/services/auth/authtoken/session_cleanup_test.go
index bca1aa824eb..7b611b3263c 100644
--- a/pkg/services/auth/authtoken/session_cleanup_test.go
+++ b/pkg/services/auth/authtoken/session_cleanup_test.go
@@ -12,25 +12,57 @@ func TestUserAuthTokenCleanup(t *testing.T) {
Convey("Test user auth token cleanup", t, func() {
ctx := createTestContext(t)
+ ctx.tokenService.Cfg.LoginMaxInactiveLifetimeDays = 7
+ ctx.tokenService.Cfg.LoginMaxLifetimeDays = 30
- insertToken := func(token string, prev string, rotatedAt int64) {
- ut := userAuthToken{AuthToken: token, PrevAuthToken: prev, CreatedAt: rotatedAt, RotatedAt: rotatedAt, UserAgent: "", ClientIp: ""}
+ insertToken := func(token string, prev string, createdAt, rotatedAt int64) {
+ ut := userAuthToken{AuthToken: token, PrevAuthToken: prev, CreatedAt: createdAt, RotatedAt: rotatedAt, UserAgent: "", ClientIp: ""}
_, err := ctx.sqlstore.NewSession().Insert(&ut)
So(err, ShouldBeNil)
}
- // insert three old tokens that should be deleted
- for i := 0; i < 3; i++ {
- insertToken(fmt.Sprintf("oldA%d", i), fmt.Sprintf("oldB%d", i), int64(i))
+ t := time.Date(2018, 12, 13, 13, 45, 0, 0, time.UTC)
+ getTime = func() time.Time {
+ return t
}
- // insert three active tokens that should not be deleted
- for i := 0; i < 3; i++ {
- insertToken(fmt.Sprintf("newA%d", i), fmt.Sprintf("newB%d", i), getTime().Unix())
- }
+ Convey("should delete tokens where token rotation age is older than or equal 7 days", func() {
+ from := t.Add(-7 * 24 * time.Hour)
- affected, err := ctx.tokenService.deleteOldSession(time.Hour)
- So(err, ShouldBeNil)
- So(affected, ShouldEqual, 3)
+ // insert three old tokens that should be deleted
+ for i := 0; i < 3; i++ {
+ insertToken(fmt.Sprintf("oldA%d", i), fmt.Sprintf("oldB%d", i), from.Unix(), from.Unix())
+ }
+
+ // insert three active tokens that should not be deleted
+ for i := 0; i < 3; i++ {
+ from = from.Add(time.Second)
+ insertToken(fmt.Sprintf("newA%d", i), fmt.Sprintf("newB%d", i), from.Unix(), from.Unix())
+ }
+
+ affected, err := ctx.tokenService.deleteExpiredTokens(7*24*time.Hour, 30*24*time.Hour)
+ So(err, ShouldBeNil)
+ So(affected, ShouldEqual, 3)
+ })
+
+ Convey("should delete tokens where token age is older than or equal 30 days", func() {
+ from := t.Add(-30 * 24 * time.Hour)
+ fromRotate := t.Add(-time.Second)
+
+ // insert three old tokens that should be deleted
+ for i := 0; i < 3; i++ {
+ insertToken(fmt.Sprintf("oldA%d", i), fmt.Sprintf("oldB%d", i), from.Unix(), fromRotate.Unix())
+ }
+
+ // insert three active tokens that should not be deleted
+ for i := 0; i < 3; i++ {
+ from = from.Add(time.Second)
+ insertToken(fmt.Sprintf("newA%d", i), fmt.Sprintf("newB%d", i), from.Unix(), fromRotate.Unix())
+ }
+
+ affected, err := ctx.tokenService.deleteExpiredTokens(7*24*time.Hour, 30*24*time.Hour)
+ So(err, ShouldBeNil)
+ So(affected, ShouldEqual, 3)
+ })
})
}
From 4de9e3598b8bb35b6f700719a6e626e77e36c197 Mon Sep 17 00:00:00 2001
From: SamuelToh
Date: Wed, 6 Feb 2019 06:41:39 +1000
Subject: [PATCH 073/228] Address review comments
---
docs/sources/http_api/annotations.md | 4 ++--
pkg/api/annotations.go | 2 +-
pkg/api/dtos/annotations.go | 10 +++++-----
3 files changed, 8 insertions(+), 8 deletions(-)
diff --git a/docs/sources/http_api/annotations.md b/docs/sources/http_api/annotations.md
index dee4ede0777..ca589581f96 100644
--- a/docs/sources/http_api/annotations.md
+++ b/docs/sources/http_api/annotations.md
@@ -189,6 +189,8 @@ Content-Type: application/json
Updates one or more properties of an annotation that matches the specified id.
+The `PATCH` operation currently supports updating of the `text`, `tags`, `time` and `timeEnd` properties. It does not handle updating of the `isRegion` and `regionId` properties. To make an annotation regional or vice versa, consider using the `PUT` operation.
+
**Example Request**:
```json
@@ -198,8 +200,6 @@ Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk
Content-Type: application/json
{
- "time":1507037197000,
- "timeEnd":1507180807095,
"text":"New Annotation Description",
"tags":["tag6","tag7","tag8"]
}
diff --git a/pkg/api/annotations.go b/pkg/api/annotations.go
index da9b55a1c16..de9d2517caa 100644
--- a/pkg/api/annotations.go
+++ b/pkg/api/annotations.go
@@ -222,7 +222,7 @@ func PatchAnnotation(c *m.ReqContext, cmd dtos.PatchAnnotationsCmd) Response {
items, err := repo.Find(&annotations.ItemQuery{AnnotationId: annotationID, OrgId: c.OrgId})
if err != nil || len(items) == 0 {
- return Error(500, "Could not find annotation to update", err)
+ return Error(404, "Could not find annotation to update", err)
}
existing := annotations.Item{
diff --git a/pkg/api/dtos/annotations.go b/pkg/api/dtos/annotations.go
index b64329e56d1..bdee8599fea 100644
--- a/pkg/api/dtos/annotations.go
+++ b/pkg/api/dtos/annotations.go
@@ -23,11 +23,11 @@ type UpdateAnnotationsCmd struct {
}
type PatchAnnotationsCmd struct {
- Id int64 `json:"id"`
- Time int64 `json:"time"`
- Text string `json:"text"`
- Tags []string `json:"tags"`
- TimeEnd int64 `json:"timeEnd"`
+ Id int64 `json:"id"`
+ Time int64 `json:"time"`
+ Text string `json:"text"`
+ Tags []string `json:"tags"`
+ TimeEnd int64 `json:"timeEnd"`
}
type DeleteAnnotationsCmd struct {
From d8658a765c568d62cfeb3e5bac6d2a55969c9e65 Mon Sep 17 00:00:00 2001
From: Marcus Efraimsson
Date: Wed, 6 Feb 2019 08:30:14 +0100
Subject: [PATCH 074/228] enhanced expiration logic for lookup token
tokens are not expired if created_at > now - LoginMaxLifetimeDays and
rotated_at > now - LoginMaxInactiveLifetimeDays
---
pkg/services/auth/authtoken/auth_token.go | 7 +-
.../auth/authtoken/auth_token_test.go | 70 +++++++++++++++++--
2 files changed, 69 insertions(+), 8 deletions(-)
diff --git a/pkg/services/auth/authtoken/auth_token.go b/pkg/services/auth/authtoken/auth_token.go
index 1fdad4dbea5..47aa925fd4d 100644
--- a/pkg/services/auth/authtoken/auth_token.go
+++ b/pkg/services/auth/authtoken/auth_token.go
@@ -81,10 +81,13 @@ func (s *UserAuthTokenServiceImpl) LookupToken(unhashedToken string) (auth.UserT
s.log.Debug("looking up token", "unhashed", unhashedToken, "hashed", hashedToken)
}
- expireBefore := getTime().Add(time.Duration(-86400*s.Cfg.LoginMaxInactiveLifetimeDays) * time.Second).Unix()
+ tokenMaxLifetime := time.Duration(s.Cfg.LoginMaxLifetimeDays) * 24 * time.Hour
+ tokenMaxInactiveLifetime := time.Duration(s.Cfg.LoginMaxInactiveLifetimeDays) * 24 * time.Hour
+ createdAfter := getTime().Add(-tokenMaxLifetime).Unix()
+ rotatedAfter := getTime().Add(-tokenMaxInactiveLifetime).Unix()
var model userAuthToken
- exists, err := s.SQLStore.NewSession().Where("(auth_token = ? OR prev_auth_token = ?) AND created_at > ?", hashedToken, hashedToken, expireBefore).Get(&model)
+ exists, err := s.SQLStore.NewSession().Where("(auth_token = ? OR prev_auth_token = ?) AND created_at > ? AND rotated_at > ?", hashedToken, hashedToken, createdAfter, rotatedAfter).Get(&model)
if err != nil {
return nil, err
}
diff --git a/pkg/services/auth/authtoken/auth_token_test.go b/pkg/services/auth/authtoken/auth_token_test.go
index 51d361a9f4f..7ecb67b2ebf 100644
--- a/pkg/services/auth/authtoken/auth_token_test.go
+++ b/pkg/services/auth/authtoken/auth_token_test.go
@@ -105,12 +105,56 @@ func TestUserAuthToken(t *testing.T) {
So(err, ShouldBeNil)
So(stillGood, ShouldNotBeNil)
- getTime = func() time.Time {
- return t.Add(24 * 7 * time.Hour)
- }
- notGood, err := userAuthTokenService.LookupToken(model.UnhashedToken)
- So(err, ShouldEqual, ErrAuthTokenNotFound)
- So(notGood, ShouldBeNil)
+ model, err = ctx.getAuthTokenByID(model.Id)
+ So(err, ShouldBeNil)
+
+ Convey("when rotated_at is 6:59:59 ago should find token", func() {
+ getTime = func() time.Time {
+ return time.Unix(model.RotatedAt, 0).Add(24 * 7 * time.Hour).Add(-time.Second)
+ }
+
+ stillGood, err = userAuthTokenService.LookupToken(stillGood.GetToken())
+ So(err, ShouldBeNil)
+ So(stillGood, ShouldNotBeNil)
+ })
+
+ Convey("when rotated_at is 7:00:00 ago should not find token", func() {
+ getTime = func() time.Time {
+ return time.Unix(model.RotatedAt, 0).Add(24 * 7 * time.Hour)
+ }
+
+ notGood, err := userAuthTokenService.LookupToken(userToken.GetToken())
+ So(err, ShouldEqual, ErrAuthTokenNotFound)
+ So(notGood, ShouldBeNil)
+ })
+
+ Convey("when rotated_at is 5 days ago and created_at is 29 days and 23:59:59 ago should not find token", func() {
+ updated, err := ctx.updateRotatedAt(model.Id, time.Unix(model.CreatedAt, 0).Add(24*25*time.Hour).Unix())
+ So(err, ShouldBeNil)
+ So(updated, ShouldBeTrue)
+
+ getTime = func() time.Time {
+ return time.Unix(model.CreatedAt, 0).Add(24 * 30 * time.Hour).Add(-time.Second)
+ }
+
+ stillGood, err = userAuthTokenService.LookupToken(stillGood.GetToken())
+ So(err, ShouldBeNil)
+ So(stillGood, ShouldNotBeNil)
+ })
+
+ Convey("when rotated_at is 5 days ago and created_at is 30 days ago should not find token", func() {
+ updated, err := ctx.updateRotatedAt(model.Id, time.Unix(model.CreatedAt, 0).Add(24*25*time.Hour).Unix())
+ So(err, ShouldBeNil)
+ So(updated, ShouldBeTrue)
+
+ getTime = func() time.Time {
+ return time.Unix(model.CreatedAt, 0).Add(24 * 30 * time.Hour)
+ }
+
+ notGood, err := userAuthTokenService.LookupToken(userToken.GetToken())
+ So(err, ShouldEqual, ErrAuthTokenNotFound)
+ So(notGood, ShouldBeNil)
+ })
})
Convey("can properly rotate tokens", func() {
@@ -384,3 +428,17 @@ func (c *testContext) markAuthTokenAsSeen(id int64) (bool, error) {
}
return rowsAffected == 1, nil
}
+
+func (c *testContext) updateRotatedAt(id, rotatedAt int64) (bool, error) {
+ sess := c.sqlstore.NewSession()
+ res, err := sess.Exec("UPDATE user_auth_token SET rotated_at = ? WHERE id = ?", rotatedAt, id)
+ if err != nil {
+ return false, err
+ }
+
+ rowsAffected, err := res.RowsAffected()
+ if err != nil {
+ return false, err
+ }
+ return rowsAffected == 1, nil
+}
From 44275d9660feabcb42ca41db2b6866b16314c340 Mon Sep 17 00:00:00 2001
From: Marcus Efraimsson
Date: Wed, 6 Feb 2019 08:45:01 +0100
Subject: [PATCH 075/228] middleware fix
---
pkg/middleware/middleware.go | 1 +
1 file changed, 1 insertion(+)
diff --git a/pkg/middleware/middleware.go b/pkg/middleware/middleware.go
index 9a3e5e1e01c..817372292b9 100644
--- a/pkg/middleware/middleware.go
+++ b/pkg/middleware/middleware.go
@@ -178,6 +178,7 @@ func initContextWithToken(authTokenService authtoken.UserAuthTokenService, ctx *
token, err := authTokenService.LookupToken(rawToken)
if err != nil {
ctx.Logger.Error("failed to look up user based on cookie", "error", err)
+ WriteSessionCookie(ctx, "", -1)
return false
}
From 1fbdd02464ff5b1c917c7ca4b13f66d4153065c4 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Torkel=20=C3=96degaard?=
Date: Wed, 6 Feb 2019 09:04:38 +0100
Subject: [PATCH 076/228] wip: tests
---
public/app/core/redux/index.ts | 2 +-
public/app/features/dashboard/containers/DashboardPage.tsx | 4 ++--
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/public/app/core/redux/index.ts b/public/app/core/redux/index.ts
index bf45d7d22df..1ed23a8d744 100644
--- a/public/app/core/redux/index.ts
+++ b/public/app/core/redux/index.ts
@@ -1,2 +1,2 @@
-export { actionCreatorFactory, noPayloadActionCreatorFactory, ActionOf } from './actionCreatorFactory';
+export { actionCreatorFactory, noPayloadActionCreatorFactory, ActionOf, ActionCreator } from './actionCreatorFactory';
export { reducerFactory } from './reducerFactory';
diff --git a/public/app/features/dashboard/containers/DashboardPage.tsx b/public/app/features/dashboard/containers/DashboardPage.tsx
index 1bd5218fd60..dfc0c1d2758 100644
--- a/public/app/features/dashboard/containers/DashboardPage.tsx
+++ b/public/app/features/dashboard/containers/DashboardPage.tsx
@@ -25,7 +25,7 @@ import { notifyApp } from 'app/core/actions';
import { StoreState, DashboardLoadingState, DashboardRouteInfo } from 'app/types';
import { DashboardModel, PanelModel } from 'app/features/dashboard/state';
-interface Props {
+export interface Props {
urlUid?: string;
urlSlug?: string;
urlType?: string;
@@ -46,7 +46,7 @@ interface Props {
updateLocation: typeof updateLocation;
}
-interface State {
+export interface State {
isSettingsOpening: boolean;
isEditing: boolean;
isFullscreen: boolean;
From 85ef2ca738c8e976d0a387728429934637841012 Mon Sep 17 00:00:00 2001
From: Marcus Efraimsson
Date: Wed, 6 Feb 2019 09:43:45 +0100
Subject: [PATCH 077/228] fix spelling
---
conf/defaults.ini | 2 +-
conf/sample.ini | 2 +-
docs/sources/auth/overview.md | 2 +-
3 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/conf/defaults.ini b/conf/defaults.ini
index c65cb93d426..41b948e53af 100644
--- a/conf/defaults.ini
+++ b/conf/defaults.ini
@@ -250,7 +250,7 @@ login_cookie_name = grafana_session
# The lifetime (days) an authenticated user can be inactive before being required to login at next visit. Default is 7 days.
login_maximum_inactive_lifetime_days = 7
-# The maximum lifetime (days) an autenticated user can be logged in since login time before being required to login. Default is 30 days.
+# The maximum lifetime (days) an authenticated user can be logged in since login time before being required to login. Default is 30 days.
login_maximum_lifetime_days = 30
# How often should auth tokens be rotated for authenticated users when being active. The default is each 10 minutes.
diff --git a/conf/sample.ini b/conf/sample.ini
index 39feb31441e..831fa31253e 100644
--- a/conf/sample.ini
+++ b/conf/sample.ini
@@ -230,7 +230,7 @@ log_queries =
# The lifetime (days) an authenticated user can be inactive before being required to login at next visit. Default is 7 days,
;login_maximum_inactive_lifetime_days = 7
-# The maximum lifetime (days) an autenticated user can be logged in since login time before being required to login. Default is 30 days.
+# The maximum lifetime (days) an authenticated user can be logged in since login time before being required to login. Default is 30 days.
;login_maximum_lifetime_days = 30
# How often should auth tokens be rotated for authenticated users when being active. The default is each 10 minutes.
diff --git a/docs/sources/auth/overview.md b/docs/sources/auth/overview.md
index e3d4c08ca5d..fba8da00a5e 100644
--- a/docs/sources/auth/overview.md
+++ b/docs/sources/auth/overview.md
@@ -58,7 +58,7 @@ login_cookie_name = grafana_session
# The lifetime (days) an authenticated user can be inactive before being required to login at next visit. Default is 7 days.
login_maximum_inactive_lifetime_days = 7
-# The maximum lifetime (days) an autenticated user can be logged in since login time before being required to login. Default is 30 days.
+# The maximum lifetime (days) an authenticated user can be logged in since login time before being required to login. Default is 30 days.
login_maximum_lifetime_days = 30
# How often should auth tokens be rotated for authenticated users when being active. The default is each 10 minutes.
From 9c64e3b4b98b1ad3b7c89d814554e58c1370033d Mon Sep 17 00:00:00 2001
From: Johannes Schill
Date: Wed, 6 Feb 2019 09:45:03 +0100
Subject: [PATCH 078/228] Revert "chore: Remove react-sizeme"
This reverts commit 260b6f5de83133f668aa15a3874eeeb20d46cbe7.
---
package.json | 1 +
yarn.lock | 28 ++++++++++++++++++++++++++++
2 files changed, 29 insertions(+)
diff --git a/package.json b/package.json
index 18c0a56f0c4..77fd92baf57 100644
--- a/package.json
+++ b/package.json
@@ -177,6 +177,7 @@
"react-highlight-words": "0.11.0",
"react-popper": "^1.3.0",
"react-redux": "^5.0.7",
+ "react-sizeme": "^2.3.6",
"react-table": "^6.8.6",
"react-transition-group": "^2.2.1",
"react-virtualized": "^9.21.0",
diff --git a/yarn.lock b/yarn.lock
index fd0c446fbce..169abd40ee4 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -3972,6 +3972,11 @@ base@^0.11.1:
mixin-deep "^1.2.0"
pascalcase "^0.1.1"
+batch-processor@1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/batch-processor/-/batch-processor-1.0.0.tgz#75c95c32b748e0850d10c2b168f6bdbe9891ace8"
+ integrity sha1-dclcMrdI4IUNEMKxaPa9vpiRrOg=
+
batch@0.6.1:
version "0.6.1"
resolved "https://registry.yarnpkg.com/batch/-/batch-0.6.1.tgz#dc34314f4e679318093fc760272525f94bf25c16"
@@ -6608,6 +6613,13 @@ elegant-spinner@^1.0.1:
resolved "https://registry.yarnpkg.com/elegant-spinner/-/elegant-spinner-1.0.1.tgz#db043521c95d7e303fd8f345bedc3349cfb0729e"
integrity sha1-2wQ1IcldfjA/2PNFvtwzSc+wcp4=
+element-resize-detector@^1.1.12:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/element-resize-detector/-/element-resize-detector-1.2.0.tgz#63344fd6f4e5ecff6f018d027e17b281fd4fa338"
+ integrity sha512-UmhNB8sIJVZeg56gEjgmMd6p37sCg8j8trVW0LZM7Wzv+kxQ5CnRHcgRKBTB/kFUSn3e7UP59kl2V2U8Du1hmg==
+ dependencies:
+ batch-processor "1.0.0"
+
elliptic@^6.0.0:
version "6.4.1"
resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.4.1.tgz#c2d0b7776911b86722c632c3c06c60f2f819939a"
@@ -10888,6 +10900,11 @@ lodash.tail@^4.1.1:
resolved "https://registry.yarnpkg.com/lodash.tail/-/lodash.tail-4.1.1.tgz#d2333a36d9e7717c8ad2f7cacafec7c32b444664"
integrity sha1-0jM6NtnncXyK0vfKyv7HwytERmQ=
+lodash.throttle@^4.1.1:
+ version "4.1.1"
+ resolved "https://registry.yarnpkg.com/lodash.throttle/-/lodash.throttle-4.1.1.tgz#c23e91b710242ac70c37f1e1cda9274cc39bf2f4"
+ integrity sha1-wj6RtxAkKscMN/HhzaknTMOb8vQ=
+
lodash.union@4.6.0, lodash.union@~4.6.0:
version "4.6.0"
resolved "https://registry.yarnpkg.com/lodash.union/-/lodash.union-4.6.0.tgz#48bb5088409f16f1821666641c44dd1aaae3cd88"
@@ -14198,6 +14215,17 @@ react-resizable@1.x:
prop-types "15.x"
react-draggable "^2.2.6 || ^3.0.3"
+react-sizeme@^2.3.6:
+ version "2.5.2"
+ resolved "https://registry.yarnpkg.com/react-sizeme/-/react-sizeme-2.5.2.tgz#e7041390cfb895ed15d896aa91d76e147e3b70b5"
+ integrity sha512-hYvcncV1FxVzPm2EhVwlOLf7Tk+k/ttO6rI7bfKUL/aL1gYzrY3DXJsdZ6nFaFgGSU/i8KC6gCoptOhBbRJpXQ==
+ dependencies:
+ element-resize-detector "^1.1.12"
+ invariant "^2.2.2"
+ lodash.debounce "^4.0.8"
+ lodash.throttle "^4.1.1"
+ shallowequal "^1.0.2"
+
react-split-pane@^0.1.84:
version "0.1.85"
resolved "https://registry.yarnpkg.com/react-split-pane/-/react-split-pane-0.1.85.tgz#64819946a99b617ffa2d20f6f45a0056b6ee4faa"
From c47c2528aa9d5075a7c3ddc0a3a22220140652c3 Mon Sep 17 00:00:00 2001
From: Johannes Schill
Date: Wed, 6 Feb 2019 09:45:09 +0100
Subject: [PATCH 079/228] Revert "chore: Replace sizeMe with AutoSizer in
DashboardGrid"
This reverts commit ae0b027d69ce0fe2946aabfe55267150151a4038.
---
.../dashboard/dashgrid/DashboardGrid.tsx | 26 +++++--------------
1 file changed, 7 insertions(+), 19 deletions(-)
diff --git a/public/app/features/dashboard/dashgrid/DashboardGrid.tsx b/public/app/features/dashboard/dashgrid/DashboardGrid.tsx
index 5a65fadd74b..658bfad3816 100644
--- a/public/app/features/dashboard/dashgrid/DashboardGrid.tsx
+++ b/public/app/features/dashboard/dashgrid/DashboardGrid.tsx
@@ -5,12 +5,13 @@ import { GRID_CELL_HEIGHT, GRID_CELL_VMARGIN, GRID_COLUMN_COUNT } from 'app/core
import { DashboardPanel } from './DashboardPanel';
import { DashboardModel, PanelModel } from '../state';
import classNames from 'classnames';
-import { AutoSizer } from 'react-virtualized';
+import sizeMe from 'react-sizeme';
let lastGridWidth = 1200;
let ignoreNextWidthChange = false;
-interface SizedReactLayoutGridProps {
+interface GridWrapperProps {
+ size: { width: number; };
layout: ReactGridLayout.Layout[];
onLayoutChange: (layout: ReactGridLayout.Layout[]) => void;
children: JSX.Element | JSX.Element[];
@@ -24,12 +25,8 @@ interface SizedReactLayoutGridProps {
isFullscreen?: boolean;
}
-interface GridWrapperProps extends SizedReactLayoutGridProps {
- sizedWidth: number;
-}
-
function GridWrapper({
- sizedWidth,
+ size,
layout,
onLayoutChange,
children,
@@ -41,8 +38,8 @@ function GridWrapper({
isResizable,
isDraggable,
isFullscreen,
-}: GridWrapperProps) {
- const width = sizedWidth > 0 ? sizedWidth : lastGridWidth;
+}: GridWrapperProps) {
+ const width = size.width > 0 ? size.width : lastGridWidth;
// logic to ignore width changes (optimization)
if (width !== lastGridWidth) {
@@ -77,16 +74,7 @@ function GridWrapper({
);
}
-const SizedReactLayoutGrid = (props: SizedReactLayoutGridProps) => (
-
- {({width}) => (
-
- )}
-
-);
+const SizedReactLayoutGrid = sizeMe({ monitorWidth: true })(GridWrapper);
export interface DashboardGridProps {
dashboard: DashboardModel;
From 4caea91164bed003b589476e77ceeae64949b568 Mon Sep 17 00:00:00 2001
From: Alexander Zobnin
Date: Wed, 6 Feb 2019 13:00:26 +0300
Subject: [PATCH 080/228] azuremonitor: fix autocomplete menu height
---
.../editor/query_field.tsx | 12 +++++++++---
1 file changed, 9 insertions(+), 3 deletions(-)
diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/editor/query_field.tsx b/public/app/plugins/datasource/grafana-azure-monitor-datasource/editor/query_field.tsx
index e62337c4982..adab7fc5414 100644
--- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/editor/query_field.tsx
+++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/editor/query_field.tsx
@@ -105,7 +105,7 @@ class QueryField extends React.Component {
this.setState({ value }, () => {
if (changed) {
// call typeahead only if query changed
- window.requestAnimationFrame(this.onTypeahead);
+ requestAnimationFrame(() => this.onTypeahead());
this.onChangeQuery();
}
});
@@ -283,12 +283,18 @@ class QueryField extends React.Component {
const rect = node.parentElement.getBoundingClientRect();
const scrollX = window.scrollX;
const scrollY = window.scrollY;
+ const screenHeight = window.innerHeight;
+
+ const menuLeft = rect.left + scrollX - 2;
+ const menuTop = rect.top + scrollY + rect.height + 4;
+ const menuHeight = screenHeight - menuTop - 10;
// Write DOM
requestAnimationFrame(() => {
menu.style.opacity = 1;
- menu.style.top = `${rect.top + scrollY + rect.height + 4}px`;
- menu.style.left = `${rect.left + scrollX - 2}px`;
+ menu.style.top = `${menuTop}px`;
+ menu.style.left = `${menuLeft}px`;
+ menu.style.maxHeight = `${menuHeight}px`;
});
}
};
From 865d1567fc0da20bd5388ce425d42857bd9d5e6b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Torkel=20=C3=96degaard?=
Date: Wed, 6 Feb 2019 11:30:42 +0100
Subject: [PATCH 081/228] Added DashboardPage tests that tests view mode
transition logic
---
.../containers/DashboardPage.test.tsx | 126 ++++++++++
.../dashboard/containers/DashboardPage.tsx | 2 +-
.../__snapshots__/DashboardPage.test.tsx.snap | 220 ++++++++++++++++++
3 files changed, 347 insertions(+), 1 deletion(-)
create mode 100644 public/app/features/dashboard/containers/DashboardPage.test.tsx
create mode 100644 public/app/features/dashboard/containers/__snapshots__/DashboardPage.test.tsx.snap
diff --git a/public/app/features/dashboard/containers/DashboardPage.test.tsx b/public/app/features/dashboard/containers/DashboardPage.test.tsx
new file mode 100644
index 00000000000..59e71c69757
--- /dev/null
+++ b/public/app/features/dashboard/containers/DashboardPage.test.tsx
@@ -0,0 +1,126 @@
+import React from 'react';
+import { shallow, ShallowWrapper } from 'enzyme';
+import { DashboardPage, Props, State } from './DashboardPage';
+import { DashboardModel } from '../state';
+import { setDashboardModel } from '../state/actions';
+import { DashboardRouteInfo, DashboardLoadingState } from 'app/types';
+
+jest.mock('sass/_variables.scss', () => ({
+ panelhorizontalpadding: 10,
+ panelVerticalPadding: 10,
+}));
+
+jest.mock('app/features/dashboard/components/DashboardSettings/SettingsCtrl', () => ({
+}));
+
+function setup(propOverrides?: Partial): ShallowWrapper {
+ const props: Props = {
+ urlUid: '11',
+ urlSlug: 'my-dash',
+ $scope: {},
+ $injector: {},
+ routeInfo: DashboardRouteInfo.Normal,
+ urlEdit: false,
+ urlFullscreen: false,
+ loadingState: DashboardLoadingState.Done,
+ isLoadingSlow: false,
+ initDashboard: jest.fn(),
+ updateLocation: jest.fn(),
+ notifyApp: jest.fn(),
+ dashboard: null,
+ setDashboardModel: setDashboardModel,
+ };
+
+ Object.assign(props, propOverrides);
+ return shallow( );
+}
+
+describe('DashboardPage', () => {
+ let wrapper: ShallowWrapper;
+
+ beforeEach(() => {
+ wrapper = setup();
+ });
+
+ describe('Given dashboard has not loaded yet', () => {
+ it('should render nothing', () => {
+ expect(wrapper).toMatchSnapshot();
+ });
+ });
+
+ describe('Given dashboard model', () => {
+ let dashboard: DashboardModel;
+
+ beforeEach(() => {
+ dashboard = new DashboardModel({
+ title: 'My dashboard',
+ panels: [
+ {
+ id: 1,
+ type: 'graph',
+ title: 'My graph',
+ gridPos: { x: 0, y: 0, w: 1, h: 1 }
+ }
+ ]
+ }, {
+ canEdit: true,
+ canSave: true,
+ });
+ wrapper.setProps({ dashboard, loadingState: DashboardLoadingState.Done });
+ });
+
+ it('Should update title', () => {
+ expect(document.title).toBe('My dashboard - Grafana');
+ });
+
+ it('After render dashboard', () => {
+ expect(wrapper).toMatchSnapshot();
+ });
+
+ describe('Given user has scrolled down and goes into fullscreen edit', () => {
+ beforeEach(() => {
+ wrapper.setState({ scrollTop: 100 });
+ wrapper.setProps({
+ urlFullscreen: true,
+ urlEdit: true,
+ urlPanelId: '1',
+ });
+ });
+
+ it('Should update model state to fullscreen & edit', () => {
+ expect(dashboard.meta.fullscreen).toBe(true);
+ expect(dashboard.meta.isEditing).toBe(true);
+ });
+
+ it('Should update component state to fullscreen and edit', () => {
+ const state = wrapper.state();
+ expect(state.isEditing).toBe(true);
+ expect(state.isFullscreen).toBe(true);
+ expect(state.rememberScrollTop).toBe(100);
+ });
+
+ describe('Given user goes back to dashboard', () => {
+ beforeEach(() => {
+ wrapper.setState({ scrollTop: 0 });
+ wrapper.setProps({
+ urlFullscreen: false,
+ urlEdit: false,
+ urlPanelId: null,
+ });
+ });
+
+ it('Should update model state normal state', () => {
+ expect(dashboard.meta.fullscreen).toBe(false);
+ expect(dashboard.meta.isEditing).toBe(false);
+ });
+
+ it('Should update component state to normal and restore scrollTop', () => {
+ const state = wrapper.state();
+ expect(state.isEditing).toBe(false);
+ expect(state.isFullscreen).toBe(false);
+ expect(state.scrollTop).toBe(100);
+ });
+ });
+ });
+ });
+});
diff --git a/public/app/features/dashboard/containers/DashboardPage.tsx b/public/app/features/dashboard/containers/DashboardPage.tsx
index dfc0c1d2758..f71838d2aa0 100644
--- a/public/app/features/dashboard/containers/DashboardPage.tsx
+++ b/public/app/features/dashboard/containers/DashboardPage.tsx
@@ -39,7 +39,7 @@ export interface Props {
urlFullscreen: boolean;
loadingState: DashboardLoadingState;
isLoadingSlow: boolean;
- dashboard: DashboardModel;
+ dashboard: DashboardModel | null;
initDashboard: typeof initDashboard;
setDashboardModel: typeof setDashboardModel;
notifyApp: typeof notifyApp;
diff --git a/public/app/features/dashboard/containers/__snapshots__/DashboardPage.test.tsx.snap b/public/app/features/dashboard/containers/__snapshots__/DashboardPage.test.tsx.snap
new file mode 100644
index 00000000000..d3808513e7b
--- /dev/null
+++ b/public/app/features/dashboard/containers/__snapshots__/DashboardPage.test.tsx.snap
@@ -0,0 +1,220 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`DashboardPage Given dashboard has not loaded yet should render nothing 1`] = `""`;
+
+exports[`DashboardPage Given dashboard model After render dashboard 1`] = `
+
+`;
From e4446f0340eef3edf3e6d54f50364fdc821732cc Mon Sep 17 00:00:00 2001
From: Alexander Zobnin
Date: Wed, 6 Feb 2019 13:52:35 +0300
Subject: [PATCH 082/228] azuremonitor: improve autocomplete UX
---
.../editor/KustoQueryField.tsx | 20 +++++++++++++++----
1 file changed, 16 insertions(+), 4 deletions(-)
diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/editor/KustoQueryField.tsx b/public/app/plugins/datasource/grafana-azure-monitor-datasource/editor/KustoQueryField.tsx
index 0a484794e8f..2a578176674 100644
--- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/editor/KustoQueryField.tsx
+++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/editor/KustoQueryField.tsx
@@ -65,7 +65,7 @@ export default class KustoQueryField extends QueryField {
this.fetchSchema();
}
- onTypeahead = (force = false) => {
+ onTypeahead = (force?: boolean) => {
const selection = window.getSelection();
if (selection.anchorNode) {
const wrapperNode = selection.anchorNode.parentElement;
@@ -152,14 +152,17 @@ export default class KustoQueryField extends QueryField {
}
// built-in
- } else if (prefix && !wrapperClasses.contains('argument')) {
+ } else if (prefix && !wrapperClasses.contains('argument') && !force) {
+ // Use only last typed word as a prefix for searching
if (modelPrefix.match(/\s$/i)) {
prefix = '';
+ return;
}
+ prefix = getLastWord(prefix);
typeaheadContext = 'context-builtin';
suggestionGroups = this.getKeywordSuggestions();
} else if (force === true) {
- typeaheadContext = 'context-builtin';
+ typeaheadContext = 'context-builtin-forced';
if (modelPrefix.match(/\s$/i)) {
prefix = '';
}
@@ -183,7 +186,7 @@ export default class KustoQueryField extends QueryField {
.filter(group => group.items.length > 0);
// console.log('onTypeahead', selection.anchorNode, wrapperClasses, text, offset, prefix, typeaheadContext);
- // console.log('onTypeahead', prefix, typeaheadContext);
+ // console.log('onTypeahead', prefix, typeaheadContext, force);
this.setState({
typeaheadPrefix: prefix,
@@ -422,3 +425,12 @@ function normalizeQuery(query: string): string {
normalizedQuery = normalizedQuery.replace('\n', ' ');
return normalizedQuery;
}
+
+function getLastWord(str: string): string {
+ const lastWordPattern = /(?:.*\s)?([^\s]+\s*)$/gi;
+ const match = lastWordPattern.exec(str);
+ if (match && match.length > 1) {
+ return match[1];
+ }
+ return '';
+}
From 6848fe0edf0480eda09efa300f075ac4f560b3ee Mon Sep 17 00:00:00 2001
From: Marcus Efraimsson
Date: Wed, 6 Feb 2019 14:26:43 +0100
Subject: [PATCH 083/228] docs: update annotaions http api
---
docs/sources/http_api/annotations.md | 50 +++++++++++++++++++++-------
1 file changed, 38 insertions(+), 12 deletions(-)
diff --git a/docs/sources/http_api/annotations.md b/docs/sources/http_api/annotations.md
index ca589581f96..e1d2876f48a 100644
--- a/docs/sources/http_api/annotations.md
+++ b/docs/sources/http_api/annotations.md
@@ -97,7 +97,7 @@ Creates an annotation in the Grafana database. The `dashboardId` and `panelId` f
**Example Request**:
-```json
+```http
POST /api/annotations HTTP/1.1
Accept: application/json
Content-Type: application/json
@@ -115,7 +115,7 @@ Content-Type: application/json
**Example Response**:
-```json
+```http
HTTP/1.1 200
Content-Type: application/json
@@ -135,7 +135,7 @@ format (string with multiple tags being separated by a space).
**Example Request**:
-```json
+```http
POST /api/annotations/graphite HTTP/1.1
Accept: application/json
Content-Type: application/json
@@ -150,7 +150,7 @@ Content-Type: application/json
**Example Response**:
-```json
+```http
HTTP/1.1 200
Content-Type: application/json
@@ -160,15 +160,15 @@ Content-Type: application/json
}
```
-## Replace Annotation
+## Update Annotation
`PUT /api/annotations/:id`
-Replaces the annotation that matches the specified id.
+Updates all properties of an annotation that matches the specified id. To only update certain property, consider using the [Patch Annotation](#patch-annotation) operation.
**Example Request**:
-```json
+```http
PUT /api/annotations/1141 HTTP/1.1
Accept: application/json
Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk
@@ -183,17 +183,28 @@ Content-Type: application/json
}
```
-## Update Annotation
+**Example Response**:
+
+```http
+HTTP/1.1 200
+Content-Type: application/json
+
+{
+ "message":"Annotation updated"
+}
+```
+
+## Patch Annotation
`PATCH /api/annotations/:id`
Updates one or more properties of an annotation that matches the specified id.
-The `PATCH` operation currently supports updating of the `text`, `tags`, `time` and `timeEnd` properties. It does not handle updating of the `isRegion` and `regionId` properties. To make an annotation regional or vice versa, consider using the `PUT` operation.
+This operation currently supports updating of the `text`, `tags`, `time` and `timeEnd` properties. It does not handle updating of the `isRegion` and `regionId` properties. To make an annotation regional or vice versa, consider using the [Update Annotation](#update-annotation) operation.
**Example Request**:
-```json
+```http
PATCH /api/annotations/1145 HTTP/1.1
Accept: application/json
Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk
@@ -205,6 +216,17 @@ Content-Type: application/json
}
```
+**Example Response**:
+
+```http
+HTTP/1.1 200
+Content-Type: application/json
+
+{
+ "message":"Annotation patched"
+}
+```
+
## Delete Annotation By Id
`DELETE /api/annotations/:id`
@@ -226,7 +248,9 @@ Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk
HTTP/1.1 200
Content-Type: application/json
-{"message":"Annotation deleted"}
+{
+ "message":"Annotation deleted"
+}
```
## Delete Annotation By RegionId
@@ -250,5 +274,7 @@ Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk
HTTP/1.1 200
Content-Type: application/json
-{"message":"Annotation region deleted"}
+{
+ "message":"Annotation region deleted"
+}
```
From a53c3b45fcf75b8b6c375f80f730066a9c3ed802 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Torkel=20=C3=96degaard?=
Date: Wed, 6 Feb 2019 14:35:53 +0100
Subject: [PATCH 084/228] Added a basic test for initDashboard thunk
---
package.json | 1 +
.../dashboard/state/initDashboard.test.ts | 130 ++++++++++++++++++
.../features/dashboard/state/initDashboard.ts | 6 +-
public/app/features/profile/state/reducers.ts | 14 ++
public/app/store/configureStore.ts | 2 +
public/app/types/user.ts | 4 +-
yarn.lock | 7 +
7 files changed, 158 insertions(+), 6 deletions(-)
create mode 100644 public/app/features/dashboard/state/initDashboard.test.ts
create mode 100644 public/app/features/profile/state/reducers.ts
diff --git a/package.json b/package.json
index 77fd92baf57..5ac751ced3f 100644
--- a/package.json
+++ b/package.json
@@ -85,6 +85,7 @@
"prettier": "1.9.2",
"react-hot-loader": "^4.3.6",
"react-test-renderer": "^16.5.0",
+ "redux-mock-store": "^1.5.3",
"regexp-replace-loader": "^1.0.1",
"sass-lint": "^1.10.2",
"sass-loader": "^7.0.1",
diff --git a/public/app/features/dashboard/state/initDashboard.test.ts b/public/app/features/dashboard/state/initDashboard.test.ts
new file mode 100644
index 00000000000..eebeb5010fb
--- /dev/null
+++ b/public/app/features/dashboard/state/initDashboard.test.ts
@@ -0,0 +1,130 @@
+import configureMockStore from 'redux-mock-store';
+import thunk from 'redux-thunk';
+import { initDashboard, InitDashboardArgs } from './initDashboard';
+import { DashboardRouteInfo, DashboardLoadingState } from 'app/types';
+
+const mockStore = configureMockStore([thunk]);
+
+interface ScenarioContext {
+ args: InitDashboardArgs;
+ timeSrv: any;
+ annotationsSrv: any;
+ unsavedChangesSrv: any;
+ variableSrv: any;
+ dashboardSrv: any;
+ keybindingSrv: any;
+ setup: (fn: () => void) => void;
+ actions: any[];
+ storeState: any;
+}
+
+type ScenarioFn = (ctx: ScenarioContext) => void;
+
+function describeInitScenario(description: string, scenarioFn: ScenarioFn) {
+ describe(description, () => {
+ const timeSrv = { init: jest.fn() };
+ const annotationsSrv = { init: jest.fn() };
+ const unsavedChangesSrv = { init: jest.fn() };
+ const variableSrv = { init: jest.fn() };
+ const dashboardSrv = { setCurrent: jest.fn() };
+ const keybindingSrv = { setupDashboardBindings: jest.fn() };
+
+ const injectorMock = {
+ get: (name: string) => {
+ switch (name) {
+ case 'timeSrv':
+ return timeSrv;
+ case 'annotationsSrv':
+ return annotationsSrv;
+ case 'unsavedChangesSrv':
+ return unsavedChangesSrv;
+ case 'dashboardSrv':
+ return dashboardSrv;
+ case 'variableSrv':
+ return variableSrv;
+ case 'keybindingSrv':
+ return keybindingSrv;
+ default:
+ throw { message: 'Unknown service ' + name };
+ }
+ },
+ };
+
+ let setupFn = () => {};
+
+ const ctx: ScenarioContext = {
+ args: {
+ $injector: injectorMock,
+ $scope: {},
+ fixUrl: false,
+ routeInfo: DashboardRouteInfo.Normal,
+ },
+ timeSrv,
+ annotationsSrv,
+ unsavedChangesSrv,
+ variableSrv,
+ dashboardSrv,
+ keybindingSrv,
+ actions: [],
+ storeState: {
+ location: {
+ query: {},
+ },
+ user: {},
+ },
+ setup: (fn: () => void) => {
+ setupFn = fn;
+ },
+ };
+
+ beforeEach(async () => {
+ setupFn();
+
+ const store = mockStore(ctx.storeState);
+
+ await store.dispatch(initDashboard(ctx.args));
+
+ ctx.actions = store.getActions();
+ });
+
+ scenarioFn(ctx);
+ });
+}
+
+describeInitScenario('Initializing new dashboard', ctx => {
+ ctx.setup(() => {
+ ctx.storeState.user.orgId = 12;
+ ctx.args.routeInfo = DashboardRouteInfo.New;
+ });
+
+ it('Should send action to set loading state to fetching', () => {
+ expect(ctx.actions[0].type).toBe('SET_DASHBOARD_LOADING_STATE');
+ expect(ctx.actions[0].payload).toBe(DashboardLoadingState.Fetching);
+ });
+
+ it('Should send action to set loading state to Initializing', () => {
+ expect(ctx.actions[1].type).toBe('SET_DASHBOARD_LOADING_STATE');
+ expect(ctx.actions[1].payload).toBe(DashboardLoadingState.Initializing);
+ });
+
+ it('Should update location with orgId query param', () => {
+ expect(ctx.actions[2].type).toBe('UPDATE_LOCATION');
+ expect(ctx.actions[2].payload.query.orgId).toBe(12);
+ });
+
+ it('Should send action to set dashboard model', () => {
+ expect(ctx.actions[3].type).toBe('SET_DASHBOARD_MODEL');
+ expect(ctx.actions[3].payload.title).toBe('New dashboard');
+ });
+
+ it('Should Initializing services', () => {
+ expect(ctx.timeSrv.init).toBeCalled();
+ expect(ctx.annotationsSrv.init).toBeCalled();
+ expect(ctx.variableSrv.init).toBeCalled();
+ expect(ctx.unsavedChangesSrv.init).toBeCalled();
+ expect(ctx.keybindingSrv.setupDashboardBindings).toBeCalled();
+ expect(ctx.dashboardSrv.setCurrent).toBeCalled();
+ });
+});
+
+
diff --git a/public/app/features/dashboard/state/initDashboard.ts b/public/app/features/dashboard/state/initDashboard.ts
index ba218c1583d..2c68435b313 100644
--- a/public/app/features/dashboard/state/initDashboard.ts
+++ b/public/app/features/dashboard/state/initDashboard.ts
@@ -9,7 +9,6 @@ import { TimeSrv } from 'app/features/dashboard/services/TimeSrv';
import { AnnotationsSrv } from 'app/features/annotations/annotations_srv';
import { VariableSrv } from 'app/features/templating/variable_srv';
import { KeybindingSrv } from 'app/core/services/keybindingSrv';
-import { config } from 'app/core/config';
// Actions
import { updateLocation } from 'app/core/actions';
@@ -150,8 +149,9 @@ export function initDashboard(args: InitDashboardArgs): ThunkResult {
}
// add missing orgId query param
- if (!getState().location.query.orgId) {
- dispatch(updateLocation({ query: { orgId: config.bootData.user.orgId }, partial: true, replace: true }));
+ const storeState = getState() ;
+ if (!storeState.location.query.orgId) {
+ dispatch(updateLocation({ query: { orgId: storeState.user.orgId }, partial: true, replace: true }));
}
// init services
diff --git a/public/app/features/profile/state/reducers.ts b/public/app/features/profile/state/reducers.ts
new file mode 100644
index 00000000000..dc6e841449e
--- /dev/null
+++ b/public/app/features/profile/state/reducers.ts
@@ -0,0 +1,14 @@
+import { UserState } from 'app/types';
+import config from 'app/core/config';
+
+export const initialState: UserState = {
+ orgId: config.bootData.user.orgId,
+};
+
+export const userReducer = (state = initialState, action: any): UserState => {
+ return state;
+};
+
+export default {
+ user: userReducer,
+};
diff --git a/public/app/store/configureStore.ts b/public/app/store/configureStore.ts
index 570a387cd74..e2c33523271 100644
--- a/public/app/store/configureStore.ts
+++ b/public/app/store/configureStore.ts
@@ -11,6 +11,7 @@ import exploreReducers from 'app/features/explore/state/reducers';
import pluginReducers from 'app/features/plugins/state/reducers';
import dataSourcesReducers from 'app/features/datasources/state/reducers';
import usersReducers from 'app/features/users/state/reducers';
+import userReducers from 'app/features/profile/state/reducers';
import organizationReducers from 'app/features/org/state/reducers';
import { setStore } from './store';
@@ -25,6 +26,7 @@ const rootReducers = {
...pluginReducers,
...dataSourcesReducers,
...usersReducers,
+ ...userReducers,
...organizationReducers,
};
diff --git a/public/app/types/user.ts b/public/app/types/user.ts
index 365411147bb..7691558ce90 100644
--- a/public/app/types/user.ts
+++ b/public/app/types/user.ts
@@ -1,5 +1,3 @@
-import { DashboardSearchHit } from './search';
-
export interface OrgUser {
avatarUrl: string;
email: string;
@@ -47,5 +45,5 @@ export interface UsersState {
}
export interface UserState {
- starredDashboards: DashboardSearchHit[];
+ orgId: number;
}
diff --git a/yarn.lock b/yarn.lock
index 169abd40ee4..df2e1cea37e 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -14582,6 +14582,13 @@ redux-logger@^3.0.6:
dependencies:
deep-diff "^0.3.5"
+redux-mock-store@^1.5.3:
+ version "1.5.3"
+ resolved "https://registry.yarnpkg.com/redux-mock-store/-/redux-mock-store-1.5.3.tgz#1f10528949b7ce8056c2532624f7cafa98576c6d"
+ integrity sha512-ryhkkb/4D4CUGpAV2ln1GOY/uh51aczjcRz9k2L2bPx/Xja3c5pSGJJPyR25GNVRXtKIExScdAgFdiXp68GmJA==
+ dependencies:
+ lodash.isplainobject "^4.0.6"
+
redux-thunk@^2.3.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/redux-thunk/-/redux-thunk-2.3.0.tgz#51c2c19a185ed5187aaa9a2d08b666d0d6467622"
From 809d4b040aedb388f5836a39c8f75316321bfca0 Mon Sep 17 00:00:00 2001
From: Marcus Efraimsson
Date: Wed, 6 Feb 2019 14:52:17 +0100
Subject: [PATCH 085/228] changelog: add notes about closing #12546
---
CHANGELOG.md | 1 +
1 file changed, 1 insertion(+)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 4cf2262f7d2..4bddf5e0f32 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,6 +6,7 @@
* **Cloudwatch**: Add `resource_arns` template variable query function [#8207](https://github.com/grafana/grafana/issues/8207), thx [@jeroenvollenbrock](https://github.com/jeroenvollenbrock)
* **Cloudwatch**: Add AWS/Neptune metrics [#14231](https://github.com/grafana/grafana/issues/14231), thx [@tcpatterson](https://github.com/tcpatterson)
* **Cloudwatch**: Add AWS RDS ServerlessDatabaseCapacity metric [#15265](https://github.com/grafana/grafana/pull/15265), thx [@larsjoergensen](https://github.com/larsjoergensen)
+* **Annotations**: Support PATCH verb in annotations http api [#12546](https://github.com/grafana/grafana/issues/12546), thx [@SamuelToh](https://github.com/SamuelToh)
# 6.0.0-beta1 (2019-01-30)
From 7eb2558fc5d8d99588f41734c887fab8ac7c1f47 Mon Sep 17 00:00:00 2001
From: Dominik Prokop
Date: Wed, 6 Feb 2019 15:06:27 +0100
Subject: [PATCH 086/228] Fix issue with graph legend color picker disapearing
on color selection
---
public/app/core/utils/ConfigProvider.tsx | 3 +--
public/app/plugins/panel/graph/graph.ts | 5 ++++-
scripts/webpack/getThemeVariable.js | 2 +-
3 files changed, 6 insertions(+), 4 deletions(-)
diff --git a/public/app/core/utils/ConfigProvider.tsx b/public/app/core/utils/ConfigProvider.tsx
index 56b6fc3d8b9..cb3ad88b191 100644
--- a/public/app/core/utils/ConfigProvider.tsx
+++ b/public/app/core/utils/ConfigProvider.tsx
@@ -21,8 +21,7 @@ export const ThemeProvider = ({ children }: { children: React.ReactNode }) => {
return (
{config => {
- const currentTheme = getCurrentThemeName();
- return {children} ;
+ return {children} ;
}}
);
diff --git a/public/app/plugins/panel/graph/graph.ts b/public/app/plugins/panel/graph/graph.ts
index 3800e147d9d..846d11ea475 100755
--- a/public/app/plugins/panel/graph/graph.ts
+++ b/public/app/plugins/panel/graph/graph.ts
@@ -28,6 +28,8 @@ import { GraphCtrl } from './module';
import { GrafanaThemeType, getValueFormat } from '@grafana/ui';
import { provideTheme } from 'app/core/utils/ConfigProvider';
+const LegendWithThemeProvider = provideTheme(Legend);
+
class GraphElement {
ctrl: GraphCtrl;
tooltip: any;
@@ -44,6 +46,7 @@ class GraphElement {
legendElem: HTMLElement;
constructor(private scope, private elem, private timeSrv) {
+
this.ctrl = scope.ctrl;
this.dashboard = this.ctrl.dashboard;
this.panel = this.ctrl.panel;
@@ -110,7 +113,7 @@ class GraphElement {
onToggleAxis: this.ctrl.onToggleAxis,
};
- const legendReactElem = React.createElement(provideTheme(Legend), legendProps);
+ const legendReactElem = React.createElement(LegendWithThemeProvider, legendProps);
ReactDOM.render(legendReactElem, this.legendElem, () => this.renderPanel());
}
diff --git a/scripts/webpack/getThemeVariable.js b/scripts/webpack/getThemeVariable.js
index 0db0a9842a8..6726f95d47c 100644
--- a/scripts/webpack/getThemeVariable.js
+++ b/scripts/webpack/getThemeVariable.js
@@ -29,7 +29,7 @@ function getThemeVariable(variablePath, themeName) {
const variable = get(theme, variablePath.getValue());
if (!variable) {
- throw new Error(`${variablePath} is not defined fo ${themeName}`);
+ throw new Error(`${variablePath.getValue()} is not defined for ${themeName.getValue()} theme`);
}
if (isHex(variable)) {
From 27a1a9e8c5b8edf16110c280e0b8559dc25d95c9 Mon Sep 17 00:00:00 2001
From: ijin08
Date: Wed, 6 Feb 2019 15:45:40 +0100
Subject: [PATCH 087/228] removed unused theme variables, removed empty
sections, aligned the order of sections in the files
---
public/sass/_variables.dark.scss | 25 +-----
public/sass/_variables.light.scss | 131 +++++++++++-------------------
2 files changed, 47 insertions(+), 109 deletions(-)
diff --git a/public/sass/_variables.dark.scss b/public/sass/_variables.dark.scss
index 7b0ed869bdc..1e9ea77d3f1 100644
--- a/public/sass/_variables.dark.scss
+++ b/public/sass/_variables.dark.scss
@@ -4,9 +4,6 @@
$theme-name: dark;
// Grays
-// -------------------------
-$black: #000;
-
// -------------------------
$black: #000;
$dark-1: #141414;
@@ -19,7 +16,6 @@ $gray-2: #8e8e8e;
$gray-3: #b3b3b3;
$gray-4: #d8d9da;
$gray-5: #ececec;
-$gray-6: #f4f5f8;
$gray-7: #fbfbfb;
$gray-blue: #212327;
@@ -34,7 +30,6 @@ $blue-dark: #005f81;
$green: #299c46;
$red: #d44a3a;
$yellow: #ecbb13;
-$pink: #ff4444;
$purple: #9933cc;
$variable: #32d1df;
$orange: #eb7b18;
@@ -68,7 +63,6 @@ $text-color-weak: $gray-2;
$text-color-faint: $dark-5;
$text-color-emphasis: $gray-5;
-$text-shadow-strong: 1px 1px 4px $black;
$text-shadow-faint: 1px 1px 4px rgb(45, 45, 45);
// gradients
@@ -100,8 +94,7 @@ $hr-border-color: $dark-4;
// Panel
// -------------------------
$panel-bg: #212124;
-$panel-border-color: $dark-1;
-$panel-border: solid 1px $panel-border-color;
+$panel-border: solid 1px $dark-1;
$panel-header-hover-bg: $dark-4;
$panel-corner: $panel-bg;
@@ -144,7 +137,6 @@ $scrollbarBorder: black;
// -------------------------
$table-bg: transparent; // overall background-color
$table-bg-accent: $dark-3; // for striping
-$table-bg-hover: $dark-4; // for hover
$table-border: $dark-3; // table and cell border
$table-bg-odd: $dark-2;
@@ -173,9 +165,6 @@ $btn-inverse-bg-hl: lighten($dark-3, 4%);
$btn-inverse-text-color: $link-color;
$btn-inverse-text-shadow: 0px 1px 0 rgba(0, 0, 0, 0.1);
-$btn-active-bg: $gray-4;
-$btn-active-text-color: $blue-dark;
-
$btn-link-color: $gray-3;
$iconContainerBackground: $black;
@@ -215,13 +204,11 @@ $dropdownBackground: $dark-3;
$dropdownBorder: rgba(0, 0, 0, 0.2);
$dropdownDividerTop: transparent;
$dropdownDividerBottom: #444;
-$dropdownDivider: $dropdownDividerBottom;
$dropdownLinkColor: $text-color;
$dropdownLinkColorHover: $white;
$dropdownLinkColorActive: $white;
-$dropdownLinkBackgroundActive: $dark-4;
$dropdownLinkBackgroundHover: $dark-4;
// COMPONENT VARIABLES
@@ -264,9 +251,6 @@ $menu-dropdown-bg: $body-bg;
$menu-dropdown-hover-bg: $dark-2;
$menu-dropdown-shadow: 5px 5px 20px -5px $black;
-// Breadcrumb
-// -------------------------
-
// Tabs
// -------------------------
$tab-border-color: $dark-4;
@@ -274,9 +258,6 @@ $tab-border-color: $dark-4;
// Toolbar
$toolbar-bg: $input-black;
-// Pagination
-// -------------------------
-
// Form states and alerts
// -------------------------
$warning-text-color: $warn;
@@ -311,7 +292,6 @@ $tooltipBackground: $black;
$tooltipColor: $gray-4;
$tooltipArrowColor: $tooltipBackground;
$tooltipBackgroundError: $brand-danger;
-$tooltipBackgroundBrand: $brand-primary;
// images
$checkboxImageUrl: '../img/checkbox.png';
@@ -380,9 +360,7 @@ $checkbox-color: $dark-1;
//Panel Edit
// -------------------------
$panel-editor-shadow: 0 0 20px black;
-$panel-editor-border: 1px solid $dark-3;
$panel-editor-side-menu-shadow: drop-shadow(0 0 10px $black);
-$panel-editor-toolbar-view-bg: $input-black;
$panel-editor-viz-item-shadow: 0 0 8px $dark-5;
$panel-editor-viz-item-border: 1px solid $dark-5;
$panel-editor-viz-item-shadow-hover: 0 0 4px $blue;
@@ -390,7 +368,6 @@ $panel-editor-viz-item-border-hover: 1px solid $blue;
$panel-editor-viz-item-bg: $input-black;
$panel-editor-tabs-line-color: #e3e3e3;
$panel-editor-viz-item-bg-hover: darken($blue, 47%);
-$panel-editor-viz-item-bg-hover-active: darken($orange, 45%);
$panel-options-group-border: none;
$panel-options-group-header-bg: $gray-blue;
diff --git a/public/sass/_variables.light.scss b/public/sass/_variables.light.scss
index 10c074e1481..1bc9da354fb 100644
--- a/public/sass/_variables.light.scss
+++ b/public/sass/_variables.light.scss
@@ -1,7 +1,3 @@
-// Cosmo 2.3.2
-// Variables
-// --------------------------------------------------
-
// Global values
// --------------------------------------------------
@@ -10,10 +6,6 @@ $theme-name: light;
// Grays
// -------------------------
$black: #000;
-
-// -------------------------
-$black: #000;
-$dark-1: #13161d;
$dark-2: #1e2028;
$dark-3: #303133;
$dark-4: #35373f;
@@ -31,13 +23,11 @@ $white: #fff;
// Accent colors
// -------------------------
$blue: #0083b3;
-$blue-dark: #005f81;
$blue-light: #00a8e6;
$green: #3aa655;
$red: #d44939;
$yellow: #ff851b;
$orange: #ff7941;
-$pink: #e671b8;
$purple: #9954bb;
$variable: $blue;
@@ -60,9 +50,9 @@ $critical: #ec2128;
// Scaffolding
// -------------------------
-
$body-bg: $gray-7;
$page-bg: $gray-7;
+
$body-color: $gray-1;
$text-color: $gray-1;
$text-color-strong: $dark-2;
@@ -70,12 +60,17 @@ $text-color-weak: $gray-2;
$text-color-faint: $gray-4;
$text-color-emphasis: $dark-5;
-$text-shadow-strong: none;
$text-shadow-faint: none;
$textShadow: none;
// gradients
-$brand-gradient: linear-gradient(to right, rgba(255, 213, 0, 1) 0%, rgba(255, 68, 0, 1) 99%, rgba(255, 68, 0, 1) 100%);
+$brand-gradient: linear-gradient(
+ to right,
+ rgba(255, 213, 0, 1) 0%,
+ rgba(255, 68, 0, 1) 99%,
+ rgba(255, 68, 0, 1) 100%
+);
+
$page-gradient: linear-gradient(180deg, $white 10px, $gray-7 100px);
$edit-gradient: linear-gradient(-60deg, $gray-7, #f5f6f9 70%, $gray-7 98%);
@@ -96,10 +91,8 @@ $hr-border-color: $dark-3 !default;
// Panel
// -------------------------
-
$panel-bg: $white;
-$panel-border-color: $gray-5;
-$panel-border: solid 1px $panel-border-color;
+$panel-border: solid 1px $gray-5;
$panel-header-hover-bg: $gray-6;
$panel-corner: $gray-4;
@@ -112,7 +105,6 @@ $divider-border-color: $gray-2;
// Graphite Target Editor
$tight-form-bg: #eaebee;
-
$tight-form-func-bg: $gray-5;
$tight-form-func-highlight-bg: $gray-6;
@@ -130,24 +122,23 @@ $list-item-bg: linear-gradient(135deg, $gray-5, $gray-6); //$card-background;
$list-item-hover-bg: darken($gray-5, 5%);
$list-item-link-color: $text-color;
$list-item-shadow: $card-shadow;
+
$empty-list-cta-bg: $gray-6;
-// Tables
-// -------------------------
-$table-bg: transparent; // overall background-color
-$table-bg-accent: $gray-5; // for striping
-$table-bg-hover: $gray-5; // for hover
-$table-bg-active: $table-bg-hover !default;
-$table-border: $gray-3; // table and cell border
-
-$table-bg-odd: $gray-6;
-$table-bg-hover: $gray-5;
-
// Scrollbars
$scrollbarBackground: $gray-5;
$scrollbarBackground2: $gray-5;
$scrollbarBorder: $gray-4;
+// Tables
+// -------------------------
+$table-bg: transparent; // overall background-color
+$table-bg-accent: $gray-5; // for striping
+$table-border: $gray-3; // table and cell border
+
+$table-bg-odd: $gray-6;
+$table-bg-hover: $gray-5;
+
// Buttons
// -------------------------
$btn-primary-bg: $brand-primary;
@@ -170,16 +161,14 @@ $btn-inverse-bg-hl: darken($gray-6, 5%);
$btn-inverse-text-color: $gray-1;
$btn-inverse-text-shadow: 0 1px 0 rgba(255, 255, 255, 0.4);
-$btn-active-bg: $white;
-$btn-active-text-color: $blue;
-
$btn-link-color: $gray-1;
+$iconContainerBackground: $white;
+
$btn-divider-left: $gray-4;
$btn-divider-right: $gray-7;
-$btn-drag-image: '../img/grab_light.svg';
-$iconContainerBackground: $white;
+$btn-drag-image: '../img/grab_light.svg';
// Forms
// -------------------------
@@ -196,29 +185,8 @@ $input-label-bg: $gray-5;
$input-label-border-color: $gray-5;
$input-color-select-arrow: $gray-1;
-// Sidemenu
-// -------------------------
-$side-menu-bg: $dark-2;
-$side-menu-bg-mobile: rgba(0, 0, 0, 0); //$gray-6;
-$side-menu-item-hover-bg: $gray-1;
-$side-menu-shadow: 5px 0px 10px -5px $gray-1;
-$side-menu-link-color: $gray-6;
-
-// Menu dropdowns
-// -------------------------
-$menu-dropdown-bg: $gray-7;
-$menu-dropdown-hover-bg: $gray-6;
-$menu-dropdown-shadow: 5px 5px 10px -5px $gray-1;
-
-// Breadcrumb
-// -------------------------
-
-// Tabs
-// -------------------------
-$tab-border-color: $gray-5;
-
-// Toolbar
-$toolbar-bg: white;
+// Input placeholder text color
+$placeholderText: $gray-2;
// search
$search-shadow: 0 5px 30px 0 $gray-4;
@@ -235,52 +203,52 @@ $dropdownBackground: $white;
$dropdownBorder: $gray-4;
$dropdownDividerTop: $gray-6;
$dropdownDividerBottom: $white;
-$dropdownDivider: $dropdownDividerTop;
$dropdownLinkColor: $dark-3;
$dropdownLinkColorHover: $link-color;
$dropdownLinkColorActive: $link-color;
-$dropdownLinkBackgroundActive: $gray-6;
$dropdownLinkBackgroundHover: $gray-6;
-// COMPONENT VARIABLES
-// --------------------------------------------------
-
-// Input placeholder text color
-// -------------------------
-$placeholderText: $gray-2;
-
-// Hr border color
-// -------------------------
-$hrBorder: $gray-3;
-
// Horizontal forms & lists
// -------------------------
$horizontalComponentOffset: 180px;
-// Wells
-// -------------------------
-
// Navbar
// -------------------------
-
$navbarHeight: 52px;
+
$navbarBackground: $white;
$navbarBorder: 1px solid $gray-4;
$navbarShadow: 0 0 3px #c1c1c1;
$navbarLinkColor: #444;
-$navbarBrandColor: $navbarLinkColor;
-
$navbarButtonBackground: lighten($navbarBackground, 3%);
$navbarButtonBackgroundHighlight: lighten($navbarBackground, 5%);
$navbar-button-border: $gray-4;
-// Pagination
+// Sidemenu
// -------------------------
+$side-menu-bg: $dark-2;
+$side-menu-bg-mobile: rgba(0, 0, 0, 0); //$gray-6;
+$side-menu-item-hover-bg: $gray-1;
+$side-menu-shadow: 5px 0px 10px -5px $gray-1;
+$side-menu-link-color: $gray-6;
+
+// Menu dropdowns
+// -------------------------
+$menu-dropdown-bg: $gray-7;
+$menu-dropdown-hover-bg: $gray-6;
+$menu-dropdown-shadow: 5px 5px 10px -5px $gray-1;
+
+// Tabs
+// -------------------------
+$tab-border-color: $gray-5;
+
+// Toolbar
+$toolbar-bg: white;
// Form states and alerts
// -------------------------
@@ -302,6 +270,7 @@ $popover-shadow: 0 0 20px $white;
$popover-help-bg: $blue;
$popover-help-color: $gray-6;
+
$popover-error-bg: $btn-danger-bg;
// Tooltips and popovers
@@ -315,7 +284,6 @@ $tooltipBackground: $gray-1;
$tooltipColor: $gray-7;
$tooltipArrowColor: $tooltipBackground; // Used by Angular tooltip
$tooltipBackgroundError: $brand-danger;
-$tooltipBackgroundBrand: $brand-primary;
// images
$checkboxImageUrl: '../img/checkbox_white.png';
@@ -327,8 +295,6 @@ $info-box-border-color: lighten($blue, 20%);
$footer-link-color: $gray-3;
$footer-link-hover: $dark-5;
-// collapse box
-
// json explorer
$json-explorer-default-color: black;
$json-explorer-string-color: green;
@@ -348,9 +314,6 @@ $json-explorer-url-color: blue;
$diff-label-bg: $gray-5;
$diff-label-fg: $gray-2;
-$diff-switch-bg: $gray-7;
-$diff-switch-disabled: $gray-5;
-
$diff-arrow-color: $dark-3;
$diff-group-bg: $gray-7;
@@ -365,6 +328,7 @@ $diff-json-new: #664e33;
$diff-json-changed-fg: $gray-6;
$diff-json-changed-num: $gray-4;
+
$diff-json-icon: $gray-4;
//Submenu
@@ -388,9 +352,7 @@ $checkbox-color: $gray-7;
//Panel Edit
// -------------------------
$panel-editor-shadow: 0px 0px 8px $gray-3;
-$panel-editor-border: 1px solid $dark-4;
$panel-editor-side-menu-shadow: drop-shadow(0 0 2px $gray-3);
-$panel-editor-toolbar-view-bg: $white;
$panel-editor-viz-item-shadow: 0 0 4px $gray-3;
$panel-editor-viz-item-border: 1px solid $gray-3;
$panel-editor-viz-item-shadow-hover: 0 0 4px $blue-light;
@@ -398,7 +360,6 @@ $panel-editor-viz-item-border-hover: 1px solid $blue-light;
$panel-editor-viz-item-bg: $white;
$panel-editor-tabs-line-color: $dark-5;
$panel-editor-viz-item-bg-hover: lighten($blue, 62%);
-$panel-editor-viz-item-bg-hover-active: lighten($orange, 34%);
$panel-options-group-border: none;
$panel-options-group-header-bg: $gray-5;
From 3ef4d20f77fffbbe416e2aa8229aa6f91b6d9474 Mon Sep 17 00:00:00 2001
From: ijin08
Date: Wed, 6 Feb 2019 15:47:03 +0100
Subject: [PATCH 088/228] removed trailing whitespace
---
public/sass/_variables.light.scss | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/public/sass/_variables.light.scss b/public/sass/_variables.light.scss
index 1bc9da354fb..97d7a374765 100644
--- a/public/sass/_variables.light.scss
+++ b/public/sass/_variables.light.scss
@@ -65,9 +65,9 @@ $textShadow: none;
// gradients
$brand-gradient: linear-gradient(
- to right,
- rgba(255, 213, 0, 1) 0%,
- rgba(255, 68, 0, 1) 99%,
+ to right,
+ rgba(255, 213, 0, 1) 0%,
+ rgba(255, 68, 0, 1) 99%,
rgba(255, 68, 0, 1) 100%
);
From 0e7b420d6a9d36ce8a676e09b95c01e8eacbad14 Mon Sep 17 00:00:00 2001
From: ijin08
Date: Wed, 6 Feb 2019 15:51:36 +0100
Subject: [PATCH 089/228] some changes i forgot to save in first push in
variables.dark
---
public/sass/_variables.dark.scss | 15 +++++----------
1 file changed, 5 insertions(+), 10 deletions(-)
diff --git a/public/sass/_variables.dark.scss b/public/sass/_variables.dark.scss
index 1e9ea77d3f1..3f9f58a680f 100644
--- a/public/sass/_variables.dark.scss
+++ b/public/sass/_variables.dark.scss
@@ -64,6 +64,7 @@ $text-color-faint: $dark-5;
$text-color-emphasis: $gray-5;
$text-shadow-faint: 1px 1px 4px rgb(45, 45, 45);
+$textShadow: none;
// gradients
$brand-gradient: linear-gradient(
@@ -130,7 +131,6 @@ $empty-list-cta-bg: $gray-blue;
// Scrollbars
$scrollbarBackground: #404357;
$scrollbarBackground2: #3a3a3a;
-
$scrollbarBorder: black;
// Tables
@@ -144,7 +144,6 @@ $table-bg-hover: $dark-3;
// Buttons
// -------------------------
-
$btn-primary-bg: #ff6600;
$btn-primary-bg-hl: #bc3e06;
@@ -189,6 +188,9 @@ $input-label-bg: $gray-blue;
$input-label-border-color: $dark-3;
$input-color-select-arrow: $white;
+// Input placeholder text color
+$placeholderText: darken($text-color, 25%);
+
// Search
$search-shadow: 0 0 30px 0 $black;
$search-filter-box-bg: $gray-blue;
@@ -211,19 +213,12 @@ $dropdownLinkColorActive: $white;
$dropdownLinkBackgroundHover: $dark-4;
-// COMPONENT VARIABLES
-// --------------------------------------------------
-
-// -------------------------
-$placeholderText: darken($text-color, 25%);
-
// Horizontal forms & lists
// -------------------------
$horizontalComponentOffset: 180px;
-// Wells
+// Navbar
// -------------------------
-
$navbarHeight: 55px;
$navbarBackground: $panel-bg;
From 16e3c193ec6cf34e2c63b8d3a93dde6d86677c3f Mon Sep 17 00:00:00 2001
From: ijin08
Date: Wed, 6 Feb 2019 16:19:56 +0100
Subject: [PATCH 090/228] replaced some hex values with variables
---
public/sass/_variables.dark.scss | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/public/sass/_variables.dark.scss b/public/sass/_variables.dark.scss
index 3f9f58a680f..149a1247b8e 100644
--- a/public/sass/_variables.dark.scss
+++ b/public/sass/_variables.dark.scss
@@ -104,12 +104,12 @@ $page-header-bg: linear-gradient(90deg, #292a2d, black);
$page-header-shadow: inset 0px -4px 14px $dark-2;
$page-header-border-color: $dark-4;
-$divider-border-color: #555;
+$divider-border-color: $gray-1;
// Graphite Target Editor
$tight-form-bg: $dark-3;
-$tight-form-func-bg: #333334;
-$tight-form-func-highlight-bg: #444445;
+$tight-form-func-bg: $dark-4;
+$tight-form-func-highlight-bg: $dark-5;
$modal-backdrop-bg: #353c42;
$code-tag-bg: $dark-1;
From a60124a88cef42bb301a261a34469eeb930475a2 Mon Sep 17 00:00:00 2001
From: Marcus Efraimsson
Date: Wed, 6 Feb 2019 16:21:16 +0100
Subject: [PATCH 091/228] change UserToken from interface to struct
---
pkg/api/login.go | 2 +-
pkg/middleware/middleware.go | 6 +-
pkg/middleware/middleware_test.go | 88 +++-----
pkg/middleware/org_redirect_test.go | 16 +-
pkg/middleware/quota_test.go | 8 +-
pkg/models/context.go | 2 +-
pkg/services/auth/auth.go | 16 +-
pkg/services/auth/authtoken/auth_token.go | 30 +--
.../auth/authtoken/auth_token_test.go | 213 ++++++++++--------
pkg/services/auth/authtoken/model.go | 78 +++----
10 files changed, 244 insertions(+), 215 deletions(-)
diff --git a/pkg/api/login.go b/pkg/api/login.go
index def24f983c1..48f8f237884 100644
--- a/pkg/api/login.go
+++ b/pkg/api/login.go
@@ -137,7 +137,7 @@ func (hs *HTTPServer) loginUserWithUser(user *m.User, c *m.ReqContext) {
hs.log.Error("failed to create auth token", "error", err)
}
- middleware.WriteSessionCookie(c, userToken.GetToken(), hs.Cfg.LoginMaxLifetimeDays)
+ middleware.WriteSessionCookie(c, userToken.UnhashedToken, hs.Cfg.LoginMaxLifetimeDays)
}
func (hs *HTTPServer) Logout(c *m.ReqContext) {
diff --git a/pkg/middleware/middleware.go b/pkg/middleware/middleware.go
index 817372292b9..762a12d09d2 100644
--- a/pkg/middleware/middleware.go
+++ b/pkg/middleware/middleware.go
@@ -182,9 +182,9 @@ func initContextWithToken(authTokenService authtoken.UserAuthTokenService, ctx *
return false
}
- query := m.GetSignedInUserQuery{UserId: token.GetUserId(), OrgId: orgID}
+ query := m.GetSignedInUserQuery{UserId: token.UserId, OrgId: orgID}
if err := bus.Dispatch(&query); err != nil {
- ctx.Logger.Error("failed to get user with id", "userId", token.GetUserId(), "error", err)
+ ctx.Logger.Error("failed to get user with id", "userId", token.UserId, "error", err)
return false
}
@@ -199,7 +199,7 @@ func initContextWithToken(authTokenService authtoken.UserAuthTokenService, ctx *
}
if rotated {
- WriteSessionCookie(ctx, token.GetToken(), setting.LoginMaxLifetimeDays)
+ WriteSessionCookie(ctx, token.UnhashedToken, setting.LoginMaxLifetimeDays)
}
return true
diff --git a/pkg/middleware/middleware_test.go b/pkg/middleware/middleware_test.go
index fdcc56da3bf..5852f9d0bcd 100644
--- a/pkg/middleware/middleware_test.go
+++ b/pkg/middleware/middleware_test.go
@@ -157,10 +157,10 @@ func TestMiddlewareContext(t *testing.T) {
return nil
})
- sc.userAuthTokenService.lookupTokenProvider = func(unhashedToken string) (auth.UserToken, error) {
- return &userTokenImpl{
- userId: 12,
- token: unhashedToken,
+ sc.userAuthTokenService.lookupTokenProvider = func(unhashedToken string) (*auth.UserToken, error) {
+ return &auth.UserToken{
+ UserId: 12,
+ UnhashedToken: unhashedToken,
}, nil
}
@@ -169,8 +169,8 @@ func TestMiddlewareContext(t *testing.T) {
Convey("should init context with user info", func() {
So(sc.context.IsSignedIn, ShouldBeTrue)
So(sc.context.UserId, ShouldEqual, 12)
- So(sc.context.UserToken.GetUserId(), ShouldEqual, 12)
- So(sc.context.UserToken.GetToken(), ShouldEqual, "token")
+ So(sc.context.UserToken.UserId, ShouldEqual, 12)
+ So(sc.context.UserToken.UnhashedToken, ShouldEqual, "token")
})
Convey("should not set cookie", func() {
@@ -186,15 +186,15 @@ func TestMiddlewareContext(t *testing.T) {
return nil
})
- sc.userAuthTokenService.lookupTokenProvider = func(unhashedToken string) (auth.UserToken, error) {
- return &userTokenImpl{
- userId: 12,
- token: unhashedToken,
+ sc.userAuthTokenService.lookupTokenProvider = func(unhashedToken string) (*auth.UserToken, error) {
+ return &auth.UserToken{
+ UserId: 12,
+ UnhashedToken: "",
}, nil
}
- sc.userAuthTokenService.tryRotateTokenProvider = func(userToken auth.UserToken, clientIP, userAgent string) (bool, error) {
- userToken.(fakeUserToken).SetToken("rotated")
+ sc.userAuthTokenService.tryRotateTokenProvider = func(userToken *auth.UserToken, clientIP, userAgent string) (bool, error) {
+ userToken.UnhashedToken = "rotated"
return true, nil
}
@@ -216,8 +216,8 @@ func TestMiddlewareContext(t *testing.T) {
Convey("should init context with user info", func() {
So(sc.context.IsSignedIn, ShouldBeTrue)
So(sc.context.UserId, ShouldEqual, 12)
- So(sc.context.UserToken.GetUserId(), ShouldEqual, 12)
- So(sc.context.UserToken.GetToken(), ShouldEqual, "rotated")
+ So(sc.context.UserToken.UserId, ShouldEqual, 12)
+ So(sc.context.UserToken.UnhashedToken, ShouldEqual, "rotated")
})
Convey("should set cookie", func() {
@@ -228,7 +228,7 @@ func TestMiddlewareContext(t *testing.T) {
middlewareScenario("Invalid/expired auth token in cookie", func(sc *scenarioContext) {
sc.withTokenSessionCookie("token")
- sc.userAuthTokenService.lookupTokenProvider = func(unhashedToken string) (auth.UserToken, error) {
+ sc.userAuthTokenService.lookupTokenProvider = func(unhashedToken string) (*auth.UserToken, error) {
return nil, authtoken.ErrAuthTokenNotFound
}
@@ -679,70 +679,48 @@ func (sc *scenarioContext) exec() {
type scenarioFunc func(c *scenarioContext)
type handlerFunc func(c *m.ReqContext)
-type fakeUserToken interface {
- auth.UserToken
- SetToken(token string)
-}
-
-type userTokenImpl struct {
- userId int64
- token string
-}
-
-func (ut *userTokenImpl) GetUserId() int64 {
- return ut.userId
-}
-
-func (ut *userTokenImpl) GetToken() string {
- return ut.token
-}
-
-func (ut *userTokenImpl) SetToken(token string) {
- ut.token = token
-}
-
type fakeUserAuthTokenService struct {
- createTokenProvider func(userId int64, clientIP, userAgent string) (auth.UserToken, error)
- tryRotateTokenProvider func(token auth.UserToken, clientIP, userAgent string) (bool, error)
- lookupTokenProvider func(unhashedToken string) (auth.UserToken, error)
- revokeTokenProvider func(token auth.UserToken) error
+ createTokenProvider func(userId int64, clientIP, userAgent string) (*auth.UserToken, error)
+ tryRotateTokenProvider func(token *auth.UserToken, clientIP, userAgent string) (bool, error)
+ lookupTokenProvider func(unhashedToken string) (*auth.UserToken, error)
+ revokeTokenProvider func(token *auth.UserToken) error
}
func newFakeUserAuthTokenService() *fakeUserAuthTokenService {
return &fakeUserAuthTokenService{
- createTokenProvider: func(userId int64, clientIP, userAgent string) (auth.UserToken, error) {
- return &userTokenImpl{
- userId: 0,
- token: "",
+ createTokenProvider: func(userId int64, clientIP, userAgent string) (*auth.UserToken, error) {
+ return &auth.UserToken{
+ UserId: 0,
+ UnhashedToken: "",
}, nil
},
- tryRotateTokenProvider: func(token auth.UserToken, clientIP, userAgent string) (bool, error) {
+ tryRotateTokenProvider: func(token *auth.UserToken, clientIP, userAgent string) (bool, error) {
return false, nil
},
- lookupTokenProvider: func(unhashedToken string) (auth.UserToken, error) {
- return &userTokenImpl{
- userId: 0,
- token: "",
+ lookupTokenProvider: func(unhashedToken string) (*auth.UserToken, error) {
+ return &auth.UserToken{
+ UserId: 0,
+ UnhashedToken: "",
}, nil
},
- revokeTokenProvider: func(token auth.UserToken) error {
+ revokeTokenProvider: func(token *auth.UserToken) error {
return nil
},
}
}
-func (s *fakeUserAuthTokenService) CreateToken(userId int64, clientIP, userAgent string) (auth.UserToken, error) {
+func (s *fakeUserAuthTokenService) CreateToken(userId int64, clientIP, userAgent string) (*auth.UserToken, error) {
return s.createTokenProvider(userId, clientIP, userAgent)
}
-func (s *fakeUserAuthTokenService) LookupToken(unhashedToken string) (auth.UserToken, error) {
+func (s *fakeUserAuthTokenService) LookupToken(unhashedToken string) (*auth.UserToken, error) {
return s.lookupTokenProvider(unhashedToken)
}
-func (s *fakeUserAuthTokenService) TryRotateToken(token auth.UserToken, clientIP, userAgent string) (bool, error) {
+func (s *fakeUserAuthTokenService) TryRotateToken(token *auth.UserToken, clientIP, userAgent string) (bool, error) {
return s.tryRotateTokenProvider(token, clientIP, userAgent)
}
-func (s *fakeUserAuthTokenService) RevokeToken(token auth.UserToken) error {
+func (s *fakeUserAuthTokenService) RevokeToken(token *auth.UserToken) error {
return s.revokeTokenProvider(token)
}
diff --git a/pkg/middleware/org_redirect_test.go b/pkg/middleware/org_redirect_test.go
index c7479b3e9bc..0cc88016f3a 100644
--- a/pkg/middleware/org_redirect_test.go
+++ b/pkg/middleware/org_redirect_test.go
@@ -26,10 +26,10 @@ func TestOrgRedirectMiddleware(t *testing.T) {
return nil
})
- sc.userAuthTokenService.lookupTokenProvider = func(unhashedToken string) (auth.UserToken, error) {
- return &userTokenImpl{
- userId: 12,
- token: "",
+ sc.userAuthTokenService.lookupTokenProvider = func(unhashedToken string) (*auth.UserToken, error) {
+ return &auth.UserToken{
+ UserId: 0,
+ UnhashedToken: "",
}, nil
}
@@ -52,10 +52,10 @@ func TestOrgRedirectMiddleware(t *testing.T) {
return nil
})
- sc.userAuthTokenService.lookupTokenProvider = func(unhashedToken string) (auth.UserToken, error) {
- return &userTokenImpl{
- userId: 12,
- token: "",
+ sc.userAuthTokenService.lookupTokenProvider = func(unhashedToken string) (*auth.UserToken, error) {
+ return &auth.UserToken{
+ UserId: 12,
+ UnhashedToken: "",
}, nil
}
diff --git a/pkg/middleware/quota_test.go b/pkg/middleware/quota_test.go
index af22f41deba..df2bf930426 100644
--- a/pkg/middleware/quota_test.go
+++ b/pkg/middleware/quota_test.go
@@ -81,10 +81,10 @@ func TestMiddlewareQuota(t *testing.T) {
return nil
})
- sc.userAuthTokenService.lookupTokenProvider = func(unhashedToken string) (auth.UserToken, error) {
- return &userTokenImpl{
- userId: 12,
- token: "",
+ sc.userAuthTokenService.lookupTokenProvider = func(unhashedToken string) (*auth.UserToken, error) {
+ return &auth.UserToken{
+ UserId: 12,
+ UnhashedToken: "",
}, nil
}
diff --git a/pkg/models/context.go b/pkg/models/context.go
index da63db63f45..bba0024c0f2 100644
--- a/pkg/models/context.go
+++ b/pkg/models/context.go
@@ -14,7 +14,7 @@ import (
type ReqContext struct {
*macaron.Context
*SignedInUser
- UserToken auth.UserToken
+ UserToken *auth.UserToken
// This should only be used by the auth_proxy
Session session.SessionStore
diff --git a/pkg/services/auth/auth.go b/pkg/services/auth/auth.go
index 31316f473f5..727ceffff79 100644
--- a/pkg/services/auth/auth.go
+++ b/pkg/services/auth/auth.go
@@ -1,6 +1,16 @@
package auth
-type UserToken interface {
- GetUserId() int64
- GetToken() string
+type UserToken struct {
+ Id int64
+ UserId int64
+ AuthToken string
+ PrevAuthToken string
+ UserAgent string
+ ClientIp string
+ AuthTokenSeen bool
+ SeenAt int64
+ RotatedAt int64
+ CreatedAt int64
+ UpdatedAt int64
+ UnhashedToken string
}
diff --git a/pkg/services/auth/authtoken/auth_token.go b/pkg/services/auth/authtoken/auth_token.go
index 47aa925fd4d..a993a37dbf3 100644
--- a/pkg/services/auth/authtoken/auth_token.go
+++ b/pkg/services/auth/authtoken/auth_token.go
@@ -40,7 +40,7 @@ func (s *UserAuthTokenServiceImpl) Init() error {
return nil
}
-func (s *UserAuthTokenServiceImpl) CreateToken(userId int64, clientIP, userAgent string) (auth.UserToken, error) {
+func (s *UserAuthTokenServiceImpl) CreateToken(userId int64, clientIP, userAgent string) (*auth.UserToken, error) {
clientIP = util.ParseIPAddress(clientIP)
token, err := util.RandomHex(16)
if err != nil {
@@ -72,10 +72,13 @@ func (s *UserAuthTokenServiceImpl) CreateToken(userId int64, clientIP, userAgent
s.log.Debug("user auth token created", "tokenId", userAuthToken.Id, "userId", userAuthToken.UserId, "clientIP", userAuthToken.ClientIp, "userAgent", userAuthToken.UserAgent, "authToken", userAuthToken.AuthToken)
- return userAuthToken.toUserToken()
+ var userToken auth.UserToken
+ err = userAuthToken.toUserToken(&userToken)
+
+ return &userToken, err
}
-func (s *UserAuthTokenServiceImpl) LookupToken(unhashedToken string) (auth.UserToken, error) {
+func (s *UserAuthTokenServiceImpl) LookupToken(unhashedToken string) (*auth.UserToken, error) {
hashedToken := hashToken(unhashedToken)
if setting.Env == setting.DEV {
s.log.Debug("looking up token", "unhashed", unhashedToken, "hashed", hashedToken)
@@ -133,18 +136,19 @@ func (s *UserAuthTokenServiceImpl) LookupToken(unhashedToken string) (auth.UserT
}
model.UnhashedToken = unhashedToken
- return model.toUserToken()
+
+ var userToken auth.UserToken
+ err = model.toUserToken(&userToken)
+
+ return &userToken, err
}
-func (s *UserAuthTokenServiceImpl) TryRotateToken(token auth.UserToken, clientIP, userAgent string) (bool, error) {
+func (s *UserAuthTokenServiceImpl) TryRotateToken(token *auth.UserToken, clientIP, userAgent string) (bool, error) {
if token == nil {
return false, nil
}
- model, err := extractModelFromToken(token)
- if err != nil {
- return false, err
- }
+ model := userAuthTokenFromUserToken(token)
now := getTime()
@@ -191,21 +195,19 @@ func (s *UserAuthTokenServiceImpl) TryRotateToken(token auth.UserToken, clientIP
s.log.Debug("auth token rotated", "affected", affected, "auth_token_id", model.Id, "userId", model.UserId)
if affected > 0 {
model.UnhashedToken = newToken
+ model.toUserToken(token)
return true, nil
}
return false, nil
}
-func (s *UserAuthTokenServiceImpl) RevokeToken(token auth.UserToken) error {
+func (s *UserAuthTokenServiceImpl) RevokeToken(token *auth.UserToken) error {
if token == nil {
return ErrAuthTokenNotFound
}
- model, err := extractModelFromToken(token)
- if err != nil {
- return err
- }
+ model := userAuthTokenFromUserToken(token)
rowsAffected, err := s.SQLStore.NewSession().Delete(model)
if err != nil {
diff --git a/pkg/services/auth/authtoken/auth_token_test.go b/pkg/services/auth/authtoken/auth_token_test.go
index 7ecb67b2ebf..a1b70db60fb 100644
--- a/pkg/services/auth/authtoken/auth_token_test.go
+++ b/pkg/services/auth/authtoken/auth_token_test.go
@@ -1,12 +1,15 @@
package authtoken
import (
+ "encoding/json"
"testing"
"time"
+ "github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/log"
+ "github.com/grafana/grafana/pkg/services/auth"
"github.com/grafana/grafana/pkg/services/sqlstore"
. "github.com/smartystreets/goconvey/convey"
)
@@ -25,28 +28,24 @@ func TestUserAuthToken(t *testing.T) {
Convey("When creating token", func() {
userToken, err := userAuthTokenService.CreateToken(userID, "192.168.10.11:1234", "some user agent")
So(err, ShouldBeNil)
- model, err := extractModelFromToken(userToken)
- So(err, ShouldBeNil)
- So(model, ShouldNotBeNil)
- So(model.AuthTokenSeen, ShouldBeFalse)
+ So(userToken, ShouldNotBeNil)
+ So(userToken.AuthTokenSeen, ShouldBeFalse)
Convey("When lookup unhashed token should return user auth token", func() {
- userToken, err := userAuthTokenService.LookupToken(model.UnhashedToken)
+ userToken, err := userAuthTokenService.LookupToken(userToken.UnhashedToken)
So(err, ShouldBeNil)
- lookedUpModel, err := extractModelFromToken(userToken)
- So(err, ShouldBeNil)
- So(lookedUpModel, ShouldNotBeNil)
- So(lookedUpModel.UserId, ShouldEqual, userID)
- So(lookedUpModel.AuthTokenSeen, ShouldBeTrue)
+ So(userToken, ShouldNotBeNil)
+ So(userToken.UserId, ShouldEqual, userID)
+ So(userToken.AuthTokenSeen, ShouldBeTrue)
- storedAuthToken, err := ctx.getAuthTokenByID(lookedUpModel.Id)
+ storedAuthToken, err := ctx.getAuthTokenByID(userToken.Id)
So(err, ShouldBeNil)
So(storedAuthToken, ShouldNotBeNil)
So(storedAuthToken.AuthTokenSeen, ShouldBeTrue)
})
Convey("When lookup hashed token should return user auth token not found error", func() {
- userToken, err := userAuthTokenService.LookupToken(model.AuthToken)
+ userToken, err := userAuthTokenService.LookupToken(userToken.AuthToken)
So(err, ShouldEqual, ErrAuthTokenNotFound)
So(userToken, ShouldBeNil)
})
@@ -55,7 +54,7 @@ func TestUserAuthToken(t *testing.T) {
err = userAuthTokenService.RevokeToken(userToken)
So(err, ShouldBeNil)
- model, err := ctx.getAuthTokenByID(model.Id)
+ model, err := ctx.getAuthTokenByID(userToken.Id)
So(err, ShouldBeNil)
So(model, ShouldBeNil)
})
@@ -66,10 +65,8 @@ func TestUserAuthToken(t *testing.T) {
})
Convey("revoking non-existing token should return error", func() {
- model.Id = 1000
- nonExistingToken, err := model.toUserToken()
- So(err, ShouldBeNil)
- err = userAuthTokenService.RevokeToken(nonExistingToken)
+ userToken.Id = 1000
+ err = userAuthTokenService.RevokeToken(userToken)
So(err, ShouldEqual, ErrAuthTokenNotFound)
})
})
@@ -77,17 +74,8 @@ func TestUserAuthToken(t *testing.T) {
Convey("expires correctly", func() {
userToken, err := userAuthTokenService.CreateToken(userID, "192.168.10.11:1234", "some user agent")
So(err, ShouldBeNil)
- model, err := extractModelFromToken(userToken)
- So(err, ShouldBeNil)
- So(model, ShouldNotBeNil)
- _, err = userAuthTokenService.LookupToken(model.UnhashedToken)
- So(err, ShouldBeNil)
-
- model, err = ctx.getAuthTokenByID(model.Id)
- So(err, ShouldBeNil)
-
- userToken, err = model.toUserToken()
+ userToken, err = userAuthTokenService.LookupToken(userToken.UnhashedToken)
So(err, ShouldBeNil)
getTime = func() time.Time {
@@ -98,14 +86,14 @@ func TestUserAuthToken(t *testing.T) {
So(err, ShouldBeNil)
So(rotated, ShouldBeTrue)
- _, err = userAuthTokenService.LookupToken(model.UnhashedToken)
+ userToken, err = userAuthTokenService.LookupToken(userToken.UnhashedToken)
So(err, ShouldBeNil)
- stillGood, err := userAuthTokenService.LookupToken(model.UnhashedToken)
+ stillGood, err := userAuthTokenService.LookupToken(userToken.UnhashedToken)
So(err, ShouldBeNil)
So(stillGood, ShouldNotBeNil)
- model, err = ctx.getAuthTokenByID(model.Id)
+ model, err := ctx.getAuthTokenByID(userToken.Id)
So(err, ShouldBeNil)
Convey("when rotated_at is 6:59:59 ago should find token", func() {
@@ -113,7 +101,7 @@ func TestUserAuthToken(t *testing.T) {
return time.Unix(model.RotatedAt, 0).Add(24 * 7 * time.Hour).Add(-time.Second)
}
- stillGood, err = userAuthTokenService.LookupToken(stillGood.GetToken())
+ stillGood, err = userAuthTokenService.LookupToken(stillGood.UnhashedToken)
So(err, ShouldBeNil)
So(stillGood, ShouldNotBeNil)
})
@@ -123,7 +111,7 @@ func TestUserAuthToken(t *testing.T) {
return time.Unix(model.RotatedAt, 0).Add(24 * 7 * time.Hour)
}
- notGood, err := userAuthTokenService.LookupToken(userToken.GetToken())
+ notGood, err := userAuthTokenService.LookupToken(userToken.UnhashedToken)
So(err, ShouldEqual, ErrAuthTokenNotFound)
So(notGood, ShouldBeNil)
})
@@ -137,7 +125,7 @@ func TestUserAuthToken(t *testing.T) {
return time.Unix(model.CreatedAt, 0).Add(24 * 30 * time.Hour).Add(-time.Second)
}
- stillGood, err = userAuthTokenService.LookupToken(stillGood.GetToken())
+ stillGood, err = userAuthTokenService.LookupToken(stillGood.UnhashedToken)
So(err, ShouldBeNil)
So(stillGood, ShouldNotBeNil)
})
@@ -151,7 +139,7 @@ func TestUserAuthToken(t *testing.T) {
return time.Unix(model.CreatedAt, 0).Add(24 * 30 * time.Hour)
}
- notGood, err := userAuthTokenService.LookupToken(userToken.GetToken())
+ notGood, err := userAuthTokenService.LookupToken(userToken.UnhashedToken)
So(err, ShouldEqual, ErrAuthTokenNotFound)
So(notGood, ShouldBeNil)
})
@@ -160,37 +148,35 @@ func TestUserAuthToken(t *testing.T) {
Convey("can properly rotate tokens", func() {
userToken, err := userAuthTokenService.CreateToken(userID, "192.168.10.11:1234", "some user agent")
So(err, ShouldBeNil)
- model, err := extractModelFromToken(userToken)
- So(err, ShouldBeNil)
- So(model, ShouldNotBeNil)
- prevToken := model.AuthToken
- unhashedPrev := model.UnhashedToken
+ prevToken := userToken.AuthToken
+ unhashedPrev := userToken.UnhashedToken
rotated, err := userAuthTokenService.TryRotateToken(userToken, "192.168.10.12:1234", "a new user agent")
So(err, ShouldBeNil)
So(rotated, ShouldBeFalse)
- updated, err := ctx.markAuthTokenAsSeen(model.Id)
+ updated, err := ctx.markAuthTokenAsSeen(userToken.Id)
So(err, ShouldBeNil)
So(updated, ShouldBeTrue)
- model, err = ctx.getAuthTokenByID(model.Id)
- So(err, ShouldBeNil)
- tok, err := model.toUserToken()
+ model, err := ctx.getAuthTokenByID(userToken.Id)
So(err, ShouldBeNil)
+ var tok auth.UserToken
+ model.toUserToken(&tok)
+
getTime = func() time.Time {
return t.Add(time.Hour)
}
- rotated, err = userAuthTokenService.TryRotateToken(tok, "192.168.10.12:1234", "a new user agent")
+ rotated, err = userAuthTokenService.TryRotateToken(&tok, "192.168.10.12:1234", "a new user agent")
So(err, ShouldBeNil)
So(rotated, ShouldBeTrue)
- unhashedToken := model.UnhashedToken
+ unhashedToken := tok.UnhashedToken
- model, err = ctx.getAuthTokenByID(model.Id)
+ model, err = ctx.getAuthTokenByID(tok.Id)
So(err, ShouldBeNil)
model.UnhashedToken = unhashedToken
@@ -205,17 +191,15 @@ func TestUserAuthToken(t *testing.T) {
lookedUpUserToken, err := userAuthTokenService.LookupToken(model.UnhashedToken)
So(err, ShouldBeNil)
- lookedUpModel, err := extractModelFromToken(lookedUpUserToken)
- So(err, ShouldBeNil)
- So(lookedUpModel, ShouldNotBeNil)
- So(lookedUpModel.AuthTokenSeen, ShouldBeTrue)
- So(lookedUpModel.SeenAt, ShouldEqual, getTime().Unix())
+ So(lookedUpUserToken, ShouldNotBeNil)
+ So(lookedUpUserToken.AuthTokenSeen, ShouldBeTrue)
+ So(lookedUpUserToken.SeenAt, ShouldEqual, getTime().Unix())
lookedUpUserToken, err = userAuthTokenService.LookupToken(unhashedPrev)
So(err, ShouldBeNil)
- So(lookedUpModel, ShouldNotBeNil)
- So(lookedUpModel.Id, ShouldEqual, model.Id)
- So(lookedUpModel.AuthTokenSeen, ShouldBeTrue)
+ So(lookedUpUserToken, ShouldNotBeNil)
+ So(lookedUpUserToken.Id, ShouldEqual, model.Id)
+ So(lookedUpUserToken.AuthTokenSeen, ShouldBeTrue)
getTime = func() time.Time {
return t.Add(time.Hour + (2 * time.Minute))
@@ -223,12 +207,10 @@ func TestUserAuthToken(t *testing.T) {
lookedUpUserToken, err = userAuthTokenService.LookupToken(unhashedPrev)
So(err, ShouldBeNil)
- lookedUpModel, err = extractModelFromToken(lookedUpUserToken)
- So(err, ShouldBeNil)
- So(lookedUpModel, ShouldNotBeNil)
- So(lookedUpModel.AuthTokenSeen, ShouldBeTrue)
+ So(lookedUpUserToken, ShouldNotBeNil)
+ So(lookedUpUserToken.AuthTokenSeen, ShouldBeTrue)
- lookedUpModel, err = ctx.getAuthTokenByID(lookedUpModel.Id)
+ lookedUpModel, err := ctx.getAuthTokenByID(lookedUpUserToken.Id)
So(err, ShouldBeNil)
So(lookedUpModel, ShouldNotBeNil)
So(lookedUpModel.AuthTokenSeen, ShouldBeFalse)
@@ -237,7 +219,7 @@ func TestUserAuthToken(t *testing.T) {
So(err, ShouldBeNil)
So(rotated, ShouldBeTrue)
- model, err = ctx.getAuthTokenByID(model.Id)
+ model, err = ctx.getAuthTokenByID(userToken.Id)
So(err, ShouldBeNil)
So(model, ShouldNotBeNil)
So(model.SeenAt, ShouldEqual, 0)
@@ -246,11 +228,9 @@ func TestUserAuthToken(t *testing.T) {
Convey("keeps prev token valid for 1 minute after it is confirmed", func() {
userToken, err := userAuthTokenService.CreateToken(userID, "192.168.10.11:1234", "some user agent")
So(err, ShouldBeNil)
- model, err := extractModelFromToken(userToken)
- So(err, ShouldBeNil)
- So(model, ShouldNotBeNil)
+ So(userToken, ShouldNotBeNil)
- lookedUpUserToken, err := userAuthTokenService.LookupToken(model.UnhashedToken)
+ lookedUpUserToken, err := userAuthTokenService.LookupToken(userToken.UnhashedToken)
So(err, ShouldBeNil)
So(lookedUpUserToken, ShouldNotBeNil)
@@ -258,7 +238,7 @@ func TestUserAuthToken(t *testing.T) {
return t.Add(10 * time.Minute)
}
- prevToken := model.UnhashedToken
+ prevToken := userToken.UnhashedToken
rotated, err := userAuthTokenService.TryRotateToken(userToken, "1.1.1.1", "firefox")
So(err, ShouldBeNil)
So(rotated, ShouldBeTrue)
@@ -267,7 +247,7 @@ func TestUserAuthToken(t *testing.T) {
return t.Add(20 * time.Minute)
}
- currentUserToken, err := userAuthTokenService.LookupToken(model.UnhashedToken)
+ currentUserToken, err := userAuthTokenService.LookupToken(userToken.UnhashedToken)
So(err, ShouldBeNil)
So(currentUserToken, ShouldNotBeNil)
@@ -279,23 +259,17 @@ func TestUserAuthToken(t *testing.T) {
Convey("will not mark token unseen when prev and current are the same", func() {
userToken, err := userAuthTokenService.CreateToken(userID, "192.168.10.11:1234", "some user agent")
So(err, ShouldBeNil)
- model, err := extractModelFromToken(userToken)
- So(err, ShouldBeNil)
- So(model, ShouldNotBeNil)
+ So(userToken, ShouldNotBeNil)
- lookedUpUserToken, err := userAuthTokenService.LookupToken(model.UnhashedToken)
+ lookedUpUserToken, err := userAuthTokenService.LookupToken(userToken.UnhashedToken)
So(err, ShouldBeNil)
- lookedUpModel, err := extractModelFromToken(lookedUpUserToken)
- So(err, ShouldBeNil)
- So(lookedUpModel, ShouldNotBeNil)
+ So(lookedUpUserToken, ShouldNotBeNil)
- lookedUpUserToken, err = userAuthTokenService.LookupToken(model.UnhashedToken)
+ lookedUpUserToken, err = userAuthTokenService.LookupToken(userToken.UnhashedToken)
So(err, ShouldBeNil)
- lookedUpModel, err = extractModelFromToken(lookedUpUserToken)
- So(err, ShouldBeNil)
- So(lookedUpModel, ShouldNotBeNil)
+ So(lookedUpUserToken, ShouldNotBeNil)
- lookedUpModel, err = ctx.getAuthTokenByID(lookedUpModel.Id)
+ lookedUpModel, err := ctx.getAuthTokenByID(lookedUpUserToken.Id)
So(err, ShouldBeNil)
So(lookedUpModel, ShouldNotBeNil)
So(lookedUpModel.AuthTokenSeen, ShouldBeTrue)
@@ -304,14 +278,12 @@ func TestUserAuthToken(t *testing.T) {
Convey("Rotate token", func() {
userToken, err := userAuthTokenService.CreateToken(userID, "192.168.10.11:1234", "some user agent")
So(err, ShouldBeNil)
- model, err := extractModelFromToken(userToken)
- So(err, ShouldBeNil)
- So(model, ShouldNotBeNil)
+ So(userToken, ShouldNotBeNil)
- prevToken := model.AuthToken
+ prevToken := userToken.AuthToken
Convey("Should rotate current token and previous token when auth token seen", func() {
- updated, err := ctx.markAuthTokenAsSeen(model.Id)
+ updated, err := ctx.markAuthTokenAsSeen(userToken.Id)
So(err, ShouldBeNil)
So(updated, ShouldBeTrue)
@@ -323,7 +295,7 @@ func TestUserAuthToken(t *testing.T) {
So(err, ShouldBeNil)
So(rotated, ShouldBeTrue)
- storedToken, err := ctx.getAuthTokenByID(model.Id)
+ storedToken, err := ctx.getAuthTokenByID(userToken.Id)
So(err, ShouldBeNil)
So(storedToken, ShouldNotBeNil)
So(storedToken.AuthTokenSeen, ShouldBeFalse)
@@ -332,7 +304,7 @@ func TestUserAuthToken(t *testing.T) {
prevToken = storedToken.AuthToken
- updated, err = ctx.markAuthTokenAsSeen(model.Id)
+ updated, err = ctx.markAuthTokenAsSeen(userToken.Id)
So(err, ShouldBeNil)
So(updated, ShouldBeTrue)
@@ -344,7 +316,7 @@ func TestUserAuthToken(t *testing.T) {
So(err, ShouldBeNil)
So(rotated, ShouldBeTrue)
- storedToken, err = ctx.getAuthTokenByID(model.Id)
+ storedToken, err = ctx.getAuthTokenByID(userToken.Id)
So(err, ShouldBeNil)
So(storedToken, ShouldNotBeNil)
So(storedToken.AuthTokenSeen, ShouldBeFalse)
@@ -353,7 +325,7 @@ func TestUserAuthToken(t *testing.T) {
})
Convey("Should rotate current token, but keep previous token when auth token not seen", func() {
- model.RotatedAt = getTime().Add(-2 * time.Minute).Unix()
+ userToken.RotatedAt = getTime().Add(-2 * time.Minute).Unix()
getTime = func() time.Time {
return t.Add(2 * time.Minute)
@@ -363,7 +335,7 @@ func TestUserAuthToken(t *testing.T) {
So(err, ShouldBeNil)
So(rotated, ShouldBeTrue)
- storedToken, err := ctx.getAuthTokenByID(model.Id)
+ storedToken, err := ctx.getAuthTokenByID(userToken.Id)
So(err, ShouldBeNil)
So(storedToken, ShouldNotBeNil)
So(storedToken.AuthTokenSeen, ShouldBeFalse)
@@ -372,6 +344,71 @@ func TestUserAuthToken(t *testing.T) {
})
})
+ Convey("When populating userAuthToken from UserToken should copy all properties", func() {
+ ut := auth.UserToken{
+ Id: 1,
+ UserId: 2,
+ AuthToken: "a",
+ PrevAuthToken: "b",
+ UserAgent: "c",
+ ClientIp: "d",
+ AuthTokenSeen: true,
+ SeenAt: 3,
+ RotatedAt: 4,
+ CreatedAt: 5,
+ UpdatedAt: 6,
+ UnhashedToken: "e",
+ }
+ utBytes, err := json.Marshal(ut)
+ So(err, ShouldBeNil)
+ utJSON, err := simplejson.NewJson(utBytes)
+ So(err, ShouldBeNil)
+ utMap := utJSON.MustMap()
+
+ var uat userAuthToken
+ uat.fromUserToken(&ut)
+ uatBytes, err := json.Marshal(uat)
+ So(err, ShouldBeNil)
+ uatJSON, err := simplejson.NewJson(uatBytes)
+ So(err, ShouldBeNil)
+ uatMap := uatJSON.MustMap()
+
+ So(uatMap, ShouldResemble, utMap)
+ })
+
+ Convey("When populating userToken from userAuthToken should copy all properties", func() {
+ uat := userAuthToken{
+ Id: 1,
+ UserId: 2,
+ AuthToken: "a",
+ PrevAuthToken: "b",
+ UserAgent: "c",
+ ClientIp: "d",
+ AuthTokenSeen: true,
+ SeenAt: 3,
+ RotatedAt: 4,
+ CreatedAt: 5,
+ UpdatedAt: 6,
+ UnhashedToken: "e",
+ }
+ uatBytes, err := json.Marshal(uat)
+ So(err, ShouldBeNil)
+ uatJSON, err := simplejson.NewJson(uatBytes)
+ So(err, ShouldBeNil)
+ uatMap := uatJSON.MustMap()
+
+ var ut auth.UserToken
+ err = uat.toUserToken(&ut)
+ So(err, ShouldBeNil)
+ utBytes, err := json.Marshal(ut)
+ So(err, ShouldBeNil)
+ utJSON, err := simplejson.NewJson(utBytes)
+ So(err, ShouldBeNil)
+ utMap := utJSON.MustMap()
+
+ So(utMap, ShouldResemble, uatMap)
+ })
+
Reset(func() {
getTime = time.Now
})
diff --git a/pkg/services/auth/authtoken/model.go b/pkg/services/auth/authtoken/model.go
index 8bd89c68b04..730f615e294 100644
--- a/pkg/services/auth/authtoken/model.go
+++ b/pkg/services/auth/authtoken/model.go
@@ -27,50 +27,52 @@ type userAuthToken struct {
UnhashedToken string `xorm:"-"`
}
-func (uat *userAuthToken) toUserToken() (auth.UserToken, error) {
+func userAuthTokenFromUserToken(ut *auth.UserToken) *userAuthToken {
+ var uat userAuthToken
+ uat.fromUserToken(ut)
+ return &uat
+}
+
+func (uat *userAuthToken) fromUserToken(ut *auth.UserToken) {
+ uat.Id = ut.Id
+ uat.UserId = ut.UserId
+ uat.AuthToken = ut.AuthToken
+ uat.PrevAuthToken = ut.PrevAuthToken
+ uat.UserAgent = ut.UserAgent
+ uat.ClientIp = ut.ClientIp
+ uat.AuthTokenSeen = ut.AuthTokenSeen
+ uat.SeenAt = ut.SeenAt
+ uat.RotatedAt = ut.RotatedAt
+ uat.CreatedAt = ut.CreatedAt
+ uat.UpdatedAt = ut.UpdatedAt
+ uat.UnhashedToken = ut.UnhashedToken
+}
+
+func (uat *userAuthToken) toUserToken(ut *auth.UserToken) error {
if uat == nil {
- return nil, fmt.Errorf("needs pointer to userAuthToken struct")
+ return fmt.Errorf("needs pointer to userAuthToken struct")
}
- return &userTokenImpl{
- userAuthToken: uat,
- }, nil
-}
+ ut.Id = uat.Id
+ ut.UserId = uat.UserId
+ ut.AuthToken = uat.AuthToken
+ ut.PrevAuthToken = uat.PrevAuthToken
+ ut.UserAgent = uat.UserAgent
+ ut.ClientIp = uat.ClientIp
+ ut.AuthTokenSeen = uat.AuthTokenSeen
+ ut.SeenAt = uat.SeenAt
+ ut.RotatedAt = uat.RotatedAt
+ ut.CreatedAt = uat.CreatedAt
+ ut.UpdatedAt = uat.UpdatedAt
+ ut.UnhashedToken = uat.UnhashedToken
-type userToken interface {
- auth.UserToken
- GetModel() *userAuthToken
-}
-
-type userTokenImpl struct {
- *userAuthToken
-}
-
-func (ut *userTokenImpl) GetUserId() int64 {
- return ut.UserId
-}
-
-func (ut *userTokenImpl) GetToken() string {
- return ut.UnhashedToken
-}
-
-func (ut *userTokenImpl) GetModel() *userAuthToken {
- return ut.userAuthToken
-}
-
-func extractModelFromToken(token auth.UserToken) (*userAuthToken, error) {
- ut, ok := token.(userToken)
- if !ok {
- return nil, fmt.Errorf("failed to cast token")
- }
-
- return ut.GetModel(), nil
+ return nil
}
// UserAuthTokenService are used for generating and validating user auth tokens
type UserAuthTokenService interface {
- CreateToken(userId int64, clientIP, userAgent string) (auth.UserToken, error)
- LookupToken(unhashedToken string) (auth.UserToken, error)
- TryRotateToken(token auth.UserToken, clientIP, userAgent string) (bool, error)
- RevokeToken(token auth.UserToken) error
+ CreateToken(userId int64, clientIP, userAgent string) (*auth.UserToken, error)
+ LookupToken(unhashedToken string) (*auth.UserToken, error)
+ TryRotateToken(token *auth.UserToken, clientIP, userAgent string) (bool, error)
+ RevokeToken(token *auth.UserToken) error
}
From eb879062f992b38779380c4f12e974271d196b19 Mon Sep 17 00:00:00 2001
From: corpglory-dev
Date: Wed, 6 Feb 2019 18:32:22 +0300
Subject: [PATCH 092/228] Rename version_test to version.test
---
public/test/core/utils/{version_test.ts => version.test.ts} | 0
1 file changed, 0 insertions(+), 0 deletions(-)
rename public/test/core/utils/{version_test.ts => version.test.ts} (100%)
diff --git a/public/test/core/utils/version_test.ts b/public/test/core/utils/version.test.ts
similarity index 100%
rename from public/test/core/utils/version_test.ts
rename to public/test/core/utils/version.test.ts
From 43b5eba8ee1196c94067c0804602ffb2695a0427 Mon Sep 17 00:00:00 2001
From: corpglory-dev
Date: Wed, 6 Feb 2019 18:33:24 +0300
Subject: [PATCH 093/228] Add failing test
---
public/test/core/utils/version.test.ts | 1 +
1 file changed, 1 insertion(+)
diff --git a/public/test/core/utils/version.test.ts b/public/test/core/utils/version.test.ts
index 91330389e24..47a31d99e69 100644
--- a/public/test/core/utils/version.test.ts
+++ b/public/test/core/utils/version.test.ts
@@ -44,6 +44,7 @@ describe('SemVersion', () => {
{ values: ['3.1.1-beta1', '3.1'], expected: true },
{ values: ['3.4.5', '4'], expected: false },
{ values: ['3.4.5', '3.5'], expected: false },
+ { values: ['6.0.0', '5.2.0'], expected: true },
];
cases.forEach(testCase => {
expect(isVersionGtOrEq(testCase.values[0], testCase.values[1])).toBe(testCase.expected);
From 8678620730b26dfa8a07bd0f01d9345d6b266ffd Mon Sep 17 00:00:00 2001
From: Marcus Efraimsson
Date: Wed, 6 Feb 2019 16:45:48 +0100
Subject: [PATCH 094/228] move UserToken and UserTokenService to models package
---
pkg/api/http_server.go | 17 ++++----
pkg/api/login.go | 4 +-
pkg/middleware/middleware.go | 5 +--
pkg/middleware/middleware_test.go | 41 +++++++++----------
pkg/middleware/org_redirect_test.go | 10 ++---
pkg/middleware/quota_test.go | 5 +--
pkg/models/context.go | 3 +-
pkg/models/user_token.go | 32 +++++++++++++++
pkg/services/auth/auth.go | 15 -------
pkg/services/auth/authtoken/auth_token.go | 15 ++++---
.../auth/authtoken/auth_token_test.go | 8 ++--
pkg/services/auth/authtoken/model.go | 16 ++------
12 files changed, 85 insertions(+), 86 deletions(-)
create mode 100644 pkg/models/user_token.go
diff --git a/pkg/api/http_server.go b/pkg/api/http_server.go
index a0a65d73244..cadf6896bf4 100644
--- a/pkg/api/http_server.go
+++ b/pkg/api/http_server.go
@@ -21,7 +21,6 @@ import (
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/plugins"
"github.com/grafana/grafana/pkg/registry"
- "github.com/grafana/grafana/pkg/services/auth/authtoken"
"github.com/grafana/grafana/pkg/services/cache"
"github.com/grafana/grafana/pkg/services/datasources"
"github.com/grafana/grafana/pkg/services/hooks"
@@ -48,14 +47,14 @@ type HTTPServer struct {
streamManager *live.StreamManager
httpSrv *http.Server
- RouteRegister routing.RouteRegister `inject:""`
- Bus bus.Bus `inject:""`
- RenderService rendering.Service `inject:""`
- Cfg *setting.Cfg `inject:""`
- HooksService *hooks.HooksService `inject:""`
- CacheService *cache.CacheService `inject:""`
- DatasourceCache datasources.CacheService `inject:""`
- AuthTokenService authtoken.UserAuthTokenService `inject:""`
+ RouteRegister routing.RouteRegister `inject:""`
+ Bus bus.Bus `inject:""`
+ RenderService rendering.Service `inject:""`
+ Cfg *setting.Cfg `inject:""`
+ HooksService *hooks.HooksService `inject:""`
+ CacheService *cache.CacheService `inject:""`
+ DatasourceCache datasources.CacheService `inject:""`
+ AuthTokenService models.UserTokenService `inject:""`
}
func (hs *HTTPServer) Init() error {
diff --git a/pkg/api/login.go b/pkg/api/login.go
index 48f8f237884..106a48dd6a8 100644
--- a/pkg/api/login.go
+++ b/pkg/api/login.go
@@ -5,8 +5,6 @@ import (
"net/http"
"net/url"
- "github.com/grafana/grafana/pkg/services/auth/authtoken"
-
"github.com/grafana/grafana/pkg/api/dtos"
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/log"
@@ -141,7 +139,7 @@ func (hs *HTTPServer) loginUserWithUser(user *m.User, c *m.ReqContext) {
}
func (hs *HTTPServer) Logout(c *m.ReqContext) {
- if err := hs.AuthTokenService.RevokeToken(c.UserToken); err != nil && err != authtoken.ErrAuthTokenNotFound {
+ if err := hs.AuthTokenService.RevokeToken(c.UserToken); err != nil && err != m.ErrUserTokenNotFound {
hs.log.Error("failed to revoke auth token", "error", err)
}
diff --git a/pkg/middleware/middleware.go b/pkg/middleware/middleware.go
index 762a12d09d2..fa335eb10d9 100644
--- a/pkg/middleware/middleware.go
+++ b/pkg/middleware/middleware.go
@@ -10,7 +10,6 @@ import (
"github.com/grafana/grafana/pkg/components/apikeygen"
"github.com/grafana/grafana/pkg/log"
m "github.com/grafana/grafana/pkg/models"
- "github.com/grafana/grafana/pkg/services/auth/authtoken"
"github.com/grafana/grafana/pkg/services/session"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/util"
@@ -24,7 +23,7 @@ var (
ReqOrgAdmin = RoleAuth(m.ROLE_ADMIN)
)
-func GetContextHandler(ats authtoken.UserAuthTokenService) macaron.Handler {
+func GetContextHandler(ats m.UserTokenService) macaron.Handler {
return func(c *macaron.Context) {
ctx := &m.ReqContext{
Context: c,
@@ -169,7 +168,7 @@ func initContextWithBasicAuth(ctx *m.ReqContext, orgId int64) bool {
return true
}
-func initContextWithToken(authTokenService authtoken.UserAuthTokenService, ctx *m.ReqContext, orgID int64) bool {
+func initContextWithToken(authTokenService m.UserTokenService, ctx *m.ReqContext, orgID int64) bool {
rawToken := ctx.GetCookie(setting.LoginCookieName)
if rawToken == "" {
return false
diff --git a/pkg/middleware/middleware_test.go b/pkg/middleware/middleware_test.go
index 5852f9d0bcd..7908be6f225 100644
--- a/pkg/middleware/middleware_test.go
+++ b/pkg/middleware/middleware_test.go
@@ -11,7 +11,6 @@ import (
msession "github.com/go-macaron/session"
"github.com/grafana/grafana/pkg/bus"
m "github.com/grafana/grafana/pkg/models"
- "github.com/grafana/grafana/pkg/services/auth"
"github.com/grafana/grafana/pkg/services/auth/authtoken"
"github.com/grafana/grafana/pkg/services/session"
"github.com/grafana/grafana/pkg/setting"
@@ -157,8 +156,8 @@ func TestMiddlewareContext(t *testing.T) {
return nil
})
- sc.userAuthTokenService.lookupTokenProvider = func(unhashedToken string) (*auth.UserToken, error) {
- return &auth.UserToken{
+ sc.userAuthTokenService.lookupTokenProvider = func(unhashedToken string) (*m.UserToken, error) {
+ return &m.UserToken{
UserId: 12,
UnhashedToken: unhashedToken,
}, nil
@@ -186,14 +185,14 @@ func TestMiddlewareContext(t *testing.T) {
return nil
})
- sc.userAuthTokenService.lookupTokenProvider = func(unhashedToken string) (*auth.UserToken, error) {
- return &auth.UserToken{
+ sc.userAuthTokenService.lookupTokenProvider = func(unhashedToken string) (*m.UserToken, error) {
+ return &m.UserToken{
UserId: 12,
UnhashedToken: "",
}, nil
}
- sc.userAuthTokenService.tryRotateTokenProvider = func(userToken *auth.UserToken, clientIP, userAgent string) (bool, error) {
+ sc.userAuthTokenService.tryRotateTokenProvider = func(userToken *m.UserToken, clientIP, userAgent string) (bool, error) {
userToken.UnhashedToken = "rotated"
return true, nil
}
@@ -228,7 +227,7 @@ func TestMiddlewareContext(t *testing.T) {
middlewareScenario("Invalid/expired auth token in cookie", func(sc *scenarioContext) {
sc.withTokenSessionCookie("token")
- sc.userAuthTokenService.lookupTokenProvider = func(unhashedToken string) (*auth.UserToken, error) {
+ sc.userAuthTokenService.lookupTokenProvider = func(unhashedToken string) (*m.UserToken, error) {
return nil, authtoken.ErrAuthTokenNotFound
}
@@ -680,47 +679,47 @@ type scenarioFunc func(c *scenarioContext)
type handlerFunc func(c *m.ReqContext)
type fakeUserAuthTokenService struct {
- createTokenProvider func(userId int64, clientIP, userAgent string) (*auth.UserToken, error)
- tryRotateTokenProvider func(token *auth.UserToken, clientIP, userAgent string) (bool, error)
- lookupTokenProvider func(unhashedToken string) (*auth.UserToken, error)
- revokeTokenProvider func(token *auth.UserToken) error
+ createTokenProvider func(userId int64, clientIP, userAgent string) (*m.UserToken, error)
+ tryRotateTokenProvider func(token *m.UserToken, clientIP, userAgent string) (bool, error)
+ lookupTokenProvider func(unhashedToken string) (*m.UserToken, error)
+ revokeTokenProvider func(token *m.UserToken) error
}
func newFakeUserAuthTokenService() *fakeUserAuthTokenService {
return &fakeUserAuthTokenService{
- createTokenProvider: func(userId int64, clientIP, userAgent string) (*auth.UserToken, error) {
- return &auth.UserToken{
+ createTokenProvider: func(userId int64, clientIP, userAgent string) (*m.UserToken, error) {
+ return &m.UserToken{
UserId: 0,
UnhashedToken: "",
}, nil
},
- tryRotateTokenProvider: func(token *auth.UserToken, clientIP, userAgent string) (bool, error) {
+ tryRotateTokenProvider: func(token *m.UserToken, clientIP, userAgent string) (bool, error) {
return false, nil
},
- lookupTokenProvider: func(unhashedToken string) (*auth.UserToken, error) {
- return &auth.UserToken{
+ lookupTokenProvider: func(unhashedToken string) (*m.UserToken, error) {
+ return &m.UserToken{
UserId: 0,
UnhashedToken: "",
}, nil
},
- revokeTokenProvider: func(token *auth.UserToken) error {
+ revokeTokenProvider: func(token *m.UserToken) error {
return nil
},
}
}
-func (s *fakeUserAuthTokenService) CreateToken(userId int64, clientIP, userAgent string) (*auth.UserToken, error) {
+func (s *fakeUserAuthTokenService) CreateToken(userId int64, clientIP, userAgent string) (*m.UserToken, error) {
return s.createTokenProvider(userId, clientIP, userAgent)
}
-func (s *fakeUserAuthTokenService) LookupToken(unhashedToken string) (*auth.UserToken, error) {
+func (s *fakeUserAuthTokenService) LookupToken(unhashedToken string) (*m.UserToken, error) {
return s.lookupTokenProvider(unhashedToken)
}
-func (s *fakeUserAuthTokenService) TryRotateToken(token *auth.UserToken, clientIP, userAgent string) (bool, error) {
+func (s *fakeUserAuthTokenService) TryRotateToken(token *m.UserToken, clientIP, userAgent string) (bool, error) {
return s.tryRotateTokenProvider(token, clientIP, userAgent)
}
-func (s *fakeUserAuthTokenService) RevokeToken(token *auth.UserToken) error {
+func (s *fakeUserAuthTokenService) RevokeToken(token *m.UserToken) error {
return s.revokeTokenProvider(token)
}
diff --git a/pkg/middleware/org_redirect_test.go b/pkg/middleware/org_redirect_test.go
index 0cc88016f3a..e01d1a68d21 100644
--- a/pkg/middleware/org_redirect_test.go
+++ b/pkg/middleware/org_redirect_test.go
@@ -3,8 +3,6 @@ package middleware
import (
"testing"
- "github.com/grafana/grafana/pkg/services/auth"
-
"fmt"
"github.com/grafana/grafana/pkg/bus"
@@ -26,8 +24,8 @@ func TestOrgRedirectMiddleware(t *testing.T) {
return nil
})
- sc.userAuthTokenService.lookupTokenProvider = func(unhashedToken string) (*auth.UserToken, error) {
- return &auth.UserToken{
+ sc.userAuthTokenService.lookupTokenProvider = func(unhashedToken string) (*m.UserToken, error) {
+ return &m.UserToken{
UserId: 0,
UnhashedToken: "",
}, nil
@@ -52,8 +50,8 @@ func TestOrgRedirectMiddleware(t *testing.T) {
return nil
})
- sc.userAuthTokenService.lookupTokenProvider = func(unhashedToken string) (*auth.UserToken, error) {
- return &auth.UserToken{
+ sc.userAuthTokenService.lookupTokenProvider = func(unhashedToken string) (*m.UserToken, error) {
+ return &m.UserToken{
UserId: 12,
UnhashedToken: "",
}, nil
diff --git a/pkg/middleware/quota_test.go b/pkg/middleware/quota_test.go
index df2bf930426..e2a6ef63377 100644
--- a/pkg/middleware/quota_test.go
+++ b/pkg/middleware/quota_test.go
@@ -5,7 +5,6 @@ import (
"github.com/grafana/grafana/pkg/bus"
m "github.com/grafana/grafana/pkg/models"
- "github.com/grafana/grafana/pkg/services/auth"
"github.com/grafana/grafana/pkg/services/session"
"github.com/grafana/grafana/pkg/setting"
. "github.com/smartystreets/goconvey/convey"
@@ -81,8 +80,8 @@ func TestMiddlewareQuota(t *testing.T) {
return nil
})
- sc.userAuthTokenService.lookupTokenProvider = func(unhashedToken string) (*auth.UserToken, error) {
- return &auth.UserToken{
+ sc.userAuthTokenService.lookupTokenProvider = func(unhashedToken string) (*m.UserToken, error) {
+ return &m.UserToken{
UserId: 12,
UnhashedToken: "",
}, nil
diff --git a/pkg/models/context.go b/pkg/models/context.go
index bba0024c0f2..b0c6ec9226d 100644
--- a/pkg/models/context.go
+++ b/pkg/models/context.go
@@ -4,7 +4,6 @@ import (
"strings"
"github.com/grafana/grafana/pkg/log"
- "github.com/grafana/grafana/pkg/services/auth"
"github.com/grafana/grafana/pkg/services/session"
"github.com/grafana/grafana/pkg/setting"
"github.com/prometheus/client_golang/prometheus"
@@ -14,7 +13,7 @@ import (
type ReqContext struct {
*macaron.Context
*SignedInUser
- UserToken *auth.UserToken
+ UserToken *UserToken
// This should only be used by the auth_proxy
Session session.SessionStore
diff --git a/pkg/models/user_token.go b/pkg/models/user_token.go
new file mode 100644
index 00000000000..c8084cf1eba
--- /dev/null
+++ b/pkg/models/user_token.go
@@ -0,0 +1,32 @@
+package models
+
+import "errors"
+
+// Typed errors
+var (
+ ErrUserTokenNotFound = errors.New("user token not found")
+)
+
+// UserToken represents a user token
+type UserToken struct {
+ Id int64
+ UserId int64
+ AuthToken string
+ PrevAuthToken string
+ UserAgent string
+ ClientIp string
+ AuthTokenSeen bool
+ SeenAt int64
+ RotatedAt int64
+ CreatedAt int64
+ UpdatedAt int64
+ UnhashedToken string
+}
+
+// UserTokenService are used for generating and validating user tokens
+type UserTokenService interface {
+ CreateToken(userId int64, clientIP, userAgent string) (*UserToken, error)
+ LookupToken(unhashedToken string) (*UserToken, error)
+ TryRotateToken(token *UserToken, clientIP, userAgent string) (bool, error)
+ RevokeToken(token *UserToken) error
+}
diff --git a/pkg/services/auth/auth.go b/pkg/services/auth/auth.go
index 727ceffff79..8832b06d188 100644
--- a/pkg/services/auth/auth.go
+++ b/pkg/services/auth/auth.go
@@ -1,16 +1 @@
package auth
-
-type UserToken struct {
- Id int64
- UserId int64
- AuthToken string
- PrevAuthToken string
- UserAgent string
- ClientIp string
- AuthTokenSeen bool
- SeenAt int64
- RotatedAt int64
- CreatedAt int64
- UpdatedAt int64
- UnhashedToken string
-}
diff --git a/pkg/services/auth/authtoken/auth_token.go b/pkg/services/auth/authtoken/auth_token.go
index a993a37dbf3..eedf38e8141 100644
--- a/pkg/services/auth/authtoken/auth_token.go
+++ b/pkg/services/auth/authtoken/auth_token.go
@@ -5,11 +5,10 @@ import (
"encoding/hex"
"time"
- "github.com/grafana/grafana/pkg/services/auth"
-
"github.com/grafana/grafana/pkg/infra/serverlock"
"github.com/grafana/grafana/pkg/log"
+ "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/registry"
"github.com/grafana/grafana/pkg/services/sqlstore"
"github.com/grafana/grafana/pkg/setting"
@@ -40,7 +39,7 @@ func (s *UserAuthTokenServiceImpl) Init() error {
return nil
}
-func (s *UserAuthTokenServiceImpl) CreateToken(userId int64, clientIP, userAgent string) (*auth.UserToken, error) {
+func (s *UserAuthTokenServiceImpl) CreateToken(userId int64, clientIP, userAgent string) (*models.UserToken, error) {
clientIP = util.ParseIPAddress(clientIP)
token, err := util.RandomHex(16)
if err != nil {
@@ -72,13 +71,13 @@ func (s *UserAuthTokenServiceImpl) CreateToken(userId int64, clientIP, userAgent
s.log.Debug("user auth token created", "tokenId", userAuthToken.Id, "userId", userAuthToken.UserId, "clientIP", userAuthToken.ClientIp, "userAgent", userAuthToken.UserAgent, "authToken", userAuthToken.AuthToken)
- var userToken auth.UserToken
+ var userToken models.UserToken
err = userAuthToken.toUserToken(&userToken)
return &userToken, err
}
-func (s *UserAuthTokenServiceImpl) LookupToken(unhashedToken string) (*auth.UserToken, error) {
+func (s *UserAuthTokenServiceImpl) LookupToken(unhashedToken string) (*models.UserToken, error) {
hashedToken := hashToken(unhashedToken)
if setting.Env == setting.DEV {
s.log.Debug("looking up token", "unhashed", unhashedToken, "hashed", hashedToken)
@@ -137,13 +136,13 @@ func (s *UserAuthTokenServiceImpl) LookupToken(unhashedToken string) (*auth.User
model.UnhashedToken = unhashedToken
- var userToken auth.UserToken
+ var userToken models.UserToken
err = model.toUserToken(&userToken)
return &userToken, err
}
-func (s *UserAuthTokenServiceImpl) TryRotateToken(token *auth.UserToken, clientIP, userAgent string) (bool, error) {
+func (s *UserAuthTokenServiceImpl) TryRotateToken(token *models.UserToken, clientIP, userAgent string) (bool, error) {
if token == nil {
return false, nil
}
@@ -202,7 +201,7 @@ func (s *UserAuthTokenServiceImpl) TryRotateToken(token *auth.UserToken, clientI
return false, nil
}
-func (s *UserAuthTokenServiceImpl) RevokeToken(token *auth.UserToken) error {
+func (s *UserAuthTokenServiceImpl) RevokeToken(token *models.UserToken) error {
if token == nil {
return ErrAuthTokenNotFound
}
diff --git a/pkg/services/auth/authtoken/auth_token_test.go b/pkg/services/auth/authtoken/auth_token_test.go
index a1b70db60fb..3021b0efa4b 100644
--- a/pkg/services/auth/authtoken/auth_token_test.go
+++ b/pkg/services/auth/authtoken/auth_token_test.go
@@ -9,7 +9,7 @@ import (
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/log"
- "github.com/grafana/grafana/pkg/services/auth"
+ "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/sqlstore"
. "github.com/smartystreets/goconvey/convey"
)
@@ -163,7 +163,7 @@ func TestUserAuthToken(t *testing.T) {
model, err := ctx.getAuthTokenByID(userToken.Id)
So(err, ShouldBeNil)
- var tok auth.UserToken
+ var tok models.UserToken
model.toUserToken(&tok)
getTime = func() time.Time {
@@ -345,7 +345,7 @@ func TestUserAuthToken(t *testing.T) {
})
Convey("When populating userAuthToken from UserToken should copy all properties", func() {
- ut := auth.UserToken{
+ ut := models.UserToken{
Id: 1,
UserId: 2,
AuthToken: "a",
@@ -397,7 +397,7 @@ func TestUserAuthToken(t *testing.T) {
So(err, ShouldBeNil)
uatMap := uatJSON.MustMap()
- var ut auth.UserToken
+ var ut models.UserToken
err = uat.toUserToken(&ut)
So(err, ShouldBeNil)
utBytes, err := json.Marshal(ut)
diff --git a/pkg/services/auth/authtoken/model.go b/pkg/services/auth/authtoken/model.go
index 730f615e294..64123f02bc6 100644
--- a/pkg/services/auth/authtoken/model.go
+++ b/pkg/services/auth/authtoken/model.go
@@ -4,7 +4,7 @@ import (
"errors"
"fmt"
- "github.com/grafana/grafana/pkg/services/auth"
+ "github.com/grafana/grafana/pkg/models"
)
// Typed errors
@@ -27,13 +27,13 @@ type userAuthToken struct {
UnhashedToken string `xorm:"-"`
}
-func userAuthTokenFromUserToken(ut *auth.UserToken) *userAuthToken {
+func userAuthTokenFromUserToken(ut *models.UserToken) *userAuthToken {
var uat userAuthToken
uat.fromUserToken(ut)
return &uat
}
-func (uat *userAuthToken) fromUserToken(ut *auth.UserToken) {
+func (uat *userAuthToken) fromUserToken(ut *models.UserToken) {
uat.Id = ut.Id
uat.UserId = ut.UserId
uat.AuthToken = ut.AuthToken
@@ -48,7 +48,7 @@ func (uat *userAuthToken) fromUserToken(ut *auth.UserToken) {
uat.UnhashedToken = ut.UnhashedToken
}
-func (uat *userAuthToken) toUserToken(ut *auth.UserToken) error {
+func (uat *userAuthToken) toUserToken(ut *models.UserToken) error {
if uat == nil {
return fmt.Errorf("needs pointer to userAuthToken struct")
}
@@ -68,11 +68,3 @@ func (uat *userAuthToken) toUserToken(ut *auth.UserToken) error {
return nil
}
-
-// UserAuthTokenService are used for generating and validating user auth tokens
-type UserAuthTokenService interface {
- CreateToken(userId int64, clientIP, userAgent string) (*auth.UserToken, error)
- LookupToken(unhashedToken string) (*auth.UserToken, error)
- TryRotateToken(token *auth.UserToken, clientIP, userAgent string) (bool, error)
- RevokeToken(token *auth.UserToken) error
-}
From 8ae066ab5d30e9049b186479fda64b62de50d544 Mon Sep 17 00:00:00 2001
From: Marcus Efraimsson
Date: Wed, 6 Feb 2019 17:02:57 +0100
Subject: [PATCH 095/228] move authtoken package into auth package
---
pkg/cmd/grafana-server/server.go | 1 +
pkg/services/auth/auth.go | 1 -
.../auth/{authtoken => }/auth_token.go | 26 ++++++++-----------
.../auth/{authtoken => }/auth_token_test.go | 19 +++++++-------
pkg/services/auth/{authtoken => }/model.go | 16 ++++++------
.../session_cleanup.go => token_cleanup.go} | 6 ++---
..._cleanup_test.go => token_cleanup_test.go} | 2 +-
7 files changed, 34 insertions(+), 37 deletions(-)
delete mode 100644 pkg/services/auth/auth.go
rename pkg/services/auth/{authtoken => }/auth_token.go (89%)
rename pkg/services/auth/{authtoken => }/auth_token_test.go (97%)
rename pkg/services/auth/{authtoken => }/model.go (88%)
rename pkg/services/auth/{authtoken/session_cleanup.go => token_cleanup.go} (87%)
rename pkg/services/auth/{authtoken/session_cleanup_test.go => token_cleanup_test.go} (99%)
diff --git a/pkg/cmd/grafana-server/server.go b/pkg/cmd/grafana-server/server.go
index 4781361b9b9..f663e6be895 100644
--- a/pkg/cmd/grafana-server/server.go
+++ b/pkg/cmd/grafana-server/server.go
@@ -32,6 +32,7 @@ import (
_ "github.com/grafana/grafana/pkg/metrics"
_ "github.com/grafana/grafana/pkg/plugins"
_ "github.com/grafana/grafana/pkg/services/alerting"
+ _ "github.com/grafana/grafana/pkg/services/auth"
_ "github.com/grafana/grafana/pkg/services/cleanup"
_ "github.com/grafana/grafana/pkg/services/notifications"
_ "github.com/grafana/grafana/pkg/services/provisioning"
diff --git a/pkg/services/auth/auth.go b/pkg/services/auth/auth.go
deleted file mode 100644
index 8832b06d188..00000000000
--- a/pkg/services/auth/auth.go
+++ /dev/null
@@ -1 +0,0 @@
-package auth
diff --git a/pkg/services/auth/authtoken/auth_token.go b/pkg/services/auth/auth_token.go
similarity index 89%
rename from pkg/services/auth/authtoken/auth_token.go
rename to pkg/services/auth/auth_token.go
index eedf38e8141..ef5dccd779f 100644
--- a/pkg/services/auth/authtoken/auth_token.go
+++ b/pkg/services/auth/auth_token.go
@@ -1,4 +1,4 @@
-package authtoken
+package auth
import (
"crypto/sha256"
@@ -16,30 +16,26 @@ import (
)
func init() {
- registry.Register(®istry.Descriptor{
- Name: "AuthTokenService",
- Instance: &UserAuthTokenServiceImpl{},
- InitPriority: registry.Low,
- })
+ registry.RegisterService(&UserAuthTokenService{})
}
var getTime = time.Now
const urgentRotateTime = 1 * time.Minute
-type UserAuthTokenServiceImpl struct {
+type UserAuthTokenService struct {
SQLStore *sqlstore.SqlStore `inject:""`
ServerLockService *serverlock.ServerLockService `inject:""`
Cfg *setting.Cfg `inject:""`
log log.Logger
}
-func (s *UserAuthTokenServiceImpl) Init() error {
+func (s *UserAuthTokenService) Init() error {
s.log = log.New("auth")
return nil
}
-func (s *UserAuthTokenServiceImpl) CreateToken(userId int64, clientIP, userAgent string) (*models.UserToken, error) {
+func (s *UserAuthTokenService) CreateToken(userId int64, clientIP, userAgent string) (*models.UserToken, error) {
clientIP = util.ParseIPAddress(clientIP)
token, err := util.RandomHex(16)
if err != nil {
@@ -77,7 +73,7 @@ func (s *UserAuthTokenServiceImpl) CreateToken(userId int64, clientIP, userAgent
return &userToken, err
}
-func (s *UserAuthTokenServiceImpl) LookupToken(unhashedToken string) (*models.UserToken, error) {
+func (s *UserAuthTokenService) LookupToken(unhashedToken string) (*models.UserToken, error) {
hashedToken := hashToken(unhashedToken)
if setting.Env == setting.DEV {
s.log.Debug("looking up token", "unhashed", unhashedToken, "hashed", hashedToken)
@@ -95,7 +91,7 @@ func (s *UserAuthTokenServiceImpl) LookupToken(unhashedToken string) (*models.Us
}
if !exists {
- return nil, ErrAuthTokenNotFound
+ return nil, models.ErrUserTokenNotFound
}
if model.AuthToken != hashedToken && model.PrevAuthToken == hashedToken && model.AuthTokenSeen {
@@ -142,7 +138,7 @@ func (s *UserAuthTokenServiceImpl) LookupToken(unhashedToken string) (*models.Us
return &userToken, err
}
-func (s *UserAuthTokenServiceImpl) TryRotateToken(token *models.UserToken, clientIP, userAgent string) (bool, error) {
+func (s *UserAuthTokenService) TryRotateToken(token *models.UserToken, clientIP, userAgent string) (bool, error) {
if token == nil {
return false, nil
}
@@ -201,9 +197,9 @@ func (s *UserAuthTokenServiceImpl) TryRotateToken(token *models.UserToken, clien
return false, nil
}
-func (s *UserAuthTokenServiceImpl) RevokeToken(token *models.UserToken) error {
+func (s *UserAuthTokenService) RevokeToken(token *models.UserToken) error {
if token == nil {
- return ErrAuthTokenNotFound
+ return models.ErrUserTokenNotFound
}
model := userAuthTokenFromUserToken(token)
@@ -215,7 +211,7 @@ func (s *UserAuthTokenServiceImpl) RevokeToken(token *models.UserToken) error {
if rowsAffected == 0 {
s.log.Debug("user auth token not found/revoked", "tokenId", model.Id, "userId", model.UserId, "clientIP", model.ClientIp, "userAgent", model.UserAgent)
- return ErrAuthTokenNotFound
+ return models.ErrUserTokenNotFound
}
s.log.Debug("user auth token revoked", "tokenId", model.Id, "userId", model.UserId, "clientIP", model.ClientIp, "userAgent", model.UserAgent)
diff --git a/pkg/services/auth/authtoken/auth_token_test.go b/pkg/services/auth/auth_token_test.go
similarity index 97%
rename from pkg/services/auth/authtoken/auth_token_test.go
rename to pkg/services/auth/auth_token_test.go
index 3021b0efa4b..3313af9f87f 100644
--- a/pkg/services/auth/authtoken/auth_token_test.go
+++ b/pkg/services/auth/auth_token_test.go
@@ -1,4 +1,4 @@
-package authtoken
+package auth
import (
"encoding/json"
@@ -46,7 +46,7 @@ func TestUserAuthToken(t *testing.T) {
Convey("When lookup hashed token should return user auth token not found error", func() {
userToken, err := userAuthTokenService.LookupToken(userToken.AuthToken)
- So(err, ShouldEqual, ErrAuthTokenNotFound)
+ So(err, ShouldEqual, models.ErrUserTokenNotFound)
So(userToken, ShouldBeNil)
})
@@ -61,13 +61,13 @@ func TestUserAuthToken(t *testing.T) {
Convey("revoking nil token should return error", func() {
err = userAuthTokenService.RevokeToken(nil)
- So(err, ShouldEqual, ErrAuthTokenNotFound)
+ So(err, ShouldEqual, models.ErrUserTokenNotFound)
})
Convey("revoking non-existing token should return error", func() {
userToken.Id = 1000
err = userAuthTokenService.RevokeToken(userToken)
- So(err, ShouldEqual, ErrAuthTokenNotFound)
+ So(err, ShouldEqual, models.ErrUserTokenNotFound)
})
})
@@ -112,7 +112,7 @@ func TestUserAuthToken(t *testing.T) {
}
notGood, err := userAuthTokenService.LookupToken(userToken.UnhashedToken)
- So(err, ShouldEqual, ErrAuthTokenNotFound)
+ So(err, ShouldEqual, models.ErrUserTokenNotFound)
So(notGood, ShouldBeNil)
})
@@ -140,7 +140,7 @@ func TestUserAuthToken(t *testing.T) {
}
notGood, err := userAuthTokenService.LookupToken(userToken.UnhashedToken)
- So(err, ShouldEqual, ErrAuthTokenNotFound)
+ So(err, ShouldEqual, models.ErrUserTokenNotFound)
So(notGood, ShouldBeNil)
})
})
@@ -164,7 +164,8 @@ func TestUserAuthToken(t *testing.T) {
So(err, ShouldBeNil)
var tok models.UserToken
- model.toUserToken(&tok)
+ err = model.toUserToken(&tok)
+ So(err, ShouldBeNil)
getTime = func() time.Time {
return t.Add(time.Hour)
@@ -419,7 +420,7 @@ func createTestContext(t *testing.T) *testContext {
t.Helper()
sqlstore := sqlstore.InitTestDB(t)
- tokenService := &UserAuthTokenServiceImpl{
+ tokenService := &UserAuthTokenService{
SQLStore: sqlstore,
Cfg: &setting.Cfg{
LoginMaxInactiveLifetimeDays: 7,
@@ -438,7 +439,7 @@ func createTestContext(t *testing.T) *testContext {
type testContext struct {
sqlstore *sqlstore.SqlStore
- tokenService *UserAuthTokenServiceImpl
+ tokenService *UserAuthTokenService
}
func (c *testContext) getAuthTokenByID(id int64) (*userAuthToken, error) {
diff --git a/pkg/services/auth/authtoken/model.go b/pkg/services/auth/model.go
similarity index 88%
rename from pkg/services/auth/authtoken/model.go
rename to pkg/services/auth/model.go
index 64123f02bc6..36652e70436 100644
--- a/pkg/services/auth/authtoken/model.go
+++ b/pkg/services/auth/model.go
@@ -1,17 +1,11 @@
-package authtoken
+package auth
import (
- "errors"
"fmt"
"github.com/grafana/grafana/pkg/models"
)
-// Typed errors
-var (
- ErrAuthTokenNotFound = errors.New("user auth token not found")
-)
-
type userAuthToken struct {
Id int64
UserId int64
@@ -33,7 +27,11 @@ func userAuthTokenFromUserToken(ut *models.UserToken) *userAuthToken {
return &uat
}
-func (uat *userAuthToken) fromUserToken(ut *models.UserToken) {
+func (uat *userAuthToken) fromUserToken(ut *models.UserToken) error {
+ if uat == nil {
+ return fmt.Errorf("needs pointer to userAuthToken struct")
+ }
+
uat.Id = ut.Id
uat.UserId = ut.UserId
uat.AuthToken = ut.AuthToken
@@ -46,6 +44,8 @@ func (uat *userAuthToken) fromUserToken(ut *models.UserToken) {
uat.CreatedAt = ut.CreatedAt
uat.UpdatedAt = ut.UpdatedAt
uat.UnhashedToken = ut.UnhashedToken
+
+ return nil
}
func (uat *userAuthToken) toUserToken(ut *models.UserToken) error {
diff --git a/pkg/services/auth/authtoken/session_cleanup.go b/pkg/services/auth/token_cleanup.go
similarity index 87%
rename from pkg/services/auth/authtoken/session_cleanup.go
rename to pkg/services/auth/token_cleanup.go
index 2b8dfb7b4e2..d0e12c9c0e1 100644
--- a/pkg/services/auth/authtoken/session_cleanup.go
+++ b/pkg/services/auth/token_cleanup.go
@@ -1,11 +1,11 @@
-package authtoken
+package auth
import (
"context"
"time"
)
-func (srv *UserAuthTokenServiceImpl) Run(ctx context.Context) error {
+func (srv *UserAuthTokenService) Run(ctx context.Context) error {
if srv.Cfg.ExpiredTokensCleanupIntervalDays <= 0 {
srv.log.Debug("cleanup of expired auth tokens are disabled")
return nil
@@ -31,7 +31,7 @@ func (srv *UserAuthTokenServiceImpl) Run(ctx context.Context) error {
}
}
-func (srv *UserAuthTokenServiceImpl) deleteExpiredTokens(maxInactiveLifetime, maxLifetime time.Duration) (int64, error) {
+func (srv *UserAuthTokenService) deleteExpiredTokens(maxInactiveLifetime, maxLifetime time.Duration) (int64, error) {
createdBefore := getTime().Add(-maxLifetime)
rotatedBefore := getTime().Add(-maxInactiveLifetime)
diff --git a/pkg/services/auth/authtoken/session_cleanup_test.go b/pkg/services/auth/token_cleanup_test.go
similarity index 99%
rename from pkg/services/auth/authtoken/session_cleanup_test.go
rename to pkg/services/auth/token_cleanup_test.go
index 7b611b3263c..410764d3f8d 100644
--- a/pkg/services/auth/authtoken/session_cleanup_test.go
+++ b/pkg/services/auth/token_cleanup_test.go
@@ -1,4 +1,4 @@
-package authtoken
+package auth
import (
"fmt"
From 7762d72ae30537058bd89ff0747846dabdda9f0f Mon Sep 17 00:00:00 2001
From: Dominik Prokop
Date: Wed, 6 Feb 2019 17:03:42 +0100
Subject: [PATCH 096/228] Updated stories to use new theming
---
packages/grafana-ui/.storybook/config.ts | 7 +++-
.../grafana-ui/.storybook/webpack.config.js | 16 +++++++-
.../ColorPicker/ColorPicker.story.tsx | 32 +++++++--------
.../ColorPicker/ColorPickerPopover.story.tsx | 41 +++++++------------
.../ColorPicker/NamedColorsPalette.story.tsx | 15 +++++--
.../ColorPicker/SpectrumPalette.story.tsx | 9 ++--
.../src/utils/storybook/withTheme.tsx | 3 +-
7 files changed, 64 insertions(+), 59 deletions(-)
diff --git a/packages/grafana-ui/.storybook/config.ts b/packages/grafana-ui/.storybook/config.ts
index 9e50c6b501a..434e717bbab 100644
--- a/packages/grafana-ui/.storybook/config.ts
+++ b/packages/grafana-ui/.storybook/config.ts
@@ -1,10 +1,15 @@
-import { configure } from '@storybook/react';
+import { configure, addDecorator } from '@storybook/react';
+import { withKnobs } from '@storybook/addon-knobs';
+import { withTheme } from '../src/utils/storybook/withTheme';
import '../../../public/sass/grafana.light.scss';
// automatically import all files ending in *.stories.tsx
const req = require.context('../src/components', true, /.story.tsx$/);
+addDecorator(withKnobs);
+addDecorator(withTheme);
+
function loadStories() {
req.keys().forEach(req);
}
diff --git a/packages/grafana-ui/.storybook/webpack.config.js b/packages/grafana-ui/.storybook/webpack.config.js
index 44de73a1e18..4f27b71bb60 100644
--- a/packages/grafana-ui/.storybook/webpack.config.js
+++ b/packages/grafana-ui/.storybook/webpack.config.js
@@ -1,7 +1,7 @@
const path = require('path');
+const getThemeVariable = require('../../../scripts/webpack/getThemeVariable');
module.exports = (baseConfig, env, config) => {
-
config.module.rules.push({
test: /\.(ts|tsx)$/,
use: [
@@ -33,7 +33,15 @@ module.exports = (baseConfig, env, config) => {
config: { path: __dirname + '../../../../scripts/webpack/postcss.config.js' },
},
},
- { loader: 'sass-loader', options: { sourceMap: false } },
+ {
+ loader: 'sass-loader',
+ options: {
+ sourceMap: false,
+ functions: {
+ 'getThemeVariable($themeVar, $themeName: dark)': getThemeVariable,
+ },
+ },
+ },
],
});
@@ -52,5 +60,9 @@ module.exports = (baseConfig, env, config) => {
});
config.resolve.extensions.push('.ts', '.tsx');
+
+ // Remove pure js loading rules as Storybook's Babel config is causing problems when mixing ES6 and CJS
+ // More about the problem we encounter: https://github.com/webpack/webpack/issues/4039
+ config.module.rules = config.module.rules.filter(rule => rule.test.toString() !== /\.(mjs|jsx?)$/.toString());
return config;
};
diff --git a/packages/grafana-ui/src/components/ColorPicker/ColorPicker.story.tsx b/packages/grafana-ui/src/components/ColorPicker/ColorPicker.story.tsx
index 19ae2fda978..1fb31e86d72 100644
--- a/packages/grafana-ui/src/components/ColorPicker/ColorPicker.story.tsx
+++ b/packages/grafana-ui/src/components/ColorPicker/ColorPicker.story.tsx
@@ -1,46 +1,43 @@
import React from 'react';
import { storiesOf } from '@storybook/react';
-import { withKnobs, boolean } from '@storybook/addon-knobs';
+import { boolean } from '@storybook/addon-knobs';
import { SeriesColorPicker, ColorPicker } from './ColorPicker';
import { action } from '@storybook/addon-actions';
import { withCenteredStory } from '../../utils/storybook/withCenteredStory';
import { UseState } from '../../utils/storybook/UseState';
-import { getThemeKnob } from '../../utils/storybook/themeKnob';
+import { renderComponentWithTheme } from '../../utils/storybook/withTheme';
const getColorPickerKnobs = () => {
return {
- selectedTheme: getThemeKnob(),
enableNamedColors: boolean('Enable named colors', false),
};
};
const ColorPickerStories = storiesOf('UI/ColorPicker/Pickers', module);
-ColorPickerStories.addDecorator(withCenteredStory).addDecorator(withKnobs);
+ColorPickerStories.addDecorator(withCenteredStory);
ColorPickerStories.add('default', () => {
- const { selectedTheme, enableNamedColors } = getColorPickerKnobs();
+ const { enableNamedColors } = getColorPickerKnobs();
+
return (
{(selectedColor, updateSelectedColor) => {
- return (
- {
- action('Color changed')(color);
- updateSelectedColor(color);
- }}
- theme={selectedTheme || undefined}
- />
- );
+ return renderComponentWithTheme(ColorPicker, {
+ enableNamedColors,
+ color: selectedColor,
+ onChange: (color: any) => {
+ action('Color changed')(color);
+ updateSelectedColor(color);
+ },
+ });
}}
);
});
ColorPickerStories.add('Series color picker', () => {
- const { selectedTheme, enableNamedColors } = getColorPickerKnobs();
+ const { enableNamedColors } = getColorPickerKnobs();
return (
@@ -52,7 +49,6 @@ ColorPickerStories.add('Series color picker', () => {
onToggleAxis={() => {}}
color={selectedColor}
onChange={color => updateSelectedColor(color)}
- theme={selectedTheme || undefined}
>
Open color picker
diff --git a/packages/grafana-ui/src/components/ColorPicker/ColorPickerPopover.story.tsx b/packages/grafana-ui/src/components/ColorPicker/ColorPickerPopover.story.tsx
index dc51819a413..d749588ee31 100644
--- a/packages/grafana-ui/src/components/ColorPicker/ColorPickerPopover.story.tsx
+++ b/packages/grafana-ui/src/components/ColorPicker/ColorPickerPopover.story.tsx
@@ -1,40 +1,27 @@
-import React from 'react';
import { storiesOf } from '@storybook/react';
import { ColorPickerPopover } from './ColorPickerPopover';
-import { withKnobs } from '@storybook/addon-knobs';
import { withCenteredStory } from '../../utils/storybook/withCenteredStory';
-import { getThemeKnob } from '../../utils/storybook/themeKnob';
import { SeriesColorPickerPopover } from './SeriesColorPickerPopover';
-
+import { renderComponentWithTheme } from '../../utils/storybook/withTheme';
const ColorPickerPopoverStories = storiesOf('UI/ColorPicker/Popovers', module);
-ColorPickerPopoverStories.addDecorator(withCenteredStory).addDecorator(withKnobs);
+ColorPickerPopoverStories.addDecorator(withCenteredStory);
ColorPickerPopoverStories.add('default', () => {
- const selectedTheme = getThemeKnob();
-
- return (
- {
- console.log(color);
- }}
- theme={selectedTheme || undefined}
- />
- );
+ return renderComponentWithTheme(ColorPickerPopover, {
+ color: '#BC67E6',
+ onChange: (color: any) => {
+ console.log(color);
+ },
+ });
});
ColorPickerPopoverStories.add('SeriesColorPickerPopover', () => {
- const selectedTheme = getThemeKnob();
-
- return (
- {
- console.log(color);
- }}
- theme={selectedTheme || undefined}
- />
- );
+ return renderComponentWithTheme(SeriesColorPickerPopover, {
+ color: '#BC67E6',
+ onChange: (color: any) => {
+ console.log(color);
+ },
+ });
});
diff --git a/packages/grafana-ui/src/components/ColorPicker/NamedColorsPalette.story.tsx b/packages/grafana-ui/src/components/ColorPicker/NamedColorsPalette.story.tsx
index af5de3b2a2d..f4901b28bfd 100644
--- a/packages/grafana-ui/src/components/ColorPicker/NamedColorsPalette.story.tsx
+++ b/packages/grafana-ui/src/components/ColorPicker/NamedColorsPalette.story.tsx
@@ -2,8 +2,9 @@ import React from 'react';
import { storiesOf } from '@storybook/react';
import { NamedColorsPalette } from './NamedColorsPalette';
import { getColorName, getColorDefinitionByName } from '../../utils/namedColorsPalette';
-import { withKnobs, select } from '@storybook/addon-knobs';
+import { select } from '@storybook/addon-knobs';
import { withCenteredStory } from '../../utils/storybook/withCenteredStory';
+import { renderComponentWithTheme } from '../../utils/storybook/withTheme';
import { UseState } from '../../utils/storybook/UseState';
const BasicGreen = getColorDefinitionByName('green');
@@ -12,7 +13,7 @@ const LightBlue = getColorDefinitionByName('light-blue');
const NamedColorsPaletteStories = storiesOf('UI/ColorPicker/Palettes/NamedColorsPalette', module);
-NamedColorsPaletteStories.addDecorator(withKnobs).addDecorator(withCenteredStory);
+NamedColorsPaletteStories.addDecorator(withCenteredStory);
NamedColorsPaletteStories.add('Named colors swatch - support for named colors', () => {
const selectedColor = select(
@@ -28,7 +29,10 @@ NamedColorsPaletteStories.add('Named colors swatch - support for named colors',
return (
{(selectedColor, updateSelectedColor) => {
- return ;
+ return renderComponentWithTheme(NamedColorsPalette, {
+ color: selectedColor,
+ onChange: updateSelectedColor,
+ });
}}
);
@@ -45,7 +49,10 @@ NamedColorsPaletteStories.add('Named colors swatch - support for named colors',
return (
{(selectedColor, updateSelectedColor) => {
- return ;
+ return renderComponentWithTheme(NamedColorsPalette, {
+ color: getColorName(selectedColor),
+ onChange: updateSelectedColor,
+ });
}}
);
diff --git a/packages/grafana-ui/src/components/ColorPicker/SpectrumPalette.story.tsx b/packages/grafana-ui/src/components/ColorPicker/SpectrumPalette.story.tsx
index b4fdaf69ed9..5fb6c569605 100644
--- a/packages/grafana-ui/src/components/ColorPicker/SpectrumPalette.story.tsx
+++ b/packages/grafana-ui/src/components/ColorPicker/SpectrumPalette.story.tsx
@@ -1,22 +1,19 @@
import React from 'react';
import { storiesOf } from '@storybook/react';
-import { withKnobs } from '@storybook/addon-knobs';
import SpectrumPalette from './SpectrumPalette';
import { withCenteredStory } from '../../utils/storybook/withCenteredStory';
import { UseState } from '../../utils/storybook/UseState';
-import { getThemeKnob } from '../../utils/storybook/themeKnob';
+import { renderComponentWithTheme } from '../../utils/storybook/withTheme';
const SpectrumPaletteStories = storiesOf('UI/ColorPicker/Palettes/SpectrumPalette', module);
-SpectrumPaletteStories.addDecorator(withCenteredStory).addDecorator(withKnobs);
+SpectrumPaletteStories.addDecorator(withCenteredStory);
SpectrumPaletteStories.add('default', () => {
- const selectedTheme = getThemeKnob();
-
return (
{(selectedColor, updateSelectedColor) => {
- return ;
+ return renderComponentWithTheme(SpectrumPalette, { color: selectedColor, onChange: updateSelectedColor });
}}
);
diff --git a/packages/grafana-ui/src/utils/storybook/withTheme.tsx b/packages/grafana-ui/src/utils/storybook/withTheme.tsx
index b1a9bca013a..5417af1de05 100644
--- a/packages/grafana-ui/src/utils/storybook/withTheme.tsx
+++ b/packages/grafana-ui/src/utils/storybook/withTheme.tsx
@@ -9,7 +9,6 @@ const ThemableStory: React.FunctionComponent<{}> = ({ children }) => {
const themeKnob = select(
'Theme',
{
- Default: GrafanaThemeType.Dark,
Light: GrafanaThemeType.Light,
Dark: GrafanaThemeType.Dark,
},
@@ -24,6 +23,8 @@ const ThemableStory: React.FunctionComponent<{}> = ({ children }) => {
);
};
+// Temporary solution. When we update to Storybook V5 we will be able to pass data from decorator to story
+// https://github.com/storybooks/storybook/issues/340#issuecomment-456013702
export const renderComponentWithTheme = (component: React.ComponentType, props: any) => {
return (
From 1e4c6b4b527df387946c34d13d3a8b7e58aa20df Mon Sep 17 00:00:00 2001
From: Dominik Prokop
Date: Wed, 6 Feb 2019 17:05:22 +0100
Subject: [PATCH 097/228] Added test for SASS variable retrieval function from
JS definition
---
jest.config.js | 2 ++
packages/grafana-ui/src/themes/index.js | 11 ++++---
scripts/webpack/getThemeVariable.js | 4 +--
scripts/webpack/getThemeVariable.test.js | 40 ++++++++++++++++++++++++
4 files changed, 50 insertions(+), 7 deletions(-)
create mode 100644 scripts/webpack/getThemeVariable.test.js
diff --git a/jest.config.js b/jest.config.js
index c5c6bcb9f5f..248435c43f2 100644
--- a/jest.config.js
+++ b/jest.config.js
@@ -6,11 +6,13 @@ module.exports = {
},
"moduleDirectories": ["node_modules", "public"],
"roots": [
+ "/scripts",
"/public/app",
"/public/test",
"/packages"
],
"testRegex": "(\\.|/)(test)\\.(jsx?|tsx?)$",
+ "testPathIgnorePatterns": ["webpack.test.js"],
"moduleFileExtensions": [
"ts",
"tsx",
diff --git a/packages/grafana-ui/src/themes/index.js b/packages/grafana-ui/src/themes/index.js
index c88cf137574..91ba25349fd 100644
--- a/packages/grafana-ui/src/themes/index.js
+++ b/packages/grafana-ui/src/themes/index.js
@@ -1,15 +1,16 @@
const darkTheme = require('./dark');
const lightTheme = require('./light');
-const getTheme = name => (name === 'light' ? lightTheme : darkTheme);
+let mockedTheme;
+
+let getTheme = name => mockedTheme || (name === 'light' ? lightTheme : darkTheme);
const mockTheme = mock => {
- const originalGetTheme = getTheme;
- getTheme = () => mock;
- return () => (getTheme = originalGetTheme);
+ mockedTheme = mock;
+ return () => (mockedTheme = null);
};
module.exports = {
getTheme,
- mockTheme
+ mockTheme,
};
diff --git a/scripts/webpack/getThemeVariable.js b/scripts/webpack/getThemeVariable.js
index 6726f95d47c..3bd9b2a53d0 100644
--- a/scripts/webpack/getThemeVariable.js
+++ b/scripts/webpack/getThemeVariable.js
@@ -29,12 +29,12 @@ function getThemeVariable(variablePath, themeName) {
const variable = get(theme, variablePath.getValue());
if (!variable) {
- throw new Error(`${variablePath.getValue()} is not defined for ${themeName.getValue()} theme`);
+ throw new Error(`${variablePath.getValue()} is not defined for ${themeName.getValue()}`);
}
if (isHex(variable)) {
const rgb = new tinycolor(variable).toRgb();
- const color = sass.types.Color(rgb.r, rgb.g, rgb.b);
+ const color = new sass.types.Color(rgb.r, rgb.g, rgb.b);
return color;
}
diff --git a/scripts/webpack/getThemeVariable.test.js b/scripts/webpack/getThemeVariable.test.js
new file mode 100644
index 00000000000..78083330890
--- /dev/null
+++ b/scripts/webpack/getThemeVariable.test.js
@@ -0,0 +1,40 @@
+const sass = require('node-sass');
+const getThemeVariable = require('./getThemeVariable');
+const { mockTheme } = require('@grafana/ui');
+
+const themeMock = {
+ color: {
+ background: '#ff0000',
+ },
+ spacing: {
+ padding: '2em',
+ },
+ typography: {
+ fontFamily: 'Arial, sans-serif',
+ },
+};
+
+describe('Variables retrieval', () => {
+ const restoreTheme = mockTheme(themeMock);
+
+ afterAll(() => {
+ restoreTheme();
+ });
+
+ it('returns sass Color for color values', () => {
+ const result = getThemeVariable({ getValue: () => 'color.background' }, { getValue: () => {} });
+ expect(result).toBeInstanceOf(sass.types.Color);
+ });
+ it('returns sass Number for dimension values', () => {
+ const result = getThemeVariable({ getValue: () => 'spacing.padding' }, { getValue: () => {} });
+ expect(result).toBeInstanceOf(sass.types.Number);
+ });
+ it('returns sass String for string values', () => {
+ const result = getThemeVariable({ getValue: () => 'typography.fontFamily' }, { getValue: () => {} });
+ expect(result).toBeInstanceOf(sass.types.String);
+ });
+
+ it('throws for unknown theme paths', () => {
+ expect(() => getThemeVariable({ getValue: () => 'what.ever' }, { getValue: () => {} })).toThrow();
+ });
+});
From dc6b27d123bd069a299a5ce8007fc14e98798fff Mon Sep 17 00:00:00 2001
From: Dominik Prokop
Date: Wed, 6 Feb 2019 17:05:43 +0100
Subject: [PATCH 098/228] Minor cleanup
---
packages/grafana-ui/src/themes/dark.js | 2 --
.../grafana-ui/src/utils/storybook/themeKnob.ts | 14 --------------
2 files changed, 16 deletions(-)
delete mode 100644 packages/grafana-ui/src/utils/storybook/themeKnob.ts
diff --git a/packages/grafana-ui/src/themes/dark.js b/packages/grafana-ui/src/themes/dark.js
index 3031018bd56..553eb537093 100644
--- a/packages/grafana-ui/src/themes/dark.js
+++ b/packages/grafana-ui/src/themes/dark.js
@@ -1,5 +1,3 @@
-
-
const defaultTheme = require('./default');
const tinycolor = require('tinycolor2');
diff --git a/packages/grafana-ui/src/utils/storybook/themeKnob.ts b/packages/grafana-ui/src/utils/storybook/themeKnob.ts
deleted file mode 100644
index a3733462bea..00000000000
--- a/packages/grafana-ui/src/utils/storybook/themeKnob.ts
+++ /dev/null
@@ -1,14 +0,0 @@
-import { select } from '@storybook/addon-knobs';
-import { GrafanaTheme } from '../../types';
-
-export const getThemeKnob = (defaultTheme: GrafanaTheme = GrafanaTheme.Dark) => {
- return select(
- 'Theme',
- {
- Default: defaultTheme,
- Light: GrafanaTheme.Light,
- Dark: GrafanaTheme.Dark,
- },
- defaultTheme
- );
-};
From 836501186f314c78a69f5ab88ca86b1d1925625c Mon Sep 17 00:00:00 2001
From: Marcus Efraimsson
Date: Wed, 6 Feb 2019 17:30:17 +0100
Subject: [PATCH 099/228] fix
---
pkg/middleware/middleware_test.go | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/pkg/middleware/middleware_test.go b/pkg/middleware/middleware_test.go
index 7908be6f225..8545c3856c9 100644
--- a/pkg/middleware/middleware_test.go
+++ b/pkg/middleware/middleware_test.go
@@ -11,7 +11,6 @@ import (
msession "github.com/go-macaron/session"
"github.com/grafana/grafana/pkg/bus"
m "github.com/grafana/grafana/pkg/models"
- "github.com/grafana/grafana/pkg/services/auth/authtoken"
"github.com/grafana/grafana/pkg/services/session"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/util"
@@ -228,7 +227,7 @@ func TestMiddlewareContext(t *testing.T) {
sc.withTokenSessionCookie("token")
sc.userAuthTokenService.lookupTokenProvider = func(unhashedToken string) (*m.UserToken, error) {
- return nil, authtoken.ErrAuthTokenNotFound
+ return nil, m.ErrUserTokenNotFound
}
sc.fakeReq("GET", "/").exec()
From 8574dca081002f36e482b572517d8f05fd44453f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Torkel=20=C3=96degaard?=
Date: Wed, 6 Feb 2019 17:31:52 +0100
Subject: [PATCH 100/228] making changes suggested in review and improving
typings
---
.../dashboard/services/DashboardSrv.ts | 30 +++++++-------
.../dashboard/state/DashboardModel.ts | 6 +--
.../app/features/dashboard/state/actions.ts | 7 +---
.../features/dashboard/state/initDashboard.ts | 37 ++++++++++-------
public/app/routes/GrafanaCtrl.ts | 11 +++--
public/app/types/dashboard.ts | 41 +++++++++++++++++--
public/app/types/store.ts | 10 +++++
7 files changed, 97 insertions(+), 45 deletions(-)
diff --git a/public/app/features/dashboard/services/DashboardSrv.ts b/public/app/features/dashboard/services/DashboardSrv.ts
index 88eb58ad345..4030765a5a3 100644
--- a/public/app/features/dashboard/services/DashboardSrv.ts
+++ b/public/app/features/dashboard/services/DashboardSrv.ts
@@ -5,7 +5,7 @@ import { DashboardModel } from '../state/DashboardModel';
import { removePanel } from '../utils/panel';
export class DashboardSrv {
- dash: DashboardModel;
+ dashboard: DashboardModel;
/** @ngInject */
constructor(private backendSrv, private $rootScope, private $location) {
@@ -19,11 +19,11 @@ export class DashboardSrv {
}
setCurrent(dashboard: DashboardModel) {
- this.dash = dashboard;
+ this.dashboard = dashboard;
}
getCurrent(): DashboardModel {
- return this.dash;
+ return this.dashboard;
}
onRemovePanel = (panelId: number) => {
@@ -124,10 +124,10 @@ export class DashboardSrv {
}
postSave(clone, data) {
- this.dash.version = data.version;
+ this.dashboard.version = data.version;
// important that these happens before location redirect below
- this.$rootScope.appEvent('dashboard-saved', this.dash);
+ this.$rootScope.appEvent('dashboard-saved', this.dashboard);
this.$rootScope.appEvent('alert-success', ['Dashboard saved']);
const newUrl = locationUtil.stripBaseFromUrl(data.url);
@@ -137,12 +137,12 @@ export class DashboardSrv {
this.$location.url(newUrl).replace();
}
- return this.dash;
+ return this.dashboard;
}
save(clone, options) {
options = options || {};
- options.folderId = options.folderId >= 0 ? options.folderId : this.dash.meta.folderId || clone.folderId;
+ options.folderId = options.folderId >= 0 ? options.folderId : this.dashboard.meta.folderId || clone.folderId;
return this.backendSrv
.saveDashboard(clone, options)
@@ -152,26 +152,26 @@ export class DashboardSrv {
saveDashboard(options?, clone?) {
if (clone) {
- this.setCurrent(this.create(clone, this.dash.meta));
+ this.setCurrent(this.create(clone, this.dashboard.meta));
}
- if (this.dash.meta.provisioned) {
+ if (this.dashboard.meta.provisioned) {
return this.showDashboardProvisionedModal();
}
- if (!this.dash.meta.canSave && options.makeEditable !== true) {
+ if (!this.dashboard.meta.canSave && options.makeEditable !== true) {
return Promise.resolve();
}
- if (this.dash.title === 'New dashboard') {
+ if (this.dashboard.title === 'New dashboard') {
return this.showSaveAsModal();
}
- if (this.dash.version > 0) {
+ if (this.dashboard.version > 0) {
return this.showSaveModal();
}
- return this.save(this.dash.getSaveModelClone(), options);
+ return this.save(this.dashboard.getSaveModelClone(), options);
}
saveJSONDashboard(json: string) {
@@ -212,8 +212,8 @@ export class DashboardSrv {
}
return promise.then(res => {
- if (this.dash && this.dash.id === dashboardId) {
- this.dash.meta.isStarred = res;
+ if (this.dashboard && this.dashboard.id === dashboardId) {
+ this.dashboard.meta.isStarred = res;
}
return res;
});
diff --git a/public/app/features/dashboard/state/DashboardModel.ts b/public/app/features/dashboard/state/DashboardModel.ts
index 743eb61f97d..aec6421d12a 100644
--- a/public/app/features/dashboard/state/DashboardModel.ts
+++ b/public/app/features/dashboard/state/DashboardModel.ts
@@ -15,7 +15,7 @@ import sortByKeys from 'app/core/utils/sort_by_keys';
import { PanelModel } from './PanelModel';
import { DashboardMigrator } from './DashboardMigrator';
import { TimeRange } from '@grafana/ui/src';
-import { UrlQueryValue } from 'app/types';
+import { UrlQueryValue, KIOSK_MODE_TV, DashboardMeta } from 'app/types';
export class DashboardModel {
id: any;
@@ -49,7 +49,7 @@ export class DashboardModel {
// repeat process cycles
iteration: number;
- meta: any;
+ meta: DashboardMeta;
events: Emitter;
static nonPersistedProperties: { [str: string]: boolean } = {
@@ -887,7 +887,7 @@ export class DashboardModel {
}
// add back navbar height
- if (kioskMode === 'tv') {
+ if (kioskMode === KIOSK_MODE_TV) {
visibleHeight += 55;
}
diff --git a/public/app/features/dashboard/state/actions.ts b/public/app/features/dashboard/state/actions.ts
index c25388bbcb8..50ff004ad48 100644
--- a/public/app/features/dashboard/state/actions.ts
+++ b/public/app/features/dashboard/state/actions.ts
@@ -1,6 +1,3 @@
-// Libaries
-import { ThunkAction } from 'redux-thunk';
-
// Services & Utils
import { getBackendSrv } from 'app/core/services/backend_srv';
import { actionCreatorFactory, noPayloadActionCreatorFactory } from 'app/core/redux';
@@ -11,7 +8,7 @@ import { loadPluginDashboards } from '../../plugins/state/actions';
import { notifyApp } from 'app/core/actions';
// Types
-import { StoreState } from 'app/types';
+import { ThunkResult } from 'app/types';
import {
DashboardAcl,
DashboardAclDTO,
@@ -26,8 +23,6 @@ export const setDashboardLoadingState = actionCreatorFactory('SET_DASHBOARD_MODEL').create();
export const setDashboardLoadingSlow = noPayloadActionCreatorFactory('SET_DASHBOARD_LOADING_SLOW').create();
-export type ThunkResult = ThunkAction;
-
export function getDashboardPermissions(id: number): ThunkResult