mirror of
https://github.com/grafana/grafana.git
synced 2025-07-28 21:12:30 +08:00

* Fix organization deletion error messages - Improve error message clarity when attempting to delete active organization - Fix incorrect 'Failed to update organization' message to 'Failed to delete organization' - Update comment to be more precise about the check being performed Fixes #92792 * Improve error handling in organization deletion service - Add contextual error message when dashboard deletion fails during org deletion - Include organization ID in error message for better debugging - Import fmt package for error formatting This helps administrators understand which specific organization failed during the deletion process when dashboard cleanup encounters issues. * Fix typo in unified resource access error message Change 'Namespace missmatch' to 'Namespace mismatch' for correct spelling.
479 lines
13 KiB
Go
479 lines
13 KiB
Go
package api
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"net/http"
|
|
"strconv"
|
|
|
|
claims "github.com/grafana/authlib/types"
|
|
"github.com/grafana/grafana/pkg/api/dtos"
|
|
"github.com/grafana/grafana/pkg/api/response"
|
|
"github.com/grafana/grafana/pkg/infra/metrics"
|
|
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
|
|
"github.com/grafana/grafana/pkg/services/org"
|
|
"github.com/grafana/grafana/pkg/util"
|
|
"github.com/grafana/grafana/pkg/web"
|
|
)
|
|
|
|
// swagger:route GET /org org getCurrentOrg
|
|
//
|
|
// Get current Organization.
|
|
//
|
|
// Responses:
|
|
// 200: getCurrentOrgResponse
|
|
// 401: unauthorisedError
|
|
// 403: forbiddenError
|
|
// 500: internalServerError
|
|
func (hs *HTTPServer) GetCurrentOrg(c *contextmodel.ReqContext) response.Response {
|
|
return hs.getOrgHelper(c.Req.Context(), c.GetOrgID())
|
|
}
|
|
|
|
// swagger:route GET /orgs/{org_id} orgs getOrgByID
|
|
//
|
|
// Get Organization by ID.
|
|
//
|
|
// Security:
|
|
// - basic:
|
|
//
|
|
// Responses:
|
|
// 200: getOrgByIDResponse
|
|
// 401: unauthorisedError
|
|
// 403: forbiddenError
|
|
// 500: internalServerError
|
|
func (hs *HTTPServer) GetOrgByID(c *contextmodel.ReqContext) response.Response {
|
|
orgId, err := strconv.ParseInt(web.Params(c.Req)[":orgId"], 10, 64)
|
|
if err != nil {
|
|
return response.Error(http.StatusBadRequest, "orgId is invalid", err)
|
|
}
|
|
return hs.getOrgHelper(c.Req.Context(), orgId)
|
|
}
|
|
|
|
// swagger:route GET /orgs/name/{org_name} orgs getOrgByName
|
|
//
|
|
// Get Organization by ID.
|
|
//
|
|
// Security:
|
|
// - basic:
|
|
//
|
|
// Responses:
|
|
// 200: getOrgByNameResponse
|
|
// 401: unauthorisedError
|
|
// 403: forbiddenError
|
|
// 500: internalServerError
|
|
func (hs *HTTPServer) GetOrgByName(c *contextmodel.ReqContext) response.Response {
|
|
orga, err := hs.orgService.GetByName(c.Req.Context(), &org.GetOrgByNameQuery{Name: web.Params(c.Req)[":name"]})
|
|
if err != nil {
|
|
if errors.Is(err, org.ErrOrgNotFound) {
|
|
return response.Error(http.StatusNotFound, "Organization not found", err)
|
|
}
|
|
|
|
return response.Error(http.StatusInternalServerError, "Failed to get organization", err)
|
|
}
|
|
result := org.OrgDetailsDTO{
|
|
ID: orga.ID,
|
|
Name: orga.Name,
|
|
Address: org.Address{
|
|
Address1: orga.Address1,
|
|
Address2: orga.Address2,
|
|
City: orga.City,
|
|
ZipCode: orga.ZipCode,
|
|
State: orga.State,
|
|
Country: orga.Country,
|
|
},
|
|
}
|
|
|
|
return response.JSON(http.StatusOK, &result)
|
|
}
|
|
|
|
func (hs *HTTPServer) getOrgHelper(ctx context.Context, orgID int64) response.Response {
|
|
query := org.GetOrgByIDQuery{ID: orgID}
|
|
|
|
res, err := hs.orgService.GetByID(ctx, &query)
|
|
if err != nil {
|
|
if errors.Is(err, org.ErrOrgNotFound) {
|
|
return response.Error(http.StatusNotFound, "Organization not found", err)
|
|
}
|
|
return response.Error(http.StatusInternalServerError, "Failed to get organization", err)
|
|
}
|
|
|
|
orga := res
|
|
result := org.OrgDetailsDTO{
|
|
ID: orga.ID,
|
|
Name: orga.Name,
|
|
Address: org.Address{
|
|
Address1: orga.Address1,
|
|
Address2: orga.Address2,
|
|
City: orga.City,
|
|
ZipCode: orga.ZipCode,
|
|
State: orga.State,
|
|
Country: orga.Country,
|
|
},
|
|
}
|
|
|
|
return response.JSON(http.StatusOK, &result)
|
|
}
|
|
|
|
// swagger:route POST /orgs orgs createOrg
|
|
//
|
|
// Create Organization.
|
|
//
|
|
// Only works if [users.allow_org_create](https://grafana.com/docs/grafana/latest/administration/configuration/#allow_org_create) is set.
|
|
//
|
|
// Responses:
|
|
// 200: createOrgResponse
|
|
// 401: unauthorisedError
|
|
// 403: forbiddenError
|
|
// 409: conflictError
|
|
// 500: internalServerError
|
|
func (hs *HTTPServer) CreateOrg(c *contextmodel.ReqContext) response.Response {
|
|
cmd := org.CreateOrgCommand{}
|
|
if err := web.Bind(c.Req, &cmd); err != nil {
|
|
return response.Error(http.StatusBadRequest, "bad request data", err)
|
|
}
|
|
|
|
if !c.IsIdentityType(claims.TypeUser) {
|
|
return response.Error(http.StatusForbidden, "Only users can create organizations", nil)
|
|
}
|
|
|
|
userID, err := c.GetInternalID()
|
|
if err != nil {
|
|
return response.Error(http.StatusInternalServerError, "Failed to parse user id", err)
|
|
}
|
|
|
|
cmd.UserID = userID
|
|
result, err := hs.orgService.CreateWithMember(c.Req.Context(), &cmd)
|
|
if err != nil {
|
|
if errors.Is(err, org.ErrOrgNameTaken) {
|
|
return response.Error(http.StatusConflict, "Organization name taken", err)
|
|
}
|
|
return response.Error(http.StatusInternalServerError, "Failed to create organization", err)
|
|
}
|
|
|
|
metrics.MApiOrgCreate.Inc()
|
|
|
|
return response.JSON(http.StatusOK, &util.DynMap{
|
|
"orgId": result.ID,
|
|
"message": "Organization created",
|
|
})
|
|
}
|
|
|
|
// swagger:route PUT /org org updateCurrentOrg
|
|
//
|
|
// Update current Organization.
|
|
//
|
|
// Responses:
|
|
// 200: okResponse
|
|
// 400: badRequestError
|
|
// 401: unauthorisedError
|
|
// 403: forbiddenError
|
|
// 500: internalServerError
|
|
func (hs *HTTPServer) UpdateCurrentOrg(c *contextmodel.ReqContext) response.Response {
|
|
form := dtos.UpdateOrgForm{}
|
|
if err := web.Bind(c.Req, &form); err != nil {
|
|
return response.Error(http.StatusBadRequest, "bad request data", err)
|
|
}
|
|
return hs.updateOrgHelper(c.Req.Context(), form, c.GetOrgID())
|
|
}
|
|
|
|
// swagger:route PUT /orgs/{org_id} orgs updateOrg
|
|
//
|
|
// Update Organization.
|
|
//
|
|
// Security:
|
|
// - basic:
|
|
//
|
|
// Responses:
|
|
// 200: okResponse
|
|
// 400: badRequestError
|
|
// 401: unauthorisedError
|
|
// 403: forbiddenError
|
|
// 500: internalServerError
|
|
func (hs *HTTPServer) UpdateOrg(c *contextmodel.ReqContext) response.Response {
|
|
form := dtos.UpdateOrgForm{}
|
|
if err := web.Bind(c.Req, &form); err != nil {
|
|
return response.Error(http.StatusBadRequest, "bad request data", err)
|
|
}
|
|
orgId, err := strconv.ParseInt(web.Params(c.Req)[":orgId"], 10, 64)
|
|
if err != nil {
|
|
return response.Error(http.StatusBadRequest, "orgId is invalid", err)
|
|
}
|
|
return hs.updateOrgHelper(c.Req.Context(), form, orgId)
|
|
}
|
|
|
|
func (hs *HTTPServer) updateOrgHelper(ctx context.Context, form dtos.UpdateOrgForm, orgID int64) response.Response {
|
|
cmd := org.UpdateOrgCommand{Name: form.Name, OrgId: orgID}
|
|
if err := hs.orgService.UpdateOrg(ctx, &cmd); err != nil {
|
|
if errors.Is(err, org.ErrOrgNameTaken) {
|
|
return response.Error(http.StatusBadRequest, "Organization name taken", err)
|
|
}
|
|
return response.Error(http.StatusInternalServerError, "Failed to update organization", err)
|
|
}
|
|
|
|
return response.Success("Organization updated")
|
|
}
|
|
|
|
// swagger:route PUT /org/address org updateCurrentOrgAddress
|
|
//
|
|
// Update current Organization's address.
|
|
//
|
|
// Responses:
|
|
// 200: okResponse
|
|
// 400: badRequestError
|
|
// 401: unauthorisedError
|
|
// 403: forbiddenError
|
|
// 500: internalServerError
|
|
func (hs *HTTPServer) UpdateCurrentOrgAddress(c *contextmodel.ReqContext) response.Response {
|
|
form := dtos.UpdateOrgAddressForm{}
|
|
if err := web.Bind(c.Req, &form); err != nil {
|
|
return response.Error(http.StatusBadRequest, "bad request data", err)
|
|
}
|
|
return hs.updateOrgAddressHelper(c.Req.Context(), form, c.GetOrgID())
|
|
}
|
|
|
|
// swagger:route PUT /orgs/{org_id}/address orgs updateOrgAddress
|
|
//
|
|
// Update Organization's address.
|
|
//
|
|
// Responses:
|
|
// 200: okResponse
|
|
// 400: badRequestError
|
|
// 401: unauthorisedError
|
|
// 403: forbiddenError
|
|
// 500: internalServerError
|
|
func (hs *HTTPServer) UpdateOrgAddress(c *contextmodel.ReqContext) response.Response {
|
|
form := dtos.UpdateOrgAddressForm{}
|
|
if err := web.Bind(c.Req, &form); err != nil {
|
|
return response.Error(http.StatusBadRequest, "bad request data", err)
|
|
}
|
|
orgId, err := strconv.ParseInt(web.Params(c.Req)[":orgId"], 10, 64)
|
|
if err != nil {
|
|
return response.Error(http.StatusBadRequest, "orgId is invalid", err)
|
|
}
|
|
return hs.updateOrgAddressHelper(c.Req.Context(), form, orgId)
|
|
}
|
|
|
|
func (hs *HTTPServer) updateOrgAddressHelper(ctx context.Context, form dtos.UpdateOrgAddressForm, orgID int64) response.Response {
|
|
cmd := org.UpdateOrgAddressCommand{
|
|
OrgID: orgID,
|
|
Address: org.Address{
|
|
Address1: form.Address1,
|
|
Address2: form.Address2,
|
|
City: form.City,
|
|
State: form.State,
|
|
ZipCode: form.ZipCode,
|
|
Country: form.Country,
|
|
},
|
|
}
|
|
|
|
if err := hs.orgService.UpdateAddress(ctx, &cmd); err != nil {
|
|
return response.Error(http.StatusInternalServerError, "Failed to update org address", err)
|
|
}
|
|
|
|
return response.Success("Address updated")
|
|
}
|
|
|
|
// swagger:route DELETE /orgs/{org_id} orgs deleteOrgByID
|
|
//
|
|
// Delete Organization.
|
|
//
|
|
// Security:
|
|
// - basic:
|
|
//
|
|
// Responses:
|
|
// 200: okResponse
|
|
// 400: badRequestError
|
|
// 401: unauthorisedError
|
|
// 403: forbiddenError
|
|
// 404: notFoundError
|
|
// 500: internalServerError
|
|
func (hs *HTTPServer) DeleteOrgByID(c *contextmodel.ReqContext) response.Response {
|
|
orgID, err := strconv.ParseInt(web.Params(c.Req)[":orgId"], 10, 64)
|
|
if err != nil {
|
|
return response.Error(http.StatusBadRequest, "orgId is invalid", err)
|
|
}
|
|
// before deleting an org, check if user is not active in the org
|
|
if c.GetOrgID() == orgID {
|
|
return response.Error(http.StatusBadRequest, "Cannot delete your active organization. Please switch to a different organization first.", nil)
|
|
}
|
|
|
|
if err := hs.orgDeletionService.Delete(c.Req.Context(), &org.DeleteOrgCommand{ID: orgID}); err != nil {
|
|
if errors.Is(err, org.ErrOrgNotFound) {
|
|
return response.Error(http.StatusNotFound, "Failed to delete organization. ID not found", nil)
|
|
}
|
|
return response.Error(http.StatusInternalServerError, "Failed to delete organization", err)
|
|
}
|
|
return response.Success("Organization deleted")
|
|
}
|
|
|
|
// swagger:route GET /orgs orgs searchOrgs
|
|
//
|
|
// Search all Organizations.
|
|
//
|
|
// Security:
|
|
// - basic:
|
|
//
|
|
// Responses:
|
|
// 200: searchOrgsResponse
|
|
// 401: unauthorisedError
|
|
// 403: forbiddenError
|
|
// 409: conflictError
|
|
// 500: internalServerError
|
|
func (hs *HTTPServer) SearchOrgs(c *contextmodel.ReqContext) response.Response {
|
|
perPage := c.QueryInt("perpage")
|
|
if perPage <= 0 {
|
|
perPage = 1000
|
|
}
|
|
|
|
page := c.QueryInt("page")
|
|
|
|
query := org.SearchOrgsQuery{
|
|
Query: c.Query("query"),
|
|
Name: c.Query("name"),
|
|
Page: page,
|
|
Limit: perPage,
|
|
}
|
|
|
|
result, err := hs.orgService.Search(c.Req.Context(), &query)
|
|
if err != nil {
|
|
return response.Error(http.StatusInternalServerError, "Failed to search orgs", err)
|
|
}
|
|
|
|
return response.JSON(http.StatusOK, result)
|
|
}
|
|
|
|
// swagger:parameters updateCurrentOrgAddress
|
|
type UpdateCurrentOrgAddressParams struct {
|
|
// in:body
|
|
// required:true
|
|
Body dtos.UpdateOrgAddressForm `json:"body"`
|
|
}
|
|
|
|
// swagger:parameters updateCurrentOrgUser
|
|
type UpdateCurrentOrgUserParams struct {
|
|
// in:body
|
|
// required:true
|
|
Body org.UpdateOrgUserCommand `json:"body"`
|
|
// in:path
|
|
// required:true
|
|
UserID int64 `json:"user_id"`
|
|
}
|
|
|
|
// swagger:parameters updateCurrentOrg
|
|
type UpdateCurrentOrgParams struct {
|
|
// in:body
|
|
// required:true
|
|
Body dtos.UpdateOrgForm `json:"body"`
|
|
}
|
|
|
|
// swagger:parameters updateOrgAddress
|
|
type UpdateOrgAddressParams struct {
|
|
// in:body
|
|
// required:true
|
|
Body dtos.UpdateOrgAddressForm `json:"body"`
|
|
// in:path
|
|
// required:true
|
|
OrgID int64 `json:"org_id"`
|
|
}
|
|
|
|
// swagger:parameters getOrgByID
|
|
type GetOrgByIDParams struct {
|
|
// in:path
|
|
// required:true
|
|
OrgID int64 `json:"org_id"`
|
|
}
|
|
|
|
// swagger:parameters deleteOrgByID
|
|
type DeleteOrgByIDParams struct {
|
|
// in:path
|
|
// required:true
|
|
OrgID int64 `json:"org_id"`
|
|
}
|
|
|
|
// swagger:parameters updateOrg
|
|
type UpdateOrgParams struct {
|
|
// in:body
|
|
// required:true
|
|
Body dtos.UpdateOrgForm `json:"body"`
|
|
// in:path
|
|
// required:true
|
|
OrgID int64 `json:"org_id"`
|
|
}
|
|
|
|
// swagger:parameters getOrgByName
|
|
type GetOrgByNameParams struct {
|
|
// in:path
|
|
// required:true
|
|
OrgName string `json:"org_name"`
|
|
}
|
|
|
|
// swagger:parameters createOrg
|
|
type CreateOrgParams struct {
|
|
// in:body
|
|
// required:true
|
|
Body org.CreateOrgCommand `json:"body"`
|
|
}
|
|
|
|
// swagger:parameters searchOrgs
|
|
type SearchOrgParams struct {
|
|
// in:query
|
|
// required:false
|
|
// default: 1
|
|
Page int `json:"page"`
|
|
// Number of items per page
|
|
// The totalCount field in the response can be used for pagination list E.g. if totalCount is equal to 100 teams and the perpage parameter is set to 10 then there are 10 pages of teams.
|
|
// in:query
|
|
// required:false
|
|
// default: 1000
|
|
PerPage int `json:"perpage"`
|
|
Name string `json:"name"`
|
|
// If set it will return results where the query value is contained in the name field. Query values with spaces need to be URL encoded.
|
|
// required:false
|
|
Query string `json:"query"`
|
|
}
|
|
|
|
// swagger:response createOrgResponse
|
|
type CreateOrgResponse struct {
|
|
// The response message
|
|
// in: body
|
|
Body struct {
|
|
// ID Identifier of the created org.
|
|
// required: true
|
|
// example: 65
|
|
OrgID int64 `json:"orgId"`
|
|
|
|
// Message Message of the created org.
|
|
// required: true
|
|
// example: Data source added
|
|
Message string `json:"message"`
|
|
} `json:"body"`
|
|
}
|
|
|
|
// swagger:response searchOrgsResponse
|
|
type SearchOrgsResponse struct {
|
|
// The response message
|
|
// in: body
|
|
Body []*org.OrgDTO `json:"body"`
|
|
}
|
|
|
|
// swagger:response getCurrentOrgResponse
|
|
type GetCurrentOrgResponse struct {
|
|
// The response message
|
|
// in: body
|
|
Body org.OrgDetailsDTO `json:"body"`
|
|
}
|
|
|
|
// swagger:response getOrgByIDResponse
|
|
type GetOrgByIDResponse struct {
|
|
// The response message
|
|
// in: body
|
|
Body org.OrgDetailsDTO `json:"body"`
|
|
}
|
|
|
|
// swagger:response getOrgByNameResponse
|
|
type GetOrgByNameResponse struct {
|
|
// The response message
|
|
// in: body
|
|
Body org.OrgDetailsDTO `json:"body"`
|
|
}
|