Commit 8fd153ed by Marcus Efraimsson Committed by GitHub

API: Restrict anonymous user information access (#18422)

Existing /api/alert-notifications now requires at least editor access.
Existing /api/alert-notifiers now requires at least editor access.
New /api/alert-notifications/lookup returns less information than
/api/alert-notifications and can be access by any authenticated user.
Existing /api/org/users now requires org admin role.
New /api/org/users/lookup returns less information than
/api/org/users and can be access by users that are org admins,
admin in any folder or admin of any team.
UserPicker component now uses /api/org/users/lookup instead
of /api/org/users.

Fixes #17318
parent ab170157
......@@ -63,6 +63,48 @@ Content-Type: application/json
```
## Get all notification channels (lookup)
Returns all notification channels, but with less detailed information.
Accessible by any authenticated user and is mainly used by providing
alert notification channels in Grafana UI when configuring alert rule.
`GET /api/alert-notifications/lookup`
**Example Request**:
```http
GET /api/alert-notifications/lookup HTTP/1.1
Accept: application/json
Content-Type: application/json
Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk
```
**Example Response**:
```http
HTTP/1.1 200
Content-Type: application/json
[
{
"id": 1,
"uid": "000000001",
"name": "Test",
"type": "email",
"isDefault": false
},
{
"id": 2,
"uid": "000000002",
"name": "Slack",
"type": "slack",
"isDefault": false
}
]
```
## Get notification channel by uid
`GET /api/alert-notifications/uid/:uid`
......
......@@ -47,6 +47,9 @@ Content-Type: application/json
`GET /api/org/users`
Returns all org users within the current organization.
Accessible to users with org admin role.
**Example Request**:
```http
......@@ -64,11 +67,47 @@ Content-Type: application/json
[
{
"orgId":1,
"userId":1,
"email":"admin@mygraf.com",
"login":"admin",
"role":"Admin"
"orgId": 1,
"userId": 1,
"email": "admin@localhost",
"avatarUrl": "/avatar/46d229b033af06a191ff2267bca9ae56",
"login": "admin",
"role": "Admin",
"lastSeenAt": "2019-08-09T11:02:49+02:00",
"lastSeenAtAge": "< 1m"
}
]
```
### Get all users within the current organization (lookup)
`GET /api/org/users/lookup`
Returns all org users within the current organization, but with less detailed information.
Accessible to users with org admin role, admin in any folder or admin of any team.
Mainly used by Grafana UI for providing list of users when adding team members and
when editing folder/dashboard permissions.
**Example Request**:
```http
GET /api/org/users/lookup HTTP/1.1
Accept: application/json
Content-Type: application/json
Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk
```
**Example Response**:
```http
HTTP/1.1 200
Content-Type: application/json
[
{
"userId": 1,
"login": "admin",
"avatarUrl": "/avatar/46d229b033af06a191ff2267bca9ae56"
}
]
```
......
......@@ -5,7 +5,7 @@ import (
"github.com/grafana/grafana/pkg/api/dtos"
"github.com/grafana/grafana/pkg/bus"
m "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/search"
. "github.com/smartystreets/goconvey/convey"
......@@ -14,24 +14,24 @@ import (
func TestAlertingApiEndpoint(t *testing.T) {
Convey("Given an alert in a dashboard with an acl", t, func() {
singleAlert := &m.Alert{Id: 1, DashboardId: 1, Name: "singlealert"}
singleAlert := &models.Alert{Id: 1, DashboardId: 1, Name: "singlealert"}
bus.AddHandler("test", func(query *m.GetAlertByIdQuery) error {
bus.AddHandler("test", func(query *models.GetAlertByIdQuery) error {
query.Result = singleAlert
return nil
})
viewerRole := m.ROLE_VIEWER
editorRole := m.ROLE_EDITOR
viewerRole := models.ROLE_VIEWER
editorRole := models.ROLE_EDITOR
aclMockResp := []*m.DashboardAclInfoDTO{}
bus.AddHandler("test", func(query *m.GetDashboardAclInfoListQuery) error {
aclMockResp := []*models.DashboardAclInfoDTO{}
bus.AddHandler("test", func(query *models.GetDashboardAclInfoListQuery) error {
query.Result = aclMockResp
return nil
})
bus.AddHandler("test", func(query *m.GetTeamsByUserQuery) error {
query.Result = []*m.TeamDTO{}
bus.AddHandler("test", func(query *models.GetTeamsByUserQuery) error {
query.Result = []*models.TeamDTO{}
return nil
})
......@@ -41,7 +41,7 @@ func TestAlertingApiEndpoint(t *testing.T) {
AlertId: 1,
Paused: true,
}
postAlertScenario("When calling POST on", "/api/alerts/1/pause", "/api/alerts/:alertId/pause", m.ROLE_EDITOR, cmd, func(sc *scenarioContext) {
postAlertScenario("When calling POST on", "/api/alerts/1/pause", "/api/alerts/:alertId/pause", models.ROLE_EDITOR, cmd, func(sc *scenarioContext) {
CallPauseAlert(sc)
So(sc.resp.Code, ShouldEqual, 403)
})
......@@ -49,9 +49,9 @@ func TestAlertingApiEndpoint(t *testing.T) {
})
Convey("When user is editor and dashboard has default ACL", func() {
aclMockResp = []*m.DashboardAclInfoDTO{
{Role: &viewerRole, Permission: m.PERMISSION_VIEW},
{Role: &editorRole, Permission: m.PERMISSION_EDIT},
aclMockResp = []*models.DashboardAclInfoDTO{
{Role: &viewerRole, Permission: models.PERMISSION_VIEW},
{Role: &editorRole, Permission: models.PERMISSION_EDIT},
}
Convey("Should be able to pause the alert", func() {
......@@ -59,22 +59,22 @@ func TestAlertingApiEndpoint(t *testing.T) {
AlertId: 1,
Paused: true,
}
postAlertScenario("When calling POST on", "/api/alerts/1/pause", "/api/alerts/:alertId/pause", m.ROLE_EDITOR, cmd, func(sc *scenarioContext) {
postAlertScenario("When calling POST on", "/api/alerts/1/pause", "/api/alerts/:alertId/pause", models.ROLE_EDITOR, cmd, func(sc *scenarioContext) {
CallPauseAlert(sc)
So(sc.resp.Code, ShouldEqual, 200)
})
})
})
loggedInUserScenarioWithRole("When calling GET on", "GET", "/api/alerts?dashboardId=1", "/api/alerts", m.ROLE_EDITOR, func(sc *scenarioContext) {
loggedInUserScenarioWithRole("When calling GET on", "GET", "/api/alerts?dashboardId=1", "/api/alerts", models.ROLE_EDITOR, func(sc *scenarioContext) {
var searchQuery *search.Query
bus.AddHandler("test", func(query *search.Query) error {
searchQuery = query
return nil
})
var getAlertsQuery *m.GetAlertsQuery
bus.AddHandler("test", func(query *m.GetAlertsQuery) error {
var getAlertsQuery *models.GetAlertsQuery
bus.AddHandler("test", func(query *models.GetAlertsQuery) error {
getAlertsQuery = query
return nil
})
......@@ -86,7 +86,7 @@ func TestAlertingApiEndpoint(t *testing.T) {
So(getAlertsQuery, ShouldNotBeNil)
})
loggedInUserScenarioWithRole("When calling GET on", "GET", "/api/alerts?dashboardId=1&dashboardId=2&folderId=3&dashboardTag=abc&dashboardQuery=dbQuery&limit=5&query=alertQuery", "/api/alerts", m.ROLE_EDITOR, func(sc *scenarioContext) {
loggedInUserScenarioWithRole("When calling GET on", "GET", "/api/alerts?dashboardId=1&dashboardId=2&folderId=3&dashboardTag=abc&dashboardQuery=dbQuery&limit=5&query=alertQuery", "/api/alerts", models.ROLE_EDITOR, func(sc *scenarioContext) {
var searchQuery *search.Query
bus.AddHandler("test", func(query *search.Query) error {
searchQuery = query
......@@ -97,8 +97,8 @@ func TestAlertingApiEndpoint(t *testing.T) {
return nil
})
var getAlertsQuery *m.GetAlertsQuery
bus.AddHandler("test", func(query *m.GetAlertsQuery) error {
var getAlertsQuery *models.GetAlertsQuery
bus.AddHandler("test", func(query *models.GetAlertsQuery) error {
getAlertsQuery = query
return nil
})
......@@ -120,7 +120,7 @@ func TestAlertingApiEndpoint(t *testing.T) {
So(getAlertsQuery.Query, ShouldEqual, "alertQuery")
})
loggedInUserScenarioWithRole("When calling GET on", "GET", "/api/alert-notifications/1", "/alert-notifications/:notificationId", m.ROLE_ADMIN, func(sc *scenarioContext) {
loggedInUserScenarioWithRole("When calling GET on", "GET", "/api/alert-notifications/1", "/alert-notifications/:notificationId", models.ROLE_ADMIN, func(sc *scenarioContext) {
sc.handlerFunc = GetAlertNotificationByID
sc.fakeReqWithParams("GET", sc.url, map[string]string{}).exec()
So(sc.resp.Code, ShouldEqual, 404)
......@@ -129,19 +129,19 @@ func TestAlertingApiEndpoint(t *testing.T) {
}
func CallPauseAlert(sc *scenarioContext) {
bus.AddHandler("test", func(cmd *m.PauseAlertCommand) error {
bus.AddHandler("test", func(cmd *models.PauseAlertCommand) error {
return nil
})
sc.fakeReqWithParams("POST", sc.url, map[string]string{}).exec()
}
func postAlertScenario(desc string, url string, routePattern string, role m.RoleType, cmd dtos.PauseAlertCommand, fn scenarioFunc) {
func postAlertScenario(desc string, url string, routePattern string, role models.RoleType, cmd dtos.PauseAlertCommand, fn scenarioFunc) {
Convey(desc+" "+url, func() {
defer bus.ClearBusHandlers()
sc := setupScenarioContext(url)
sc.defaultHandler = Wrap(func(c *m.ReqContext) Response {
sc.defaultHandler = Wrap(func(c *models.ReqContext) Response {
sc.context = c
sc.context.UserId = TestUserID
sc.context.OrgId = TestOrgID
......
......@@ -183,6 +183,7 @@ func (hs *HTTPServer) registerRoutes() {
apiRoute.Group("/org", func(orgRoute routing.RouteRegister) {
orgRoute.Put("/", bind(dtos.UpdateOrgForm{}), Wrap(UpdateOrgCurrent))
orgRoute.Put("/address", bind(dtos.UpdateOrgAddressForm{}), Wrap(UpdateOrgAddressCurrent))
orgRoute.Get("/users", Wrap(GetOrgUsersForCurrentOrg))
orgRoute.Post("/users", quota("user"), bind(models.AddOrgUserCommand{}), Wrap(AddOrgUserToCurrentOrg))
orgRoute.Patch("/users/:userId", bind(models.UpdateOrgUserCommand{}), Wrap(UpdateOrgUserForCurrentOrg))
orgRoute.Delete("/users/:userId", Wrap(RemoveOrgUserForCurrentOrg))
......@@ -199,7 +200,7 @@ func (hs *HTTPServer) registerRoutes() {
// current org without requirement of user to be org admin
apiRoute.Group("/org", func(orgRoute routing.RouteRegister) {
orgRoute.Get("/users", Wrap(GetOrgUsersForCurrentOrg))
orgRoute.Get("/users/lookup", Wrap(GetOrgUsersForCurrentOrgLookup))
})
// create new org
......@@ -343,10 +344,10 @@ func (hs *HTTPServer) registerRoutes() {
alertsRoute.Get("/states-for-dashboard", Wrap(GetAlertStatesForDashboard))
})
apiRoute.Get("/alert-notifications", Wrap(GetAlertNotifications))
apiRoute.Get("/alert-notifiers", Wrap(GetAlertNotifiers))
apiRoute.Get("/alert-notifiers", reqEditorRole, Wrap(GetAlertNotifiers))
apiRoute.Group("/alert-notifications", func(alertNotifications routing.RouteRegister) {
alertNotifications.Get("/", Wrap(GetAlertNotifications))
alertNotifications.Post("/test", bind(dtos.NotificationTestCommand{}), Wrap(NotificationTest))
alertNotifications.Post("/", bind(models.CreateAlertNotificationCommand{}), Wrap(CreateAlertNotification))
alertNotifications.Put("/:notificationId", bind(models.UpdateAlertNotificationCommand{}), Wrap(UpdateAlertNotification))
......@@ -357,6 +358,11 @@ func (hs *HTTPServer) registerRoutes() {
alertNotifications.Delete("/uid/:uid", Wrap(DeleteAlertNotificationByUID))
}, reqEditorRole)
// alert notifications without requirement of user to be org editor
apiRoute.Group("/alert-notifications", func(orgRoute routing.RouteRegister) {
orgRoute.Get("/lookup", Wrap(GetAlertNotificationLookup))
})
apiRoute.Get("/annotations", Wrap(GetAnnotations))
apiRoute.Post("/annotations/mass-delete", reqOrgAdmin, bind(dtos.DeleteAnnotationsCmd{}), Wrap(DeleteAnnotations))
......
......@@ -77,6 +77,24 @@ type AlertNotification struct {
Settings *simplejson.Json `json:"settings"`
}
func NewAlertNotificationLookup(notification *models.AlertNotification) *AlertNotificationLookup {
return &AlertNotificationLookup{
Id: notification.Id,
Uid: notification.Uid,
Name: notification.Name,
Type: notification.Type,
IsDefault: notification.IsDefault,
}
}
type AlertNotificationLookup struct {
Id int64 `json:"id"`
Uid string `json:"uid"`
Name string `json:"name"`
Type string `json:"type"`
IsDefault bool `json:"isDefault"`
}
type AlertTestCommand struct {
Dashboard *simplejson.Json `json:"dashboard" binding:"Required"`
PanelId int64 `json:"panelId" binding:"Required"`
......
......@@ -50,3 +50,9 @@ type ResetUserPasswordForm struct {
NewPassword string `json:"newPassword"`
ConfirmPassword string `json:"confirmPassword"`
}
type UserLookupDTO struct {
UserID int64 `json:"userId"`
Login string `json:"login"`
AvatarURL string `json:"avatarUrl"`
}
......@@ -3,27 +3,27 @@ package api
import (
"github.com/grafana/grafana/pkg/api/dtos"
"github.com/grafana/grafana/pkg/bus"
m "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/models"
)
// POST /api/org/users
func AddOrgUserToCurrentOrg(c *m.ReqContext, cmd m.AddOrgUserCommand) Response {
func AddOrgUserToCurrentOrg(c *models.ReqContext, cmd models.AddOrgUserCommand) Response {
cmd.OrgId = c.OrgId
return addOrgUserHelper(cmd)
}
// POST /api/orgs/:orgId/users
func AddOrgUser(c *m.ReqContext, cmd m.AddOrgUserCommand) Response {
func AddOrgUser(c *models.ReqContext, cmd models.AddOrgUserCommand) Response {
cmd.OrgId = c.ParamsInt64(":orgId")
return addOrgUserHelper(cmd)
}
func addOrgUserHelper(cmd m.AddOrgUserCommand) Response {
func addOrgUserHelper(cmd models.AddOrgUserCommand) Response {
if !cmd.Role.IsValid() {
return Error(400, "Invalid role specified", nil)
}
userQuery := m.GetUserByLoginQuery{LoginOrEmail: cmd.LoginOrEmail}
userQuery := models.GetUserByLoginQuery{LoginOrEmail: cmd.LoginOrEmail}
err := bus.Dispatch(&userQuery)
if err != nil {
return Error(404, "User not found", nil)
......@@ -34,7 +34,7 @@ func addOrgUserHelper(cmd m.AddOrgUserCommand) Response {
cmd.UserId = userToAdd.Id
if err := bus.Dispatch(&cmd); err != nil {
if err == m.ErrOrgUserAlreadyAdded {
if err == models.ErrOrgUserAlreadyAdded {
return Error(409, "User is already member of this organization", nil)
}
return Error(500, "Could not add user to organization", err)
......@@ -44,54 +44,115 @@ func addOrgUserHelper(cmd m.AddOrgUserCommand) Response {
}
// GET /api/org/users
func GetOrgUsersForCurrentOrg(c *m.ReqContext) Response {
return getOrgUsersHelper(c.OrgId, c.Query("query"), c.QueryInt("limit"))
func GetOrgUsersForCurrentOrg(c *models.ReqContext) Response {
result, err := getOrgUsersHelper(c.OrgId, c.Query("query"), c.QueryInt("limit"))
if err != nil {
return Error(500, "Failed to get users for current organization", err)
}
return JSON(200, result)
}
// GET /api/org/users/lookup
func GetOrgUsersForCurrentOrgLookup(c *models.ReqContext) Response {
isAdmin, err := isOrgAdminFolderAdminOrTeamAdmin(c)
if err != nil {
return Error(500, "Failed to get users for current organization", err)
}
if !isAdmin {
return Error(403, "Permission denied", nil)
}
orgUsers, err := getOrgUsersHelper(c.OrgId, c.Query("query"), c.QueryInt("limit"))
if err != nil {
return Error(500, "Failed to get users for current organization", err)
}
result := make([]*dtos.UserLookupDTO, 0)
for _, u := range orgUsers {
result = append(result, &dtos.UserLookupDTO{
UserID: u.UserId,
Login: u.Login,
AvatarURL: u.AvatarUrl,
})
}
return JSON(200, result)
}
func isOrgAdminFolderAdminOrTeamAdmin(c *models.ReqContext) (bool, error) {
if c.OrgRole == models.ROLE_ADMIN {
return true, nil
}
hasAdminPermissionInFoldersQuery := models.HasAdminPermissionInFoldersQuery{SignedInUser: c.SignedInUser}
if err := bus.Dispatch(&hasAdminPermissionInFoldersQuery); err != nil {
return false, err
}
if hasAdminPermissionInFoldersQuery.Result {
return true, nil
}
isAdminOfTeamsQuery := models.IsAdminOfTeamsQuery{SignedInUser: c.SignedInUser}
if err := bus.Dispatch(&isAdminOfTeamsQuery); err != nil {
return false, err
}
return isAdminOfTeamsQuery.Result, nil
}
// GET /api/orgs/:orgId/users
func GetOrgUsers(c *m.ReqContext) Response {
return getOrgUsersHelper(c.ParamsInt64(":orgId"), "", 0)
func GetOrgUsers(c *models.ReqContext) Response {
result, err := getOrgUsersHelper(c.ParamsInt64(":orgId"), "", 0)
if err != nil {
return Error(500, "Failed to get users for organization", err)
}
return JSON(200, result)
}
func getOrgUsersHelper(orgID int64, query string, limit int) Response {
q := m.GetOrgUsersQuery{
func getOrgUsersHelper(orgID int64, query string, limit int) ([]*models.OrgUserDTO, error) {
q := models.GetOrgUsersQuery{
OrgId: orgID,
Query: query,
Limit: limit,
}
if err := bus.Dispatch(&q); err != nil {
return Error(500, "Failed to get account user", err)
return nil, err
}
for _, user := range q.Result {
user.AvatarUrl = dtos.GetGravatarUrl(user.Email)
}
return JSON(200, q.Result)
return q.Result, nil
}
// PATCH /api/org/users/:userId
func UpdateOrgUserForCurrentOrg(c *m.ReqContext, cmd m.UpdateOrgUserCommand) Response {
func UpdateOrgUserForCurrentOrg(c *models.ReqContext, cmd models.UpdateOrgUserCommand) Response {
cmd.OrgId = c.OrgId
cmd.UserId = c.ParamsInt64(":userId")
return updateOrgUserHelper(cmd)
}
// PATCH /api/orgs/:orgId/users/:userId
func UpdateOrgUser(c *m.ReqContext, cmd m.UpdateOrgUserCommand) Response {
func UpdateOrgUser(c *models.ReqContext, cmd models.UpdateOrgUserCommand) Response {
cmd.OrgId = c.ParamsInt64(":orgId")
cmd.UserId = c.ParamsInt64(":userId")
return updateOrgUserHelper(cmd)
}
func updateOrgUserHelper(cmd m.UpdateOrgUserCommand) Response {
func updateOrgUserHelper(cmd models.UpdateOrgUserCommand) Response {
if !cmd.Role.IsValid() {
return Error(400, "Invalid role specified", nil)
}
if err := bus.Dispatch(&cmd); err != nil {
if err == m.ErrLastOrgAdmin {
if err == models.ErrLastOrgAdmin {
return Error(400, "Cannot change role so that there is no organization admin left", nil)
}
return Error(500, "Failed update org user", err)
......@@ -101,8 +162,8 @@ func updateOrgUserHelper(cmd m.UpdateOrgUserCommand) Response {
}
// DELETE /api/org/users/:userId
func RemoveOrgUserForCurrentOrg(c *m.ReqContext) Response {
return removeOrgUserHelper(&m.RemoveOrgUserCommand{
func RemoveOrgUserForCurrentOrg(c *models.ReqContext) Response {
return removeOrgUserHelper(&models.RemoveOrgUserCommand{
UserId: c.ParamsInt64(":userId"),
OrgId: c.OrgId,
ShouldDeleteOrphanedUser: true,
......@@ -110,16 +171,16 @@ func RemoveOrgUserForCurrentOrg(c *m.ReqContext) Response {
}
// DELETE /api/orgs/:orgId/users/:userId
func RemoveOrgUser(c *m.ReqContext) Response {
return removeOrgUserHelper(&m.RemoveOrgUserCommand{
func RemoveOrgUser(c *models.ReqContext) Response {
return removeOrgUserHelper(&models.RemoveOrgUserCommand{
UserId: c.ParamsInt64(":userId"),
OrgId: c.ParamsInt64(":orgId"),
})
}
func removeOrgUserHelper(cmd *m.RemoveOrgUserCommand) Response {
func removeOrgUserHelper(cmd *models.RemoveOrgUserCommand) Response {
if err := bus.Dispatch(cmd); err != nil {
if err == m.ErrLastOrgAdmin {
if err == models.ErrLastOrgAdmin {
return Error(400, "Cannot remove last organization admin", nil)
}
return Error(500, "Failed to remove user from organization", err)
......
......@@ -98,3 +98,8 @@ type HasEditPermissionInFoldersQuery struct {
SignedInUser *SignedInUser
Result bool
}
type HasAdminPermissionInFoldersQuery struct {
SignedInUser *SignedInUser
Result bool
}
......@@ -88,3 +88,8 @@ type SearchTeamQueryResult struct {
Page int `json:"page"`
PerPage int `json:"perPage"`
}
type IsAdminOfTeamsQuery struct {
SignedInUser *SignedInUser
Result bool
}
......@@ -5,7 +5,7 @@ import (
. "github.com/smartystreets/goconvey/convey"
m "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/models"
)
type Test struct {
......@@ -17,11 +17,11 @@ func TestDataAccess(t *testing.T) {
Convey("Testing DB", t, func() {
InitTestDB(t)
Convey("Can add datasource", func() {
err := AddDataSource(&m.AddDataSourceCommand{
err := AddDataSource(&models.AddDataSourceCommand{
OrgId: 10,
Name: "laban",
Type: m.DS_INFLUXDB,
Access: m.DS_ACCESS_DIRECT,
Type: models.DS_INFLUXDB,
Access: models.DS_ACCESS_DIRECT,
Url: "http://test",
Database: "site",
ReadOnly: true,
......@@ -29,7 +29,7 @@ func TestDataAccess(t *testing.T) {
So(err, ShouldBeNil)
query := m.GetDataSourcesQuery{OrgId: 10}
query := models.GetDataSourcesQuery{OrgId: 10}
err = GetDataSources(&query)
So(err, ShouldBeNil)
......@@ -43,28 +43,28 @@ func TestDataAccess(t *testing.T) {
})
Convey("Given a datasource", func() {
err := AddDataSource(&m.AddDataSourceCommand{
err := AddDataSource(&models.AddDataSourceCommand{
OrgId: 10,
Name: "nisse",
Type: m.DS_GRAPHITE,
Access: m.DS_ACCESS_DIRECT,
Type: models.DS_GRAPHITE,
Access: models.DS_ACCESS_DIRECT,
Url: "http://test",
})
So(err, ShouldBeNil)
query := m.GetDataSourcesQuery{OrgId: 10}
query := models.GetDataSourcesQuery{OrgId: 10}
err = GetDataSources(&query)
So(err, ShouldBeNil)
ds := query.Result[0]
Convey(" updated ", func() {
cmd := &m.UpdateDataSourceCommand{
cmd := &models.UpdateDataSourceCommand{
Id: ds.Id,
OrgId: 10,
Name: "nisse",
Type: m.DS_GRAPHITE,
Access: m.DS_ACCESS_PROXY,
Type: models.DS_GRAPHITE,
Access: models.DS_ACCESS_PROXY,
Url: "http://test",
Version: ds.Version,
}
......@@ -75,27 +75,27 @@ func TestDataAccess(t *testing.T) {
})
Convey("when someone else updated between read and update", func() {
query := m.GetDataSourcesQuery{OrgId: 10}
query := models.GetDataSourcesQuery{OrgId: 10}
err = GetDataSources(&query)
So(err, ShouldBeNil)
ds := query.Result[0]
intendedUpdate := &m.UpdateDataSourceCommand{
intendedUpdate := &models.UpdateDataSourceCommand{
Id: ds.Id,
OrgId: 10,
Name: "nisse",
Type: m.DS_GRAPHITE,
Access: m.DS_ACCESS_PROXY,
Type: models.DS_GRAPHITE,
Access: models.DS_ACCESS_PROXY,
Url: "http://test",
Version: ds.Version,
}
updateFromOtherUser := &m.UpdateDataSourceCommand{
updateFromOtherUser := &models.UpdateDataSourceCommand{
Id: ds.Id,
OrgId: 10,
Name: "nisse",
Type: m.DS_GRAPHITE,
Access: m.DS_ACCESS_PROXY,
Type: models.DS_GRAPHITE,
Access: models.DS_ACCESS_PROXY,
Url: "http://test",
Version: ds.Version,
}
......@@ -108,12 +108,12 @@ func TestDataAccess(t *testing.T) {
})
Convey("updating datasource without version", func() {
cmd := &m.UpdateDataSourceCommand{
cmd := &models.UpdateDataSourceCommand{
Id: ds.Id,
OrgId: 10,
Name: "nisse",
Type: m.DS_GRAPHITE,
Access: m.DS_ACCESS_PROXY,
Type: models.DS_GRAPHITE,
Access: models.DS_ACCESS_PROXY,
Url: "http://test",
}
......@@ -124,12 +124,12 @@ func TestDataAccess(t *testing.T) {
})
Convey("updating datasource without higher version", func() {
cmd := &m.UpdateDataSourceCommand{
cmd := &models.UpdateDataSourceCommand{
Id: ds.Id,
OrgId: 10,
Name: "nisse",
Type: m.DS_GRAPHITE,
Access: m.DS_ACCESS_PROXY,
Type: models.DS_GRAPHITE,
Access: models.DS_ACCESS_PROXY,
Url: "http://test",
Version: 90000,
}
......@@ -142,7 +142,7 @@ func TestDataAccess(t *testing.T) {
})
Convey("Can delete datasource by id", func() {
err := DeleteDataSourceById(&m.DeleteDataSourceByIdCommand{Id: ds.Id, OrgId: ds.OrgId})
err := DeleteDataSourceById(&models.DeleteDataSourceByIdCommand{Id: ds.Id, OrgId: ds.OrgId})
So(err, ShouldBeNil)
GetDataSources(&query)
......@@ -150,7 +150,7 @@ func TestDataAccess(t *testing.T) {
})
Convey("Can delete datasource by name", func() {
err := DeleteDataSourceByName(&m.DeleteDataSourceByNameCommand{Name: ds.Name, OrgId: ds.OrgId})
err := DeleteDataSourceByName(&models.DeleteDataSourceByNameCommand{Name: ds.Name, OrgId: ds.OrgId})
So(err, ShouldBeNil)
GetDataSources(&query)
......@@ -158,7 +158,7 @@ func TestDataAccess(t *testing.T) {
})
Convey("Can not delete datasource with wrong orgId", func() {
err := DeleteDataSourceById(&m.DeleteDataSourceByIdCommand{Id: ds.Id, OrgId: 123123})
err := DeleteDataSourceById(&models.DeleteDataSourceByIdCommand{Id: ds.Id, OrgId: 123123})
So(err, ShouldBeNil)
GetDataSources(&query)
......
......@@ -6,7 +6,7 @@ import (
"time"
"github.com/grafana/grafana/pkg/bus"
m "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/models"
)
func init() {
......@@ -21,6 +21,7 @@ func init() {
bus.AddHandler("sql", UpdateTeamMember)
bus.AddHandler("sql", RemoveTeamMember)
bus.AddHandler("sql", GetTeamMembers)
bus.AddHandler("sql", IsAdminOfTeams)
}
func getTeamSearchSqlBase() string {
......@@ -45,16 +46,16 @@ func getTeamSelectSqlBase() string {
FROM team as team `
}
func CreateTeam(cmd *m.CreateTeamCommand) error {
func CreateTeam(cmd *models.CreateTeamCommand) error {
return inTransaction(func(sess *DBSession) error {
if isNameTaken, err := isTeamNameTaken(cmd.OrgId, cmd.Name, 0, sess); err != nil {
return err
} else if isNameTaken {
return m.ErrTeamNameTaken
return models.ErrTeamNameTaken
}
team := m.Team{
team := models.Team{
Name: cmd.Name,
Email: cmd.Email,
OrgId: cmd.OrgId,
......@@ -70,16 +71,16 @@ func CreateTeam(cmd *m.CreateTeamCommand) error {
})
}
func UpdateTeam(cmd *m.UpdateTeamCommand) error {
func UpdateTeam(cmd *models.UpdateTeamCommand) error {
return inTransaction(func(sess *DBSession) error {
if isNameTaken, err := isTeamNameTaken(cmd.OrgId, cmd.Name, cmd.Id, sess); err != nil {
return err
} else if isNameTaken {
return m.ErrTeamNameTaken
return models.ErrTeamNameTaken
}
team := m.Team{
team := models.Team{
Name: cmd.Name,
Email: cmd.Email,
Updated: time.Now(),
......@@ -94,7 +95,7 @@ func UpdateTeam(cmd *m.UpdateTeamCommand) error {
}
if affectedRows == 0 {
return m.ErrTeamNotFound
return models.ErrTeamNotFound
}
return nil
......@@ -102,7 +103,7 @@ func UpdateTeam(cmd *m.UpdateTeamCommand) error {
}
// DeleteTeam will delete a team, its member and any permissions connected to the team
func DeleteTeam(cmd *m.DeleteTeamCommand) error {
func DeleteTeam(cmd *models.DeleteTeamCommand) error {
return inTransaction(func(sess *DBSession) error {
if _, err := teamExists(cmd.OrgId, cmd.Id, sess); err != nil {
return err
......@@ -128,14 +129,14 @@ func teamExists(orgId int64, teamId int64, sess *DBSession) (bool, error) {
if res, err := sess.Query("SELECT 1 from team WHERE org_id=? and id=?", orgId, teamId); err != nil {
return false, err
} else if len(res) != 1 {
return false, m.ErrTeamNotFound
return false, models.ErrTeamNotFound
}
return true, nil
}
func isTeamNameTaken(orgId int64, name string, existingId int64, sess *DBSession) (bool, error) {
var team m.Team
var team models.Team
exists, err := sess.Where("org_id=? and name=?", orgId, name).Get(&team)
if err != nil {
......@@ -149,9 +150,9 @@ func isTeamNameTaken(orgId int64, name string, existingId int64, sess *DBSession
return false, nil
}
func SearchTeams(query *m.SearchTeamsQuery) error {
query.Result = m.SearchTeamQueryResult{
Teams: make([]*m.TeamDTO, 0),
func SearchTeams(query *models.SearchTeamsQuery) error {
query.Result = models.SearchTeamQueryResult{
Teams: make([]*models.TeamDTO, 0),
}
queryWithWildcards := "%" + query.Query + "%"
......@@ -189,7 +190,7 @@ func SearchTeams(query *m.SearchTeamsQuery) error {
return err
}
team := m.Team{}
team := models.Team{}
countSess := x.Table("team")
if query.Query != "" {
countSess.Where(`name `+dialect.LikeStr()+` ?`, queryWithWildcards)
......@@ -205,13 +206,13 @@ func SearchTeams(query *m.SearchTeamsQuery) error {
return err
}
func GetTeamById(query *m.GetTeamByIdQuery) error {
func GetTeamById(query *models.GetTeamByIdQuery) error {
var sql bytes.Buffer
sql.WriteString(getTeamSelectSqlBase())
sql.WriteString(` WHERE team.org_id = ? and team.id = ?`)
var team m.TeamDTO
var team models.TeamDTO
exists, err := x.SQL(sql.String(), query.OrgId, query.Id).Get(&team)
if err != nil {
......@@ -219,7 +220,7 @@ func GetTeamById(query *m.GetTeamByIdQuery) error {
}
if !exists {
return m.ErrTeamNotFound
return models.ErrTeamNotFound
}
query.Result = &team
......@@ -227,8 +228,8 @@ func GetTeamById(query *m.GetTeamByIdQuery) error {
}
// GetTeamsByUser is used by the Guardian when checking a users' permissions
func GetTeamsByUser(query *m.GetTeamsByUserQuery) error {
query.Result = make([]*m.TeamDTO, 0)
func GetTeamsByUser(query *models.GetTeamsByUserQuery) error {
query.Result = make([]*models.TeamDTO, 0)
var sql bytes.Buffer
......@@ -241,19 +242,19 @@ func GetTeamsByUser(query *m.GetTeamsByUserQuery) error {
}
// AddTeamMember adds a user to a team
func AddTeamMember(cmd *m.AddTeamMemberCommand) error {
func AddTeamMember(cmd *models.AddTeamMemberCommand) error {
return inTransaction(func(sess *DBSession) error {
if res, err := sess.Query("SELECT 1 from team_member WHERE org_id=? and team_id=? and user_id=?", cmd.OrgId, cmd.TeamId, cmd.UserId); err != nil {
return err
} else if len(res) == 1 {
return m.ErrTeamMemberAlreadyAdded
return models.ErrTeamMemberAlreadyAdded
}
if _, err := teamExists(cmd.OrgId, cmd.TeamId, sess); err != nil {
return err
}
entity := m.TeamMember{
entity := models.TeamMember{
OrgId: cmd.OrgId,
TeamId: cmd.TeamId,
UserId: cmd.UserId,
......@@ -268,23 +269,23 @@ func AddTeamMember(cmd *m.AddTeamMemberCommand) error {
})
}
func getTeamMember(sess *DBSession, orgId int64, teamId int64, userId int64) (m.TeamMember, error) {
func getTeamMember(sess *DBSession, orgId int64, teamId int64, userId int64) (models.TeamMember, error) {
rawSql := `SELECT * FROM team_member WHERE org_id=? and team_id=? and user_id=?`
var member m.TeamMember
var member models.TeamMember
exists, err := sess.SQL(rawSql, orgId, teamId, userId).Get(&member)
if err != nil {
return member, err
}
if !exists {
return member, m.ErrTeamMemberNotFound
return member, models.ErrTeamMemberNotFound
}
return member, nil
}
// UpdateTeamMember updates a team member
func UpdateTeamMember(cmd *m.UpdateTeamMemberCommand) error {
func UpdateTeamMember(cmd *models.UpdateTeamMemberCommand) error {
return inTransaction(func(sess *DBSession) error {
member, err := getTeamMember(sess, cmd.OrgId, cmd.TeamId, cmd.UserId)
if err != nil {
......@@ -298,7 +299,7 @@ func UpdateTeamMember(cmd *m.UpdateTeamMemberCommand) error {
}
}
if cmd.Permission != m.PERMISSION_ADMIN { // make sure we don't get invalid permission levels in store
if cmd.Permission != models.PERMISSION_ADMIN { // make sure we don't get invalid permission levels in store
cmd.Permission = 0
}
......@@ -310,7 +311,7 @@ func UpdateTeamMember(cmd *m.UpdateTeamMemberCommand) error {
}
// RemoveTeamMember removes a member from a team
func RemoveTeamMember(cmd *m.RemoveTeamMemberCommand) error {
func RemoveTeamMember(cmd *models.RemoveTeamMemberCommand) error {
return inTransaction(func(sess *DBSession) error {
if _, err := teamExists(cmd.OrgId, cmd.TeamId, sess); err != nil {
return err
......@@ -330,7 +331,7 @@ func RemoveTeamMember(cmd *m.RemoveTeamMemberCommand) error {
}
rows, err := res.RowsAffected()
if rows == 0 {
return m.ErrTeamMemberNotFound
return models.ErrTeamMemberNotFound
}
return err
......@@ -340,7 +341,7 @@ func RemoveTeamMember(cmd *m.RemoveTeamMemberCommand) error {
func isLastAdmin(sess *DBSession, orgId int64, teamId int64, userId int64) (bool, error) {
rawSql := "SELECT user_id FROM team_member WHERE org_id=? and team_id=? and permission=?"
userIds := []*int64{}
err := sess.SQL(rawSql, orgId, teamId, m.PERMISSION_ADMIN).Find(&userIds)
err := sess.SQL(rawSql, orgId, teamId, models.PERMISSION_ADMIN).Find(&userIds)
if err != nil {
return false, err
}
......@@ -354,15 +355,15 @@ func isLastAdmin(sess *DBSession, orgId int64, teamId int64, userId int64) (bool
}
if isAdmin && len(userIds) == 1 {
return true, m.ErrLastTeamAdmin
return true, models.ErrLastTeamAdmin
}
return false, err
}
// GetTeamMembers return a list of members for the specified team
func GetTeamMembers(query *m.GetTeamMembersQuery) error {
query.Result = make([]*m.TeamMemberDTO, 0)
func GetTeamMembers(query *models.GetTeamMembersQuery) error {
query.Result = make([]*models.TeamMemberDTO, 0)
sess := x.Table("team_member")
sess.Join("INNER", x.Dialect().Quote("user"), fmt.Sprintf("team_member.user_id=%s.id", x.Dialect().Quote("user")))
......@@ -392,3 +393,21 @@ func GetTeamMembers(query *m.GetTeamMembersQuery) error {
err := sess.Find(&query.Result)
return err
}
func IsAdminOfTeams(query *models.IsAdminOfTeamsQuery) error {
builder := &SqlBuilder{}
builder.Write("SELECT COUNT(team.id) AS count FROM team INNER JOIN team_member ON team_member.team_id = team.id WHERE team.org_id = ? AND team_member.user_id = ? AND team_member.permission = ?", query.SignedInUser.OrgId, query.SignedInUser.UserId, models.PERMISSION_ADMIN)
type teamCount struct {
Count int64
}
resp := make([]*teamCount, 0)
if err := x.SQL(builder.GetSqlString(), builder.params...).Find(&resp); err != nil {
return err
}
query.Result = len(resp) > 0 && resp[0].Count > 0
return nil
}
......@@ -44,12 +44,12 @@ export class UserPicker extends Component<Props, State> {
}
return backendSrv
.get(`/api/org/users?query=${query}&limit=10`)
.get(`/api/org/users/lookup?query=${query}&limit=10`)
.then((result: any) => {
return result.map((user: any) => ({
id: user.userId,
value: user.userId,
label: user.login === user.email ? user.login : `${user.login} - ${user.email}`,
label: user.login,
imgUrl: user.avatarUrl,
login: user.login,
}));
......
......@@ -71,7 +71,7 @@ export class AlertTabCtrl {
this.alertNotifications = [];
this.alertHistory = [];
return this.backendSrv.get('/api/alert-notifications').then((res: any) => {
return this.backendSrv.get('/api/alert-notifications/lookup').then((res: any) => {
this.notifications = res;
this.initModel();
......
......@@ -79,7 +79,7 @@ export class GettingStarted extends PureComponent<PanelProps, State> {
href: 'org/users?gettingstarted',
check: () => {
return getBackendSrv()
.get('/api/org/users')
.get('/api/org/users/lookup')
.then((res: any) => {
return res.length > 1;
});
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment