HH-437: harden production auth and tenant authorization (#84)
* HH-437 harden auth and account authorization * HH-437 reject revoked platform access --------- Co-authored-by: Rogee <rogee@ipao.vip>
This commit is contained in:
@@ -80,7 +80,7 @@ func (h *PlatformAccountHandler) List(c *gin.Context) {
|
||||
// Reference: Chatwoot Platform::Api::V1::AccountsController#show
|
||||
// Requires: Permissible verification
|
||||
func (h *PlatformAccountHandler) Show(c *gin.Context) {
|
||||
accountID, err := parseUintParam(c, "id")
|
||||
accountID, err := parseUintAnyParam(c, "account_id", "id")
|
||||
if err != nil {
|
||||
response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, "invalid account ID")
|
||||
return
|
||||
@@ -154,7 +154,7 @@ func (h *PlatformAccountHandler) Create(c *gin.Context) {
|
||||
// Reference: Chatwoot Platform::Api::V1::AccountsController#update
|
||||
// Requires: Permissible verification
|
||||
func (h *PlatformAccountHandler) Update(c *gin.Context) {
|
||||
accountID, err := parseUintParam(c, "id")
|
||||
accountID, err := parseUintAnyParam(c, "account_id", "id")
|
||||
if err != nil {
|
||||
response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, "invalid account ID")
|
||||
return
|
||||
@@ -212,7 +212,7 @@ func (h *PlatformAccountHandler) Update(c *gin.Context) {
|
||||
// Reference: Chatwoot Platform::Api::V1::AccountsController#destroy
|
||||
// Requires: Permissible verification
|
||||
func (h *PlatformAccountHandler) Destroy(c *gin.Context) {
|
||||
accountID, err := parseUintParam(c, "id")
|
||||
accountID, err := parseUintAnyParam(c, "account_id", "id")
|
||||
if err != nil {
|
||||
response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, "invalid account ID")
|
||||
return
|
||||
|
||||
@@ -92,6 +92,10 @@ func (h *PlatformAccountUserHandler) Create(c *gin.Context) {
|
||||
response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
if perm, err := h.permissibleRepo.FindByPlatformAppAndResource(c.Request.Context(), platformAppID, model.PermissibleTypeUser, req.UserID); err != nil || perm == nil {
|
||||
response.AbortWithStatusError(c, http.StatusForbidden, response.ErrForbidden, "non permissible resource")
|
||||
return
|
||||
}
|
||||
|
||||
acctUser, err := h.accountRepo.UpsertAccountUser(c.Request.Context(), accountID, req.UserID, req.Role)
|
||||
if err != nil {
|
||||
|
||||
@@ -93,10 +93,10 @@ func setupPlatformTokenTestE2E(t *testing.T) (*gin.Engine, *repository.Permissib
|
||||
platformGroup.DELETE("/users/:id", platformUser.Destroy)
|
||||
|
||||
platformGroup.GET("/accounts", platformAccount.List)
|
||||
platformGroup.GET("/accounts/:id", platformAccount.Show)
|
||||
platformGroup.GET("/accounts/:account_id", platformAccount.Show)
|
||||
platformGroup.POST("/accounts", platformAccount.Create)
|
||||
platformGroup.PATCH("/accounts/:id", platformAccount.Update)
|
||||
platformGroup.DELETE("/accounts/:id", platformAccount.Destroy)
|
||||
platformGroup.PATCH("/accounts/:account_id", platformAccount.Update)
|
||||
platformGroup.DELETE("/accounts/:account_id", platformAccount.Destroy)
|
||||
|
||||
platformGroup.GET("/agent_bots", platformAgentBot.List)
|
||||
platformGroup.GET("/agent_bots/:id", platformAgentBot.Show)
|
||||
@@ -105,11 +105,10 @@ func setupPlatformTokenTestE2E(t *testing.T) (*gin.Engine, *repository.Permissib
|
||||
platformGroup.DELETE("/agent_bots/:id", platformAgentBot.Destroy)
|
||||
platformGroup.POST("/agent_bots/:id/delete_avatar", platformAgentBot.DeleteAvatar)
|
||||
|
||||
// Gin wildcard constraint: nested routes under accounts/:id must reuse :id.
|
||||
platformGroup.GET("/accounts/:id/account_users", platformAccountUser.Index)
|
||||
platformGroup.POST("/accounts/:id/account_users", platformAccountUser.Create)
|
||||
platformGroup.DELETE("/accounts/:id/account_users/destroy", platformAccountUser.Destroy)
|
||||
platformGroup.DELETE("/accounts/:id/account_users/:user_id", platformAccountUser.Destroy)
|
||||
platformGroup.GET("/accounts/:account_id/account_users", platformAccountUser.Index)
|
||||
platformGroup.POST("/accounts/:account_id/account_users", platformAccountUser.Create)
|
||||
platformGroup.DELETE("/accounts/:account_id/account_users/destroy", platformAccountUser.Destroy)
|
||||
platformGroup.DELETE("/accounts/:account_id/account_users/:user_id", platformAccountUser.Destroy)
|
||||
|
||||
return engine, permissibleRepo, userRepo, accountRepo
|
||||
}
|
||||
@@ -334,6 +333,25 @@ func TestPlatformAccountE2E_Show(t *testing.T) {
|
||||
assert.Equal(t, "Show Account", showData["name"])
|
||||
}
|
||||
|
||||
func TestPlatformAccountE2E_Update(t *testing.T) {
|
||||
engine, _, _, _ := setupPlatformTokenTestE2E(t)
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
req := httptest.NewRequest(http.MethodPost, "/platform/api/v1/accounts", bytes.NewBufferString(`{"name":"Original Account"}`))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
engine.ServeHTTP(w, req)
|
||||
require.Equal(t, http.StatusCreated, w.Code)
|
||||
accountID := parseID(t, unpackData(t, w.Body.Bytes()))
|
||||
|
||||
w = httptest.NewRecorder()
|
||||
req = httptest.NewRequest(http.MethodPatch, "/platform/api/v1/accounts/"+accountID, bytes.NewBufferString(`{"name":"Updated Account"}`))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
engine.ServeHTTP(w, req)
|
||||
|
||||
require.Equal(t, http.StatusOK, w.Code)
|
||||
assert.Equal(t, "Updated Account", unpackData(t, w.Body.Bytes())["name"])
|
||||
}
|
||||
|
||||
func TestPlatformAccountE2E_Destroy(t *testing.T) {
|
||||
engine, _, _, _ := setupPlatformTokenTestE2E(t)
|
||||
|
||||
@@ -465,6 +483,27 @@ func TestPlatformAccountUserE2E_Create(t *testing.T) {
|
||||
assert.Equal(t, "administrator", accountUser["role"])
|
||||
}
|
||||
|
||||
func TestPlatformAccountUserE2E_CreateRejectsNonPermissibleUser(t *testing.T) {
|
||||
engine, _, userRepo, _ := setupPlatformTokenTestE2E(t)
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
req := httptest.NewRequest(http.MethodPost, "/platform/api/v1/accounts", bytes.NewBufferString(`{"name":"Permissible Account"}`))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
engine.ServeHTTP(w, req)
|
||||
require.Equal(t, http.StatusCreated, w.Code)
|
||||
accountID := parseID(t, unpackData(t, w.Body.Bytes()))
|
||||
|
||||
user := &model.User{Name: "Non-permissible User", Email: "non-permissible@example.com", Provider: "email", Active: true}
|
||||
require.NoError(t, userRepo.Create(t.Context(), user))
|
||||
|
||||
w = httptest.NewRecorder()
|
||||
req = httptest.NewRequest(http.MethodPost, "/platform/api/v1/accounts/"+accountID+"/account_users", bytes.NewBufferString(fmt.Sprintf(`{"user_id":%d}`, user.ID)))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
engine.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(t, http.StatusForbidden, w.Code)
|
||||
}
|
||||
|
||||
func TestPlatformAccountUserE2E_Index(t *testing.T) {
|
||||
engine, _, _, _ := setupPlatformTokenTestE2E(t)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user