2014-03-16 18:24:13 +09:00
|
|
|
// Copyright 2014 The Gogs Authors. All rights reserved.
|
2019-06-16 16:50:46 +09:00
|
|
|
// Copyright 2019 The Gitea Authors. All rights reserved.
|
2022-11-28 03:20:29 +09:00
|
|
|
// SPDX-License-Identifier: MIT
|
2014-03-16 18:24:13 +09:00
|
|
|
|
2021-12-10 17:14:24 +09:00
|
|
|
package asymkey
|
2014-02-18 00:57:23 +09:00
|
|
|
|
|
|
|
import (
|
2021-12-10 17:14:24 +09:00
|
|
|
"context"
|
2014-02-18 00:57:23 +09:00
|
|
|
"fmt"
|
2014-03-16 18:24:13 +09:00
|
|
|
"strings"
|
2014-02-18 00:57:23 +09:00
|
|
|
"time"
|
2014-03-03 05:25:09 +09:00
|
|
|
|
2022-01-02 22:12:35 +09:00
|
|
|
"code.gitea.io/gitea/models/auth"
|
2021-09-19 20:49:59 +09:00
|
|
|
"code.gitea.io/gitea/models/db"
|
2021-11-28 20:58:28 +09:00
|
|
|
"code.gitea.io/gitea/models/perm"
|
2021-11-24 18:49:20 +09:00
|
|
|
user_model "code.gitea.io/gitea/models/user"
|
2016-11-11 01:24:48 +09:00
|
|
|
"code.gitea.io/gitea/modules/log"
|
2019-08-15 23:46:21 +09:00
|
|
|
"code.gitea.io/gitea/modules/timeutil"
|
2020-08-12 05:05:34 +09:00
|
|
|
"code.gitea.io/gitea/modules/util"
|
2014-02-18 00:57:23 +09:00
|
|
|
|
2021-11-17 21:34:35 +09:00
|
|
|
"golang.org/x/crypto/ssh"
|
2021-07-24 19:16:34 +09:00
|
|
|
"xorm.io/builder"
|
2014-03-18 03:03:58 +09:00
|
|
|
)
|
|
|
|
|
2016-11-26 09:36:03 +09:00
|
|
|
// KeyType specifies the key type
|
2015-08-06 23:48:11 +09:00
|
|
|
type KeyType int
|
|
|
|
|
|
|
|
const (
|
2016-11-26 09:36:03 +09:00
|
|
|
// KeyTypeUser specifies the user key
|
2016-11-08 01:53:22 +09:00
|
|
|
KeyTypeUser = iota + 1
|
2016-11-26 09:36:03 +09:00
|
|
|
// KeyTypeDeploy specifies the deploy key
|
2016-11-08 01:53:22 +09:00
|
|
|
KeyTypeDeploy
|
2020-10-11 09:38:09 +09:00
|
|
|
// KeyTypePrincipal specifies the authorized principal key
|
|
|
|
KeyTypePrincipal
|
2015-08-06 23:48:11 +09:00
|
|
|
)
|
|
|
|
|
2016-07-26 11:47:25 +09:00
|
|
|
// PublicKey represents a user or deploy SSH public key.
|
2014-02-18 00:57:23 +09:00
|
|
|
type PublicKey struct {
|
2021-11-28 20:58:28 +09:00
|
|
|
ID int64 `xorm:"pk autoincr"`
|
|
|
|
OwnerID int64 `xorm:"INDEX NOT NULL"`
|
|
|
|
Name string `xorm:"NOT NULL"`
|
|
|
|
Fingerprint string `xorm:"INDEX NOT NULL"`
|
2022-08-22 22:32:28 +09:00
|
|
|
Content string `xorm:"MEDIUMTEXT NOT NULL"`
|
2021-11-28 20:58:28 +09:00
|
|
|
Mode perm.AccessMode `xorm:"NOT NULL DEFAULT 2"`
|
|
|
|
Type KeyType `xorm:"NOT NULL DEFAULT 1"`
|
|
|
|
LoginSourceID int64 `xorm:"NOT NULL DEFAULT 0"`
|
2016-03-10 09:53:30 +09:00
|
|
|
|
2019-08-15 23:46:21 +09:00
|
|
|
CreatedUnix timeutil.TimeStamp `xorm:"created"`
|
|
|
|
UpdatedUnix timeutil.TimeStamp `xorm:"updated"`
|
|
|
|
HasRecentActivity bool `xorm:"-"`
|
|
|
|
HasUsed bool `xorm:"-"`
|
2021-12-19 14:37:18 +09:00
|
|
|
Verified bool `xorm:"NOT NULL DEFAULT false"`
|
2015-08-06 23:48:11 +09:00
|
|
|
}
|
|
|
|
|
2021-09-19 20:49:59 +09:00
|
|
|
func init() {
|
|
|
|
db.RegisterModel(new(PublicKey))
|
|
|
|
}
|
|
|
|
|
2017-10-02 01:52:35 +09:00
|
|
|
// AfterLoad is invoked from XORM after setting the values of all fields of this object.
|
|
|
|
func (key *PublicKey) AfterLoad() {
|
2017-12-11 13:37:04 +09:00
|
|
|
key.HasUsed = key.UpdatedUnix > key.CreatedUnix
|
2019-08-15 23:46:21 +09:00
|
|
|
key.HasRecentActivity = key.UpdatedUnix.AddDuration(7*24*time.Hour) > timeutil.TimeStampNow()
|
2014-02-18 00:57:23 +09:00
|
|
|
}
|
|
|
|
|
2016-07-26 11:47:25 +09:00
|
|
|
// OmitEmail returns content of public key without email address.
|
2016-11-26 09:36:03 +09:00
|
|
|
func (key *PublicKey) OmitEmail() string {
|
|
|
|
return strings.Join(strings.Split(key.Content, " ")[:2], " ")
|
2014-11-23 16:33:47 +09:00
|
|
|
}
|
|
|
|
|
2016-07-26 11:47:25 +09:00
|
|
|
// AuthorizedString returns formatted public key string for authorized_keys file.
|
2021-07-24 19:16:34 +09:00
|
|
|
//
|
|
|
|
// TODO: Consider dropping this function
|
2016-07-26 11:47:25 +09:00
|
|
|
func (key *PublicKey) AuthorizedString() string {
|
2021-07-24 19:16:34 +09:00
|
|
|
return AuthorizedStringForKey(key)
|
2019-06-16 16:50:46 +09:00
|
|
|
}
|
|
|
|
|
2022-05-20 23:08:52 +09:00
|
|
|
func addKey(ctx context.Context, key *PublicKey) (err error) {
|
2018-10-21 06:25:14 +09:00
|
|
|
if len(key.Fingerprint) == 0 {
|
2022-06-05 04:18:50 +09:00
|
|
|
key.Fingerprint, err = CalcFingerprint(key.Content)
|
2017-02-14 15:12:52 +09:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2014-03-16 19:16:03 +09:00
|
|
|
}
|
|
|
|
|
|
|
|
// Save SSH key.
|
2022-05-20 23:08:52 +09:00
|
|
|
if err = db.Insert(ctx, key); err != nil {
|
2014-03-16 19:16:03 +09:00
|
|
|
return err
|
2015-08-06 23:48:11 +09:00
|
|
|
}
|
2015-12-11 19:02:33 +09:00
|
|
|
|
2016-07-26 11:47:25 +09:00
|
|
|
return appendAuthorizedKeysToFile(key)
|
2015-08-06 23:48:11 +09:00
|
|
|
}
|
|
|
|
|
|
|
|
// AddPublicKey adds new public key to database and authorized_keys file.
|
2022-01-02 22:12:35 +09:00
|
|
|
func AddPublicKey(ownerID int64, name, content string, authSourceID int64) (*PublicKey, error) {
|
2016-02-17 07:01:56 +09:00
|
|
|
log.Trace(content)
|
2017-02-14 15:12:52 +09:00
|
|
|
|
2022-06-05 04:18:50 +09:00
|
|
|
fingerprint, err := CalcFingerprint(content)
|
2017-02-14 15:12:52 +09:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2022-11-13 05:18:50 +09:00
|
|
|
ctx, committer, err := db.TxContext(db.DefaultContext)
|
2021-11-22 00:41:00 +09:00
|
|
|
if err != nil {
|
2019-02-04 08:56:53 +09:00
|
|
|
return nil, err
|
|
|
|
}
|
2021-11-22 00:41:00 +09:00
|
|
|
defer committer.Close()
|
2019-02-04 08:56:53 +09:00
|
|
|
|
2022-05-20 23:08:52 +09:00
|
|
|
if err := checkKeyFingerprint(ctx, fingerprint); err != nil {
|
2015-12-03 14:24:37 +09:00
|
|
|
return nil, err
|
2014-02-18 00:57:23 +09:00
|
|
|
}
|
|
|
|
|
2015-08-06 23:48:11 +09:00
|
|
|
// Key name of same user cannot be duplicated.
|
2022-05-20 23:08:52 +09:00
|
|
|
has, err := db.GetEngine(ctx).
|
2016-11-11 00:16:32 +09:00
|
|
|
Where("owner_id = ? AND name = ?", ownerID, name).
|
|
|
|
Get(new(PublicKey))
|
2015-08-06 23:48:11 +09:00
|
|
|
if err != nil {
|
2015-12-03 14:24:37 +09:00
|
|
|
return nil, err
|
2015-08-06 23:48:11 +09:00
|
|
|
} else if has {
|
2015-12-03 14:24:37 +09:00
|
|
|
return nil, ErrKeyNameAlreadyUsed{ownerID, name}
|
2015-08-06 23:48:11 +09:00
|
|
|
}
|
|
|
|
|
|
|
|
key := &PublicKey{
|
2018-05-24 13:59:02 +09:00
|
|
|
OwnerID: ownerID,
|
|
|
|
Name: name,
|
|
|
|
Fingerprint: fingerprint,
|
|
|
|
Content: content,
|
2021-11-28 20:58:28 +09:00
|
|
|
Mode: perm.AccessModeWrite,
|
2018-05-24 13:59:02 +09:00
|
|
|
Type: KeyTypeUser,
|
2022-01-02 22:12:35 +09:00
|
|
|
LoginSourceID: authSourceID,
|
2015-08-06 23:48:11 +09:00
|
|
|
}
|
2022-05-20 23:08:52 +09:00
|
|
|
if err = addKey(ctx, key); err != nil {
|
2022-10-25 04:29:17 +09:00
|
|
|
return nil, fmt.Errorf("addKey: %w", err)
|
2015-08-06 23:48:11 +09:00
|
|
|
}
|
|
|
|
|
2021-11-22 00:41:00 +09:00
|
|
|
return key, committer.Commit()
|
2014-02-18 00:57:23 +09:00
|
|
|
}
|
|
|
|
|
2015-08-06 23:48:11 +09:00
|
|
|
// GetPublicKeyByID returns public key by given ID.
|
|
|
|
func GetPublicKeyByID(keyID int64) (*PublicKey, error) {
|
2014-08-10 07:40:10 +09:00
|
|
|
key := new(PublicKey)
|
2021-09-24 00:45:36 +09:00
|
|
|
has, err := db.GetEngine(db.DefaultContext).
|
2020-03-23 00:12:55 +09:00
|
|
|
ID(keyID).
|
2016-11-11 00:16:32 +09:00
|
|
|
Get(key)
|
2014-08-10 07:40:10 +09:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
} else if !has {
|
2015-08-06 23:48:11 +09:00
|
|
|
return nil, ErrKeyNotExist{keyID}
|
2014-08-10 07:40:10 +09:00
|
|
|
}
|
|
|
|
return key, nil
|
|
|
|
}
|
|
|
|
|
2022-05-20 23:08:52 +09:00
|
|
|
// SearchPublicKeyByContent searches content as prefix (leak e-mail part)
|
|
|
|
// and returns public key found.
|
|
|
|
func SearchPublicKeyByContent(ctx context.Context, content string) (*PublicKey, error) {
|
2015-11-09 06:59:56 +09:00
|
|
|
key := new(PublicKey)
|
2022-05-20 23:08:52 +09:00
|
|
|
has, err := db.GetEngine(ctx).
|
2016-11-11 00:16:32 +09:00
|
|
|
Where("content like ?", content+"%").
|
|
|
|
Get(key)
|
2015-11-09 06:59:56 +09:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
} else if !has {
|
|
|
|
return nil, ErrKeyNotExist{}
|
|
|
|
}
|
|
|
|
return key, nil
|
|
|
|
}
|
|
|
|
|
2022-05-20 23:08:52 +09:00
|
|
|
// SearchPublicKeyByContentExact searches content
|
2018-12-28 02:28:48 +09:00
|
|
|
// and returns public key found.
|
2022-05-20 23:08:52 +09:00
|
|
|
func SearchPublicKeyByContentExact(ctx context.Context, content string) (*PublicKey, error) {
|
2020-10-11 09:38:09 +09:00
|
|
|
key := new(PublicKey)
|
2022-05-20 23:08:52 +09:00
|
|
|
has, err := db.GetEngine(ctx).
|
2020-10-11 09:38:09 +09:00
|
|
|
Where("content = ?", content).
|
|
|
|
Get(key)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
} else if !has {
|
|
|
|
return nil, ErrKeyNotExist{}
|
|
|
|
}
|
|
|
|
return key, nil
|
|
|
|
}
|
|
|
|
|
2018-11-01 12:40:49 +09:00
|
|
|
// SearchPublicKey returns a list of public keys matching the provided arguments.
|
|
|
|
func SearchPublicKey(uid int64, fingerprint string) ([]*PublicKey, error) {
|
|
|
|
keys := make([]*PublicKey, 0, 5)
|
|
|
|
cond := builder.NewCond()
|
|
|
|
if uid != 0 {
|
|
|
|
cond = cond.And(builder.Eq{"owner_id": uid})
|
|
|
|
}
|
|
|
|
if fingerprint != "" {
|
|
|
|
cond = cond.And(builder.Eq{"fingerprint": fingerprint})
|
|
|
|
}
|
2021-09-24 00:45:36 +09:00
|
|
|
return keys, db.GetEngine(db.DefaultContext).Where(cond).Find(&keys)
|
2018-11-01 12:40:49 +09:00
|
|
|
}
|
|
|
|
|
2014-11-12 20:48:50 +09:00
|
|
|
// ListPublicKeys returns a list of public keys belongs to given user.
|
2021-09-24 20:32:56 +09:00
|
|
|
func ListPublicKeys(uid int64, listOptions db.ListOptions) ([]*PublicKey, error) {
|
2021-09-24 00:45:36 +09:00
|
|
|
sess := db.GetEngine(db.DefaultContext).Where("owner_id = ? AND type != ?", uid, KeyTypePrincipal)
|
2020-01-25 04:00:29 +09:00
|
|
|
if listOptions.Page != 0 {
|
2021-09-24 20:32:56 +09:00
|
|
|
sess = db.SetSessionPagination(sess, &listOptions)
|
2020-01-25 04:00:29 +09:00
|
|
|
|
|
|
|
keys := make([]*PublicKey, 0, listOptions.PageSize)
|
|
|
|
return keys, sess.Find(&keys)
|
|
|
|
}
|
|
|
|
|
2014-07-26 13:24:27 +09:00
|
|
|
keys := make([]*PublicKey, 0, 5)
|
2020-01-25 04:00:29 +09:00
|
|
|
return keys, sess.Find(&keys)
|
2014-05-08 01:09:30 +09:00
|
|
|
}
|
|
|
|
|
2021-08-12 21:43:08 +09:00
|
|
|
// CountPublicKeys count public keys a user has
|
|
|
|
func CountPublicKeys(userID int64) (int64, error) {
|
2021-09-24 00:45:36 +09:00
|
|
|
sess := db.GetEngine(db.DefaultContext).Where("owner_id = ? AND type != ?", userID, KeyTypePrincipal)
|
2021-08-12 21:43:08 +09:00
|
|
|
return sess.Count(&PublicKey{})
|
|
|
|
}
|
|
|
|
|
2021-07-24 19:16:34 +09:00
|
|
|
// ListPublicKeysBySource returns a list of synchronized public keys for a given user and login source.
|
2022-01-02 22:12:35 +09:00
|
|
|
func ListPublicKeysBySource(uid, authSourceID int64) ([]*PublicKey, error) {
|
2018-05-24 13:59:02 +09:00
|
|
|
keys := make([]*PublicKey, 0, 5)
|
2021-09-24 00:45:36 +09:00
|
|
|
return keys, db.GetEngine(db.DefaultContext).
|
2022-01-02 22:12:35 +09:00
|
|
|
Where("owner_id = ? AND login_source_id = ?", uid, authSourceID).
|
2018-05-24 13:59:02 +09:00
|
|
|
Find(&keys)
|
|
|
|
}
|
|
|
|
|
2017-04-08 09:40:38 +09:00
|
|
|
// UpdatePublicKeyUpdated updates public key use time.
|
|
|
|
func UpdatePublicKeyUpdated(id int64) error {
|
2017-07-20 12:15:10 +09:00
|
|
|
// Check if key exists before update as affected rows count is unreliable
|
|
|
|
// and will return 0 affected rows if two updates are made at the same time
|
2021-09-24 00:45:36 +09:00
|
|
|
if cnt, err := db.GetEngine(db.DefaultContext).ID(id).Count(&PublicKey{}); err != nil {
|
2017-07-20 12:15:10 +09:00
|
|
|
return err
|
|
|
|
} else if cnt != 1 {
|
|
|
|
return ErrKeyNotExist{id}
|
|
|
|
}
|
|
|
|
|
2021-09-24 00:45:36 +09:00
|
|
|
_, err := db.GetEngine(db.DefaultContext).ID(id).Cols("updated_unix").Update(&PublicKey{
|
2019-08-15 23:46:21 +09:00
|
|
|
UpdatedUnix: timeutil.TimeStampNow(),
|
2017-04-08 09:40:38 +09:00
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2021-12-10 17:14:24 +09:00
|
|
|
// DeletePublicKeys does the actual key deletion but does not update authorized_keys file.
|
|
|
|
func DeletePublicKeys(ctx context.Context, keyIDs ...int64) error {
|
2016-07-26 18:26:48 +09:00
|
|
|
if len(keyIDs) == 0 {
|
2015-08-06 23:48:11 +09:00
|
|
|
return nil
|
2014-03-23 03:27:03 +09:00
|
|
|
}
|
2014-05-07 05:28:52 +09:00
|
|
|
|
2021-12-10 17:14:24 +09:00
|
|
|
_, err := db.GetEngine(ctx).In("id", keyIDs).Delete(new(PublicKey))
|
2016-07-26 18:26:48 +09:00
|
|
|
return err
|
2014-02-18 00:57:23 +09:00
|
|
|
}
|
2015-01-01 03:07:51 +09:00
|
|
|
|
2020-12-26 13:24:47 +09:00
|
|
|
// PublicKeysAreExternallyManaged returns whether the provided KeyID represents an externally managed Key
|
|
|
|
func PublicKeysAreExternallyManaged(keys []*PublicKey) ([]bool, error) {
|
2022-01-02 22:12:35 +09:00
|
|
|
sources := make([]*auth.Source, 0, 5)
|
2020-12-26 13:24:47 +09:00
|
|
|
externals := make([]bool, len(keys))
|
|
|
|
keyloop:
|
|
|
|
for i, key := range keys {
|
|
|
|
if key.LoginSourceID == 0 {
|
|
|
|
externals[i] = false
|
|
|
|
continue keyloop
|
|
|
|
}
|
|
|
|
|
2022-01-02 22:12:35 +09:00
|
|
|
var source *auth.Source
|
2020-12-26 13:24:47 +09:00
|
|
|
|
|
|
|
sourceloop:
|
|
|
|
for _, s := range sources {
|
|
|
|
if s.ID == key.LoginSourceID {
|
|
|
|
source = s
|
|
|
|
break sourceloop
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if source == nil {
|
|
|
|
var err error
|
2022-01-02 22:12:35 +09:00
|
|
|
source, err = auth.GetSourceByID(key.LoginSourceID)
|
2020-12-26 13:24:47 +09:00
|
|
|
if err != nil {
|
2022-01-02 22:12:35 +09:00
|
|
|
if auth.IsErrSourceNotExist(err) {
|
2020-12-26 13:24:47 +09:00
|
|
|
externals[i] = false
|
2022-01-02 22:12:35 +09:00
|
|
|
sources[i] = &auth.Source{
|
2020-12-26 13:24:47 +09:00
|
|
|
ID: key.LoginSourceID,
|
|
|
|
}
|
|
|
|
continue keyloop
|
|
|
|
}
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-01-02 22:12:35 +09:00
|
|
|
if sshKeyProvider, ok := source.Cfg.(auth.SSHKeyProvider); ok && sshKeyProvider.ProvidesSSHKeys() {
|
2020-12-26 13:24:47 +09:00
|
|
|
// Disable setting SSH keys for this user
|
|
|
|
externals[i] = true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return externals, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// PublicKeyIsExternallyManaged returns whether the provided KeyID represents an externally managed Key
|
|
|
|
func PublicKeyIsExternallyManaged(id int64) (bool, error) {
|
|
|
|
key, err := GetPublicKeyByID(id)
|
|
|
|
if err != nil {
|
|
|
|
return false, err
|
|
|
|
}
|
|
|
|
if key.LoginSourceID == 0 {
|
|
|
|
return false, nil
|
|
|
|
}
|
2022-01-02 22:12:35 +09:00
|
|
|
source, err := auth.GetSourceByID(key.LoginSourceID)
|
2020-12-26 13:24:47 +09:00
|
|
|
if err != nil {
|
2022-01-02 22:12:35 +09:00
|
|
|
if auth.IsErrSourceNotExist(err) {
|
2020-12-26 13:24:47 +09:00
|
|
|
return false, nil
|
|
|
|
}
|
|
|
|
return false, err
|
|
|
|
}
|
2022-01-02 22:12:35 +09:00
|
|
|
if sshKeyProvider, ok := source.Cfg.(auth.SSHKeyProvider); ok && sshKeyProvider.ProvidesSSHKeys() {
|
2020-12-26 13:24:47 +09:00
|
|
|
// Disable setting SSH keys for this user
|
|
|
|
return true, nil
|
|
|
|
}
|
|
|
|
return false, nil
|
|
|
|
}
|
|
|
|
|
2021-07-24 19:16:34 +09:00
|
|
|
// deleteKeysMarkedForDeletion returns true if ssh keys needs update
|
|
|
|
func deleteKeysMarkedForDeletion(keys []string) (bool, error) {
|
|
|
|
// Start session
|
2022-11-13 05:18:50 +09:00
|
|
|
ctx, committer, err := db.TxContext(db.DefaultContext)
|
2021-11-22 00:41:00 +09:00
|
|
|
if err != nil {
|
2021-07-24 19:16:34 +09:00
|
|
|
return false, err
|
2015-12-03 14:24:37 +09:00
|
|
|
}
|
2021-11-22 00:41:00 +09:00
|
|
|
defer committer.Close()
|
2015-12-03 14:24:37 +09:00
|
|
|
|
2021-07-24 19:16:34 +09:00
|
|
|
// Delete keys marked for deletion
|
|
|
|
var sshKeysNeedUpdate bool
|
|
|
|
for _, KeyToDelete := range keys {
|
2022-05-20 23:08:52 +09:00
|
|
|
key, err := SearchPublicKeyByContent(ctx, KeyToDelete)
|
2015-12-06 07:13:13 +09:00
|
|
|
if err != nil {
|
2021-07-24 19:16:34 +09:00
|
|
|
log.Error("SearchPublicKeyByContent: %v", err)
|
|
|
|
continue
|
2015-08-06 23:48:11 +09:00
|
|
|
}
|
2021-12-10 17:14:24 +09:00
|
|
|
if err = DeletePublicKeys(ctx, key.ID); err != nil {
|
2021-07-24 19:16:34 +09:00
|
|
|
log.Error("deletePublicKeys: %v", err)
|
|
|
|
continue
|
2019-01-10 03:10:46 +09:00
|
|
|
}
|
2021-07-24 19:16:34 +09:00
|
|
|
sshKeysNeedUpdate = true
|
2015-08-06 23:48:11 +09:00
|
|
|
}
|
|
|
|
|
2021-11-22 00:41:00 +09:00
|
|
|
if err := committer.Commit(); err != nil {
|
2021-07-24 19:16:34 +09:00
|
|
|
return false, err
|
2020-10-11 09:38:09 +09:00
|
|
|
}
|
|
|
|
|
2021-07-24 19:16:34 +09:00
|
|
|
return sshKeysNeedUpdate, nil
|
2020-10-11 09:38:09 +09:00
|
|
|
}
|
|
|
|
|
2021-07-24 19:16:34 +09:00
|
|
|
// AddPublicKeysBySource add a users public keys. Returns true if there are changes.
|
2022-01-02 22:12:35 +09:00
|
|
|
func AddPublicKeysBySource(usr *user_model.User, s *auth.Source, sshPublicKeys []string) bool {
|
2021-07-24 19:16:34 +09:00
|
|
|
var sshKeysNeedUpdate bool
|
|
|
|
for _, sshKey := range sshPublicKeys {
|
|
|
|
var err error
|
|
|
|
found := false
|
|
|
|
keys := []byte(sshKey)
|
|
|
|
loop:
|
|
|
|
for len(keys) > 0 && err == nil {
|
|
|
|
var out ssh.PublicKey
|
|
|
|
// We ignore options as they are not relevant to Gitea
|
|
|
|
out, _, _, keys, err = ssh.ParseAuthorizedKey(keys)
|
2020-10-11 09:38:09 +09:00
|
|
|
if err != nil {
|
2021-07-24 19:16:34 +09:00
|
|
|
break loop
|
2020-10-11 09:38:09 +09:00
|
|
|
}
|
2021-07-24 19:16:34 +09:00
|
|
|
found = true
|
|
|
|
marshalled := string(ssh.MarshalAuthorizedKey(out))
|
|
|
|
marshalled = marshalled[:len(marshalled)-1]
|
|
|
|
sshKeyName := fmt.Sprintf("%s-%s", s.Name, ssh.FingerprintSHA256(out))
|
|
|
|
|
|
|
|
if _, err := AddPublicKey(usr.ID, sshKeyName, marshalled, s.ID); err != nil {
|
|
|
|
if IsErrKeyAlreadyExist(err) {
|
|
|
|
log.Trace("AddPublicKeysBySource[%s]: Public SSH Key %s already exists for user", sshKeyName, usr.Name)
|
|
|
|
} else {
|
|
|
|
log.Error("AddPublicKeysBySource[%s]: Error adding Public SSH Key for user %s: %v", sshKeyName, usr.Name, err)
|
2020-10-11 09:38:09 +09:00
|
|
|
}
|
2021-07-24 19:16:34 +09:00
|
|
|
} else {
|
|
|
|
log.Trace("AddPublicKeysBySource[%s]: Added Public SSH Key for user %s", sshKeyName, usr.Name)
|
|
|
|
sshKeysNeedUpdate = true
|
2020-10-11 09:38:09 +09:00
|
|
|
}
|
2021-07-24 19:16:34 +09:00
|
|
|
}
|
|
|
|
if !found && err != nil {
|
|
|
|
log.Warn("AddPublicKeysBySource[%s]: Skipping invalid Public SSH Key for user %s: %v", s.Name, usr.Name, sshKey)
|
2020-10-11 09:38:09 +09:00
|
|
|
}
|
|
|
|
}
|
2021-07-24 19:16:34 +09:00
|
|
|
return sshKeysNeedUpdate
|
2020-10-11 09:38:09 +09:00
|
|
|
}
|
|
|
|
|
2021-07-24 19:16:34 +09:00
|
|
|
// SynchronizePublicKeys updates a users public keys. Returns true if there are changes.
|
2022-01-02 22:12:35 +09:00
|
|
|
func SynchronizePublicKeys(usr *user_model.User, s *auth.Source, sshPublicKeys []string) bool {
|
2021-07-24 19:16:34 +09:00
|
|
|
var sshKeysNeedUpdate bool
|
2020-10-11 09:38:09 +09:00
|
|
|
|
2021-07-24 19:16:34 +09:00
|
|
|
log.Trace("synchronizePublicKeys[%s]: Handling Public SSH Key synchronization for user %s", s.Name, usr.Name)
|
2020-10-11 09:38:09 +09:00
|
|
|
|
2021-07-24 19:16:34 +09:00
|
|
|
// Get Public Keys from DB with current LDAP source
|
|
|
|
var giteaKeys []string
|
|
|
|
keys, err := ListPublicKeysBySource(usr.ID, s.ID)
|
|
|
|
if err != nil {
|
|
|
|
log.Error("synchronizePublicKeys[%s]: Error listing Public SSH Keys for user %s: %v", s.Name, usr.Name, err)
|
2020-10-11 09:38:09 +09:00
|
|
|
}
|
|
|
|
|
2021-07-24 19:16:34 +09:00
|
|
|
for _, v := range keys {
|
|
|
|
giteaKeys = append(giteaKeys, v.OmitEmail())
|
2020-10-11 09:38:09 +09:00
|
|
|
}
|
|
|
|
|
2021-07-24 19:16:34 +09:00
|
|
|
// Process the provided keys to remove duplicates and name part
|
|
|
|
var providedKeys []string
|
|
|
|
for _, v := range sshPublicKeys {
|
|
|
|
sshKeySplit := strings.Split(v, " ")
|
|
|
|
if len(sshKeySplit) > 1 {
|
|
|
|
key := strings.Join(sshKeySplit[:2], " ")
|
Improve utils of slices (#22379)
- Move the file `compare.go` and `slice.go` to `slice.go`.
- Fix `ExistsInSlice`, it's buggy
- It uses `sort.Search`, so it assumes that the input slice is sorted.
- It passes `func(i int) bool { return slice[i] == target })` to
`sort.Search`, that's incorrect, check the doc of `sort.Search`.
- Conbine `IsInt64InSlice(int64, []int64)` and `ExistsInSlice(string,
[]string)` to `SliceContains[T]([]T, T)`.
- Conbine `IsSliceInt64Eq([]int64, []int64)` and `IsEqualSlice([]string,
[]string)` to `SliceSortedEqual[T]([]T, T)`.
- Add `SliceEqual[T]([]T, T)` as a distinction from
`SliceSortedEqual[T]([]T, T)`.
- Redesign `RemoveIDFromList([]int64, int64) ([]int64, bool)` to
`SliceRemoveAll[T]([]T, T) []T`.
- Add `SliceContainsFunc[T]([]T, func(T) bool)` and
`SliceRemoveAllFunc[T]([]T, func(T) bool)` for general use.
- Add comments to explain why not `golang.org/x/exp/slices`.
- Add unit tests.
2023-01-11 14:31:16 +09:00
|
|
|
if !util.SliceContainsString(providedKeys, key) {
|
2021-07-24 19:16:34 +09:00
|
|
|
providedKeys = append(providedKeys, key)
|
2020-11-28 11:42:08 +09:00
|
|
|
}
|
|
|
|
}
|
2020-10-11 09:38:09 +09:00
|
|
|
}
|
|
|
|
|
2021-07-24 19:16:34 +09:00
|
|
|
// Check if Public Key sync is needed
|
Improve utils of slices (#22379)
- Move the file `compare.go` and `slice.go` to `slice.go`.
- Fix `ExistsInSlice`, it's buggy
- It uses `sort.Search`, so it assumes that the input slice is sorted.
- It passes `func(i int) bool { return slice[i] == target })` to
`sort.Search`, that's incorrect, check the doc of `sort.Search`.
- Conbine `IsInt64InSlice(int64, []int64)` and `ExistsInSlice(string,
[]string)` to `SliceContains[T]([]T, T)`.
- Conbine `IsSliceInt64Eq([]int64, []int64)` and `IsEqualSlice([]string,
[]string)` to `SliceSortedEqual[T]([]T, T)`.
- Add `SliceEqual[T]([]T, T)` as a distinction from
`SliceSortedEqual[T]([]T, T)`.
- Redesign `RemoveIDFromList([]int64, int64) ([]int64, bool)` to
`SliceRemoveAll[T]([]T, T) []T`.
- Add `SliceContainsFunc[T]([]T, func(T) bool)` and
`SliceRemoveAllFunc[T]([]T, func(T) bool)` for general use.
- Add comments to explain why not `golang.org/x/exp/slices`.
- Add unit tests.
2023-01-11 14:31:16 +09:00
|
|
|
if util.SliceSortedEqual(giteaKeys, providedKeys) {
|
2021-07-24 19:16:34 +09:00
|
|
|
log.Trace("synchronizePublicKeys[%s]: Public Keys are already in sync for %s (Source:%v/DB:%v)", s.Name, usr.Name, len(providedKeys), len(giteaKeys))
|
|
|
|
return false
|
2020-10-11 09:38:09 +09:00
|
|
|
}
|
2021-07-24 19:16:34 +09:00
|
|
|
log.Trace("synchronizePublicKeys[%s]: Public Key needs update for user %s (Source:%v/DB:%v)", s.Name, usr.Name, len(providedKeys), len(giteaKeys))
|
2020-10-11 09:38:09 +09:00
|
|
|
|
2021-07-24 19:16:34 +09:00
|
|
|
// Add new Public SSH Keys that doesn't already exist in DB
|
|
|
|
var newKeys []string
|
|
|
|
for _, key := range providedKeys {
|
Improve utils of slices (#22379)
- Move the file `compare.go` and `slice.go` to `slice.go`.
- Fix `ExistsInSlice`, it's buggy
- It uses `sort.Search`, so it assumes that the input slice is sorted.
- It passes `func(i int) bool { return slice[i] == target })` to
`sort.Search`, that's incorrect, check the doc of `sort.Search`.
- Conbine `IsInt64InSlice(int64, []int64)` and `ExistsInSlice(string,
[]string)` to `SliceContains[T]([]T, T)`.
- Conbine `IsSliceInt64Eq([]int64, []int64)` and `IsEqualSlice([]string,
[]string)` to `SliceSortedEqual[T]([]T, T)`.
- Add `SliceEqual[T]([]T, T)` as a distinction from
`SliceSortedEqual[T]([]T, T)`.
- Redesign `RemoveIDFromList([]int64, int64) ([]int64, bool)` to
`SliceRemoveAll[T]([]T, T) []T`.
- Add `SliceContainsFunc[T]([]T, func(T) bool)` and
`SliceRemoveAllFunc[T]([]T, func(T) bool)` for general use.
- Add comments to explain why not `golang.org/x/exp/slices`.
- Add unit tests.
2023-01-11 14:31:16 +09:00
|
|
|
if !util.SliceContainsString(giteaKeys, key) {
|
2021-07-24 19:16:34 +09:00
|
|
|
newKeys = append(newKeys, key)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if AddPublicKeysBySource(usr, s, newKeys) {
|
|
|
|
sshKeysNeedUpdate = true
|
2020-10-11 09:38:09 +09:00
|
|
|
}
|
|
|
|
|
2021-07-24 19:16:34 +09:00
|
|
|
// Mark keys from DB that no longer exist in the source for deletion
|
|
|
|
var giteaKeysToDelete []string
|
|
|
|
for _, giteaKey := range giteaKeys {
|
Improve utils of slices (#22379)
- Move the file `compare.go` and `slice.go` to `slice.go`.
- Fix `ExistsInSlice`, it's buggy
- It uses `sort.Search`, so it assumes that the input slice is sorted.
- It passes `func(i int) bool { return slice[i] == target })` to
`sort.Search`, that's incorrect, check the doc of `sort.Search`.
- Conbine `IsInt64InSlice(int64, []int64)` and `ExistsInSlice(string,
[]string)` to `SliceContains[T]([]T, T)`.
- Conbine `IsSliceInt64Eq([]int64, []int64)` and `IsEqualSlice([]string,
[]string)` to `SliceSortedEqual[T]([]T, T)`.
- Add `SliceEqual[T]([]T, T)` as a distinction from
`SliceSortedEqual[T]([]T, T)`.
- Redesign `RemoveIDFromList([]int64, int64) ([]int64, bool)` to
`SliceRemoveAll[T]([]T, T) []T`.
- Add `SliceContainsFunc[T]([]T, func(T) bool)` and
`SliceRemoveAllFunc[T]([]T, func(T) bool)` for general use.
- Add comments to explain why not `golang.org/x/exp/slices`.
- Add unit tests.
2023-01-11 14:31:16 +09:00
|
|
|
if !util.SliceContainsString(providedKeys, giteaKey) {
|
2021-07-24 19:16:34 +09:00
|
|
|
log.Trace("synchronizePublicKeys[%s]: Marking Public SSH Key for deletion for user %s: %v", s.Name, usr.Name, giteaKey)
|
|
|
|
giteaKeysToDelete = append(giteaKeysToDelete, giteaKey)
|
|
|
|
}
|
2020-10-11 09:38:09 +09:00
|
|
|
}
|
|
|
|
|
2021-07-24 19:16:34 +09:00
|
|
|
// Delete keys from DB that no longer exist in the source
|
|
|
|
needUpd, err := deleteKeysMarkedForDeletion(giteaKeysToDelete)
|
2020-11-28 11:42:08 +09:00
|
|
|
if err != nil {
|
2021-07-24 19:16:34 +09:00
|
|
|
log.Error("synchronizePublicKeys[%s]: Error deleting Public Keys marked for deletion for user %s: %v", s.Name, usr.Name, err)
|
2020-11-28 11:42:08 +09:00
|
|
|
}
|
2021-07-24 19:16:34 +09:00
|
|
|
if needUpd {
|
|
|
|
sshKeysNeedUpdate = true
|
2020-10-11 09:38:09 +09:00
|
|
|
}
|
2021-07-24 19:16:34 +09:00
|
|
|
|
|
|
|
return sshKeysNeedUpdate
|
2020-10-11 09:38:09 +09:00
|
|
|
}
|