Compare commits

..

27 Commits

Author SHA1 Message Date
Torkel Ödegaard
af6e28366b build: updated version 2018-02-28 18:42:58 +01:00
Marcus Efraimsson
3d4e99d8be Merge pull request #11040 from grafana/permission-checks
Add permission check for dashboard diff api route
2018-02-28 18:23:39 +01:00
Laurent Godet
59704ee939 Fix Github OAuth not working with private Organizations (#11028)
* Fix Github OAuth not working with private organizations

* Update documentation
2018-02-28 13:08:15 +01:00
Torkel Ödegaard
e67b1ebf33 Merge branch 'master' of github.com:grafana/grafana 2018-02-28 13:06:16 +01:00
Marcus Efraimsson
576deb37e8 login: hide sign up if configured so. Fixes #11041 2018-02-28 13:01:35 +01:00
Torkel Ödegaard
0c6b34e216 Merge branch 'tag-variables' of https://github.com/DanCech/grafana into DanCech-tag-variables 2018-02-28 12:39:53 +01:00
Marcus Efraimsson
c298c7f3c9 Merge pull request #11034 from grafana/10701_permissions
Only allow override permissions that is higher
2018-02-28 09:15:46 +01:00
Marcus Efraimsson
f44e476580 permissions: fix validation of permissions before update
Did a bad pointer comparison so extended the tests for duplicate permissions.
2018-02-28 08:48:28 +01:00
Daniel Lee
ecf438f8d0 dashboard: add permission check for diff api route
ref #10770
2018-02-27 17:53:30 +01:00
Marcus Efraimsson
f4d9536ce1 Merge pull request #11031 from jganseman/patch-1
grammar fix, add dir, and remove redundant info
2018-02-27 16:36:44 +01:00
Marcus Efraimsson
71a0a298bf permissions: remove client validation and handle server validation 2018-02-27 16:04:45 +01:00
Marcus Efraimsson
f76b98d252 dashboards: change dashboard/folder permission error messages 2018-02-27 16:04:45 +01:00
Marcus Efraimsson
03f8eff880 dashboards: handle new guardian error responses and update tests
Using a mocked guardian instead and relies on the actual guardian tests for verifying
permission handling correctness
2018-02-27 16:04:45 +01:00
Marcus Efraimsson
3c14cecd50 folders: handle new guardian error responses and add tests 2018-02-27 16:04:45 +01:00
Marcus Efraimsson
955dfcc8fe dashboards: don't allow override of permissions with a lower precedence
If a dashboard inherits permissions from a folder, don't allow same permission to be added
to the dashboard with a lower permission.
Add backend validation so that you cannot add same permission to folder/dashboard, for example
same user/team with different permissions
2018-02-27 16:04:45 +01:00
Marcel Anacker
69006a7764 Alerting: Fix OK state doesn't show up in Microsoft Teams (#11032)
* Alerting: Fix OK state doesn't show up in Microsoft Teams

* Fix formatting
2018-02-27 15:48:36 +01:00
jganseman
7d91b73774 grammar fix, add dir, and remove redundant info 2018-02-27 13:26:53 +01:00
Torkel Ödegaard
dff2c14aed fix: changed react-grid-layout to use grafana fork to a commit before 15503084fb, this fixes all the panel movement bugs, fixes #10831 2018-02-27 11:34:29 +01:00
Patrick O'Carroll
7fb34f7918 gave scroll-canvas-dashboard 100% height in kiosk-mode, fixes #11010 (#11017) 2018-02-26 11:52:48 +01:00
Daniel Lee
6f4c043dd6 docs: update to install pages for beta5 2018-02-26 11:05:37 +01:00
Daniel Lee
ef67d20971 changelog: update for v5.0.0-beta5 2018-02-26 11:05:37 +01:00
Marcus Efraimsson
a7639855e4 Merge pull request #11014 from mtulio/doc-fix-config
[doc] Fix extra alerting options in installation->configuration
2018-02-26 09:54:10 +01:00
Marco Tulio R Braga
1a170133ca [doc] Fix extra alerting options in installation->configuration 2018-02-26 03:04:39 -03:00
Dan Cech
abf498fc7f improve maintainability 2018-02-23 09:51:19 -05:00
Dan Cech
dc6f977304 support [[variable:type]] syntax 2018-02-22 18:45:11 -05:00
Brian Gann
fa193fa888 offer template variables for tags 2018-02-22 18:45:11 -05:00
Brian Gann
b316dfea98 feature for issue #9911 2018-02-22 18:45:11 -05:00
32 changed files with 1347 additions and 324 deletions

View File

@@ -1,3 +1,15 @@
# 5.0.0-beta5 (2018-02-26)
### Fixes
- **Orgs** Unable to switch org when too many orgs listed [#10774](https://github.com/grafana/grafana/issues/10774)
- **Folders** Make it easier/explicit to access/modify folders using the API [#10630](https://github.com/grafana/grafana/issues/10630)
- **Dashboard** Scrollbar works incorrectly in Grafana 5.0 Beta4 in some cases [#10982](https://github.com/grafana/grafana/issues/10982)
- **ElasticSearch** Custom aggregation sizes no longer allowed for Elasticsearch [#10124](https://github.com/grafana/grafana/issues/10124)
- **oauth** Github OAuth with allowed organizations fails to login [#10964](https://github.com/grafana/grafana/issues/10964)
- **heatmap** Heatmap panel has partially hidden legend [#10793](https://github.com/grafana/grafana/issues/10793)
- **snapshots** Expired snapshots not being cleaned up [#10996](https://github.com/grafana/grafana/pull/10996)
# 5.0.0-beta4 (2018-02-19)
### Fixes

View File

@@ -248,7 +248,7 @@ enabled = false
allow_sign_up = true
client_id = some_id
client_secret = some_secret
scopes = user:email
scopes = user:email,read:org
auth_url = https://github.com/login/oauth/authorize
token_url = https://github.com/login/oauth/access_token
api_url = https://api.github.com/user

View File

@@ -61,7 +61,7 @@ Content-Type: application/json
"client_id":"some_id",
"client_secret":"************",
"enabled":"false",
"scopes":"user:email",
"scopes":"user:email,read:org",
"team_ids":"",
"token_url":"https://github.com/login/oauth/access_token"
},

View File

@@ -354,7 +354,7 @@ enabled = true
allow_sign_up = true
client_id = YOUR_GITHUB_APP_CLIENT_ID
client_secret = YOUR_GITHUB_APP_CLIENT_SECRET
scopes = user:email
scopes = user:email,read:org
auth_url = https://github.com/login/oauth/authorize
token_url = https://github.com/login/oauth/access_token
api_url = https://api.github.com/user
@@ -387,6 +387,7 @@ scopes = user:email,read:org
team_ids = 150,300
auth_url = https://github.com/login/oauth/authorize
token_url = https://github.com/login/oauth/access_token
api_url = https://api.github.com/user
allow_sign_up = true
```
@@ -405,6 +406,7 @@ client_secret = YOUR_GITHUB_APP_CLIENT_SECRET
scopes = user:email,read:org
auth_url = https://github.com/login/oauth/authorize
token_url = https://github.com/login/oauth/access_token
api_url = https://api.github.com/user
allow_sign_up = true
# space-delimited organization names
allowed_organizations = github google
@@ -875,6 +877,4 @@ Defaults to true. Set to false to disable alerting engine and hide Alerting from
### execute_alerts
### execute_alerts = true
Makes it possible to turn off alert rule execution.

View File

@@ -16,7 +16,7 @@ weight = 1
Description | Download
------------ | -------------
Stable for Debian-based Linux | [grafana_4.6.3_amd64.deb](https://s3-us-west-2.amazonaws.com/grafana-releases/release/grafana_4.6.3_amd64.deb)
Beta for Debian-based Linux | [grafana_5.0.0-beta4_amd64.deb](https://s3-us-west-2.amazonaws.com/grafana-releases/release/grafana_5.0.0-beta4_amd64.deb)
Beta for Debian-based Linux | [grafana_5.0.0-beta5_amd64.deb](https://s3-us-west-2.amazonaws.com/grafana-releases/release/grafana_5.0.0-beta5_amd64.deb)
Read [Upgrading Grafana]({{< relref "installation/upgrading.md" >}}) for tips and guidance on updating an existing
installation.
@@ -33,9 +33,9 @@ sudo dpkg -i grafana_4.6.3_amd64.deb
## Install Latest Beta
```bash
wget https://s3-us-west-2.amazonaws.com/grafana-releases/release/grafana_5.0.0-beta4_amd64.deb
wget https://s3-us-west-2.amazonaws.com/grafana-releases/release/grafana_5.0.0-beta5_amd64.deb
sudo apt-get install -y adduser libfontconfig
sudo dpkg -i grafana_5.0.0-beta4_amd64.deb
sudo dpkg -i grafana_5.0.0-beta5_amd64.deb
```
## APT Repository

View File

@@ -16,7 +16,7 @@ weight = 2
Description | Download
------------ | -------------
Stable for CentOS / Fedora / OpenSuse / Redhat Linux | [4.6.3 (x86-64 rpm)](https://s3-us-west-2.amazonaws.com/grafana-releases/release/grafana-4.6.3-1.x86_64.rpm)
Latest Beta for CentOS / Fedora / OpenSuse / Redhat Linux | [5.0.0-beta4 (x86-64 rpm)](https://s3-us-west-2.amazonaws.com/grafana-releases/release/grafana-5.0.0-beta4.x86_64.rpm)
Latest Beta for CentOS / Fedora / OpenSuse / Redhat Linux | [5.0.0-beta5 (x86-64 rpm)](https://s3-us-west-2.amazonaws.com/grafana-releases/release/grafana-5.0.0-beta5.x86_64.rpm)
Read [Upgrading Grafana]({{< relref "installation/upgrading.md" >}}) for tips and guidance on updating an existing
installation.
@@ -32,7 +32,7 @@ $ sudo yum install https://s3-us-west-2.amazonaws.com/grafana-releases/release/g
## Install Beta
```bash
$ sudo yum install https://s3-us-west-2.amazonaws.com/grafana-releases/release/grafana-5.0.0-beta4.x86_64.rpm
$ sudo yum install https://s3-us-west-2.amazonaws.com/grafana-releases/release/grafana-5.0.0-beta5.x86_64.rpm
```
Or install manually using `rpm`.

View File

@@ -14,7 +14,7 @@ weight = 3
Description | Download
------------ | -------------
Latest stable package for Windows | [grafana.4.6.3.windows-x64.zip](https://s3-us-west-2.amazonaws.com/grafana-releases/release/grafana-4.6.3.windows-x64.zip)
Latest beta package for Windows | [grafana.5.0.0-beta4.windows-x64.zip](https://s3-us-west-2.amazonaws.com/grafana-releases/release/grafana-5.0.0-beta4.windows-x64.zip)
Latest beta package for Windows | [grafana.5.0.0-beta5.windows-x64.zip](https://s3-us-west-2.amazonaws.com/grafana-releases/release/grafana-5.0.0-beta5.windows-x64.zip)
Read [Upgrading Grafana]({{< relref "installation/upgrading.md" >}}) for tips and guidance on updating an existing
installation.
@@ -31,9 +31,9 @@ on windows. Edit `custom.ini` and uncomment the `http_port`
configuration option (`;` is the comment character in ini files) and change it to something like `8080` or similar.
That port should not require extra Windows privileges.
Start Grafana by executing `grafana-server.exe`, preferably from the
Start Grafana by executing `grafana-server.exe`, located in the `bin` directory, preferably from the
command line. If you want to run Grafana as windows service, download
[NSSM](https://nssm.cc/). It is very easy add Grafana as a Windows
[NSSM](https://nssm.cc/). It is very easy to add Grafana as a Windows
service using that tool.
Read more about the [configuration options]({{< relref "configuration.md" >}}).
@@ -43,7 +43,3 @@ Read more about the [configuration options]({{< relref "configuration.md" >}}).
The Grafana backend includes Sqlite3 which requires GCC to compile. So
in order to compile Grafana on Windows you need to install GCC. We
recommend [TDM-GCC](http://tdm-gcc.tdragon.net/download).
Copy `conf/sample.ini` to a file named `conf/custom.ini` and change the
web server port to something like 8080. The default Grafana port, 3000,
requires special privileges on Windows.

View File

@@ -4,7 +4,7 @@
"company": "Grafana Labs"
},
"name": "grafana",
"version": "5.0.0-beta5",
"version": "5.0.0",
"repository": {
"type": "git",
"url": "http://github.com/grafana/grafana.git"
@@ -155,7 +155,7 @@
"prop-types": "^15.6.0",
"react": "^16.2.0",
"react-dom": "^16.2.0",
"react-grid-layout": "^0.16.2",
"react-grid-layout-grafana": "0.16.0",
"react-highlight-words": "^0.10.0",
"react-popper": "^0.7.5",
"react-select": "^1.1.0",

View File

@@ -1,6 +1,6 @@
#! /usr/bin/env bash
deb_ver=5.0.0-beta4
rpm_ver=5.0.0-beta4
deb_ver=5.0.0-beta5
rpm_ver=5.0.0-beta5
wget https://s3-us-west-2.amazonaws.com/grafana-releases/release/grafana_${deb_ver}_amd64.deb

View File

@@ -411,6 +411,18 @@ func GetDashboardVersion(c *middleware.Context) Response {
// POST /api/dashboards/calculate-diff performs diffs on two dashboards
func CalculateDashboardDiff(c *middleware.Context, apiOptions dtos.CalculateDiffOptions) Response {
guardianBase := guardian.New(apiOptions.Base.DashboardId, c.OrgId, c.SignedInUser)
if canSave, err := guardianBase.CanSave(); err != nil || !canSave {
return dashboardGuardianResponse(err)
}
if apiOptions.Base.DashboardId != apiOptions.New.DashboardId {
guardianNew := guardian.New(apiOptions.New.DashboardId, c.OrgId, c.SignedInUser)
if canSave, err := guardianNew.CanSave(); err != nil || !canSave {
return dashboardGuardianResponse(err)
}
}
options := dashdiffs.Options{
OrgId: c.OrgId,
DiffType: dashdiffs.ParseDiffType(apiOptions.DiffType),
@@ -436,9 +448,9 @@ func CalculateDashboardDiff(c *middleware.Context, apiOptions dtos.CalculateDiff
if options.DiffType == dashdiffs.DiffDelta {
return Respond(200, result.Delta).Header("Content-Type", "application/json")
} else {
return Respond(200, result.Delta).Header("Content-Type", "text/html")
}
return Respond(200, result.Delta).Header("Content-Type", "text/html")
}
// RestoreDashboardVersion restores a dashboard to the given version.

View File

@@ -18,13 +18,13 @@ func GetDashboardPermissionList(c *middleware.Context) Response {
return rsp
}
guardian := guardian.New(dashId, c.OrgId, c.SignedInUser)
g := guardian.New(dashId, c.OrgId, c.SignedInUser)
if canAdmin, err := guardian.CanAdmin(); err != nil || !canAdmin {
if canAdmin, err := g.CanAdmin(); err != nil || !canAdmin {
return dashboardGuardianResponse(err)
}
acl, err := guardian.GetAcl()
acl, err := g.GetAcl()
if err != nil {
return ApiError(500, "Failed to get dashboard permissions", err)
}
@@ -46,8 +46,8 @@ func UpdateDashboardPermissions(c *middleware.Context, apiCmd dtos.UpdateDashboa
return rsp
}
guardian := guardian.New(dashId, c.OrgId, c.SignedInUser)
if canAdmin, err := guardian.CanAdmin(); err != nil || !canAdmin {
g := guardian.New(dashId, c.OrgId, c.SignedInUser)
if canAdmin, err := g.CanAdmin(); err != nil || !canAdmin {
return dashboardGuardianResponse(err)
}
@@ -67,8 +67,13 @@ func UpdateDashboardPermissions(c *middleware.Context, apiCmd dtos.UpdateDashboa
})
}
if okToUpdate, err := guardian.CheckPermissionBeforeUpdate(m.PERMISSION_ADMIN, cmd.Items); err != nil || !okToUpdate {
if okToUpdate, err := g.CheckPermissionBeforeUpdate(m.PERMISSION_ADMIN, cmd.Items); err != nil || !okToUpdate {
if err != nil {
if err == guardian.ErrGuardianPermissionExists ||
err == guardian.ErrGuardianOverride {
return ApiError(400, err.Error(), err)
}
return ApiError(500, "Error while checking dashboard permissions", err)
}

View File

@@ -8,183 +8,180 @@ import (
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/middleware"
m "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/guardian"
. "github.com/smartystreets/goconvey/convey"
)
func TestDashboardPermissionApiEndpoint(t *testing.T) {
Convey("Given a dashboard with permissions", t, func() {
mockResult := []*m.DashboardAclInfoDTO{
{OrgId: 1, DashboardId: 1, UserId: 2, Permission: m.PERMISSION_VIEW},
{OrgId: 1, DashboardId: 1, UserId: 3, Permission: m.PERMISSION_EDIT},
{OrgId: 1, DashboardId: 1, UserId: 4, Permission: m.PERMISSION_ADMIN},
{OrgId: 1, DashboardId: 1, TeamId: 1, Permission: m.PERMISSION_VIEW},
{OrgId: 1, DashboardId: 1, TeamId: 2, Permission: m.PERMISSION_ADMIN},
}
dtoRes := transformDashboardAclsToDTOs(mockResult)
getDashboardQueryResult := m.NewDashboard("Dash")
var getDashboardNotFoundError error
bus.AddHandler("test", func(query *m.GetDashboardQuery) error {
query.Result = getDashboardQueryResult
return getDashboardNotFoundError
})
bus.AddHandler("test", func(query *m.GetDashboardAclInfoListQuery) error {
query.Result = dtoRes
return nil
})
bus.AddHandler("test", func(query *m.GetDashboardAclInfoListQuery) error {
query.Result = mockResult
return nil
})
teamResp := []*m.Team{}
bus.AddHandler("test", func(query *m.GetTeamsByUserQuery) error {
query.Result = teamResp
return nil
})
// This tests four scenarios:
// 1. user is an org admin
// 2. user is an org editor AND has been granted admin permission for the dashboard
// 3. user is an org viewer AND has been granted edit permission for the dashboard
// 4. user is an org editor AND has no permissions for the dashboard
Convey("When user is org admin", func() {
loggedInUserScenarioWithRole("When calling GET on", "GET", "/api/dashboards/id/1/permissions", "/api/dashboards/id/:dashboardsId/permissions", m.ROLE_ADMIN, func(sc *scenarioContext) {
Convey("Should be able to access ACL", func() {
sc.handlerFunc = GetDashboardPermissionList
sc.fakeReqWithParams("GET", sc.url, map[string]string{}).exec()
So(sc.resp.Code, ShouldEqual, 200)
respJSON, err := simplejson.NewJson(sc.resp.Body.Bytes())
So(err, ShouldBeNil)
So(len(respJSON.MustArray()), ShouldEqual, 5)
So(respJSON.GetIndex(0).Get("userId").MustInt(), ShouldEqual, 2)
So(respJSON.GetIndex(0).Get("permission").MustInt(), ShouldEqual, m.PERMISSION_VIEW)
})
Convey("Dashboard permissions test", t, func() {
Convey("Given dashboard not exists", func() {
bus.AddHandler("test", func(query *m.GetDashboardQuery) error {
return m.ErrDashboardNotFound
})
loggedInUserScenarioWithRole("When calling GET on", "GET", "/api/dashboards/id/2/permissions", "/api/dashboards/id/:dashboardId/permissions", m.ROLE_ADMIN, func(sc *scenarioContext) {
getDashboardNotFoundError = m.ErrDashboardNotFound
sc.handlerFunc = GetDashboardPermissionList
sc.fakeReqWithParams("GET", sc.url, map[string]string{}).exec()
Convey("Should not be able to access ACL", func() {
So(sc.resp.Code, ShouldEqual, 404)
})
loggedInUserScenarioWithRole("When calling GET on", "GET", "/api/dashboards/id/1/permissions", "/api/dashboards/id/:id/permissions", m.ROLE_EDITOR, func(sc *scenarioContext) {
callGetDashboardPermissions(sc)
So(sc.resp.Code, ShouldEqual, 404)
})
Convey("Should not be able to update permissions for non-existing dashboard", func() {
cmd := dtos.UpdateDashboardAclCommand{
Items: []dtos.DashboardAclUpdateItem{
{UserId: 1000, Permission: m.PERMISSION_ADMIN},
},
}
cmd := dtos.UpdateDashboardAclCommand{
Items: []dtos.DashboardAclUpdateItem{
{UserId: 1000, Permission: m.PERMISSION_ADMIN},
},
}
postAclScenario("When calling POST on", "/api/dashboards/id/1/permissions", "/api/dashboards/id/:dashboardId/permissions", m.ROLE_ADMIN, cmd, func(sc *scenarioContext) {
getDashboardNotFoundError = m.ErrDashboardNotFound
CallPostAcl(sc)
So(sc.resp.Code, ShouldEqual, 404)
})
updateDashboardPermissionScenario("When calling POST on", "/api/dashboards/id/1/permissions", "/api/dashboards/id/:id/permissions", cmd, func(sc *scenarioContext) {
callUpdateDashboardPermissions(sc)
So(sc.resp.Code, ShouldEqual, 404)
})
})
Convey("When user is org editor and has admin permission in the ACL", func() {
loggedInUserScenarioWithRole("When calling GET on", "GET", "/api/dashboards/id/1/permissions", "/api/dashboards/id/:dashboardId/permissions", m.ROLE_EDITOR, func(sc *scenarioContext) {
mockResult = append(mockResult, &m.DashboardAclInfoDTO{OrgId: 1, DashboardId: 1, UserId: 1, Permission: m.PERMISSION_ADMIN})
Convey("Given user has no admin permissions", func() {
origNewGuardian := guardian.New
guardian.MockDashboardGuardian(&guardian.FakeDashboardGuardian{CanAdminValue: false})
Convey("Should be able to access ACL", func() {
sc.handlerFunc = GetDashboardPermissionList
sc.fakeReqWithParams("GET", sc.url, map[string]string{}).exec()
So(sc.resp.Code, ShouldEqual, 200)
})
getDashboardQueryResult := m.NewDashboard("Dash")
bus.AddHandler("test", func(query *m.GetDashboardQuery) error {
query.Result = getDashboardQueryResult
return nil
})
Convey("Should not be able to downgrade their own Admin permission", func() {
cmd := dtos.UpdateDashboardAclCommand{
Items: []dtos.DashboardAclUpdateItem{
{UserId: TestUserID, Permission: m.PERMISSION_EDIT},
},
}
postAclScenario("When calling POST on", "/api/dashboards/id/1/permissions", "/api/dashboards/id/:dashboardId/permissions", m.ROLE_EDITOR, cmd, func(sc *scenarioContext) {
mockResult = append(mockResult, &m.DashboardAclInfoDTO{OrgId: 1, DashboardId: 1, UserId: 1, Permission: m.PERMISSION_ADMIN})
CallPostAcl(sc)
So(sc.resp.Code, ShouldEqual, 403)
})
loggedInUserScenarioWithRole("When calling GET on", "GET", "/api/dashboards/id/1/permissions", "/api/dashboards/id/:id/permissions", m.ROLE_EDITOR, func(sc *scenarioContext) {
callGetDashboardPermissions(sc)
So(sc.resp.Code, ShouldEqual, 403)
})
Convey("Should be able to update permissions", func() {
cmd := dtos.UpdateDashboardAclCommand{
Items: []dtos.DashboardAclUpdateItem{
{UserId: TestUserID, Permission: m.PERMISSION_ADMIN},
{UserId: 2, Permission: m.PERMISSION_EDIT},
},
}
cmd := dtos.UpdateDashboardAclCommand{
Items: []dtos.DashboardAclUpdateItem{
{UserId: 1000, Permission: m.PERMISSION_ADMIN},
},
}
postAclScenario("When calling POST on", "/api/dashboards/id/1/permissions", "/api/dashboards/id/:dashboardId/permissions", m.ROLE_EDITOR, cmd, func(sc *scenarioContext) {
mockResult = append(mockResult, &m.DashboardAclInfoDTO{OrgId: 1, DashboardId: 1, UserId: 1, Permission: m.PERMISSION_ADMIN})
CallPostAcl(sc)
So(sc.resp.Code, ShouldEqual, 200)
})
updateDashboardPermissionScenario("When calling POST on", "/api/dashboards/id/1/permissions", "/api/dashboards/id/:id/permissions", cmd, func(sc *scenarioContext) {
callUpdateDashboardPermissions(sc)
So(sc.resp.Code, ShouldEqual, 403)
})
})
Convey("When user is org viewer and has edit permission in the ACL", func() {
loggedInUserScenarioWithRole("When calling GET on", "GET", "/api/dashboards/id/1/permissions", "/api/dashboards/id/:dashboardId/permissions", m.ROLE_VIEWER, func(sc *scenarioContext) {
mockResult = append(mockResult, &m.DashboardAclInfoDTO{OrgId: 1, DashboardId: 1, UserId: 1, Permission: m.PERMISSION_EDIT})
// Getting the permissions is an Admin permission
Convey("Should not be able to get list of permissions from ACL", func() {
sc.handlerFunc = GetDashboardPermissionList
sc.fakeReqWithParams("GET", sc.url, map[string]string{}).exec()
So(sc.resp.Code, ShouldEqual, 403)
})
Reset(func() {
guardian.New = origNewGuardian
})
})
Convey("When user is org editor and not in the ACL", func() {
loggedInUserScenarioWithRole("When calling GET on", "GET", "/api/dashboards/id/1/permissions", "/api/dashboards/id/:dashboardsId/permissions", m.ROLE_EDITOR, func(sc *scenarioContext) {
Convey("Given user has admin permissions and permissions to update", func() {
origNewGuardian := guardian.New
guardian.MockDashboardGuardian(&guardian.FakeDashboardGuardian{
CanAdminValue: true,
CheckPermissionBeforeUpdateValue: true,
GetAclValue: []*m.DashboardAclInfoDTO{
{OrgId: 1, DashboardId: 1, UserId: 2, Permission: m.PERMISSION_VIEW},
{OrgId: 1, DashboardId: 1, UserId: 3, Permission: m.PERMISSION_EDIT},
{OrgId: 1, DashboardId: 1, UserId: 4, Permission: m.PERMISSION_ADMIN},
{OrgId: 1, DashboardId: 1, TeamId: 1, Permission: m.PERMISSION_VIEW},
{OrgId: 1, DashboardId: 1, TeamId: 2, Permission: m.PERMISSION_ADMIN},
},
})
Convey("Should not be able to access ACL", func() {
sc.handlerFunc = GetDashboardPermissionList
sc.fakeReqWithParams("GET", sc.url, map[string]string{}).exec()
getDashboardQueryResult := m.NewDashboard("Dash")
bus.AddHandler("test", func(query *m.GetDashboardQuery) error {
query.Result = getDashboardQueryResult
return nil
})
So(sc.resp.Code, ShouldEqual, 403)
})
loggedInUserScenarioWithRole("When calling GET on", "GET", "/api/dashboards/id/1/permissions", "/api/dashboards/id/:id/permissions", m.ROLE_ADMIN, func(sc *scenarioContext) {
callGetDashboardPermissions(sc)
So(sc.resp.Code, ShouldEqual, 200)
respJSON, err := simplejson.NewJson(sc.resp.Body.Bytes())
So(err, ShouldBeNil)
So(len(respJSON.MustArray()), ShouldEqual, 5)
So(respJSON.GetIndex(0).Get("userId").MustInt(), ShouldEqual, 2)
So(respJSON.GetIndex(0).Get("permission").MustInt(), ShouldEqual, m.PERMISSION_VIEW)
})
cmd := dtos.UpdateDashboardAclCommand{
Items: []dtos.DashboardAclUpdateItem{
{UserId: 1000, Permission: m.PERMISSION_ADMIN},
},
}
updateDashboardPermissionScenario("When calling POST on", "/api/dashboards/id/1/permissions", "/api/dashboards/id/:id/permissions", cmd, func(sc *scenarioContext) {
callUpdateDashboardPermissions(sc)
So(sc.resp.Code, ShouldEqual, 200)
})
Reset(func() {
guardian.New = origNewGuardian
})
})
Convey("When trying to update permissions with duplicate permissions", func() {
origNewGuardian := guardian.New
guardian.MockDashboardGuardian(&guardian.FakeDashboardGuardian{
CanAdminValue: true,
CheckPermissionBeforeUpdateValue: false,
CheckPermissionBeforeUpdateError: guardian.ErrGuardianPermissionExists,
})
getDashboardQueryResult := m.NewDashboard("Dash")
bus.AddHandler("test", func(query *m.GetDashboardQuery) error {
query.Result = getDashboardQueryResult
return nil
})
cmd := dtos.UpdateDashboardAclCommand{
Items: []dtos.DashboardAclUpdateItem{
{UserId: 1000, Permission: m.PERMISSION_ADMIN},
},
}
updateDashboardPermissionScenario("When calling POST on", "/api/dashboards/id/1/permissions", "/api/dashboards/id/:id/permissions", cmd, func(sc *scenarioContext) {
callUpdateDashboardPermissions(sc)
So(sc.resp.Code, ShouldEqual, 400)
})
Reset(func() {
guardian.New = origNewGuardian
})
})
Convey("When trying to override inherited permissions with lower presedence", func() {
origNewGuardian := guardian.New
guardian.MockDashboardGuardian(&guardian.FakeDashboardGuardian{
CanAdminValue: true,
CheckPermissionBeforeUpdateValue: false,
CheckPermissionBeforeUpdateError: guardian.ErrGuardianOverride},
)
getDashboardQueryResult := m.NewDashboard("Dash")
bus.AddHandler("test", func(query *m.GetDashboardQuery) error {
query.Result = getDashboardQueryResult
return nil
})
cmd := dtos.UpdateDashboardAclCommand{
Items: []dtos.DashboardAclUpdateItem{
{UserId: 1000, Permission: m.PERMISSION_ADMIN},
},
}
updateDashboardPermissionScenario("When calling POST on", "/api/dashboards/id/1/permissions", "/api/dashboards/id/:id/permissions", cmd, func(sc *scenarioContext) {
callUpdateDashboardPermissions(sc)
So(sc.resp.Code, ShouldEqual, 400)
})
Reset(func() {
guardian.New = origNewGuardian
})
})
})
}
func transformDashboardAclsToDTOs(acls []*m.DashboardAclInfoDTO) []*m.DashboardAclInfoDTO {
dtos := make([]*m.DashboardAclInfoDTO, 0)
for _, acl := range acls {
dto := &m.DashboardAclInfoDTO{
OrgId: acl.OrgId,
DashboardId: acl.DashboardId,
Permission: acl.Permission,
UserId: acl.UserId,
TeamId: acl.TeamId,
}
dtos = append(dtos, dto)
}
return dtos
func callGetDashboardPermissions(sc *scenarioContext) {
sc.handlerFunc = GetDashboardPermissionList
sc.fakeReqWithParams("GET", sc.url, map[string]string{}).exec()
}
func CallPostAcl(sc *scenarioContext) {
func callUpdateDashboardPermissions(sc *scenarioContext) {
bus.AddHandler("test", func(cmd *m.UpdateDashboardAclCommand) error {
return nil
})
@@ -192,7 +189,7 @@ func CallPostAcl(sc *scenarioContext) {
sc.fakeReqWithParams("POST", sc.url, map[string]string{}).exec()
}
func postAclScenario(desc string, url string, routePattern string, role m.RoleType, cmd dtos.UpdateDashboardAclCommand, fn scenarioFunc) {
func updateDashboardPermissionScenario(desc string, url string, routePattern string, cmd dtos.UpdateDashboardAclCommand, fn scenarioFunc) {
Convey(desc+" "+url, func() {
defer bus.ClearBusHandlers()
@@ -200,9 +197,8 @@ func postAclScenario(desc string, url string, routePattern string, role m.RoleTy
sc.defaultHandler = wrap(func(c *middleware.Context) Response {
sc.context = c
sc.context.UserId = TestUserID
sc.context.OrgId = TestOrgID
sc.context.OrgRole = role
sc.context.UserId = TestUserID
return UpdateDashboardPermissions(c, cmd)
})

View File

@@ -743,6 +743,53 @@ func TestDashboardApiEndpoint(t *testing.T) {
}
})
})
Convey("Given two dashboards being compared", t, func() {
mockResult := []*m.DashboardAclInfoDTO{}
bus.AddHandler("test", func(query *m.GetDashboardAclInfoListQuery) error {
query.Result = mockResult
return nil
})
bus.AddHandler("test", func(query *m.GetDashboardVersionQuery) error {
query.Result = &m.DashboardVersion{
Data: simplejson.NewFromAny(map[string]interface{}{
"title": "Dash" + string(query.DashboardId),
}),
}
return nil
})
cmd := dtos.CalculateDiffOptions{
Base: dtos.CalculateDiffTarget{
DashboardId: 1,
Version: 1,
},
New: dtos.CalculateDiffTarget{
DashboardId: 2,
Version: 2,
},
DiffType: "basic",
}
Convey("when user does not have permission", func() {
role := m.ROLE_VIEWER
postDiffScenario("When calling POST on", "/api/dashboards/calculate-diff", "/api/dashboards/calculate-diff", cmd, role, func(sc *scenarioContext) {
CallPostDashboard(sc)
So(sc.resp.Code, ShouldEqual, 403)
})
})
Convey("when user does have permission", func() {
role := m.ROLE_ADMIN
postDiffScenario("When calling POST on", "/api/dashboards/calculate-diff", "/api/dashboards/calculate-diff", cmd, role, func(sc *scenarioContext) {
CallPostDashboard(sc)
So(sc.resp.Code, ShouldEqual, 200)
})
})
})
}
func GetDashboardShouldReturn200(sc *scenarioContext) dtos.DashboardFullWithMeta {
@@ -835,6 +882,28 @@ func postDashboardScenario(desc string, url string, routePattern string, mock *d
})
}
func postDiffScenario(desc string, url string, routePattern string, cmd dtos.CalculateDiffOptions, role m.RoleType, fn scenarioFunc) {
Convey(desc+" "+url, func() {
defer bus.ClearBusHandlers()
sc := setupScenarioContext(url)
sc.defaultHandler = wrap(func(c *middleware.Context) Response {
sc.context = c
sc.context.SignedInUser = &m.SignedInUser{
OrgId: TestOrgID,
UserId: TestUserID,
}
sc.context.OrgRole = role
return CalculateDashboardDiff(c, cmd)
})
sc.m.Post(routePattern, sc.defaultHandler)
fn(sc)
})
}
func (sc *scenarioContext) ToJson() *simplejson.Json {
var result *simplejson.Json
err := json.NewDecoder(sc.resp.Body).Decode(&result)

View File

@@ -19,13 +19,13 @@ func GetFolderPermissionList(c *middleware.Context) Response {
return toFolderError(err)
}
guardian := guardian.New(folder.Id, c.OrgId, c.SignedInUser)
g := guardian.New(folder.Id, c.OrgId, c.SignedInUser)
if canAdmin, err := guardian.CanAdmin(); err != nil || !canAdmin {
if canAdmin, err := g.CanAdmin(); err != nil || !canAdmin {
return toFolderError(m.ErrFolderAccessDenied)
}
acl, err := guardian.GetAcl()
acl, err := g.GetAcl()
if err != nil {
return ApiError(500, "Failed to get folder permissions", err)
}
@@ -50,8 +50,8 @@ func UpdateFolderPermissions(c *middleware.Context, apiCmd dtos.UpdateDashboardA
return toFolderError(err)
}
guardian := guardian.New(folder.Id, c.OrgId, c.SignedInUser)
canAdmin, err := guardian.CanAdmin()
g := guardian.New(folder.Id, c.OrgId, c.SignedInUser)
canAdmin, err := g.CanAdmin()
if err != nil {
return toFolderError(err)
}
@@ -76,8 +76,13 @@ func UpdateFolderPermissions(c *middleware.Context, apiCmd dtos.UpdateDashboardA
})
}
if okToUpdate, err := guardian.CheckPermissionBeforeUpdate(m.PERMISSION_ADMIN, cmd.Items); err != nil || !okToUpdate {
if okToUpdate, err := g.CheckPermissionBeforeUpdate(m.PERMISSION_ADMIN, cmd.Items); err != nil || !okToUpdate {
if err != nil {
if err == guardian.ErrGuardianPermissionExists ||
err == guardian.ErrGuardianOverride {
return ApiError(400, err.Error(), err)
}
return ApiError(500, "Error while checking folder permissions", err)
}

View File

@@ -5,6 +5,7 @@ import (
"github.com/grafana/grafana/pkg/api/dtos"
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/middleware"
m "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/dashboards"
@@ -15,6 +16,35 @@ import (
func TestFolderPermissionApiEndpoint(t *testing.T) {
Convey("Folder permissions test", t, func() {
Convey("Given folder not exists", func() {
mock := &fakeFolderService{
GetFolderByUidError: m.ErrFolderNotFound,
}
origNewFolderService := dashboards.NewFolderService
mockFolderService(mock)
loggedInUserScenarioWithRole("When calling GET on", "GET", "/api/folders/uid/permissions", "/api/folders/:uid/permissions", m.ROLE_EDITOR, func(sc *scenarioContext) {
callGetFolderPermissions(sc)
So(sc.resp.Code, ShouldEqual, 404)
})
cmd := dtos.UpdateDashboardAclCommand{
Items: []dtos.DashboardAclUpdateItem{
{UserId: 1000, Permission: m.PERMISSION_ADMIN},
},
}
updateFolderPermissionScenario("When calling POST on", "/api/folders/uid/permissions", "/api/folders/:uid/permissions", cmd, func(sc *scenarioContext) {
callUpdateFolderPermissions(sc)
So(sc.resp.Code, ShouldEqual, 404)
})
Reset(func() {
dashboards.NewFolderService = origNewFolderService
})
})
Convey("Given user has no admin permissions", func() {
origNewGuardian := guardian.New
guardian.MockDashboardGuardian(&guardian.FakeDashboardGuardian{CanAdminValue: false})
@@ -54,7 +84,17 @@ func TestFolderPermissionApiEndpoint(t *testing.T) {
Convey("Given user has admin permissions and permissions to update", func() {
origNewGuardian := guardian.New
guardian.MockDashboardGuardian(&guardian.FakeDashboardGuardian{CanAdminValue: true, CheckPermissionBeforeUpdateValue: true})
guardian.MockDashboardGuardian(&guardian.FakeDashboardGuardian{
CanAdminValue: true,
CheckPermissionBeforeUpdateValue: true,
GetAclValue: []*m.DashboardAclInfoDTO{
{OrgId: 1, DashboardId: 1, UserId: 2, Permission: m.PERMISSION_VIEW},
{OrgId: 1, DashboardId: 1, UserId: 3, Permission: m.PERMISSION_EDIT},
{OrgId: 1, DashboardId: 1, UserId: 4, Permission: m.PERMISSION_ADMIN},
{OrgId: 1, DashboardId: 1, TeamId: 1, Permission: m.PERMISSION_VIEW},
{OrgId: 1, DashboardId: 1, TeamId: 2, Permission: m.PERMISSION_ADMIN},
},
})
mock := &fakeFolderService{
GetFolderByUidResult: &m.Folder{
@@ -70,6 +110,11 @@ func TestFolderPermissionApiEndpoint(t *testing.T) {
loggedInUserScenarioWithRole("When calling GET on", "GET", "/api/folders/uid/permissions", "/api/folders/:uid/permissions", m.ROLE_ADMIN, func(sc *scenarioContext) {
callGetFolderPermissions(sc)
So(sc.resp.Code, ShouldEqual, 200)
respJSON, err := simplejson.NewJson(sc.resp.Body.Bytes())
So(err, ShouldBeNil)
So(len(respJSON.MustArray()), ShouldEqual, 5)
So(respJSON.GetIndex(0).Get("userId").MustInt(), ShouldEqual, 2)
So(respJSON.GetIndex(0).Get("permission").MustInt(), ShouldEqual, m.PERMISSION_VIEW)
})
cmd := dtos.UpdateDashboardAclCommand{
@@ -88,6 +133,78 @@ func TestFolderPermissionApiEndpoint(t *testing.T) {
dashboards.NewFolderService = origNewFolderService
})
})
Convey("When trying to update permissions with duplicate permissions", func() {
origNewGuardian := guardian.New
guardian.MockDashboardGuardian(&guardian.FakeDashboardGuardian{
CanAdminValue: true,
CheckPermissionBeforeUpdateValue: false,
CheckPermissionBeforeUpdateError: guardian.ErrGuardianPermissionExists,
})
mock := &fakeFolderService{
GetFolderByUidResult: &m.Folder{
Id: 1,
Uid: "uid",
Title: "Folder",
},
}
origNewFolderService := dashboards.NewFolderService
mockFolderService(mock)
cmd := dtos.UpdateDashboardAclCommand{
Items: []dtos.DashboardAclUpdateItem{
{UserId: 1000, Permission: m.PERMISSION_ADMIN},
},
}
updateFolderPermissionScenario("When calling POST on", "/api/folders/uid/permissions", "/api/folders/:uid/permissions", cmd, func(sc *scenarioContext) {
callUpdateFolderPermissions(sc)
So(sc.resp.Code, ShouldEqual, 400)
})
Reset(func() {
guardian.New = origNewGuardian
dashboards.NewFolderService = origNewFolderService
})
})
Convey("When trying to override inherited permissions with lower presedence", func() {
origNewGuardian := guardian.New
guardian.MockDashboardGuardian(&guardian.FakeDashboardGuardian{
CanAdminValue: true,
CheckPermissionBeforeUpdateValue: false,
CheckPermissionBeforeUpdateError: guardian.ErrGuardianOverride},
)
mock := &fakeFolderService{
GetFolderByUidResult: &m.Folder{
Id: 1,
Uid: "uid",
Title: "Folder",
},
}
origNewFolderService := dashboards.NewFolderService
mockFolderService(mock)
cmd := dtos.UpdateDashboardAclCommand{
Items: []dtos.DashboardAclUpdateItem{
{UserId: 1000, Permission: m.PERMISSION_ADMIN},
},
}
updateFolderPermissionScenario("When calling POST on", "/api/folders/uid/permissions", "/api/folders/:uid/permissions", cmd, func(sc *scenarioContext) {
callUpdateFolderPermissions(sc)
So(sc.resp.Code, ShouldEqual, 400)
})
Reset(func() {
guardian.New = origNewGuardian
dashboards.NewFolderService = origNewFolderService
})
})
})
}

View File

@@ -68,6 +68,27 @@ type DashboardAclInfoDTO struct {
Url string `json:"url"`
}
func (dto *DashboardAclInfoDTO) hasSameRoleAs(other *DashboardAclInfoDTO) bool {
if dto.Role == nil || other.Role == nil {
return false
}
return dto.UserId <= 0 && dto.TeamId <= 0 && dto.UserId == other.UserId && dto.TeamId == other.TeamId && *dto.Role == *other.Role
}
func (dto *DashboardAclInfoDTO) hasSameUserAs(other *DashboardAclInfoDTO) bool {
return dto.UserId > 0 && dto.UserId == other.UserId
}
func (dto *DashboardAclInfoDTO) hasSameTeamAs(other *DashboardAclInfoDTO) bool {
return dto.TeamId > 0 && dto.TeamId == other.TeamId
}
// IsDuplicateOf returns true if other item has same role, same user or same team
func (dto *DashboardAclInfoDTO) IsDuplicateOf(other *DashboardAclInfoDTO) bool {
return dto.hasSameRoleAs(other) || dto.hasSameUserAs(other) || dto.hasSameTeamAs(other)
}
//
// COMMANDS
//

View File

@@ -82,6 +82,8 @@ func (this *TeamsNotifier) Notify(evalContext *alerting.EvalContext) error {
message := this.Mention
if evalContext.Rule.State != m.AlertStateOK { //dont add message when going back to alert state ok.
message += " " + evalContext.Rule.Message
} else {
message += " " // summary must not be empty
}
body := map[string]interface{}{

View File

@@ -1,12 +1,19 @@
package guardian
import (
"errors"
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/log"
m "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/setting"
)
var (
ErrGuardianPermissionExists = errors.New("Permission already exists")
ErrGuardianOverride = errors.New("You can only override a permission to be higher")
)
// DashboardGuardian to be used for guard against operations without access on dashboard and acl
type DashboardGuardian interface {
CanSave() (bool, error)
@@ -119,14 +126,51 @@ func (g *dashboardGuardianImpl) checkAcl(permission m.PermissionType, acl []*m.D
}
func (g *dashboardGuardianImpl) CheckPermissionBeforeUpdate(permission m.PermissionType, updatePermissions []*m.DashboardAcl) (bool, error) {
if g.user.OrgRole == m.ROLE_ADMIN {
return true, nil
acl := []*m.DashboardAclInfoDTO{}
adminRole := m.ROLE_ADMIN
everyoneWithAdminRole := &m.DashboardAclInfoDTO{DashboardId: g.dashId, UserId: 0, TeamId: 0, Role: &adminRole, Permission: m.PERMISSION_ADMIN}
// validate that duplicate permissions don't exists
for _, p := range updatePermissions {
aclItem := &m.DashboardAclInfoDTO{DashboardId: p.DashboardId, UserId: p.UserId, TeamId: p.TeamId, Role: p.Role, Permission: p.Permission}
if aclItem.IsDuplicateOf(everyoneWithAdminRole) {
return false, ErrGuardianPermissionExists
}
for _, a := range acl {
if a.IsDuplicateOf(aclItem) {
return false, ErrGuardianPermissionExists
}
}
acl = append(acl, aclItem)
}
acl := []*m.DashboardAclInfoDTO{}
existingPermissions, err := g.GetAcl()
if err != nil {
return false, err
}
for _, p := range updatePermissions {
acl = append(acl, &m.DashboardAclInfoDTO{UserId: p.UserId, TeamId: p.TeamId, Role: p.Role, Permission: p.Permission})
// validate overridden permissions to be higher
for _, a := range acl {
for _, existingPerm := range existingPermissions {
// handle default permissions
if existingPerm.DashboardId == -1 {
existingPerm.DashboardId = g.dashId
}
if a.DashboardId == existingPerm.DashboardId {
continue
}
if a.IsDuplicateOf(existingPerm) && a.Permission <= existingPerm.Permission {
return false, ErrGuardianOverride
}
}
}
if g.user.OrgRole == m.ROLE_ADMIN {
return true, nil
}
return g.checkAcl(permission, acl)
@@ -143,6 +187,13 @@ func (g *dashboardGuardianImpl) GetAcl() ([]*m.DashboardAclInfoDTO, error) {
return nil, err
}
for _, a := range query.Result {
// handle default permissions
if a.DashboardId == -1 {
a.DashboardId = g.dashId
}
}
g.acl = query.Result
return g.acl, nil
}
@@ -169,6 +220,8 @@ type FakeDashboardGuardian struct {
CanAdminValue bool
HasPermissionValue bool
CheckPermissionBeforeUpdateValue bool
CheckPermissionBeforeUpdateError error
GetAclValue []*m.DashboardAclInfoDTO
}
func (g *FakeDashboardGuardian) CanSave() (bool, error) {
@@ -192,11 +245,11 @@ func (g *FakeDashboardGuardian) HasPermission(permission m.PermissionType) (bool
}
func (g *FakeDashboardGuardian) CheckPermissionBeforeUpdate(permission m.PermissionType, updatePermissions []*m.DashboardAcl) (bool, error) {
return g.CheckPermissionBeforeUpdateValue, nil
return g.CheckPermissionBeforeUpdateValue, g.CheckPermissionBeforeUpdateError
}
func (g *FakeDashboardGuardian) GetAcl() ([]*m.DashboardAclInfoDTO, error) {
return nil, nil
return g.GetAclValue, nil
}
func MockDashboardGuardian(mock *FakeDashboardGuardian) {

View File

@@ -0,0 +1,711 @@
package guardian
import (
"fmt"
"testing"
"github.com/grafana/grafana/pkg/bus"
m "github.com/grafana/grafana/pkg/models"
. "github.com/smartystreets/goconvey/convey"
)
func TestGuardian(t *testing.T) {
Convey("Guardian permission tests", t, func() {
orgRoleScenario("Given user has admin org role", m.ROLE_ADMIN, func(sc *scenarioContext) {
canAdmin, _ := sc.g.CanAdmin()
canEdit, _ := sc.g.CanEdit()
canSave, _ := sc.g.CanSave()
canView, _ := sc.g.CanView()
So(canAdmin, ShouldBeTrue)
So(canEdit, ShouldBeTrue)
So(canSave, ShouldBeTrue)
So(canView, ShouldBeTrue)
Convey("When trying to update permissions", func() {
Convey("With duplicate user permissions should return error", func() {
p := []*m.DashboardAcl{
{OrgId: 1, DashboardId: 1, UserId: 1, Permission: m.PERMISSION_VIEW},
{OrgId: 1, DashboardId: 1, UserId: 1, Permission: m.PERMISSION_ADMIN},
}
_, err := sc.g.CheckPermissionBeforeUpdate(m.PERMISSION_ADMIN, p)
So(err, ShouldEqual, ErrGuardianPermissionExists)
})
Convey("With duplicate team permissions should return error", func() {
p := []*m.DashboardAcl{
{OrgId: 1, DashboardId: 1, TeamId: 1, Permission: m.PERMISSION_VIEW},
{OrgId: 1, DashboardId: 1, TeamId: 1, Permission: m.PERMISSION_ADMIN},
}
_, err := sc.g.CheckPermissionBeforeUpdate(m.PERMISSION_ADMIN, p)
So(err, ShouldEqual, ErrGuardianPermissionExists)
})
Convey("With duplicate everyone with editor role permission should return error", func() {
r := m.ROLE_EDITOR
p := []*m.DashboardAcl{
{OrgId: 1, DashboardId: 1, Role: &r, Permission: m.PERMISSION_VIEW},
{OrgId: 1, DashboardId: 1, Role: &r, Permission: m.PERMISSION_ADMIN},
}
_, err := sc.g.CheckPermissionBeforeUpdate(m.PERMISSION_ADMIN, p)
So(err, ShouldEqual, ErrGuardianPermissionExists)
})
Convey("With duplicate everyone with viewer role permission should return error", func() {
r := m.ROLE_VIEWER
p := []*m.DashboardAcl{
{OrgId: 1, DashboardId: 1, Role: &r, Permission: m.PERMISSION_VIEW},
{OrgId: 1, DashboardId: 1, Role: &r, Permission: m.PERMISSION_ADMIN},
}
_, err := sc.g.CheckPermissionBeforeUpdate(m.PERMISSION_ADMIN, p)
So(err, ShouldEqual, ErrGuardianPermissionExists)
})
Convey("With everyone with admin role permission should return error", func() {
r := m.ROLE_ADMIN
p := []*m.DashboardAcl{
{OrgId: 1, DashboardId: 1, Role: &r, Permission: m.PERMISSION_ADMIN},
}
_, err := sc.g.CheckPermissionBeforeUpdate(m.PERMISSION_ADMIN, p)
So(err, ShouldEqual, ErrGuardianPermissionExists)
})
})
Convey("Given default permissions", func() {
editor := m.ROLE_EDITOR
viewer := m.ROLE_VIEWER
existingPermissions := []*m.DashboardAclInfoDTO{
{OrgId: 1, DashboardId: -1, Role: &editor, Permission: m.PERMISSION_EDIT},
{OrgId: 1, DashboardId: -1, Role: &viewer, Permission: m.PERMISSION_VIEW},
}
bus.AddHandler("test", func(query *m.GetDashboardAclInfoListQuery) error {
query.Result = existingPermissions
return nil
})
Convey("When trying to update dashboard permissions without everyone with role editor can edit should be allowed", func() {
r := m.ROLE_VIEWER
p := []*m.DashboardAcl{
{OrgId: 1, DashboardId: 1, Role: &r, Permission: m.PERMISSION_VIEW},
}
ok, _ := sc.g.CheckPermissionBeforeUpdate(m.PERMISSION_ADMIN, p)
So(ok, ShouldBeTrue)
})
Convey("When trying to update dashboard permissions without everyone with role viewer can view should be allowed", func() {
r := m.ROLE_EDITOR
p := []*m.DashboardAcl{
{OrgId: 1, DashboardId: 1, Role: &r, Permission: m.PERMISSION_EDIT},
}
ok, _ := sc.g.CheckPermissionBeforeUpdate(m.PERMISSION_ADMIN, p)
So(ok, ShouldBeTrue)
})
})
Convey("Given parent folder has user admin permission", func() {
existingPermissions := []*m.DashboardAclInfoDTO{
{OrgId: 1, DashboardId: 2, UserId: 1, Permission: m.PERMISSION_ADMIN},
}
bus.AddHandler("test", func(query *m.GetDashboardAclInfoListQuery) error {
query.Result = existingPermissions
return nil
})
Convey("When trying to update dashboard permissions with admin user permission should return error", func() {
p := []*m.DashboardAcl{
{OrgId: 1, DashboardId: 3, UserId: 1, Permission: m.PERMISSION_ADMIN},
}
_, err := sc.g.CheckPermissionBeforeUpdate(m.PERMISSION_ADMIN, p)
So(err, ShouldEqual, ErrGuardianOverride)
})
Convey("When trying to update dashboard permissions with edit user permission should return error", func() {
p := []*m.DashboardAcl{
{OrgId: 1, DashboardId: 3, UserId: 1, Permission: m.PERMISSION_EDIT},
}
_, err := sc.g.CheckPermissionBeforeUpdate(m.PERMISSION_ADMIN, p)
So(err, ShouldEqual, ErrGuardianOverride)
})
Convey("When trying to update dashboard permissions with view user permission should return error", func() {
p := []*m.DashboardAcl{
{OrgId: 1, DashboardId: 3, UserId: 1, Permission: m.PERMISSION_VIEW},
}
_, err := sc.g.CheckPermissionBeforeUpdate(m.PERMISSION_ADMIN, p)
So(err, ShouldEqual, ErrGuardianOverride)
})
})
Convey("Given parent folder has user edit permission", func() {
existingPermissions := []*m.DashboardAclInfoDTO{
{OrgId: 1, DashboardId: 2, UserId: 1, Permission: m.PERMISSION_EDIT},
}
bus.AddHandler("test", func(query *m.GetDashboardAclInfoListQuery) error {
query.Result = existingPermissions
return nil
})
Convey("When trying to update dashboard permissions with admin user permission should be allowed", func() {
p := []*m.DashboardAcl{
{OrgId: 1, DashboardId: 3, UserId: 1, Permission: m.PERMISSION_ADMIN},
}
ok, _ := sc.g.CheckPermissionBeforeUpdate(m.PERMISSION_ADMIN, p)
So(ok, ShouldBeTrue)
})
Convey("When trying to update dashboard permissions with edit user permission should return error", func() {
p := []*m.DashboardAcl{
{OrgId: 1, DashboardId: 3, UserId: 1, Permission: m.PERMISSION_EDIT},
}
_, err := sc.g.CheckPermissionBeforeUpdate(m.PERMISSION_ADMIN, p)
So(err, ShouldEqual, ErrGuardianOverride)
})
Convey("When trying to update dashboard permissions with view user permission should return error", func() {
p := []*m.DashboardAcl{
{OrgId: 1, DashboardId: 3, UserId: 1, Permission: m.PERMISSION_VIEW},
}
_, err := sc.g.CheckPermissionBeforeUpdate(m.PERMISSION_ADMIN, p)
So(err, ShouldEqual, ErrGuardianOverride)
})
})
Convey("Given parent folder has user view permission", func() {
existingPermissions := []*m.DashboardAclInfoDTO{
{OrgId: 1, DashboardId: 2, UserId: 1, Permission: m.PERMISSION_VIEW},
}
bus.AddHandler("test", func(query *m.GetDashboardAclInfoListQuery) error {
query.Result = existingPermissions
return nil
})
Convey("When trying to update dashboard permissions with admin user permission should be allowed", func() {
p := []*m.DashboardAcl{
{OrgId: 1, DashboardId: 3, UserId: 1, Permission: m.PERMISSION_ADMIN},
}
ok, _ := sc.g.CheckPermissionBeforeUpdate(m.PERMISSION_ADMIN, p)
So(ok, ShouldBeTrue)
})
Convey("When trying to update dashboard permissions with edit user permission should be allowed", func() {
p := []*m.DashboardAcl{
{OrgId: 1, DashboardId: 3, UserId: 1, Permission: m.PERMISSION_EDIT},
}
ok, _ := sc.g.CheckPermissionBeforeUpdate(m.PERMISSION_ADMIN, p)
So(ok, ShouldBeTrue)
})
Convey("When trying to update dashboard permissions with view user permission should return error", func() {
p := []*m.DashboardAcl{
{OrgId: 1, DashboardId: 3, UserId: 1, Permission: m.PERMISSION_VIEW},
}
_, err := sc.g.CheckPermissionBeforeUpdate(m.PERMISSION_ADMIN, p)
So(err, ShouldEqual, ErrGuardianOverride)
})
})
Convey("Given parent folder has team admin permission", func() {
existingPermissions := []*m.DashboardAclInfoDTO{
{OrgId: 1, DashboardId: 2, TeamId: 1, Permission: m.PERMISSION_ADMIN},
}
bus.AddHandler("test", func(query *m.GetDashboardAclInfoListQuery) error {
query.Result = existingPermissions
return nil
})
Convey("When trying to update dashboard permissions with admin team permission should return error", func() {
p := []*m.DashboardAcl{
{OrgId: 1, DashboardId: 3, TeamId: 1, Permission: m.PERMISSION_ADMIN},
}
_, err := sc.g.CheckPermissionBeforeUpdate(m.PERMISSION_ADMIN, p)
So(err, ShouldEqual, ErrGuardianOverride)
})
Convey("When trying to update dashboard permissions with edit team permission should return error", func() {
p := []*m.DashboardAcl{
{OrgId: 1, DashboardId: 3, TeamId: 1, Permission: m.PERMISSION_EDIT},
}
_, err := sc.g.CheckPermissionBeforeUpdate(m.PERMISSION_ADMIN, p)
So(err, ShouldEqual, ErrGuardianOverride)
})
Convey("When trying to update dashboard permissions with view team permission should return error", func() {
p := []*m.DashboardAcl{
{OrgId: 1, DashboardId: 3, TeamId: 1, Permission: m.PERMISSION_VIEW},
}
_, err := sc.g.CheckPermissionBeforeUpdate(m.PERMISSION_ADMIN, p)
So(err, ShouldEqual, ErrGuardianOverride)
})
})
Convey("Given parent folder has team edit permission", func() {
existingPermissions := []*m.DashboardAclInfoDTO{
{OrgId: 1, DashboardId: 2, TeamId: 1, Permission: m.PERMISSION_EDIT},
}
bus.AddHandler("test", func(query *m.GetDashboardAclInfoListQuery) error {
query.Result = existingPermissions
return nil
})
Convey("When trying to update dashboard permissions with admin team permission should be allowed", func() {
p := []*m.DashboardAcl{
{OrgId: 1, DashboardId: 3, TeamId: 1, Permission: m.PERMISSION_ADMIN},
}
ok, _ := sc.g.CheckPermissionBeforeUpdate(m.PERMISSION_ADMIN, p)
So(ok, ShouldBeTrue)
})
Convey("When trying to update dashboard permissions with edit team permission should return error", func() {
p := []*m.DashboardAcl{
{OrgId: 1, DashboardId: 3, TeamId: 1, Permission: m.PERMISSION_EDIT},
}
_, err := sc.g.CheckPermissionBeforeUpdate(m.PERMISSION_ADMIN, p)
So(err, ShouldEqual, ErrGuardianOverride)
})
Convey("When trying to update dashboard permissions with view team permission should return error", func() {
p := []*m.DashboardAcl{
{OrgId: 1, DashboardId: 3, TeamId: 1, Permission: m.PERMISSION_VIEW},
}
_, err := sc.g.CheckPermissionBeforeUpdate(m.PERMISSION_ADMIN, p)
So(err, ShouldEqual, ErrGuardianOverride)
})
})
Convey("Given parent folder has team view permission", func() {
existingPermissions := []*m.DashboardAclInfoDTO{
{OrgId: 1, DashboardId: 2, TeamId: 1, Permission: m.PERMISSION_VIEW},
}
bus.AddHandler("test", func(query *m.GetDashboardAclInfoListQuery) error {
query.Result = existingPermissions
return nil
})
Convey("When trying to update dashboard permissions with admin team permission should be allowed", func() {
p := []*m.DashboardAcl{
{OrgId: 1, DashboardId: 3, TeamId: 1, Permission: m.PERMISSION_ADMIN},
}
ok, _ := sc.g.CheckPermissionBeforeUpdate(m.PERMISSION_ADMIN, p)
So(ok, ShouldBeTrue)
})
Convey("When trying to update dashboard permissions with edit team permission should be allowed", func() {
p := []*m.DashboardAcl{
{OrgId: 1, DashboardId: 3, TeamId: 1, Permission: m.PERMISSION_EDIT},
}
ok, _ := sc.g.CheckPermissionBeforeUpdate(m.PERMISSION_ADMIN, p)
So(ok, ShouldBeTrue)
})
Convey("When trying to update dashboard permissions with view team permission should return error", func() {
p := []*m.DashboardAcl{
{OrgId: 1, DashboardId: 3, TeamId: 1, Permission: m.PERMISSION_VIEW},
}
_, err := sc.g.CheckPermissionBeforeUpdate(m.PERMISSION_ADMIN, p)
So(err, ShouldEqual, ErrGuardianOverride)
})
})
Convey("Given parent folder has editor role with edit permission", func() {
r := m.ROLE_EDITOR
existingPermissions := []*m.DashboardAclInfoDTO{
{OrgId: 1, DashboardId: 2, Role: &r, Permission: m.PERMISSION_EDIT},
}
bus.AddHandler("test", func(query *m.GetDashboardAclInfoListQuery) error {
query.Result = existingPermissions
return nil
})
Convey("When trying to update dashboard permissions with everyone with editor role can admin permission should be allowed", func() {
p := []*m.DashboardAcl{
{OrgId: 1, DashboardId: 3, Role: &r, Permission: m.PERMISSION_ADMIN},
}
ok, _ := sc.g.CheckPermissionBeforeUpdate(m.PERMISSION_ADMIN, p)
So(ok, ShouldBeTrue)
})
Convey("When trying to update dashboard permissions with everyone with editor role can edit permission should return error", func() {
p := []*m.DashboardAcl{
{OrgId: 1, DashboardId: 3, Role: &r, Permission: m.PERMISSION_EDIT},
}
_, err := sc.g.CheckPermissionBeforeUpdate(m.PERMISSION_ADMIN, p)
So(err, ShouldEqual, ErrGuardianOverride)
})
Convey("When trying to update dashboard permissions with everyone with editor role can view permission should return error", func() {
p := []*m.DashboardAcl{
{OrgId: 1, DashboardId: 3, Role: &r, Permission: m.PERMISSION_VIEW},
}
_, err := sc.g.CheckPermissionBeforeUpdate(m.PERMISSION_ADMIN, p)
So(err, ShouldEqual, ErrGuardianOverride)
})
})
Convey("Given parent folder has editor role with view permission", func() {
r := m.ROLE_EDITOR
existingPermissions := []*m.DashboardAclInfoDTO{
{OrgId: 1, DashboardId: 2, Role: &r, Permission: m.PERMISSION_VIEW},
}
bus.AddHandler("test", func(query *m.GetDashboardAclInfoListQuery) error {
query.Result = existingPermissions
return nil
})
Convey("When trying to update dashboard permissions with everyone with viewer role can admin permission should be allowed", func() {
p := []*m.DashboardAcl{
{OrgId: 1, DashboardId: 3, Role: &r, Permission: m.PERMISSION_ADMIN},
}
ok, _ := sc.g.CheckPermissionBeforeUpdate(m.PERMISSION_ADMIN, p)
So(ok, ShouldBeTrue)
})
Convey("When trying to update dashboard permissions with everyone with viewer role can edit permission should be allowed", func() {
p := []*m.DashboardAcl{
{OrgId: 1, DashboardId: 3, Role: &r, Permission: m.PERMISSION_EDIT},
}
ok, _ := sc.g.CheckPermissionBeforeUpdate(m.PERMISSION_ADMIN, p)
So(ok, ShouldBeTrue)
})
Convey("When trying to update dashboard permissions with everyone with viewer role can view permission should return error", func() {
p := []*m.DashboardAcl{
{OrgId: 1, DashboardId: 3, Role: &r, Permission: m.PERMISSION_VIEW},
}
_, err := sc.g.CheckPermissionBeforeUpdate(m.PERMISSION_ADMIN, p)
So(err, ShouldEqual, ErrGuardianOverride)
})
})
})
orgRoleScenario("Given user has editor org role", m.ROLE_EDITOR, func(sc *scenarioContext) {
everyoneWithRoleScenario(m.ROLE_EDITOR, m.PERMISSION_ADMIN, sc, func(sc *scenarioContext) {
canAdmin, _ := sc.g.CanAdmin()
canEdit, _ := sc.g.CanEdit()
canSave, _ := sc.g.CanSave()
canView, _ := sc.g.CanView()
So(canAdmin, ShouldBeTrue)
So(canEdit, ShouldBeTrue)
So(canSave, ShouldBeTrue)
So(canView, ShouldBeTrue)
})
everyoneWithRoleScenario(m.ROLE_EDITOR, m.PERMISSION_EDIT, sc, func(sc *scenarioContext) {
canAdmin, _ := sc.g.CanAdmin()
canEdit, _ := sc.g.CanEdit()
canSave, _ := sc.g.CanSave()
canView, _ := sc.g.CanView()
So(canAdmin, ShouldBeFalse)
So(canEdit, ShouldBeTrue)
So(canSave, ShouldBeTrue)
So(canView, ShouldBeTrue)
})
everyoneWithRoleScenario(m.ROLE_EDITOR, m.PERMISSION_VIEW, sc, func(sc *scenarioContext) {
canAdmin, _ := sc.g.CanAdmin()
canEdit, _ := sc.g.CanEdit()
canSave, _ := sc.g.CanSave()
canView, _ := sc.g.CanView()
So(canAdmin, ShouldBeFalse)
So(canEdit, ShouldBeFalse)
So(canSave, ShouldBeFalse)
So(canView, ShouldBeTrue)
})
everyoneWithRoleScenario(m.ROLE_VIEWER, m.PERMISSION_ADMIN, sc, func(sc *scenarioContext) {
canAdmin, _ := sc.g.CanAdmin()
canEdit, _ := sc.g.CanEdit()
canSave, _ := sc.g.CanSave()
canView, _ := sc.g.CanView()
So(canAdmin, ShouldBeFalse)
So(canEdit, ShouldBeFalse)
So(canSave, ShouldBeFalse)
So(canView, ShouldBeFalse)
})
everyoneWithRoleScenario(m.ROLE_VIEWER, m.PERMISSION_EDIT, sc, func(sc *scenarioContext) {
canAdmin, _ := sc.g.CanAdmin()
canEdit, _ := sc.g.CanEdit()
canSave, _ := sc.g.CanSave()
canView, _ := sc.g.CanView()
So(canAdmin, ShouldBeFalse)
So(canEdit, ShouldBeFalse)
So(canSave, ShouldBeFalse)
So(canView, ShouldBeFalse)
})
everyoneWithRoleScenario(m.ROLE_VIEWER, m.PERMISSION_VIEW, sc, func(sc *scenarioContext) {
canAdmin, _ := sc.g.CanAdmin()
canEdit, _ := sc.g.CanEdit()
canSave, _ := sc.g.CanSave()
canView, _ := sc.g.CanView()
So(canAdmin, ShouldBeFalse)
So(canEdit, ShouldBeFalse)
So(canSave, ShouldBeFalse)
So(canView, ShouldBeFalse)
})
userWithPermissionScenario(m.PERMISSION_ADMIN, sc, func(sc *scenarioContext) {
canAdmin, _ := sc.g.CanAdmin()
canEdit, _ := sc.g.CanEdit()
canSave, _ := sc.g.CanSave()
canView, _ := sc.g.CanView()
So(canAdmin, ShouldBeTrue)
So(canEdit, ShouldBeTrue)
So(canSave, ShouldBeTrue)
So(canView, ShouldBeTrue)
})
userWithPermissionScenario(m.PERMISSION_EDIT, sc, func(sc *scenarioContext) {
canAdmin, _ := sc.g.CanAdmin()
canEdit, _ := sc.g.CanEdit()
canSave, _ := sc.g.CanSave()
canView, _ := sc.g.CanView()
So(canAdmin, ShouldBeFalse)
So(canEdit, ShouldBeTrue)
So(canSave, ShouldBeTrue)
So(canView, ShouldBeTrue)
})
userWithPermissionScenario(m.PERMISSION_VIEW, sc, func(sc *scenarioContext) {
canAdmin, _ := sc.g.CanAdmin()
canEdit, _ := sc.g.CanEdit()
canSave, _ := sc.g.CanSave()
canView, _ := sc.g.CanView()
So(canAdmin, ShouldBeFalse)
So(canEdit, ShouldBeFalse)
So(canSave, ShouldBeFalse)
So(canView, ShouldBeTrue)
})
teamWithPermissionScenario(m.PERMISSION_ADMIN, sc, func(sc *scenarioContext) {
canAdmin, _ := sc.g.CanAdmin()
canEdit, _ := sc.g.CanEdit()
canSave, _ := sc.g.CanSave()
canView, _ := sc.g.CanView()
So(canAdmin, ShouldBeTrue)
So(canEdit, ShouldBeTrue)
So(canSave, ShouldBeTrue)
So(canView, ShouldBeTrue)
})
teamWithPermissionScenario(m.PERMISSION_EDIT, sc, func(sc *scenarioContext) {
canAdmin, _ := sc.g.CanAdmin()
canEdit, _ := sc.g.CanEdit()
canSave, _ := sc.g.CanSave()
canView, _ := sc.g.CanView()
So(canAdmin, ShouldBeFalse)
So(canEdit, ShouldBeTrue)
So(canSave, ShouldBeTrue)
So(canView, ShouldBeTrue)
})
teamWithPermissionScenario(m.PERMISSION_VIEW, sc, func(sc *scenarioContext) {
canAdmin, _ := sc.g.CanAdmin()
canEdit, _ := sc.g.CanEdit()
canSave, _ := sc.g.CanSave()
canView, _ := sc.g.CanView()
So(canAdmin, ShouldBeFalse)
So(canEdit, ShouldBeFalse)
So(canSave, ShouldBeFalse)
So(canView, ShouldBeTrue)
})
Convey("When trying to update permissions should return false", func() {
p := []*m.DashboardAcl{
{OrgId: 1, DashboardId: 1, UserId: 1, Permission: m.PERMISSION_VIEW},
{OrgId: 1, DashboardId: 1, UserId: 1, Permission: m.PERMISSION_ADMIN},
}
ok, _ := sc.g.CheckPermissionBeforeUpdate(m.PERMISSION_ADMIN, p)
So(ok, ShouldBeFalse)
})
})
orgRoleScenario("Given user has viewer org role", m.ROLE_VIEWER, func(sc *scenarioContext) {
everyoneWithRoleScenario(m.ROLE_EDITOR, m.PERMISSION_ADMIN, sc, func(sc *scenarioContext) {
canAdmin, _ := sc.g.CanAdmin()
canEdit, _ := sc.g.CanEdit()
canSave, _ := sc.g.CanSave()
canView, _ := sc.g.CanView()
So(canAdmin, ShouldBeFalse)
So(canEdit, ShouldBeFalse)
So(canSave, ShouldBeFalse)
So(canView, ShouldBeFalse)
})
everyoneWithRoleScenario(m.ROLE_EDITOR, m.PERMISSION_EDIT, sc, func(sc *scenarioContext) {
canAdmin, _ := sc.g.CanAdmin()
canEdit, _ := sc.g.CanEdit()
canSave, _ := sc.g.CanSave()
canView, _ := sc.g.CanView()
So(canAdmin, ShouldBeFalse)
So(canEdit, ShouldBeFalse)
So(canSave, ShouldBeFalse)
So(canView, ShouldBeFalse)
})
everyoneWithRoleScenario(m.ROLE_EDITOR, m.PERMISSION_VIEW, sc, func(sc *scenarioContext) {
canAdmin, _ := sc.g.CanAdmin()
canEdit, _ := sc.g.CanEdit()
canSave, _ := sc.g.CanSave()
canView, _ := sc.g.CanView()
So(canAdmin, ShouldBeFalse)
So(canEdit, ShouldBeFalse)
So(canSave, ShouldBeFalse)
So(canView, ShouldBeFalse)
})
everyoneWithRoleScenario(m.ROLE_VIEWER, m.PERMISSION_ADMIN, sc, func(sc *scenarioContext) {
canAdmin, _ := sc.g.CanAdmin()
canEdit, _ := sc.g.CanEdit()
canSave, _ := sc.g.CanSave()
canView, _ := sc.g.CanView()
So(canAdmin, ShouldBeTrue)
So(canEdit, ShouldBeTrue)
So(canSave, ShouldBeTrue)
So(canView, ShouldBeTrue)
})
everyoneWithRoleScenario(m.ROLE_VIEWER, m.PERMISSION_EDIT, sc, func(sc *scenarioContext) {
canAdmin, _ := sc.g.CanAdmin()
canEdit, _ := sc.g.CanEdit()
canSave, _ := sc.g.CanSave()
canView, _ := sc.g.CanView()
So(canAdmin, ShouldBeFalse)
So(canEdit, ShouldBeTrue)
So(canSave, ShouldBeTrue)
So(canView, ShouldBeTrue)
})
everyoneWithRoleScenario(m.ROLE_VIEWER, m.PERMISSION_VIEW, sc, func(sc *scenarioContext) {
canAdmin, _ := sc.g.CanAdmin()
canEdit, _ := sc.g.CanEdit()
canSave, _ := sc.g.CanSave()
canView, _ := sc.g.CanView()
So(canAdmin, ShouldBeFalse)
So(canEdit, ShouldBeFalse)
So(canSave, ShouldBeFalse)
So(canView, ShouldBeTrue)
})
userWithPermissionScenario(m.PERMISSION_ADMIN, sc, func(sc *scenarioContext) {
canAdmin, _ := sc.g.CanAdmin()
canEdit, _ := sc.g.CanEdit()
canSave, _ := sc.g.CanSave()
canView, _ := sc.g.CanView()
So(canAdmin, ShouldBeTrue)
So(canEdit, ShouldBeTrue)
So(canSave, ShouldBeTrue)
So(canView, ShouldBeTrue)
})
userWithPermissionScenario(m.PERMISSION_EDIT, sc, func(sc *scenarioContext) {
canAdmin, _ := sc.g.CanAdmin()
canEdit, _ := sc.g.CanEdit()
canSave, _ := sc.g.CanSave()
canView, _ := sc.g.CanView()
So(canAdmin, ShouldBeFalse)
So(canEdit, ShouldBeTrue)
So(canSave, ShouldBeTrue)
So(canView, ShouldBeTrue)
})
userWithPermissionScenario(m.PERMISSION_VIEW, sc, func(sc *scenarioContext) {
canAdmin, _ := sc.g.CanAdmin()
canEdit, _ := sc.g.CanEdit()
canSave, _ := sc.g.CanSave()
canView, _ := sc.g.CanView()
So(canAdmin, ShouldBeFalse)
So(canEdit, ShouldBeFalse)
So(canSave, ShouldBeFalse)
So(canView, ShouldBeTrue)
})
Convey("When trying to update permissions should return false", func() {
p := []*m.DashboardAcl{
{OrgId: 1, DashboardId: 1, UserId: 1, Permission: m.PERMISSION_VIEW},
{OrgId: 1, DashboardId: 1, UserId: 1, Permission: m.PERMISSION_ADMIN},
}
ok, _ := sc.g.CheckPermissionBeforeUpdate(m.PERMISSION_ADMIN, p)
So(ok, ShouldBeFalse)
})
})
})
}
type scenarioContext struct {
g DashboardGuardian
}
type scenarioFunc func(c *scenarioContext)
func orgRoleScenario(desc string, role m.RoleType, fn scenarioFunc) {
user := &m.SignedInUser{
UserId: 1,
OrgId: 1,
OrgRole: role,
}
guard := New(1, 1, user)
sc := &scenarioContext{
g: guard,
}
Convey(desc, func() {
fn(sc)
})
}
func permissionScenario(desc string, sc *scenarioContext, permissions []*m.DashboardAclInfoDTO, fn scenarioFunc) {
bus.ClearBusHandlers()
bus.AddHandler("test", func(query *m.GetDashboardAclInfoListQuery) error {
query.Result = permissions
return nil
})
teams := []*m.Team{}
for _, p := range permissions {
if p.TeamId > 0 {
teams = append(teams, &m.Team{Id: p.TeamId})
}
}
bus.AddHandler("test", func(query *m.GetTeamsByUserQuery) error {
query.Result = teams
return nil
})
Convey(desc, func() {
fn(sc)
})
}
func userWithPermissionScenario(permission m.PermissionType, sc *scenarioContext, fn scenarioFunc) {
p := []*m.DashboardAclInfoDTO{
{OrgId: 1, DashboardId: 1, UserId: 1, Permission: permission},
}
permissionScenario(fmt.Sprintf("and user has permission to %s item", permission), sc, p, fn)
}
func teamWithPermissionScenario(permission m.PermissionType, sc *scenarioContext, fn scenarioFunc) {
p := []*m.DashboardAclInfoDTO{
{OrgId: 1, DashboardId: 1, TeamId: 1, Permission: permission},
}
permissionScenario(fmt.Sprintf("and team has permission to %s item", permission), sc, p, fn)
}
func everyoneWithRoleScenario(role m.RoleType, permission m.PermissionType, sc *scenarioContext, fn scenarioFunc) {
p := []*m.DashboardAclInfoDTO{
{OrgId: 1, DashboardId: 1, UserId: -1, Role: &role, Permission: permission},
}
permissionScenario(fmt.Sprintf("and everyone with %s role can %s item", role, permission), sc, p, fn)
}

View File

@@ -195,10 +195,9 @@ func (s *SocialGithub) FetchOrganizations(client *http.Client, organizationsUrl
func (s *SocialGithub) UserInfo(client *http.Client, token *oauth2.Token) (*BasicUserInfo, error) {
var data struct {
Id int `json:"id"`
Login string `json:"login"`
Email string `json:"email"`
OrganizationsUrl string `json:"organizations_url"`
Id int `json:"id"`
Login string `json:"login"`
Email string `json:"email"`
}
response, err := HttpGet(client, s.apiUrl)
@@ -217,11 +216,13 @@ func (s *SocialGithub) UserInfo(client *http.Client, token *oauth2.Token) (*Basi
Email: data.Email,
}
organizationsUrl := fmt.Sprintf(s.apiUrl + "/orgs")
if !s.IsTeamMember(client) {
return nil, ErrMissingTeamMembership
}
if !s.IsOrganizationMember(client, data.OrganizationsUrl) {
if !s.IsOrganizationMember(client, organizationsUrl) {
return nil, ErrMissingOrganizationMembership
}

View File

@@ -17,7 +17,7 @@ describe('AddPermissions', () => {
])
);
backendSrv.post = jest.fn();
backendSrv.post = jest.fn(() => Promise.resolve({}));
store = RootStore.create(
{},

View File

@@ -135,14 +135,6 @@ class AddPermissions extends Component<IProps, any> {
</div>
</div>
</form>
{permissions.error ? (
<div className="gf-form width-17">
<span ng-if="ctrl.error" className="text-error p-l-1">
<i className="fa fa-warning" />
{permissions.error}
</span>
</div>
) : null}
</div>
);
}

View File

@@ -1,5 +1,5 @@
import React from 'react';
import ReactGridLayout from 'react-grid-layout';
import ReactGridLayout from 'react-grid-layout-grafana';
import { GRID_CELL_HEIGHT, GRID_CELL_VMARGIN, GRID_COLUMN_COUNT } from 'app/core/constants';
import { DashboardPanel } from './DashboardPanel';
import { DashboardModel } from '../dashboard_model';
@@ -50,7 +50,8 @@ function GridWrapper({
onResize={onResize}
onResizeStop={onResizeStop}
onDragStop={onDragStop}
onLayoutChange={onLayoutChange}>
onLayoutChange={onLayoutChange}
>
{children}
</ReactGridLayout>
);
@@ -178,7 +179,7 @@ export class DashboardGrid extends React.Component<DashboardGridProps, any> {
panelElements.push(
<div key={panel.id.toString()} className={panelClasses}>
<DashboardPanel panel={panel} getPanelContainer={this.props.getPanelContainer} />
</div>,
</div>
);
}
@@ -196,7 +197,8 @@ export class DashboardGrid extends React.Component<DashboardGridProps, any> {
onWidthChange={this.onWidthChange}
onDragStop={this.onDragStop}
onResize={this.onResize}
onResizeStop={this.onResizeStop}>
onResizeStop={this.onResizeStop}
>
{this.renderPanels()}
</SizedReactLayoutGrid>
);

View File

@@ -31,12 +31,40 @@ describe('templateSrv', function() {
expect(target).toBe('this.mupp.filters');
});
it('should replace ${test} with scoped value', function() {
var target = _templateSrv.replace('this.${test}.filters', {
test: { value: 'mupp', text: 'asd' },
});
expect(target).toBe('this.mupp.filters');
});
it('should replace ${test:glob} with scoped value', function() {
var target = _templateSrv.replace('this.${test:glob}.filters', {
test: { value: 'mupp', text: 'asd' },
});
expect(target).toBe('this.mupp.filters');
});
it('should replace $test with scoped text', function() {
var target = _templateSrv.replaceWithText('this.$test.filters', {
test: { value: 'mupp', text: 'asd' },
});
expect(target).toBe('this.asd.filters');
});
it('should replace ${test} with scoped text', function() {
var target = _templateSrv.replaceWithText('this.${test}.filters', {
test: { value: 'mupp', text: 'asd' },
});
expect(target).toBe('this.asd.filters');
});
it('should replace ${test:glob} with scoped text', function() {
var target = _templateSrv.replaceWithText('this.${test:glob}.filters', {
test: { value: 'mupp', text: 'asd' },
});
expect(target).toBe('this.asd.filters');
});
});
describe('getAdhocFilters', function() {
@@ -79,18 +107,34 @@ describe('templateSrv', function() {
]);
});
it('should replace $test with globbed value', function() {
var target = _templateSrv.replace('this.$test.filters', {}, 'glob');
expect(target).toBe('this.{value1,value2}.filters');
});
it('should replace ${test} with globbed value', function() {
var target = _templateSrv.replace('this.${test}.filters', {}, 'glob');
expect(target).toBe('this.{value1,value2}.filters');
});
it('should replace ${test:glob} with globbed value', function() {
var target = _templateSrv.replace('this.${test:glob}.filters', {});
expect(target).toBe('this.{value1,value2}.filters');
});
it('should replace $test with piped value', function() {
var target = _templateSrv.replace('this=$test', {}, 'pipe');
expect(target).toBe('this=value1|value2');
});
it('should replace $test with piped value', function() {
var target = _templateSrv.replace('this=$test', {}, 'pipe');
it('should replace ${test} with piped value', function() {
var target = _templateSrv.replace('this=${test}', {}, 'pipe');
expect(target).toBe('this=value1|value2');
});
it('should replace ${test:pipe} with piped value', function() {
var target = _templateSrv.replace('this=${test:pipe}', {});
expect(target).toBe('this=value1|value2');
});
});
@@ -111,6 +155,16 @@ describe('templateSrv', function() {
var target = _templateSrv.replace('this.$test.filters', {}, 'glob');
expect(target).toBe('this.{value1,value2}.filters');
});
it('should replace ${test} with formatted all value', function() {
var target = _templateSrv.replace('this.${test}.filters', {}, 'glob');
expect(target).toBe('this.{value1,value2}.filters');
});
it('should replace ${test:glob} with formatted all value', function() {
var target = _templateSrv.replace('this.${test:glob}.filters', {});
expect(target).toBe('this.{value1,value2}.filters');
});
});
describe('variable with all option and custom value', function() {
@@ -131,6 +185,16 @@ describe('templateSrv', function() {
expect(target).toBe('this.*.filters');
});
it('should replace ${test} with formatted all value', function() {
var target = _templateSrv.replace('this.${test}.filters', {}, 'glob');
expect(target).toBe('this.*.filters');
});
it('should replace ${test:glob} with formatted all value', function() {
var target = _templateSrv.replace('this.${test:glob}.filters', {});
expect(target).toBe('this.*.filters');
});
it('should not escape custom all value', function() {
var target = _templateSrv.replace('this.$test', {}, 'regex');
expect(target).toBe('this.*');
@@ -143,6 +207,18 @@ describe('templateSrv', function() {
var target = _templateSrv.replace('this:$test', {}, 'lucene');
expect(target).toBe('this:value\\/4');
});
it('should properly escape ${test} with lucene escape sequences', function() {
initTemplateSrv([{ type: 'query', name: 'test', current: { value: 'value/4' } }]);
var target = _templateSrv.replace('this:${test}', {}, 'lucene');
expect(target).toBe('this:value\\/4');
});
it('should properly escape ${test:lucene} with lucene escape sequences', function() {
initTemplateSrv([{ type: 'query', name: 'test', current: { value: 'value/4' } }]);
var target = _templateSrv.replace('this:${test:lucene}', {});
expect(target).toBe('this:value\\/4');
});
});
describe('format variable to string values', function() {

View File

@@ -8,7 +8,13 @@ function luceneEscape(value) {
export class TemplateSrv {
variables: any[];
private regex = /\$(\w+)|\[\[([\s\S]+?)\]\]/g;
/*
* This regex matches 3 types of variable reference with an optional format specifier
* \$(\w+) $var1
* \[\[([\s\S]+?)(?::(\w+))?\]\] [[var2]] or [[var2:fmt2]]
* \${(\w+)(?::(\w+))?} ${var3} or ${var3:fmt3}
*/
private regex = /\$(\w+)|\[\[([\s\S]+?)(?::(\w+))?\]\]|\${(\w+)(?::(\w+))?}/g;
private index = {};
private grafanaVariables = {};
private builtIns = {};
@@ -89,6 +95,9 @@ export class TemplateSrv {
}
var escapedValues = _.map(value, kbn.regexEscape);
if (escapedValues.length === 1) {
return escapedValues[0];
}
return '(' + escapedValues.join('|') + ')';
}
case 'lucene': {
@@ -140,8 +149,8 @@ export class TemplateSrv {
str = _.escape(str);
this.regex.lastIndex = 0;
return str.replace(this.regex, (match, g1, g2) => {
if (this.index[g1 || g2] || this.builtIns[g1 || g2]) {
return str.replace(this.regex, (match, var1, var2, fmt2, var3) => {
if (this.index[var1 || var2 || var3] || this.builtIns[var1 || var2 || var3]) {
return '<span class="template-variable">' + match + '</span>';
}
return match;
@@ -167,11 +176,11 @@ export class TemplateSrv {
var variable, systemValue, value;
this.regex.lastIndex = 0;
return target.replace(this.regex, (match, g1, g2) => {
variable = this.index[g1 || g2];
return target.replace(this.regex, (match, var1, var2, fmt2, var3, fmt3) => {
variable = this.index[var1 || var2 || var3];
format = fmt2 || fmt3 || format;
if (scopedVars) {
value = scopedVars[g1 || g2];
value = scopedVars[var1 || var2 || var3];
if (value) {
return this.formatValue(value.value, format, variable);
}
@@ -212,15 +221,15 @@ export class TemplateSrv {
var variable;
this.regex.lastIndex = 0;
return target.replace(this.regex, (match, g1, g2) => {
return target.replace(this.regex, (match, var1, var2, fmt2, var3) => {
if (scopedVars) {
var option = scopedVars[g1 || g2];
var option = scopedVars[var1 || var2 || var3];
if (option) {
return option.text;
}
}
variable = this.index[g1 || g2];
variable = this.index[var1 || var2 || var3];
if (!variable) {
return match;
}

View File

@@ -59,7 +59,7 @@
Sign in with {{oauth.generic_oauth.name}}
</a>
</div>
<div class="login-signup-box">
<div class="login-signup-box" ng-show="!disableUserSignUp">
<div class="login-signup-title p-r-1">
New to Grafana?
</div>

View File

@@ -348,6 +348,10 @@ export class GraphiteQueryCtrl extends QueryCtrl {
let tagKey = tag.key;
return this.datasource.getTagValuesAutoComplete(tagExpressions, tagKey, valuePrefix).then(values => {
let altValues = _.map(values, 'text');
// Add template variables as additional values
_.eachRight(this.templateSrv.variables, variable => {
altValues.push('${' + variable.name + ':regex}');
});
return mapToDropdownOptions(altValues);
});
}

View File

@@ -1,10 +1,10 @@
import { PermissionsStore, aclTypeValues } from './PermissionsStore';
import { PermissionsStore } from './PermissionsStore';
import { backendSrv } from 'test/mocks/common';
describe('PermissionsStore', () => {
let store;
beforeEach(() => {
beforeEach(async () => {
backendSrv.get.mockReturnValue(
Promise.resolve([
{ id: 2, dashboardId: 1, role: 'Viewer', permission: 1, permissionName: 'View' },
@@ -20,7 +20,7 @@ describe('PermissionsStore', () => {
])
);
backendSrv.post = jest.fn();
backendSrv.post = jest.fn(() => Promise.resolve({}));
store = PermissionsStore.create(
{
@@ -32,14 +32,14 @@ describe('PermissionsStore', () => {
}
);
return store.load(1, false, false);
await store.load(1, false, false);
});
it('should save update on permission change', () => {
it('should save update on permission change', async () => {
expect(store.items[0].permission).toBe(1);
expect(store.items[0].permissionName).toBe('View');
store.updatePermissionOnIndex(0, 2, 'Edit');
await store.updatePermissionOnIndex(0, 2, 'Edit');
expect(store.items[0].permission).toBe(2);
expect(store.items[0].permissionName).toBe('Edit');
@@ -47,69 +47,18 @@ describe('PermissionsStore', () => {
expect(backendSrv.post.mock.calls[0][0]).toBe('/api/dashboards/id/1/permissions');
});
it('should save removed permissions automatically', () => {
it('should save removed permissions automatically', async () => {
expect(store.items.length).toBe(3);
store.removeStoreItem(2);
await store.removeStoreItem(2);
expect(store.items.length).toBe(2);
expect(backendSrv.post.mock.calls.length).toBe(1);
expect(backendSrv.post.mock.calls[0][0]).toBe('/api/dashboards/id/1/permissions');
});
describe('when duplicate team permissions are added', () => {
beforeEach(() => {
const newItem = {
teamId: 10,
team: 'tester-team',
permission: 1,
dashboardId: 1,
};
store.resetNewType();
store.newItem.setTeam(newItem.teamId, newItem.team);
store.newItem.setPermission(newItem.permission);
store.addStoreItem();
store.newItem.setTeam(newItem.teamId, newItem.team);
store.newItem.setPermission(newItem.permission);
store.addStoreItem();
});
it('should return a validation error', () => {
expect(store.items.length).toBe(4);
expect(store.error).toBe('This permission exists already.');
expect(backendSrv.post.mock.calls.length).toBe(1);
});
});
describe('when duplicate user permissions are added', () => {
beforeEach(() => {
expect(store.items.length).toBe(3);
const newItem = {
userId: 10,
userLogin: 'tester1',
permission: 1,
dashboardId: 1,
};
store.setNewType(aclTypeValues.USER.value);
store.newItem.setUser(newItem.userId, newItem.userLogin);
store.newItem.setPermission(newItem.permission);
store.addStoreItem();
store.setNewType(aclTypeValues.USER.value);
store.newItem.setUser(newItem.userId, newItem.userLogin);
store.newItem.setPermission(newItem.permission);
store.addStoreItem();
});
it('should return a validation error', () => {
expect(store.items.length).toBe(4);
expect(store.error).toBe('This permission exists already.');
expect(backendSrv.post.mock.calls.length).toBe(1);
});
});
describe('when one inherited and one not inherited team permission are added', () => {
beforeEach(() => {
beforeEach(async () => {
const overridingItemForChildDashboard = {
team: 'MyTestTeam',
dashboardId: 1,
@@ -120,11 +69,7 @@ describe('PermissionsStore', () => {
store.resetNewType();
store.newItem.setTeam(overridingItemForChildDashboard.teamId, overridingItemForChildDashboard.team);
store.newItem.setPermission(overridingItemForChildDashboard.permission);
store.addStoreItem();
});
it('should allowing overriding the inherited permission and not throw a validation error', () => {
expect(store.error).toBe(null);
await store.addStoreItem();
});
it('should add new overriding permission', () => {

View File

@@ -1,8 +1,6 @@
import { types, getEnv, flow } from 'mobx-state-tree';
import { PermissionsStoreItem } from './PermissionsStoreItem';
const duplicateError = 'This permission exists already.';
export const permissionOptions = [
{ value: 1, label: 'View', description: 'Can view dashboards.' },
{ value: 2, label: 'Edit', description: 'Can add, edit and delete dashboards.' },
@@ -75,7 +73,6 @@ export const PermissionsStore = types
isFolder: types.maybe(types.boolean),
dashboardId: types.maybe(types.number),
items: types.optional(types.array(PermissionsStoreItem), []),
error: types.maybe(types.string),
originalItems: types.optional(types.array(PermissionsStoreItem), []),
newType: types.optional(types.string, defaultNewType),
newItem: types.maybe(NewPermissionsItem),
@@ -88,7 +85,6 @@ export const PermissionsStore = types
return isDuplicate(it, item);
});
if (dupe) {
self.error = duplicateError;
return false;
}
@@ -96,8 +92,7 @@ export const PermissionsStore = types
},
}))
.actions(self => {
const resetNewType = () => {
self.error = null;
const resetNewTypeInternal = () => {
self.newItem = NewPermissionsItem.create();
};
@@ -115,11 +110,9 @@ export const PermissionsStore = types
self.items = items;
self.originalItems = items;
self.fetching = false;
self.error = null;
}),
addStoreItem: flow(function* addStoreItem() {
self.error = null;
let item = {
type: self.newItem.type,
permission: self.newItem.permission,
@@ -147,19 +140,21 @@ export const PermissionsStore = types
throw Error('Unknown type: ' + self.newItem.type);
}
if (!self.isValid(item)) {
return undefined;
}
const updatedItems = self.items.peek();
const newItem = prepareItem(item, self.dashboardId, self.isFolder, self.isInRoot);
updatedItems.push(newItem);
self.items.push(prepareItem(item, self.dashboardId, self.isFolder, self.isInRoot));
resetNewType();
return updateItems(self);
try {
yield updateItems(self, updatedItems);
self.items.push(newItem);
resetNewTypeInternal();
} catch {}
yield Promise.resolve();
}),
removeStoreItem: flow(function* removeStoreItem(idx: number) {
self.error = null;
self.items.splice(idx, 1);
return updateItems(self);
yield updateItems(self, self.items.peek());
}),
updatePermissionOnIndex: flow(function* updatePermissionOnIndex(
@@ -167,9 +162,8 @@ export const PermissionsStore = types
permission: number,
permissionName: string
) {
self.error = null;
self.items[idx].updatePermission(permission, permissionName);
return updateItems(self);
yield updateItems(self, self.items.peek());
}),
setNewType(newType: string) {
@@ -177,7 +171,7 @@ export const PermissionsStore = types
},
resetNewType() {
resetNewType();
resetNewTypeInternal();
},
toggleAddPermissions() {
@@ -190,12 +184,10 @@ export const PermissionsStore = types
};
});
const updateItems = self => {
self.error = null;
const updateItems = (self, items) => {
const backendSrv = getEnv(self).backendSrv;
const updated = [];
for (let item of self.items) {
for (let item of items) {
if (item.inherited) {
continue;
}
@@ -208,16 +200,9 @@ const updateItems = self => {
});
}
let res;
try {
res = backendSrv.post(`/api/dashboards/id/${self.dashboardId}/permissions`, {
items: updated,
});
} catch (error) {
self.error = error;
}
return res;
return backendSrv.post(`/api/dashboards/id/${self.dashboardId}/permissions`, {
items: updated,
});
};
const prepareServerResponse = (response, dashboardId: number, isFolder: boolean, isInRoot: boolean) => {

View File

@@ -1,4 +1,4 @@
@import '~react-grid-layout/css/styles.css';
@import '~react-grid-layout-grafana/css/styles.css';
@import '~react-resizable/css/styles.css';
.panel-in-fullscreen {

View File

@@ -3,6 +3,9 @@
.navbar {
display: none;
}
.scroll-canvas--dashboard {
height: 100%;
}
}
.playlist-active,

View File

@@ -8275,16 +8275,23 @@ react-dom@^16.2.0:
object-assign "^4.1.1"
prop-types "^15.6.0"
"react-draggable@^2.2.6 || ^3.0.3", react-draggable@^3.0.3:
"react-draggable@^2.2.6 || ^3.0.3":
version "3.0.3"
resolved "https://registry.yarnpkg.com/react-draggable/-/react-draggable-3.0.3.tgz#a6f9b3a7171981b76dadecf238316925cb9eacf4"
dependencies:
classnames "^2.2.5"
prop-types "^15.5.10"
react-grid-layout@^0.16.2:
version "0.16.2"
resolved "https://registry.yarnpkg.com/react-grid-layout/-/react-grid-layout-0.16.2.tgz#ef09b0b6db4a9635799663658277ee2d26fa2994"
react-draggable@^3.0.3:
version "3.0.5"
resolved "https://registry.yarnpkg.com/react-draggable/-/react-draggable-3.0.5.tgz#c031e0ed4313531f9409d6cd84c8ebcec0ddfe2d"
dependencies:
classnames "^2.2.5"
prop-types "^15.6.0"
react-grid-layout-grafana@0.16.0:
version "0.16.0"
resolved "https://registry.yarnpkg.com/react-grid-layout-grafana/-/react-grid-layout-grafana-0.16.0.tgz#12242153fcd0bb80a26af8e41694bc2fde788b3a"
dependencies:
classnames "2.x"
lodash.isequal "^4.0.0"