mirror of
https://github.com/harness/drone.git
synced 2025-05-09 03:22:25 +08:00

This change introduces the concept of a principal (abstraction of call identity), and adds a new service account type principal. Also adds support for different tokens (session, PAT, SAT, OAuth2) and adds auth.Session which is being used to capture information about the caller and call method.
242 lines
6.3 KiB
Go
242 lines
6.3 KiB
Go
// Copyright 2021 Harness Inc. All rights reserved.
|
|
// Use of this source code is governed by the Polyform Free Trial License
|
|
// that can be found in the LICENSE.md file for this repository.
|
|
|
|
package database
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
"strconv"
|
|
|
|
"github.com/harness/gitness/internal/store"
|
|
"github.com/harness/gitness/types"
|
|
"github.com/harness/gitness/types/enum"
|
|
"github.com/pkg/errors"
|
|
|
|
"github.com/jmoiron/sqlx"
|
|
)
|
|
|
|
var _ store.UserStore = (*UserStore)(nil)
|
|
|
|
// NewUserStore returns a new UserStore.
|
|
func NewUserStore(db *sqlx.DB) *UserStore {
|
|
return &UserStore{db}
|
|
}
|
|
|
|
// UserStore implements a UserStore backed by a relational
|
|
// database.
|
|
type UserStore struct {
|
|
db *sqlx.DB
|
|
}
|
|
|
|
// Find finds the user by id.
|
|
func (s *UserStore) Find(ctx context.Context, id int64) (*types.User, error) {
|
|
dst := new(types.User)
|
|
if err := s.db.GetContext(ctx, dst, userSelectID, id); err != nil {
|
|
return nil, processSQLErrorf(err, "Select by id query failed")
|
|
}
|
|
return dst, nil
|
|
}
|
|
|
|
// FindEmail finds the user by email.
|
|
func (s *UserStore) FindEmail(ctx context.Context, email string) (*types.User, error) {
|
|
dst := new(types.User)
|
|
if err := s.db.GetContext(ctx, dst, userSelectEmail, email); err != nil {
|
|
return nil, processSQLErrorf(err, "Select by email query failed")
|
|
}
|
|
return dst, nil
|
|
}
|
|
|
|
// FindKey finds the user unique key (email or id).
|
|
func (s *UserStore) FindKey(ctx context.Context, key string) (*types.User, error) {
|
|
id, err := strconv.ParseInt(key, 10, 64)
|
|
if err == nil {
|
|
return s.Find(ctx, id)
|
|
}
|
|
return s.FindEmail(ctx, key)
|
|
}
|
|
|
|
// List returns a list of users.
|
|
func (s *UserStore) List(ctx context.Context, opts *types.UserFilter) ([]*types.User, error) {
|
|
dst := []*types.User{}
|
|
|
|
// if the user does not provide any customer filter
|
|
// or sorting we use the default select statement.
|
|
if opts.Sort == enum.UserAttrNone {
|
|
err := s.db.SelectContext(ctx, &dst, userSelect, limit(opts.Size), offset(opts.Page, opts.Size))
|
|
if err != nil {
|
|
return nil, processSQLErrorf(err, "Failed executing default list query")
|
|
}
|
|
return dst, nil
|
|
}
|
|
|
|
// else we construct the sql statement.
|
|
stmt := builder.Select("*").From("users")
|
|
stmt = stmt.Limit(uint64(limit(opts.Size)))
|
|
stmt = stmt.Offset(uint64(offset(opts.Page, opts.Size)))
|
|
|
|
switch opts.Sort {
|
|
case enum.UserAttrName, enum.UserAttrNone:
|
|
// NOTE: string concatenation is safe because the
|
|
// order attribute is an enum and is not user-defined,
|
|
// and is therefore not subject to injection attacks.
|
|
stmt = stmt.OrderBy("principal_name " + opts.Order.String())
|
|
case enum.UserAttrCreated:
|
|
stmt = stmt.OrderBy("principal_created " + opts.Order.String())
|
|
case enum.UserAttrUpdated:
|
|
stmt = stmt.OrderBy("principal_updated " + opts.Order.String())
|
|
case enum.UserAttrEmail:
|
|
stmt = stmt.OrderBy("principal_user_email " + opts.Order.String())
|
|
case enum.UserAttrID:
|
|
stmt = stmt.OrderBy("principal_id " + opts.Order.String())
|
|
case enum.UserAttrAdmin:
|
|
stmt = stmt.OrderBy("principal_admin " + opts.Order.String())
|
|
}
|
|
|
|
sql, _, err := stmt.ToSql()
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "Failed to convert query to sql")
|
|
}
|
|
|
|
if err = s.db.SelectContext(ctx, &dst, sql); err != nil {
|
|
return nil, processSQLErrorf(err, "Failed executing custom list query")
|
|
}
|
|
|
|
return dst, nil
|
|
}
|
|
|
|
// Create saves the user details.
|
|
func (s *UserStore) Create(ctx context.Context, user *types.User) error {
|
|
query, arg, err := s.db.BindNamed(userInsert, user)
|
|
if err != nil {
|
|
return processSQLErrorf(err, "Failed to bind user object")
|
|
}
|
|
|
|
if err = s.db.QueryRowContext(ctx, query, arg...).Scan(&user.ID); err != nil {
|
|
return processSQLErrorf(err, "Insert query failed")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Update updates the user details.
|
|
func (s *UserStore) Update(ctx context.Context, user *types.User) error {
|
|
query, arg, err := s.db.BindNamed(userUpdate, user)
|
|
if err != nil {
|
|
return processSQLErrorf(err, "Failed to bind user object")
|
|
}
|
|
|
|
if _, err = s.db.ExecContext(ctx, query, arg...); err != nil {
|
|
return processSQLErrorf(err, "Update query failed")
|
|
}
|
|
|
|
return err
|
|
}
|
|
|
|
// Delete deletes the user.
|
|
func (s *UserStore) Delete(ctx context.Context, user *types.User) error {
|
|
tx, err := s.db.BeginTx(ctx, nil)
|
|
if err != nil {
|
|
return processSQLErrorf(err, "Failed to start a new transaction")
|
|
}
|
|
defer func(tx *sql.Tx) {
|
|
_ = tx.Rollback()
|
|
}(tx)
|
|
// delete the user
|
|
if _, err = tx.ExecContext(ctx, userDelete, user.ID); err != nil {
|
|
return processSQLErrorf(err, "The delete query failed")
|
|
}
|
|
return tx.Commit()
|
|
}
|
|
|
|
// Count returns a count of users.
|
|
func (s *UserStore) Count(ctx context.Context) (int64, error) {
|
|
var count int64
|
|
err := s.db.QueryRowContext(ctx, userCount).Scan(&count)
|
|
if err != nil {
|
|
return 0, processSQLErrorf(err, "Failed executing count query")
|
|
}
|
|
return count, nil
|
|
}
|
|
|
|
const userCount = `
|
|
SELECT count(*)
|
|
FROM principals
|
|
WHERE principal_type = "user"
|
|
`
|
|
|
|
const userBase = `
|
|
SELECT
|
|
principal_id
|
|
,principal_name
|
|
,principal_admin
|
|
,principal_externalId
|
|
,principal_blocked
|
|
,principal_salt
|
|
,principal_created
|
|
,principal_updated
|
|
,principal_user_email
|
|
,principal_user_password
|
|
FROM principals
|
|
`
|
|
|
|
const userSelect = userBase + `
|
|
WHERE principal_type = "user"
|
|
ORDER BY principal_name ASC
|
|
LIMIT $1 OFFSET $2
|
|
`
|
|
|
|
const userSelectID = userBase + `
|
|
WHERE principal_type = "user" AND principal_id = $1
|
|
`
|
|
|
|
const userSelectEmail = userBase + `
|
|
WHERE principal_type = "user" AND principal_user_email = $1
|
|
`
|
|
|
|
const userDelete = `
|
|
DELETE FROM principals
|
|
WHERE principal_type = "user" AND principal_id = $1
|
|
`
|
|
|
|
const userInsert = `
|
|
INSERT INTO principals (
|
|
principal_type
|
|
,principal_name
|
|
,principal_admin
|
|
,principal_externalId
|
|
,principal_blocked
|
|
,principal_salt
|
|
,principal_created
|
|
,principal_updated
|
|
,principal_user_email
|
|
,principal_user_password
|
|
) values (
|
|
"user"
|
|
,:principal_name
|
|
,:principal_admin
|
|
,:principal_externalId
|
|
,:principal_blocked
|
|
,:principal_salt
|
|
,:principal_created
|
|
,:principal_updated
|
|
,:principal_user_email
|
|
,:principal_user_password
|
|
) RETURNING principal_id
|
|
`
|
|
|
|
const userUpdate = `
|
|
UPDATE principals
|
|
SET
|
|
principal_name = :principal_name
|
|
,principal_admin = :principal_admin
|
|
,principal_externalId = :principal_externalId
|
|
,principal_blocked = :principal_blocked
|
|
,principal_salt = :principal_salt
|
|
,principal_updated = :principal_updated
|
|
,principal_user_email = :principal_user_email
|
|
,principal_user_password = :principal_user_password
|
|
WHERE principal_type = "user" AND principal_id = :principal_id
|
|
`
|