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
 }

Reply via email to