Files
portainer/api/http/handler/customtemplates/customtemplate_delete_test.go
2026-06-15 14:59:07 -03:00

297 lines
8.8 KiB
Go

package customtemplates
import (
"net/http"
"net/http/httptest"
"testing"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/dataservices"
"github.com/portainer/portainer/api/http/security"
"github.com/gorilla/mux"
"github.com/stretchr/testify/require"
)
func TestCustomTemplateDelete_NotFound(t *testing.T) {
t.Parallel()
handler, _, _ := newTestHandler(t)
r := httptest.NewRequest(http.MethodDelete, "/custom_templates/99", nil)
r = mux.SetURLVars(r, map[string]string{"id": "99"})
r = r.WithContext(security.StoreRestrictedRequestContext(r, &security.RestrictedRequestContext{UserID: 1, IsAdmin: true}))
rr := httptest.NewRecorder()
herr := handler.customTemplateDelete(rr, r)
require.NotNil(t, herr)
require.Equal(t, http.StatusNotFound, herr.StatusCode)
}
func TestCustomTemplateDelete_Forbidden(t *testing.T) {
t.Parallel()
handler, store, _ := newTestHandler(t)
err := store.UpdateTx(func(tx dataservices.DataStoreTx) error {
err := tx.CustomTemplate().Create(&portainer.CustomTemplate{
ID: 1,
CreatedByUserID: 1,
})
require.NoError(t, err)
return nil
})
require.NoError(t, err)
// User 2 did not create this template and is not an admin
r := httptest.NewRequest(http.MethodDelete, "/custom_templates/1", nil)
r = mux.SetURLVars(r, map[string]string{"id": "1"})
r = r.WithContext(security.StoreRestrictedRequestContext(r, &security.RestrictedRequestContext{UserID: 2}))
rr := httptest.NewRecorder()
herr := handler.customTemplateDelete(rr, r)
require.NotNil(t, herr)
require.Equal(t, http.StatusForbidden, herr.StatusCode)
}
func TestCustomTemplateDelete_CreatorDeniedWhenAdminOnly(t *testing.T) {
t.Parallel()
handler, store, _ := newTestHandler(t)
err := store.UpdateTx(func(tx dataservices.DataStoreTx) error {
err := tx.CustomTemplate().Create(&portainer.CustomTemplate{
ID: 1,
CreatedByUserID: 2,
})
require.NoError(t, err)
err = tx.ResourceControl().Create(&portainer.ResourceControl{
ID: 1,
ResourceID: "1",
Type: portainer.CustomTemplateResourceControl,
AdministratorsOnly: true,
})
require.NoError(t, err)
return nil
})
require.NoError(t, err)
// User 2 created the template but an admin later changed it to admins-only
r := httptest.NewRequest(http.MethodDelete, "/custom_templates/1", nil)
r = mux.SetURLVars(r, map[string]string{"id": "1"})
r = r.WithContext(security.StoreRestrictedRequestContext(r, &security.RestrictedRequestContext{UserID: 2}))
rr := httptest.NewRecorder()
herr := handler.customTemplateDelete(rr, r)
require.NotNil(t, herr)
require.Equal(t, http.StatusForbidden, herr.StatusCode)
}
func TestCustomTemplateDelete_CreatorDeniedWithoutResourceControl(t *testing.T) {
t.Parallel()
handler, store, _ := newTestHandler(t)
err := store.UpdateTx(func(tx dataservices.DataStoreTx) error {
return tx.CustomTemplate().Create(&portainer.CustomTemplate{
ID: 1,
CreatedByUserID: 2,
})
})
require.NoError(t, err)
// User 2 created this template but there is no resource control
r := httptest.NewRequest(http.MethodDelete, "/custom_templates/1", nil)
r = mux.SetURLVars(r, map[string]string{"id": "1"})
r = r.WithContext(security.StoreRestrictedRequestContext(r, &security.RestrictedRequestContext{UserID: 2}))
rr := httptest.NewRecorder()
herr := handler.customTemplateDelete(rr, r)
require.NotNil(t, herr)
require.Equal(t, http.StatusForbidden, herr.StatusCode)
}
func TestCustomTemplateDelete_Success(t *testing.T) {
t.Parallel()
handler, store, _ := newTestHandler(t)
err := store.UpdateTx(func(tx dataservices.DataStoreTx) error {
err := tx.CustomTemplate().Create(&portainer.CustomTemplate{
ID: 1,
CreatedByUserID: 2,
})
require.NoError(t, err)
err = tx.ResourceControl().Create(&portainer.ResourceControl{
ID: 1,
ResourceID: "1",
Type: portainer.CustomTemplateResourceControl,
UserAccesses: []portainer.UserResourceAccess{{UserID: 2}},
})
require.NoError(t, err)
return nil
})
require.NoError(t, err)
r := httptest.NewRequest(http.MethodDelete, "/custom_templates/1", nil)
r = mux.SetURLVars(r, map[string]string{"id": "1"})
r = r.WithContext(security.StoreRestrictedRequestContext(r, &security.RestrictedRequestContext{UserID: 2}))
rr := httptest.NewRecorder()
herr := handler.customTemplateDelete(rr, r)
require.Nil(t, herr)
require.Equal(t, http.StatusNoContent, rr.Code)
err = store.ViewTx(func(tx dataservices.DataStoreTx) error {
_, err := tx.CustomTemplate().Read(1)
require.True(t, tx.IsErrObjectNotFound(err))
return nil
})
require.NoError(t, err)
}
func TestCustomTemplateDelete_AdminCanDeleteAdminOnly(t *testing.T) {
t.Parallel()
handler, store, _ := newTestHandler(t)
err := store.UpdateTx(func(tx dataservices.DataStoreTx) error {
err := tx.CustomTemplate().Create(&portainer.CustomTemplate{
ID: 1,
CreatedByUserID: 2,
})
require.NoError(t, err)
err = tx.ResourceControl().Create(&portainer.ResourceControl{
ID: 1,
ResourceID: "1",
Type: portainer.CustomTemplateResourceControl,
AdministratorsOnly: true,
})
require.NoError(t, err)
return nil
})
require.NoError(t, err)
r := httptest.NewRequest(http.MethodDelete, "/custom_templates/1", nil)
r = mux.SetURLVars(r, map[string]string{"id": "1"})
r = r.WithContext(security.StoreRestrictedRequestContext(r, &security.RestrictedRequestContext{UserID: 1, IsAdmin: true}))
rr := httptest.NewRecorder()
herr := handler.customTemplateDelete(rr, r)
require.Nil(t, herr)
require.Equal(t, http.StatusNoContent, rr.Code)
}
func TestCustomTemplateDelete_PublicTemplateAllowsAnyUser(t *testing.T) {
t.Parallel()
handler, store, _ := newTestHandler(t)
err := store.UpdateTx(func(tx dataservices.DataStoreTx) error {
err := tx.CustomTemplate().Create(&portainer.CustomTemplate{
ID: 1,
CreatedByUserID: 1,
})
require.NoError(t, err)
err = tx.ResourceControl().Create(&portainer.ResourceControl{
ID: 1,
ResourceID: "1",
Type: portainer.CustomTemplateResourceControl,
Public: true,
})
require.NoError(t, err)
return nil
})
require.NoError(t, err)
// User 2 is not the creator but the template is public
r := httptest.NewRequest(http.MethodDelete, "/custom_templates/1", nil)
r = mux.SetURLVars(r, map[string]string{"id": "1"})
r = r.WithContext(security.StoreRestrictedRequestContext(r, &security.RestrictedRequestContext{UserID: 2}))
rr := httptest.NewRecorder()
herr := handler.customTemplateDelete(rr, r)
require.Nil(t, herr)
require.Equal(t, http.StatusNoContent, rr.Code)
}
func TestCustomTemplateDelete_NonCreatorForbiddenWithPrivateRC(t *testing.T) {
t.Parallel()
handler, store, _ := newTestHandler(t)
err := store.UpdateTx(func(tx dataservices.DataStoreTx) error {
err := tx.CustomTemplate().Create(&portainer.CustomTemplate{
ID: 1,
CreatedByUserID: 1,
})
require.NoError(t, err)
err = tx.ResourceControl().Create(&portainer.ResourceControl{
ID: 1,
ResourceID: "1",
Type: portainer.CustomTemplateResourceControl,
UserAccesses: []portainer.UserResourceAccess{{UserID: 1}},
})
require.NoError(t, err)
return nil
})
require.NoError(t, err)
// User 2 is not the creator and the template has a private resource control
r := httptest.NewRequest(http.MethodDelete, "/custom_templates/1", nil)
r = mux.SetURLVars(r, map[string]string{"id": "1"})
r = r.WithContext(security.StoreRestrictedRequestContext(r, &security.RestrictedRequestContext{UserID: 2}))
rr := httptest.NewRecorder()
herr := handler.customTemplateDelete(rr, r)
require.NotNil(t, herr)
require.Equal(t, http.StatusForbidden, herr.StatusCode)
}
func TestCustomTemplateDelete_CreatorDeniedWithoutAccess(t *testing.T) {
t.Parallel()
handler, store, _ := newTestHandler(t)
err := store.UpdateTx(func(tx dataservices.DataStoreTx) error {
err := tx.CustomTemplate().Create(&portainer.CustomTemplate{
ID: 1,
CreatedByUserID: 2,
})
require.NoError(t, err)
// RC exists but only grants access to user 3, not the creator (user 2)
err = tx.ResourceControl().Create(&portainer.ResourceControl{
ID: 1,
ResourceID: "1",
Type: portainer.CustomTemplateResourceControl,
UserAccesses: []portainer.UserResourceAccess{{UserID: 3}},
})
require.NoError(t, err)
return nil
})
require.NoError(t, err)
r := httptest.NewRequest(http.MethodDelete, "/custom_templates/1", nil)
r = mux.SetURLVars(r, map[string]string{"id": "1"})
r = r.WithContext(security.StoreRestrictedRequestContext(r, &security.RestrictedRequestContext{UserID: 2}))
rr := httptest.NewRecorder()
herr := handler.customTemplateDelete(rr, r)
require.NotNil(t, herr)
require.Equal(t, http.StatusForbidden, herr.StatusCode)
}