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),
}
}