This is an automated email from the ASF dual-hosted git repository.
klesh pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/incubator-devlake.git
The following commit(s) were added to refs/heads/main by this push:
new 9b7394390 feat(webhook): plugin webhook's detail and list api can get
api key info now (#6050)
9b7394390 is described below
commit 9b73943908a5dcc62c82841d9a5a09c636d9bad2
Author: Lynwee <[email protected]>
AuthorDate: Mon Sep 11 18:24:05 2023 +0800
feat(webhook): plugin webhook's detail and list api can get api key info
now (#6050)
* feat(webhook): plugin webhook's detail and list api can get api key info
now
* feat(apikeys): remove hashed api key field from api key details
* style(apikeys): format apikeys.go with `-s` option
---------
Co-authored-by: Klesh Wong <[email protected]>
---
backend/core/models/api_key.go | 8 +++-
backend/helpers/apikeyhelper/apikeyhelper.go | 4 ++
backend/plugins/webhook/api/connection.go | 63 ++++++++++++++++++++--------
backend/plugins/webhook/api/init.go | 6 +--
backend/plugins/webhook/models/connection.go | 6 ---
backend/server/services/apikeys.go | 3 ++
6 files changed, 59 insertions(+), 31 deletions(-)
diff --git a/backend/core/models/api_key.go b/backend/core/models/api_key.go
index 4ef12c5ed..6484e788b 100644
--- a/backend/core/models/api_key.go
+++ b/backend/core/models/api_key.go
@@ -28,17 +28,21 @@ type ApiKey struct {
common.Creator
common.Updater
Name string `json:"name"`
- ApiKey string `json:"apiKey"`
+ ApiKey string `json:"apiKey,omitempty"`
ExpiredAt *time.Time `json:"expiredAt"`
AllowedPath string `json:"allowedPath"`
Type string `json:"type"`
Extra string `json:"extra"`
}
-func (ApiKey) TableName() string {
+func (apiKey *ApiKey) TableName() string {
return "_devlake_api_keys"
}
+func (apiKey *ApiKey) RemoveHashedApiKey() {
+ apiKey.ApiKey = ""
+}
+
type ApiInputApiKey struct {
Name string `json:"name" validate:"required,max=255"`
Type string `json:"type" validate:"required"`
diff --git a/backend/helpers/apikeyhelper/apikeyhelper.go
b/backend/helpers/apikeyhelper/apikeyhelper.go
index 81968796d..6d2678dd5 100644
--- a/backend/helpers/apikeyhelper/apikeyhelper.go
+++ b/backend/helpers/apikeyhelper/apikeyhelper.go
@@ -205,6 +205,10 @@ func (c *ApiKeyHelper) GetApiKey(tx dal.Dal,
additionalClauses ...dal.Clause) (*
return apiKey, err
}
+func (c *ApiKeyHelper) GenApiKeyNameForPlugin(pluginName string, connectionId
uint64) string {
+ return fmt.Sprintf("%s-%d", pluginName, connectionId)
+}
+
func (c *ApiKeyHelper) generateApiKey() (apiKey string, hashedApiKey string,
err errors.Error) {
apiKey, randomLetterErr := utils.RandLetterBytes(apiKeyLen)
if randomLetterErr != nil {
diff --git a/backend/plugins/webhook/api/connection.go
b/backend/plugins/webhook/api/connection.go
index bbf192b0c..d86854856 100644
--- a/backend/plugins/webhook/api/connection.go
+++ b/backend/plugins/webhook/api/connection.go
@@ -21,6 +21,7 @@ import (
"fmt"
"github.com/apache/incubator-devlake/core/dal"
"github.com/apache/incubator-devlake/core/errors"
+ coreModels "github.com/apache/incubator-devlake/core/models"
"github.com/apache/incubator-devlake/core/plugin"
"github.com/apache/incubator-devlake/plugins/webhook/models"
"net/http"
@@ -32,7 +33,7 @@ import (
// @Description Create webhook connection, example: {"name":"Webhook data
connection name"}
// @Tags plugins/webhook
// @Param body body models.WebhookConnection true "json body"
-// @Success 200 {object} models.WebhookConnection
+// @Success 200 {object} WebhookConnectionResponse
// @Failure 400 {string} errcode.Error "Bad Request"
// @Failure 500 {string} errcode.Error "Internal Error"
// @Router /plugins/webhook/connections [POST]
@@ -45,7 +46,7 @@ func PostConnections(input *plugin.ApiResourceInput)
(*plugin.ApiResourceOutput,
return nil, err
}
logger.Info("connection: %+v", connection)
- name := fmt.Sprintf("%s-%d", pluginName, connection.ID)
+ name := apiKeyHelper.GenApiKeyNameForPlugin(pluginName, connection.ID)
allowedPath := fmt.Sprintf("/plugins/%s/connections/%d/.*", pluginName,
connection.ID)
extra := fmt.Sprintf("connectionId:%d", connection.ID)
apiKeyRecord, err := apiKeyHelper.CreateForPlugin(tx, input.User, name,
pluginName, allowedPath, extra)
@@ -60,13 +61,14 @@ func PostConnections(input *plugin.ApiResourceInput)
(*plugin.ApiResourceOutput,
logger.Info("transaction commit: %s", err)
}
- apiOutputConnection := models.ApiOutputWebhookConnection{
- WebhookConnection: *connection,
- ApiKey: apiKeyRecord,
+ webhookConnectionResponse, err := formatConnection(connection, false)
+ if err != nil {
+ return nil, err
}
- logger.Info("api output connection: %+v", apiOutputConnection)
+ webhookConnectionResponse.ApiKey = apiKeyRecord
+ logger.Info("api output connection: %+v", webhookConnectionResponse)
- return &plugin.ApiResourceOutput{Body: apiOutputConnection, Status:
http.StatusOK}, nil
+ return &plugin.ApiResourceOutput{Body: webhookConnectionResponse,
Status: http.StatusOK}, nil
}
// PatchConnection
@@ -129,18 +131,19 @@ func DeleteConnection(input *plugin.ApiResourceInput)
(*plugin.ApiResourceOutput
type WebhookConnectionResponse struct {
models.WebhookConnection
- PostIssuesEndpoint string `json:"postIssuesEndpoint"`
- CloseIssuesEndpoint string `json:"closeIssuesEndpoint"`
- PostPipelineTaskEndpoint string `json:"postPipelineTaskEndpoint"`
- PostPipelineDeployTaskEndpoint string
`json:"postPipelineDeployTaskEndpoint"`
- ClosePipelineEndpoint string `json:"closePipelineEndpoint"`
+ PostIssuesEndpoint string
`json:"postIssuesEndpoint"`
+ CloseIssuesEndpoint string
`json:"closeIssuesEndpoint"`
+ PostPipelineTaskEndpoint string
`json:"postPipelineTaskEndpoint"`
+ PostPipelineDeployTaskEndpoint string
`json:"postPipelineDeployTaskEndpoint"`
+ ClosePipelineEndpoint string
`json:"closePipelineEndpoint"`
+ ApiKey *coreModels.ApiKey
`json:"apiKey,omitempty"`
}
// ListConnections
// @Summary get all webhook connections
// @Description Get all webhook connections
// @Tags plugins/webhook
-// @Success 200 {object} []WebhookConnectionResponse
+// @Success 200 {object} []*WebhookConnectionResponse
// @Failure 400 {string} errcode.Error "Bad Request"
// @Failure 500 {string} errcode.Error "Internal Error"
// @Router /plugins/webhook/connections [GET]
@@ -150,9 +153,13 @@ func ListConnections(input *plugin.ApiResourceInput)
(*plugin.ApiResourceOutput,
if err != nil {
return nil, err
}
- responseList := []WebhookConnectionResponse{}
+ var responseList []*WebhookConnectionResponse
for _, connection := range connections {
- responseList = append(responseList,
*formatConnection(&connection))
+ webhookConnectionResponse, err := formatConnection(&connection,
true)
+ if err != nil {
+ return nil, err
+ }
+ responseList = append(responseList, webhookConnectionResponse)
}
return &plugin.ApiResourceOutput{Body: responseList, Status:
http.StatusOK}, nil
}
@@ -168,16 +175,36 @@ func ListConnections(input *plugin.ApiResourceInput)
(*plugin.ApiResourceOutput,
func GetConnection(input *plugin.ApiResourceInput) (*plugin.ApiResourceOutput,
errors.Error) {
connection := &models.WebhookConnection{}
err := connectionHelper.First(connection, input.Params)
- response := formatConnection(connection)
+ if err != nil {
+ logger.Error(err, "query connection")
+ return nil, err
+ }
+ response, err := formatConnection(connection, true)
return &plugin.ApiResourceOutput{Body: response}, err
}
-func formatConnection(connection *models.WebhookConnection)
*WebhookConnectionResponse {
+func formatConnection(connection *models.WebhookConnection, withApiKeyInfo
bool) (*WebhookConnectionResponse, errors.Error) {
response := &WebhookConnectionResponse{WebhookConnection: *connection}
response.PostIssuesEndpoint =
fmt.Sprintf(`/plugins/webhook/connections/%d/issues`, connection.ID)
response.CloseIssuesEndpoint =
fmt.Sprintf(`/plugins/webhook/connections/%d/issue/:issueKey/close`,
connection.ID)
response.PostPipelineTaskEndpoint =
fmt.Sprintf(`/plugins/webhook/connections/%d/cicd_tasks`, connection.ID)
response.PostPipelineDeployTaskEndpoint =
fmt.Sprintf(`/plugins/webhook/connections/%d/deployments`, connection.ID)
response.ClosePipelineEndpoint =
fmt.Sprintf(`/plugins/webhook/connections/%d/cicd_pipeline/:pipelineName/finish`,
connection.ID)
- return response
+ if withApiKeyInfo {
+ db := basicRes.GetDal()
+ apiKeyName := apiKeyHelper.GenApiKeyNameForPlugin(pluginName,
connection.ID)
+ apiKey, err := apiKeyHelper.GetApiKey(db, dal.Where("name = ?",
apiKeyName))
+ if err != nil {
+ if db.IsErrorNotFound(err) {
+ logger.Info("api key with name: %s not found in
db", apiKeyName)
+ } else {
+ logger.Error(err, "query api key from db, name:
%s", apiKeyName)
+ return nil, err
+ }
+ } else {
+ response.ApiKey = apiKey
+ response.ApiKey.RemoveHashedApiKey() // delete the
hashed api key to reduce the attack surface.
+ }
+ }
+ return response, nil
}
diff --git a/backend/plugins/webhook/api/init.go
b/backend/plugins/webhook/api/init.go
index c2304e402..848dd888c 100644
--- a/backend/plugins/webhook/api/init.go
+++ b/backend/plugins/webhook/api/init.go
@@ -38,10 +38,6 @@ func Init(br context.BasicRes, p plugin.PluginMeta) {
basicRes = br
logger = basicRes.GetLogger()
vld = validator.New()
- connectionHelper = api.NewConnectionHelper(
- basicRes,
- vld,
- p.Name(),
- )
+ connectionHelper = api.NewConnectionHelper(basicRes, vld, p.Name())
apiKeyHelper = apikeyhelper.NewApiKeyHelper(basicRes, logger)
}
diff --git a/backend/plugins/webhook/models/connection.go
b/backend/plugins/webhook/models/connection.go
index 53185a8cf..c9d56b9f6 100644
--- a/backend/plugins/webhook/models/connection.go
+++ b/backend/plugins/webhook/models/connection.go
@@ -18,15 +18,9 @@ limitations under the License.
package models
import (
- "github.com/apache/incubator-devlake/core/models"
helper "github.com/apache/incubator-devlake/helpers/pluginhelper/api"
)
-type ApiOutputWebhookConnection struct {
- WebhookConnection `mapstructure:",squash"`
- ApiKey *models.ApiKey `json:"apiKey,omitempty"`
-}
-
type WebhookConnection struct {
helper.BaseConnection `mapstructure:",squash"`
}
diff --git a/backend/server/services/apikeys.go
b/backend/server/services/apikeys.go
index e45003f32..f1a499057 100644
--- a/backend/server/services/apikeys.go
+++ b/backend/server/services/apikeys.go
@@ -57,6 +57,9 @@ func GetApiKeys(query *ApiKeysQuery) ([]*models.ApiKey,
int64, errors.Error) {
if err != nil {
return nil, 0, errors.Default.Wrap(err, "error finding DB api
key")
}
+ for idx := range apiKeys {
+ apiKeys[idx].RemoveHashedApiKey() // delete the hashed api key
to reduce the attack surface.
+ }
return apiKeys, count, nil
}