浏览代码

Restrict Explore UI to Editor and Admin roles

Access is restricted via not showing in the following places:

* hide from sidemenu
* hide from panel header menu
* disable keybinding `x`

Also adds a `roles` property to reactContainer routes that will be
checked if `roles` is set, and on failure redirects to `/`.
David Kaltschmidt 7 年之前
父节点
当前提交
f69654fcd5

+ 1 - 1
pkg/api/index.go

@@ -128,7 +128,7 @@ func setIndexViewData(c *m.ReqContext) (*dtos.IndexViewData, error) {
 		Children: dashboardChildNavs,
 	})
 
-	if setting.ExploreEnabled {
+	if setting.ExploreEnabled && (c.OrgRole == m.ROLE_ADMIN || c.OrgRole == m.ROLE_EDITOR) {
 		data.NavTree = append(data.NavTree, &dtos.NavLink{
 			Text:     "Explore",
 			Id:       "explore",

+ 18 - 15
public/app/core/services/keybindingSrv.ts

@@ -14,7 +14,7 @@ export class KeybindingSrv {
   timepickerOpen = false;
 
   /** @ngInject */
-  constructor(private $rootScope, private $location, private datasourceSrv, private timeSrv) {
+  constructor(private $rootScope, private $location, private datasourceSrv, private timeSrv, private contextSrv) {
     // clear out all shortcuts on route change
     $rootScope.$on('$routeChangeSuccess', () => {
       Mousetrap.reset();
@@ -177,21 +177,24 @@ export class KeybindingSrv {
       }
     });
 
-    this.bind('x', async () => {
-      if (dashboard.meta.focusPanelId) {
-        const panel = dashboard.getPanelById(dashboard.meta.focusPanelId);
-        const datasource = await this.datasourceSrv.get(panel.datasource);
-        if (datasource && datasource.supportsExplore) {
-          const range = this.timeSrv.timeRangeForUrl();
-          const state = {
-            ...datasource.getExploreState(panel),
-            range,
-          };
-          const exploreState = encodePathComponent(JSON.stringify(state));
-          this.$location.url(`/explore/${exploreState}`);
+    // jump to explore if permissions allow
+    if (this.contextSrv.isEditor) {
+      this.bind('x', async () => {
+        if (dashboard.meta.focusPanelId) {
+          const panel = dashboard.getPanelById(dashboard.meta.focusPanelId);
+          const datasource = await this.datasourceSrv.get(panel.datasource);
+          if (datasource && datasource.supportsExplore) {
+            const range = this.timeSrv.timeRangeForUrl();
+            const state = {
+              ...datasource.getExploreState(panel),
+              range,
+            };
+            const exploreState = encodePathComponent(JSON.stringify(state));
+            this.$location.url(`/explore/${exploreState}`);
+          }
         }
-      }
-    });
+      });
+    }
 
     // delete panel
     this.bind('p r', () => {

+ 3 - 1
public/app/features/panel/metrics_panel_ctrl.ts

@@ -16,6 +16,7 @@ class MetricsPanelCtrl extends PanelCtrl {
   datasourceName: any;
   $q: any;
   $timeout: any;
+  contextSrv: any;
   datasourceSrv: any;
   timeSrv: any;
   templateSrv: any;
@@ -37,6 +38,7 @@ class MetricsPanelCtrl extends PanelCtrl {
     // make metrics tab the default
     this.editorTabIndex = 1;
     this.$q = $injector.get('$q');
+    this.contextSrv = $injector.get('contextSrv');
     this.datasourceSrv = $injector.get('datasourceSrv');
     this.timeSrv = $injector.get('timeSrv');
     this.templateSrv = $injector.get('templateSrv');
@@ -312,7 +314,7 @@ class MetricsPanelCtrl extends PanelCtrl {
 
   getAdditionalMenuItems() {
     const items = [];
-    if (this.datasource && this.datasource.supportsExplore) {
+    if (this.contextSrv.isEditor && this.datasource && this.datasource.supportsExplore) {
       items.push({
         text: 'Explore',
         click: 'ctrl.explore();',

+ 18 - 2
public/app/routes/ReactContainer.tsx

@@ -6,6 +6,7 @@ import coreModule from 'app/core/core_module';
 import { store } from 'app/stores/store';
 import { BackendSrv } from 'app/core/services/backend_srv';
 import { DatasourceSrv } from 'app/features/plugins/datasource_srv';
+import { ContextSrv } from 'app/core/services/context_srv';
 
 function WrapInProvider(store, Component, props) {
   return (
@@ -16,16 +17,31 @@ function WrapInProvider(store, Component, props) {
 }
 
 /** @ngInject */
-export function reactContainer($route, $location, backendSrv: BackendSrv, datasourceSrv: DatasourceSrv) {
+export function reactContainer(
+  $route,
+  $location,
+  backendSrv: BackendSrv,
+  datasourceSrv: DatasourceSrv,
+  contextSrv: ContextSrv
+) {
   return {
     restrict: 'E',
     template: '',
     link(scope, elem) {
-      let component = $route.current.locals.component;
+      // Check permissions for this component
+      const { roles } = $route.current.locals;
+      if (roles && roles.length) {
+        if (!roles.some(r => contextSrv.hasRole(r))) {
+          $location.url('/');
+        }
+      }
+
+      let { component } = $route.current.locals;
       // Dynamic imports return whole module, need to extract default export
       if (component.default) {
         component = component.default;
       }
+
       const props = {
         backendSrv: backendSrv,
         datasourceSrv: datasourceSrv,

+ 1 - 0
public/app/routes/routes.ts

@@ -113,6 +113,7 @@ export function setupAngularRoutes($routeProvider, $locationProvider) {
     .when('/explore/:initial?', {
       template: '<react-container />',
       resolve: {
+        roles: () => ['Editor', 'Admin'],
         component: () => import(/* webpackChunkName: "explore" */ 'app/containers/Explore/Wrapper'),
       },
     })