You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@devlake.apache.org by kl...@apache.org on 2023/11/16 03:54:13 UTC

(incubator-devlake) branch main updated: fix(webhook): add migration script when webhooks have no API keys (#6472)

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 b6d8d8a59 fix(webhook): add migration script when webhooks have no API keys (#6472)
b6d8d8a59 is described below

commit b6d8d8a59f098ebceed9e15e098db419e418166f
Author: Lynwee <li...@merico.dev>
AuthorDate: Thu Nov 16 11:54:09 2023 +0800

    fix(webhook): add migration script when webhooks have no API keys (#6472)
    
    * fix(webhook): add migration script when webhooks have no API keys
    
    * refactor(webhook): fix lint errors
    
    * refactor(webhook): fix ci errors
    
    * refactor(webhook): fix lint errors
    
    * refactor(webhook): fix lint errors
---
 .../migrationscripts/20231116_add_api_keys.go      | 163 +++++++++++++++++++++
 .../{register.go => archived/api_keys.go}          |  25 +++-
 .../webhook/models/migrationscripts/register.go    |   1 +
 3 files changed, 182 insertions(+), 7 deletions(-)

diff --git a/backend/plugins/webhook/models/migrationscripts/20231116_add_api_keys.go b/backend/plugins/webhook/models/migrationscripts/20231116_add_api_keys.go
new file mode 100644
index 000000000..c2be2a2ad
--- /dev/null
+++ b/backend/plugins/webhook/models/migrationscripts/20231116_add_api_keys.go
@@ -0,0 +1,163 @@
+/*
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements.  See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License.  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package migrationscripts
+
+import (
+	"crypto/hmac"
+	"crypto/rand"
+	"crypto/sha256"
+	"fmt"
+	"github.com/apache/incubator-devlake/core/config"
+	"github.com/apache/incubator-devlake/core/context"
+	"github.com/apache/incubator-devlake/core/dal"
+	"github.com/apache/incubator-devlake/core/errors"
+	archivedBase "github.com/apache/incubator-devlake/core/models/migrationscripts/archived"
+	"github.com/apache/incubator-devlake/core/plugin"
+	"github.com/apache/incubator-devlake/plugins/webhook/models/migrationscripts/archived"
+	"math/big"
+	"regexp"
+	"strings"
+	"time"
+)
+
+var _ plugin.MigrationScript = (*addApiKeys)(nil)
+
+const (
+	creator         = "migration_scripts"
+	pluginName      = "webhook"
+	apiKeyLen       = 128
+	encodeKeyEnvStr = "ENCRYPTION_SECRET"
+	letterBytes     = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
+)
+
+type addApiKeys struct{}
+
+func randLetterBytes(n int) (string, errors.Error) {
+	if n < 0 {
+		return "", errors.Default.New("n must be greater than 0")
+	}
+	ret := make([]byte, n)
+	bi := big.NewInt(int64(len(letterBytes)))
+	for i := 0; i < n; i++ {
+		num, err := rand.Int(rand.Reader, bi)
+		if err != nil {
+			return "", errors.Convert(err)
+		}
+		ret[i] = letterBytes[num.Int64()]
+	}
+
+	return string(ret), nil
+}
+
+func generateApiKey() (apiKey string, hashedApiKey string, err errors.Error) {
+	apiKey, randomLetterErr := randLetterBytes(apiKeyLen)
+	if randomLetterErr != nil {
+		err = errors.Default.Wrap(randomLetterErr, "random letters")
+		return
+	}
+	hashedApiKey, err = digestToken(apiKey)
+	return apiKey, hashedApiKey, err
+}
+
+func digestToken(token string) (string, errors.Error) {
+	cfg := config.GetConfig()
+	encryptionSecret := strings.TrimSpace(cfg.GetString(encodeKeyEnvStr))
+	h := hmac.New(sha256.New, []byte(encryptionSecret))
+	if _, err := h.Write([]byte(token)); err != nil {
+		return "", errors.Default.Wrap(err, "hmac write token")
+	}
+	hashedApiKey := fmt.Sprintf("%x", h.Sum(nil))
+	return hashedApiKey, nil
+}
+
+func createForPlugin(db dal.Dal, user string, name string, pluginName string, allowedPath string, extra string) errors.Error {
+	if _, err := regexp.Compile(allowedPath); err != nil {
+		return errors.Default.Wrap(err, fmt.Sprintf("compile allowed path: %s", allowedPath))
+	}
+	apiKey, hashedApiKey, err := generateApiKey()
+	if err != nil {
+		return err
+	}
+
+	now := time.Now()
+	apiKeyRecord := &archived.ApiKey{
+		Model: archivedBase.Model{
+			CreatedAt: now,
+			UpdatedAt: now,
+		},
+		Name:        name,
+		ApiKey:      hashedApiKey,
+		ExpiredAt:   nil,
+		AllowedPath: allowedPath,
+		Type:        fmt.Sprintf("plugin:%s", pluginName),
+		Extra:       extra,
+	}
+	if user != "" {
+		apiKeyRecord.Creator = archivedBase.Creator{
+			Creator:      user,
+			CreatorEmail: "",
+		}
+		apiKeyRecord.Updater = archivedBase.Updater{
+			Updater:      user,
+			UpdaterEmail: "",
+		}
+	}
+	if err := db.Create(apiKeyRecord); err != nil {
+		if db.IsDuplicationError(err) {
+			return errors.BadInput.New(fmt.Sprintf("An api key with name [%s] has already exists", name))
+		}
+		return errors.Default.Wrap(err, "error creating DB api key")
+	}
+	apiKeyRecord.ApiKey = apiKey
+	return nil
+}
+
+func (u *addApiKeys) Up(baseRes context.BasicRes) errors.Error {
+	db := baseRes.GetDal()
+	if !db.HasTable(archived.WebhookConnection{}.TableName()) {
+		return nil
+	}
+	var webhooks []archived.WebhookConnection
+	if err := db.All(&webhooks); err != nil {
+		return err
+	}
+	for _, webhook := range webhooks {
+		name := fmt.Sprintf("%s-%d", pluginName, webhook.ID)
+		apiKey := &archived.ApiKey{}
+		if err := db.First(apiKey, dal.Where("name = ?", name)); err != nil {
+			if db.IsErrorNotFound(err) {
+				allowedPath := fmt.Sprintf("/plugins/%s/connections/%d/.*", pluginName, webhook.ID)
+				extra := fmt.Sprintf("connectionId:%d", webhook.ID)
+				if err := createForPlugin(db, creator, name, pluginName, allowedPath, extra); err != nil {
+					return err
+				}
+			} else {
+				return err
+			}
+		}
+	}
+	return nil
+}
+
+func (*addApiKeys) Version() uint64 {
+	return 20231116103000
+}
+
+func (*addApiKeys) Name() string {
+	return "associate api keys for webhook record automatically"
+}
diff --git a/backend/plugins/webhook/models/migrationscripts/register.go b/backend/plugins/webhook/models/migrationscripts/archived/api_keys.go
similarity index 58%
copy from backend/plugins/webhook/models/migrationscripts/register.go
copy to backend/plugins/webhook/models/migrationscripts/archived/api_keys.go
index ec054748c..7bb247adf 100644
--- a/backend/plugins/webhook/models/migrationscripts/register.go
+++ b/backend/plugins/webhook/models/migrationscripts/archived/api_keys.go
@@ -15,15 +15,26 @@ See the License for the specific language governing permissions and
 limitations under the License.
 */
 
-package migrationscripts
+package archived
 
 import (
-	"github.com/apache/incubator-devlake/core/plugin"
+	"github.com/apache/incubator-devlake/core/models/migrationscripts/archived"
+	"time"
 )
 
-// All return all the migration scripts
-func All() []plugin.MigrationScript {
-	return []plugin.MigrationScript{
-		new(addInitTables),
-	}
+// ApiKey is the basic of api key management.
+type ApiKey struct {
+	archived.Model
+	archived.Creator
+	archived.Updater
+	Name        string     `json:"name"`
+	ApiKey      string     `json:"apiKey,omitempty"`
+	ExpiredAt   *time.Time `json:"expiredAt"`
+	AllowedPath string     `json:"allowedPath"`
+	Type        string     `json:"type"`
+	Extra       string     `json:"extra"`
+}
+
+func (apiKey *ApiKey) TableName() string {
+	return "_devlake_api_keys"
 }
diff --git a/backend/plugins/webhook/models/migrationscripts/register.go b/backend/plugins/webhook/models/migrationscripts/register.go
index ec054748c..4f3b8a656 100644
--- a/backend/plugins/webhook/models/migrationscripts/register.go
+++ b/backend/plugins/webhook/models/migrationscripts/register.go
@@ -25,5 +25,6 @@ import (
 func All() []plugin.MigrationScript {
 	return []plugin.MigrationScript{
 		new(addInitTables),
+		new(addApiKeys),
 	}
 }