blob: 703409c9a71b032b2144e1f87303dc139944d157 [file] [log] [blame]
/*
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 helper
import (
"encoding/base64"
"fmt"
"github.com/apache/incubator-devlake/errors"
"reflect"
"strconv"
"strings"
"github.com/apache/incubator-devlake/models/common"
"github.com/apache/incubator-devlake/plugins/core"
"github.com/apache/incubator-devlake/plugins/core/dal"
"github.com/go-playground/validator/v10"
)
// BaseConnection FIXME ...
type BaseConnection struct {
Name string `gorm:"type:varchar(100);uniqueIndex" json:"name" validate:"required"`
common.Model
}
// BasicAuth FIXME ...
type BasicAuth struct {
Username string `mapstructure:"username" validate:"required" json:"username"`
Password string `mapstructure:"password" validate:"required" json:"password" encrypt:"yes"`
}
// GetEncodedToken FIXME ...
func (ba BasicAuth) GetEncodedToken() string {
return base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("%v:%v", ba.Username, ba.Password)))
}
// AccessToken FIXME ...
type AccessToken struct {
Token string `mapstructure:"token" validate:"required" json:"token" encrypt:"yes"`
}
// AppKey FIXME ...
type AppKey struct {
AppId string `mapstructure:"app_id" validate:"required" json:"appId"`
SecretKey string `mapstructure:"secret_key" validate:"required" json:"secretKey" encrypt:"yes"`
}
// RestConnection FIXME ...
type RestConnection struct {
BaseConnection `mapstructure:",squash"`
Endpoint string `mapstructure:"endpoint" validate:"required" json:"endpoint"`
Proxy string `mapstructure:"proxy" json:"proxy"`
RateLimitPerHour int `comment:"api request rate limit per hour" json:"rateLimitPerHour"`
}
// ConnectionApiHelper is used to write the CURD of connection
type ConnectionApiHelper struct {
encKey string
log core.Logger
db dal.Dal
validator *validator.Validate
}
// NewConnectionHelper FIXME ...
func NewConnectionHelper(
basicRes core.BasicRes,
vld *validator.Validate,
) *ConnectionApiHelper {
if vld == nil {
vld = validator.New()
}
return &ConnectionApiHelper{
encKey: basicRes.GetConfig(core.EncodeKeyEnvStr),
log: basicRes.GetLogger(),
db: basicRes.GetDal(),
validator: vld,
}
}
// Create a connection record based on request body
func (c *ConnectionApiHelper) Create(connection interface{}, input *core.ApiResourceInput) errors.Error {
// update fields from request body
err := c.merge(connection, input.Body)
if err != nil {
return err
}
return c.save(connection)
}
// Patch (Modify) a connection record based on request body
func (c *ConnectionApiHelper) Patch(connection interface{}, input *core.ApiResourceInput) errors.Error {
err := c.First(connection, input.Params)
if err != nil {
return err
}
err = c.merge(connection, input.Body)
if err != nil {
return err
}
return c.save(connection)
}
// First finds connection from db by parsing request input and decrypt it
func (c *ConnectionApiHelper) First(connection interface{}, params map[string]string) errors.Error {
connectionId := params["connectionId"]
if connectionId == "" {
return errors.BadInput.New("missing connectionId")
}
id, err := strconv.ParseUint(connectionId, 10, 64)
if err != nil || id < 1 {
return errors.BadInput.New("invalid connectionId")
}
return c.FirstById(connection, id)
}
// FirstById finds connection from db by id and decrypt it
func (c *ConnectionApiHelper) FirstById(connection interface{}, id uint64) errors.Error {
err := c.db.First(connection, dal.Where("id = ?", id))
if err != nil {
return err
}
c.decrypt(connection)
return nil
}
// List returns all connections with password/token decrypted
func (c *ConnectionApiHelper) List(connections interface{}) errors.Error {
err := c.db.All(connections)
if err != nil {
return err
}
conns := reflect.ValueOf(connections).Elem()
for i := 0; i < conns.Len(); i++ {
c.decrypt(conns.Index(i).Addr().Interface())
}
return nil
}
// Delete connection
func (c *ConnectionApiHelper) Delete(connection interface{}) errors.Error {
return c.db.Delete(connection)
}
func (c *ConnectionApiHelper) merge(connection interface{}, body map[string]interface{}) errors.Error {
return Decode(body, connection, c.validator)
}
func (c *ConnectionApiHelper) save(connection interface{}) errors.Error {
c.encrypt(connection)
err := c.db.CreateOrUpdate(connection)
if err != nil {
if strings.Contains(err.Error(), "duplicate") {
return errors.BadInput.Wrap(err, "duplicated Connection Name")
}
return err
}
c.decrypt(connection)
return nil
}
func (c *ConnectionApiHelper) decrypt(connection interface{}) {
err := UpdateEncryptFields(connection, func(encrypted string) (string, errors.Error) {
return core.Decrypt(c.encKey, encrypted)
})
if err != nil {
c.log.Error(err, "failed to decrypt")
}
}
func (c *ConnectionApiHelper) encrypt(connection interface{}) {
err := UpdateEncryptFields(connection, func(plaintext string) (string, errors.Error) {
return core.Encrypt(c.encKey, plaintext)
})
if err != nil {
c.log.Error(err, "failed to encrypt")
}
}
// UpdateEncryptFields update fields of val with tag `encrypt:"yes|true"`
func UpdateEncryptFields(val interface{}, update func(in string) (string, errors.Error)) errors.Error {
v := reflect.ValueOf(val)
if v.Kind() != reflect.Ptr {
panic(errors.Default.New(fmt.Sprintf("val is not a pointer: %v", val)))
}
e := v.Elem()
if e.Kind() != reflect.Struct {
panic(errors.Default.New(fmt.Sprintf("*val is not a struct: %v", val)))
}
t := e.Type()
for i := 0; i < t.NumField(); i++ {
field := t.Field(i)
if !field.IsExported() {
continue
}
if field.Type.Kind() == reflect.Struct {
err := UpdateEncryptFields(e.Field(i).Addr().Interface(), update)
if err != nil {
return err
}
} else if field.Type.Kind() == reflect.Ptr && field.Type.Elem().Kind() == reflect.Struct {
fmt.Printf("field : %v\n", e.Field(i).Interface())
err := UpdateEncryptFields(e.Field(i).Interface(), update)
if err != nil {
return err
}
} else if field.Type.Kind() == reflect.String {
tagValue := field.Tag.Get("encrypt")
if tagValue == "yes" || tagValue == "true" {
out, err := update(e.Field(i).String())
if err != nil {
return err
}
e.Field(i).Set(reflect.ValueOf(out))
}
}
}
return nil
}