mirror of
https://github.com/portainer/portainer.git
synced 2025-08-02 20:35:25 +02:00
feat(stacks): support compose v2.0 stack (#1963)
This commit is contained in:
parent
ef15cd30eb
commit
e3d564325b
174 changed files with 7898 additions and 5849 deletions
35
api/http/handler/teammemberships/handler.go
Normal file
35
api/http/handler/teammemberships/handler.go
Normal file
|
@ -0,0 +1,35 @@
|
|||
package teammemberships
|
||||
|
||||
import (
|
||||
"github.com/portainer/portainer"
|
||||
httperror "github.com/portainer/portainer/http/error"
|
||||
"github.com/portainer/portainer/http/security"
|
||||
|
||||
"net/http"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
|
||||
// Handler is the HTTP handler used to handle team membership operations.
|
||||
type Handler struct {
|
||||
*mux.Router
|
||||
TeamMembershipService portainer.TeamMembershipService
|
||||
ResourceControlService portainer.ResourceControlService
|
||||
}
|
||||
|
||||
// NewHandler creates a handler to manage team membership operations.
|
||||
func NewHandler(bouncer *security.RequestBouncer) *Handler {
|
||||
h := &Handler{
|
||||
Router: mux.NewRouter(),
|
||||
}
|
||||
h.Handle("/team_memberships",
|
||||
bouncer.RestrictedAccess(httperror.LoggerHandler(h.teamMembershipCreate))).Methods(http.MethodPost)
|
||||
h.Handle("/team_memberships",
|
||||
bouncer.RestrictedAccess(httperror.LoggerHandler(h.teamMembershipList))).Methods(http.MethodGet)
|
||||
h.Handle("/team_memberships/{id}",
|
||||
bouncer.RestrictedAccess(httperror.LoggerHandler(h.teamMembershipUpdate))).Methods(http.MethodPut)
|
||||
h.Handle("/team_memberships/{id}",
|
||||
bouncer.RestrictedAccess(httperror.LoggerHandler(h.teamMembershipDelete))).Methods(http.MethodDelete)
|
||||
|
||||
return h
|
||||
}
|
74
api/http/handler/teammemberships/teammembership_create.go
Normal file
74
api/http/handler/teammemberships/teammembership_create.go
Normal file
|
@ -0,0 +1,74 @@
|
|||
package teammemberships
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/portainer/portainer"
|
||||
httperror "github.com/portainer/portainer/http/error"
|
||||
"github.com/portainer/portainer/http/request"
|
||||
"github.com/portainer/portainer/http/response"
|
||||
"github.com/portainer/portainer/http/security"
|
||||
)
|
||||
|
||||
type teamMembershipCreatePayload struct {
|
||||
UserID int
|
||||
TeamID int
|
||||
Role int
|
||||
}
|
||||
|
||||
func (payload *teamMembershipCreatePayload) Validate(r *http.Request) error {
|
||||
if payload.UserID == 0 {
|
||||
return portainer.Error("Invalid UserID")
|
||||
}
|
||||
if payload.TeamID == 0 {
|
||||
return portainer.Error("Invalid TeamID")
|
||||
}
|
||||
if payload.Role != 1 && payload.Role != 2 {
|
||||
return portainer.Error("Invalid role value. Value must be one of: 1 (leader) or 2 (member)")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// POST request on /api/team_memberships
|
||||
func (handler *Handler) teamMembershipCreate(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
|
||||
var payload teamMembershipCreatePayload
|
||||
err := request.DecodeAndValidateJSONPayload(r, &payload)
|
||||
if err != nil {
|
||||
return &httperror.HandlerError{http.StatusBadRequest, "Invalid request payload", err}
|
||||
}
|
||||
|
||||
securityContext, err := security.RetrieveRestrictedRequestContext(r)
|
||||
if err != nil {
|
||||
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve info from request context", err}
|
||||
}
|
||||
|
||||
if !security.AuthorizedTeamManagement(portainer.TeamID(payload.TeamID), securityContext) {
|
||||
return &httperror.HandlerError{http.StatusForbidden, "Permission denied to manage team memberships", portainer.ErrResourceAccessDenied}
|
||||
}
|
||||
|
||||
memberships, err := handler.TeamMembershipService.TeamMembershipsByUserID(portainer.UserID(payload.UserID))
|
||||
if err != nil {
|
||||
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve team memberships from the database", err}
|
||||
}
|
||||
|
||||
if len(memberships) > 0 {
|
||||
for _, membership := range memberships {
|
||||
if membership.UserID == portainer.UserID(payload.UserID) && membership.TeamID == portainer.TeamID(payload.TeamID) {
|
||||
return &httperror.HandlerError{http.StatusConflict, "Team membership already registered", portainer.ErrTeamMembershipAlreadyExists}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
membership := &portainer.TeamMembership{
|
||||
UserID: portainer.UserID(payload.UserID),
|
||||
TeamID: portainer.TeamID(payload.TeamID),
|
||||
Role: portainer.MembershipRole(payload.Role),
|
||||
}
|
||||
|
||||
err = handler.TeamMembershipService.CreateTeamMembership(membership)
|
||||
if err != nil {
|
||||
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to persist team memberships inside the database", err}
|
||||
}
|
||||
|
||||
return response.JSON(w, membership)
|
||||
}
|
42
api/http/handler/teammemberships/teammembership_delete.go
Normal file
42
api/http/handler/teammemberships/teammembership_delete.go
Normal file
|
@ -0,0 +1,42 @@
|
|||
package teammemberships
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/portainer/portainer"
|
||||
httperror "github.com/portainer/portainer/http/error"
|
||||
"github.com/portainer/portainer/http/request"
|
||||
"github.com/portainer/portainer/http/response"
|
||||
"github.com/portainer/portainer/http/security"
|
||||
)
|
||||
|
||||
// DELETE request on /api/team_memberships/:id
|
||||
func (handler *Handler) teamMembershipDelete(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
|
||||
membershipID, err := request.RetrieveNumericRouteVariableValue(r, "id")
|
||||
if err != nil {
|
||||
return &httperror.HandlerError{http.StatusBadRequest, "Invalid membership identifier route variable", err}
|
||||
}
|
||||
|
||||
membership, err := handler.TeamMembershipService.TeamMembership(portainer.TeamMembershipID(membershipID))
|
||||
if err == portainer.ErrTeamMembershipNotFound {
|
||||
return &httperror.HandlerError{http.StatusNotFound, "Unable to find a team membership with the specified identifier inside the database", err}
|
||||
} else if err != nil {
|
||||
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to find a team membership with the specified identifier inside the database", err}
|
||||
}
|
||||
|
||||
securityContext, err := security.RetrieveRestrictedRequestContext(r)
|
||||
if err != nil {
|
||||
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve info from request context", err}
|
||||
}
|
||||
|
||||
if !security.AuthorizedTeamManagement(membership.TeamID, securityContext) {
|
||||
return &httperror.HandlerError{http.StatusForbidden, "Permission denied to delete the membership", portainer.ErrResourceAccessDenied}
|
||||
}
|
||||
|
||||
err = handler.TeamMembershipService.DeleteTeamMembership(portainer.TeamMembershipID(membershipID))
|
||||
if err != nil {
|
||||
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to remove the team membership from the database", err}
|
||||
}
|
||||
|
||||
return response.Empty(w)
|
||||
}
|
29
api/http/handler/teammemberships/teammembership_list.go
Normal file
29
api/http/handler/teammemberships/teammembership_list.go
Normal file
|
@ -0,0 +1,29 @@
|
|||
package teammemberships
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/portainer/portainer"
|
||||
httperror "github.com/portainer/portainer/http/error"
|
||||
"github.com/portainer/portainer/http/response"
|
||||
"github.com/portainer/portainer/http/security"
|
||||
)
|
||||
|
||||
// GET request on /api/team_memberships
|
||||
func (handler *Handler) teamMembershipList(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
|
||||
securityContext, err := security.RetrieveRestrictedRequestContext(r)
|
||||
if err != nil {
|
||||
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve info from request context", err}
|
||||
}
|
||||
|
||||
if !securityContext.IsAdmin && !securityContext.IsTeamLeader {
|
||||
return &httperror.HandlerError{http.StatusForbidden, "Permission denied to list team memberships", portainer.ErrResourceAccessDenied}
|
||||
}
|
||||
|
||||
memberships, err := handler.TeamMembershipService.TeamMemberships()
|
||||
if err != nil {
|
||||
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve team memberships from the database", err}
|
||||
}
|
||||
|
||||
return response.JSON(w, memberships)
|
||||
}
|
75
api/http/handler/teammemberships/teammembership_update.go
Normal file
75
api/http/handler/teammemberships/teammembership_update.go
Normal file
|
@ -0,0 +1,75 @@
|
|||
package teammemberships
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/portainer/portainer"
|
||||
httperror "github.com/portainer/portainer/http/error"
|
||||
"github.com/portainer/portainer/http/request"
|
||||
"github.com/portainer/portainer/http/response"
|
||||
"github.com/portainer/portainer/http/security"
|
||||
)
|
||||
|
||||
type teamMembershipUpdatePayload struct {
|
||||
UserID int
|
||||
TeamID int
|
||||
Role int
|
||||
}
|
||||
|
||||
func (payload *teamMembershipUpdatePayload) Validate(r *http.Request) error {
|
||||
if payload.UserID == 0 {
|
||||
return portainer.Error("Invalid UserID")
|
||||
}
|
||||
if payload.TeamID == 0 {
|
||||
return portainer.Error("Invalid TeamID")
|
||||
}
|
||||
if payload.Role != 1 && payload.Role != 2 {
|
||||
return portainer.Error("Invalid role value. Value must be one of: 1 (leader) or 2 (member)")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// PUT request on /api/team_memberships/:id
|
||||
func (handler *Handler) teamMembershipUpdate(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
|
||||
membershipID, err := request.RetrieveNumericRouteVariableValue(r, "id")
|
||||
if err != nil {
|
||||
return &httperror.HandlerError{http.StatusBadRequest, "Invalid membership identifier route variable", err}
|
||||
}
|
||||
|
||||
var payload teamMembershipUpdatePayload
|
||||
err = request.DecodeAndValidateJSONPayload(r, &payload)
|
||||
if err != nil {
|
||||
return &httperror.HandlerError{http.StatusBadRequest, "Invalid request payload", err}
|
||||
}
|
||||
|
||||
securityContext, err := security.RetrieveRestrictedRequestContext(r)
|
||||
if err != nil {
|
||||
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve info from request context", err}
|
||||
}
|
||||
|
||||
if !security.AuthorizedTeamManagement(portainer.TeamID(payload.TeamID), securityContext) {
|
||||
return &httperror.HandlerError{http.StatusForbidden, "Permission denied to update the membership", portainer.ErrResourceAccessDenied}
|
||||
}
|
||||
|
||||
membership, err := handler.TeamMembershipService.TeamMembership(portainer.TeamMembershipID(membershipID))
|
||||
if err == portainer.ErrTeamMembershipNotFound {
|
||||
return &httperror.HandlerError{http.StatusNotFound, "Unable to find a team membership with the specified identifier inside the database", err}
|
||||
} else if err != nil {
|
||||
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to find a team membership with the specified identifier inside the database", err}
|
||||
}
|
||||
|
||||
if securityContext.IsTeamLeader && membership.Role != portainer.MembershipRole(payload.Role) {
|
||||
return &httperror.HandlerError{http.StatusForbidden, "Permission denied to update the role of membership", portainer.ErrResourceAccessDenied}
|
||||
}
|
||||
|
||||
membership.UserID = portainer.UserID(payload.UserID)
|
||||
membership.TeamID = portainer.TeamID(payload.TeamID)
|
||||
membership.Role = portainer.MembershipRole(payload.Role)
|
||||
|
||||
err = handler.TeamMembershipService.UpdateTeamMembership(membership.ID, membership)
|
||||
if err != nil {
|
||||
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to persist membership changes inside the database", err}
|
||||
}
|
||||
|
||||
return response.JSON(w, membership)
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue