drone/app/store/database/publickey.go

357 lines
9.3 KiB
Go

// Copyright 2023 Harness, Inc.
//
// Licensed 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 database
import (
"context"
"fmt"
"strings"
"github.com/harness/gitness/app/store"
"github.com/harness/gitness/errors"
"github.com/harness/gitness/store/database"
"github.com/harness/gitness/store/database/dbtx"
"github.com/harness/gitness/types"
"github.com/harness/gitness/types/enum"
"github.com/Masterminds/squirrel"
"github.com/guregu/null"
"github.com/jmoiron/sqlx"
)
var _ store.PublicKeyStore = PublicKeyStore{}
// NewPublicKeyStore returns a new PublicKeyStore.
func NewPublicKeyStore(db *sqlx.DB) PublicKeyStore {
return PublicKeyStore{
db: db,
}
}
// PublicKeyStore implements a store.PublicKeyStore backed by a relational database.
type PublicKeyStore struct {
db *sqlx.DB
}
type publicKey struct {
ID int64 `db:"public_key_id"`
PrincipalID int64 `db:"public_key_principal_id"`
Created int64 `db:"public_key_created"`
Verified null.Int `db:"public_key_verified"`
Identifier string `db:"public_key_identifier"`
Usage string `db:"public_key_usage"`
Fingerprint string `db:"public_key_fingerprint"`
Content string `db:"public_key_content"`
Comment string `db:"public_key_comment"`
Type string `db:"public_key_type"`
}
const (
publicKeyColumns = `
public_key_id
,public_key_principal_id
,public_key_created
,public_key_verified
,public_key_identifier
,public_key_usage
,public_key_fingerprint
,public_key_content
,public_key_comment
,public_key_type`
publicKeySelectBase = `
SELECT` + publicKeyColumns + `
FROM public_keys`
)
// Find fetches a job by its unique identifier.
func (s PublicKeyStore) Find(ctx context.Context, id int64) (*types.PublicKey, error) {
const sqlQuery = publicKeySelectBase + `
WHERE public_key_id = $1`
db := dbtx.GetAccessor(ctx, s.db)
result := &publicKey{}
if err := db.GetContext(ctx, result, sqlQuery, id); err != nil {
return nil, database.ProcessSQLErrorf(ctx, err, "Failed to find public key by id")
}
key := mapToPublicKey(result)
return &key, nil
}
// FindByIdentifier returns a public key given a principal ID and an identifier.
func (s PublicKeyStore) FindByIdentifier(
ctx context.Context,
principalID int64,
identifier string,
) (*types.PublicKey, error) {
const sqlQuery = publicKeySelectBase + `
WHERE public_key_principal_id = $1 and LOWER(public_key_identifier) = $2`
db := dbtx.GetAccessor(ctx, s.db)
result := &publicKey{}
if err := db.GetContext(ctx, result, sqlQuery, principalID, strings.ToLower(identifier)); err != nil {
return nil, database.ProcessSQLErrorf(ctx, err, "Failed to find public key by principal and identifier")
}
key := mapToPublicKey(result)
return &key, nil
}
// Create creates a new public key.
func (s PublicKeyStore) Create(ctx context.Context, key *types.PublicKey) error {
const sqlQuery = `
INSERT INTO public_keys (
public_key_principal_id
,public_key_created
,public_key_verified
,public_key_identifier
,public_key_usage
,public_key_fingerprint
,public_key_content
,public_key_comment
,public_key_type
) values (
:public_key_principal_id
,:public_key_created
,:public_key_verified
,:public_key_identifier
,:public_key_usage
,:public_key_fingerprint
,:public_key_content
,:public_key_comment
,:public_key_type
) RETURNING public_key_id`
db := dbtx.GetAccessor(ctx, s.db)
dbKey := mapToInternalPublicKey(key)
query, arg, err := db.BindNamed(sqlQuery, &dbKey)
if err != nil {
return database.ProcessSQLErrorf(ctx, err, "Failed to bind public key object")
}
if err = db.QueryRowContext(ctx, query, arg...).Scan(&dbKey.ID); err != nil {
return database.ProcessSQLErrorf(ctx, err, "Insert public key query failed")
}
key.ID = dbKey.ID
return nil
}
// DeleteByIdentifier deletes a public key.
func (s PublicKeyStore) DeleteByIdentifier(ctx context.Context, principalID int64, identifier string) error {
const sqlQuery = `DELETE FROM public_keys WHERE public_key_principal_id = $1 and LOWER(public_key_identifier) = $2`
db := dbtx.GetAccessor(ctx, s.db)
result, err := db.ExecContext(ctx, sqlQuery, principalID, strings.ToLower(identifier))
if err != nil {
return database.ProcessSQLErrorf(ctx, err, "Delete public key query failed")
}
count, err := result.RowsAffected()
if err != nil {
return database.ProcessSQLErrorf(ctx, err, "RowsAffected after delete of public key failed")
}
if count == 0 {
return errors.NotFound("Key not found")
}
return nil
}
// MarkAsVerified updates the public key to mark it as verified.
func (s PublicKeyStore) MarkAsVerified(ctx context.Context, id int64, verified int64) error {
const sqlQuery = `
UPDATE public_keys
SET public_key_verified = $1
WHERE public_key_id = $2`
if _, err := dbtx.GetAccessor(ctx, s.db).ExecContext(ctx, sqlQuery, verified, id); err != nil {
return database.ProcessSQLErrorf(ctx, err, "Failed to mark public key as varified")
}
return nil
}
func (s PublicKeyStore) Count(
ctx context.Context,
principalID int64,
filter *types.PublicKeyFilter,
) (int, error) {
stmt := database.Builder.
Select("count(*)").
From("public_keys").
Where("public_key_principal_id = ?", principalID)
stmt = s.applyQueryFilter(stmt, filter)
sql, args, err := stmt.ToSql()
if err != nil {
return 0, fmt.Errorf("failed to convert query to sql: %w", err)
}
db := dbtx.GetAccessor(ctx, s.db)
var count int
if err := db.QueryRowContext(ctx, sql, args...).Scan(&count); err != nil {
return 0, database.ProcessSQLErrorf(ctx, err, "failed to execute count public keys query")
}
return count, nil
}
// List returns the public keys for the principal.
func (s PublicKeyStore) List(
ctx context.Context,
principalID int64,
filter *types.PublicKeyFilter,
) ([]types.PublicKey, error) {
stmt := database.Builder.
Select(publicKeyColumns).
From("public_keys").
Where("public_key_principal_id = ?", principalID)
stmt = s.applyQueryFilter(stmt, filter)
stmt = s.applySortFilter(stmt, filter)
sql, args, err := stmt.ToSql()
if err != nil {
return nil, fmt.Errorf("failed to convert query to sql: %w", err)
}
db := dbtx.GetAccessor(ctx, s.db)
keys := make([]publicKey, 0)
if err = db.SelectContext(ctx, &keys, sql, args...); err != nil {
return nil, database.ProcessSQLErrorf(ctx, err, "failed to execute list public keys query")
}
return mapToPublicKeys(keys), nil
}
// ListByFingerprint returns public keys given a fingerprint and key usage.
func (s PublicKeyStore) ListByFingerprint(
ctx context.Context,
fingerprint string,
) ([]types.PublicKey, error) {
stmt := database.Builder.
Select(publicKeyColumns).
From("public_keys").
Where("public_key_fingerprint = ?", fingerprint).
OrderBy("public_key_created ASC")
sql, args, err := stmt.ToSql()
if err != nil {
return nil, fmt.Errorf("failed to convert query to sql: %w", err)
}
db := dbtx.GetAccessor(ctx, s.db)
keys := make([]publicKey, 0)
if err = db.SelectContext(ctx, &keys, sql, args...); err != nil {
return nil, database.ProcessSQLErrorf(ctx, err, "failed to execute public keys by fingerprint query")
}
return mapToPublicKeys(keys), nil
}
func (PublicKeyStore) applyQueryFilter(
stmt squirrel.SelectBuilder,
filter *types.PublicKeyFilter,
) squirrel.SelectBuilder {
if filter.Query != "" {
stmt = stmt.Where("LOWER(public_key_identifier) LIKE ?",
fmt.Sprintf("%%%s%%", strings.ToLower(filter.Query)))
}
return stmt
}
func (PublicKeyStore) applySortFilter(
stmt squirrel.SelectBuilder,
filter *types.PublicKeyFilter,
) squirrel.SelectBuilder {
stmt = stmt.Limit(database.Limit(filter.Size))
stmt = stmt.Offset(database.Offset(filter.Page, filter.Size))
order := filter.Order
if order == enum.OrderDefault {
order = enum.OrderAsc
}
switch filter.Sort {
case enum.PublicKeySortIdentifier:
stmt = stmt.OrderBy("public_key_identifier " + order.String())
case enum.PublicKeySortCreated:
stmt = stmt.OrderBy("public_key_created " + order.String())
}
return stmt
}
func mapToInternalPublicKey(in *types.PublicKey) publicKey {
return publicKey{
ID: in.ID,
PrincipalID: in.PrincipalID,
Created: in.Created,
Verified: null.IntFromPtr(in.Verified),
Identifier: in.Identifier,
Usage: string(in.Usage),
Fingerprint: in.Fingerprint,
Content: in.Content,
Comment: in.Comment,
Type: in.Type,
}
}
func mapToPublicKey(in *publicKey) types.PublicKey {
return types.PublicKey{
ID: in.ID,
PrincipalID: in.PrincipalID,
Created: in.Created,
Verified: in.Verified.Ptr(),
Identifier: in.Identifier,
Usage: enum.PublicKeyUsage(in.Usage),
Fingerprint: in.Fingerprint,
Content: in.Content,
Comment: in.Comment,
Type: in.Type,
}
}
func mapToPublicKeys(
keys []publicKey,
) []types.PublicKey {
res := make([]types.PublicKey, len(keys))
for i := 0; i < len(keys); i++ {
res[i] = mapToPublicKey(&keys[i])
}
return res
}