code refactoring and package version publisher id correction

This commit is contained in:
dancheg97 2023-06-21 23:30:07 +03:00
parent 808a7aed67
commit b354f027ff
7 changed files with 461 additions and 442 deletions

View File

@ -258,3 +258,21 @@ func PackDb(src, dst string) error {
}
return os.Symlink(dst, symlink)
}
// Join database or package names to prevent collisions with same packages in
// different user spaces. Skips empty strings and returns name joined with
// dots.
func Join(s ...string) string {
rez := ""
for i, v := range s {
if v == "" {
continue
}
if i+1 == len(s) {
rez += v
continue
}
rez += v + "."
}
return rez
}

View File

@ -4,6 +4,7 @@
package packages
import (
"bytes"
"io"
"path"
"strings"
@ -63,3 +64,17 @@ func RelativePathToKey(relativePath string) (BlobHash256Key, error) {
return BlobHash256Key(parts[2]), nil
}
// Save data with specified string key.
func (s *ContentStore) SaveStrBytes(key string, data []byte) error {
return s.Save(BlobHash256Key(key), bytes.NewReader(data), int64(len(data)))
}
// Get data related to provided key.
func (s *ContentStore) GetStrBytes(key string) ([]byte, error) {
obj, err := s.Get(BlobHash256Key(key))
if err != nil {
return nil, err
}
return io.ReadAll(obj)
}

View File

@ -4,43 +4,27 @@
package arch
import (
"bytes"
"encoding/hex"
"errors"
"fmt"
"io"
"net/http"
"os"
"path"
"strings"
"code.gitea.io/gitea/models/db"
packages_model "code.gitea.io/gitea/models/packages"
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/json"
packages_module "code.gitea.io/gitea/modules/packages"
arch_module "code.gitea.io/gitea/modules/packages/arch"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/routers/api/packages/helper"
packages_service "code.gitea.io/gitea/services/packages"
"github.com/ProtonMail/gopenpgp/v2/crypto"
"github.com/google/uuid"
arch_service "code.gitea.io/gitea/services/packages/arch"
)
// Push new package to arch package registry.
func Push(ctx *context.Context) {
// Creating connector that will help with keys/blobs.
connector := Connector{ctx: ctx}
// Getting some information related to package from headers.
filename := ctx.Req.Header.Get("filename")
email := ctx.Req.Header.Get("email")
sign := ctx.Req.Header.Get("sign")
owner := ctx.Req.Header.Get("owner")
distro := ctx.Req.Header.Get("distro")
var (
filename = ctx.Req.Header.Get("filename")
email = ctx.Req.Header.Get("email")
sign = ctx.Req.Header.Get("sign")
owner = ctx.Req.Header.Get("owner")
distro = ctx.Req.Header.Get("distro")
)
// Decoding package signature.
sigdata, err := hex.DecodeString(sign)
@ -48,49 +32,6 @@ func Push(ctx *context.Context) {
apiError(ctx, http.StatusBadRequest, err)
return
}
pgpsig := crypto.NewPGPSignature(sigdata)
// Validating that user is allowed to push to specified namespace.
err = connector.ValidateNamespace(owner, email)
if err != nil {
apiError(ctx, http.StatusBadRequest, err)
return
}
// Getting GPG keys related to specific user. After keys have been recieved,
// this function will find one key related to email provided in request.
armoredKeys, err := connector.GetValidKeys(email)
if err != nil {
apiError(ctx, http.StatusBadRequest, err)
return
}
var matchedKeyring *crypto.KeyRing
for _, armor := range armoredKeys {
pgpkey, err := crypto.NewKeyFromArmored(armor)
if err != nil {
apiError(ctx, http.StatusBadRequest, err)
return
}
keyring, err := crypto.NewKeyRing(pgpkey)
if err != nil {
apiError(ctx, http.StatusBadRequest, err)
return
}
for _, idnt := range keyring.GetIdentities() {
if idnt.Email == email {
matchedKeyring = keyring
break
}
}
if matchedKeyring != nil {
break
}
}
if matchedKeyring == nil {
msg := "GPG key related to " + email + " not found"
apiError(ctx, http.StatusBadRequest, msg)
return
}
// Read package to memory and create plain GPG message to validate signature.
pkgdata, err := io.ReadAll(ctx.Req.Body)
@ -100,23 +41,19 @@ func Push(ctx *context.Context) {
}
defer ctx.Req.Body.Close()
pgpmes := crypto.NewPlainMessage(pkgdata)
// Validate package signature with user's GPG key related to his email.
err = matchedKeyring.VerifyDetached(pgpmes, pgpsig, crypto.GetUnixTime())
// Get user and organization owning arch package.
user, org, err := arch_service.IdentifyOwner(ctx, owner, email)
if err != nil {
apiError(ctx, http.StatusUnauthorized, "unable to validate package signature")
apiError(ctx, http.StatusUnauthorized, err)
return
}
// Create temporary directory for arch database operations.
tmpdir := path.Join(setting.Repository.Upload.TempPath, uuid.New().String())
err = os.MkdirAll(tmpdir, os.ModePerm)
// Validate package signature with user's GnuPG key.
err = arch_service.ValidatePackageSignature(ctx, pkgdata, sigdata, user)
if err != nil {
apiError(ctx, http.StatusInternalServerError, "unable to create tmp path")
apiError(ctx, http.StatusUnauthorized, err)
return
}
defer os.RemoveAll(tmpdir)
// Parse metadata contained in arch package archive.
md, err := arch_module.EjectMetadata(filename, setting.Domain, pkgdata)
@ -125,211 +62,43 @@ func Push(ctx *context.Context) {
return
}
// Arch database related filenames, pathes and folders.
dbname := Join(owner, distro, setting.Domain, "db.tar.gz")
dbpath := path.Join(tmpdir, dbname)
dbfolder := path.Join(tmpdir, dbname) + ".folder"
dbsymlink := strings.TrimSuffix(dbname, ".tar.gz")
dbsymlinkpath := path.Join(tmpdir, dbsymlink)
// Get existing arch package database, related to specific userspace from
// file storage, and save it on disk, then unpack it's contents to related
// folder. If database is not found in storage, create empty directory to
// store package related information.
dbdata, err := connector.Get(dbname)
if err == nil {
err = os.WriteFile(dbpath, dbdata, os.ModePerm)
if err != nil {
apiError(ctx, http.StatusInternalServerError, err)
return
}
err = arch_module.UnpackDb(dbpath, dbfolder)
if err != nil {
apiError(ctx, http.StatusInternalServerError, err)
return
}
}
if err != nil {
err = os.MkdirAll(dbfolder, os.ModePerm)
if err != nil {
apiError(ctx, http.StatusInternalServerError, err)
return
}
}
// Update database folder with metadata for new package.
err = md.PutToDb(dbfolder, os.ModePerm)
// Get package property from DB if exists/create new one.
dbpkg, err := arch_service.CreateGetPackage(ctx, org, md.Name)
if err != nil {
apiError(ctx, http.StatusInternalServerError, err)
return
}
// Create database archive and related symlink.
err = arch_module.PackDb(dbfolder, dbpath)
// Create or get package version from DB if exists/create new one.
dbpkgver, err := arch_service.CreateGetPackageVersion(ctx, md, dbpkg, user)
if err != nil {
apiError(ctx, http.StatusInternalServerError, err)
return
}
// Save namespace related arch repository database.
f, err := os.Open(dbpath)
if err != nil {
apiError(ctx, http.StatusInternalServerError, err)
return
}
defer f.Close()
dbfi, err := f.Stat()
if err != nil {
apiError(ctx, http.StatusInternalServerError, err)
return
}
err = connector.Save(dbname, f, dbfi.Size())
// Automatically connect repository for provided package if name matched.
err = arch_service.RepositoryAutoconnect(ctx, owner, md.Name, dbpkg)
if err != nil {
apiError(ctx, http.StatusInternalServerError, err)
return
}
// Save namespace related arch repository db archive.
f, err = os.Open(dbsymlinkpath)
if err != nil {
apiError(ctx, http.StatusInternalServerError, err)
return
}
defer f.Close()
dbarchivefi, err := f.Stat()
if err != nil {
apiError(ctx, http.StatusInternalServerError, err)
return
}
err = connector.Save(dbsymlink, f, dbarchivefi.Size())
// Save package file data to gitea storage and update database.
err = arch_service.SavePackageFile(ctx, pkgdata, distro, filename, dbpkgver.ID)
if err != nil {
apiError(ctx, http.StatusInternalServerError, err)
return
}
// Create package in database.
pkg, err := packages_model.TryInsertPackage(ctx, &packages_model.Package{
OwnerID: connector.org.ID,
Type: packages_model.TypeArch,
Name: md.Name,
LowerName: strings.ToLower(md.Name),
})
if errors.Is(err, packages_model.ErrDuplicatePackage) {
pkg, err = packages_model.GetPackageByName(
ctx, connector.org.ID,
packages_model.TypeArch, md.Name,
)
if err != nil {
apiError(ctx, http.StatusInternalServerError, err)
return
}
}
// Save package signature data to gitea storage and update database.
err = arch_service.SavePackageFile(ctx, sigdata, distro, filename+".sig", dbpkgver.ID)
if err != nil {
apiError(ctx, http.StatusInternalServerError, err)
return
}
// Check if repository for package with provided owner exists.
repo, err := repo_model.GetRepositoryByOwnerAndName(ctx, owner, md.Name)
if err == nil {
err = packages_model.SetRepositoryLink(ctx, pkg.ID, repo.ID)
if err != nil {
apiError(ctx, http.StatusInternalServerError, err)
return
}
}
// Create new package version in database.
rawjsonmetadata, err := json.Marshal(&md)
if err != nil {
apiError(ctx, http.StatusInternalServerError, err)
return
}
ver, err := packages_model.GetOrInsertVersion(ctx, &packages_model.PackageVersion{
PackageID: pkg.ID,
CreatorID: connector.org.ID,
Version: md.Version,
LowerVersion: strings.ToLower(md.Version),
CreatedUnix: timeutil.TimeStampNow(),
MetadataJSON: string(rawjsonmetadata),
})
if err != nil {
if errors.Is(err, packages_model.ErrDuplicatePackageVersion) {
apiError(ctx, http.StatusConflict, err)
return
}
apiError(ctx, http.StatusInternalServerError, err)
return
}
// Create package blob and db file for package file.
pkgreader := bytes.NewReader(pkgdata)
fbuf, err := packages_module.CreateHashedBufferFromReader(pkgreader)
if err != nil {
apiError(ctx, http.StatusInternalServerError, err)
return
}
defer fbuf.Close()
filepb, ok, err := packages_model.GetOrInsertBlob(
ctx, packages_service.NewPackageBlob(fbuf),
)
if err != nil {
apiError(ctx, http.StatusInternalServerError, fmt.Errorf("%v %t", err, ok))
return
}
err = connector.Save(filepb.HashSHA256, fbuf, filepb.Size)
if err != nil {
apiError(ctx, http.StatusInternalServerError, err)
return
}
_, err = packages_model.TryInsertFile(ctx, &packages_model.PackageFile{
VersionID: ver.ID,
BlobID: filepb.ID,
Name: filename,
LowerName: strings.ToLower(filename),
CompositeKey: distro + "-" + filename,
IsLead: true,
CreatedUnix: timeutil.TimeStampNow(),
})
if err != nil {
apiError(ctx, http.StatusInternalServerError, err)
return
}
// Create package blob for package signature.
sigreader := bytes.NewReader(sigdata)
sbuf, err := packages_module.CreateHashedBufferFromReader(sigreader)
if err != nil {
apiError(ctx, http.StatusInternalServerError, err)
return
}
defer fbuf.Close()
sigpb, ok, err := packages_model.GetOrInsertBlob(
ctx, packages_service.NewPackageBlob(sbuf),
)
if err != nil {
apiError(ctx, http.StatusInternalServerError, fmt.Errorf("%v %t", err, ok))
return
}
err = connector.Save(sigpb.HashSHA256, sbuf, sigpb.Size)
if err != nil {
apiError(ctx, http.StatusInternalServerError, err)
return
}
_, err = packages_model.TryInsertFile(ctx, &packages_model.PackageFile{
VersionID: ver.ID,
BlobID: sigpb.ID,
Name: filename + ".sig",
LowerName: strings.ToLower(filename + ".sig"),
CompositeKey: distro + "-" + filename + ".sig",
IsLead: false,
CreatedUnix: timeutil.TimeStampNow(),
})
// Update pacman databases with new package.
err = arch_service.UpdatePacmanDatabases(ctx, md, distro, owner)
if err != nil {
apiError(ctx, http.StatusInternalServerError, err)
return
@ -340,68 +109,41 @@ func Push(ctx *context.Context) {
// Get file from arch package registry.
func Get(ctx *context.Context) {
filename := ctx.Params("file")
owner := ctx.Params("owner")
distro := ctx.Params("distro")
// arch := ctx.Params("arch")
var (
file = ctx.Params("file")
owner = ctx.Params("owner")
distro = ctx.Params("distro")
arch = ctx.Params("arch")
)
cs := packages_module.NewContentStore()
if strings.HasSuffix(filename, "tar.zst") || strings.HasSuffix(filename, "zst.sig") {
db := db.GetEngine(ctx)
pkgfile := &packages_model.PackageFile{
CompositeKey: distro + "-" + filename,
}
ok, err := db.Get(pkgfile)
if err != nil || !ok {
apiError(
ctx, http.StatusInternalServerError,
fmt.Errorf("%+v %t", err, ok),
)
return
}
blob, err := packages_model.GetBlobByID(ctx, pkgfile.BlobID)
if err != nil {
apiError(ctx, http.StatusInternalServerError, err)
return
}
obj, err := cs.Get(packages_module.BlobHash256Key(blob.HashSHA256))
if err != nil {
apiError(ctx, http.StatusInternalServerError, err)
return
}
data, err := io.ReadAll(obj)
if err != nil {
apiError(ctx, http.StatusInternalServerError, err)
return
}
_, err = ctx.Resp.Write(data)
if err != nil {
apiError(ctx, http.StatusInternalServerError, err)
return
}
ctx.Resp.WriteHeader(http.StatusOK)
return
}
if strings.HasSuffix(filename, ".db.tar.gz") || strings.HasSuffix(filename, ".db") {
filename = strings.TrimPrefix(filename, owner+".")
obj, err := cs.Get(packages_module.BlobHash256Key(Join(owner, distro, filename)))
// Packages are stored in different way from pacman databases, and loaded
// with LoadPackageFile function.
if strings.HasSuffix(file, "tar.zst") || strings.HasSuffix(file, "zst.sig") {
pkgdata, err := arch_service.LoadPackageFile(ctx, distro, file)
if err != nil {
apiError(ctx, http.StatusNotFound, err)
return
}
data, err := io.ReadAll(obj)
_, err = ctx.Resp.Write(pkgdata)
if err != nil {
apiError(ctx, http.StatusInternalServerError, err)
return
}
ctx.Resp.WriteHeader(http.StatusOK)
return
}
// Pacman databases are stored directly in gitea file storage and could be
// loaded with name as a key.
if strings.HasSuffix(file, ".db.tar.gz") || strings.HasSuffix(file, ".db") {
data, err := arch_service.LoadPacmanDatabase(ctx, owner, distro, arch, file)
if err != nil {
apiError(ctx, http.StatusNotFound, err)
return
}
_, err = ctx.Resp.Write(data)
if err != nil {
apiError(ctx, http.StatusInternalServerError, err)

View File

@ -1,133 +0,0 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package arch
import (
"errors"
"io"
"code.gitea.io/gitea/models/asymkey"
"code.gitea.io/gitea/models/db"
organization_model "code.gitea.io/gitea/models/organization"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/log"
packages_module "code.gitea.io/gitea/modules/packages"
)
// Connector helps to retrieve GPG keys related to package validation and
// manage blobs related to specific user spaces:
// 1 - Check if user is allowed to push package to specific namespace.
// 2 - Retrieving GPG keys related to provided email.
// 3 - Get/put arch arch package/signature/database files to connected file
// storage.
type Connector struct {
ctx *context.Context
user *user_model.User
org *organization_model.Organization
}
// This function will find user related to provided email adress and check if
// he is able to push packages to provided namespace (user/organization/or
// empty namespace allowed for admin users).
func (c *Connector) ValidateNamespace(namespace, email string) error {
var err error
c.user, err = user_model.GetUserByEmail(c.ctx, email)
if err != nil {
log.Error("unable to get user with email: %s %v", email, err)
return err
}
if namespace == "" && c.user.IsAdmin {
c.org = (*organization_model.Organization)(c.user)
return nil
}
if c.user.Name != namespace && c.org == nil {
c.org, err = organization_model.GetOrgByName(c.ctx, namespace)
if err != nil {
log.Error("unable to organization: %s %v", namespace, err)
return err
}
ismember, err := c.org.IsOrgMember(c.user.ID)
if err != nil {
log.Error(
"unable to check if user belongs to organization: %s %s %v",
c.user.Name, email, err,
)
return err
}
if !ismember {
log.Error("user %s is not member of organization: %s", c.user.Name, email)
return errors.New("user is not member of organization: " + namespace)
}
} else {
c.org = (*organization_model.Organization)(c.user)
}
return nil
}
// This function will try to find user related to specific email. And check
// that user is allowed to push to 'owner' namespace (package owner, could
// be empty, user or organization).
// After namespace check, this function
func (c *Connector) GetValidKeys(email string) ([]string, error) {
keys, err := asymkey.ListGPGKeys(c.ctx, c.user.ID, db.ListOptions{
ListAll: true,
})
if err != nil {
log.Error("unable to get keys related to user: %v", err)
return nil, errors.New("unable to get public keys")
}
if len(keys) == 0 {
log.Error("no keys related to user")
return nil, errors.New("no keys for user with email: " + email)
}
var keyarmors []string
for _, key := range keys {
k, err := asymkey.GetGPGImportByKeyID(key.KeyID)
if err != nil {
log.Error("unable to import GPG key by ID: %v", err)
return nil, errors.New("internal error")
}
keyarmors = append(keyarmors, k.Content)
}
return keyarmors, nil
}
// Get specific file content from content storage.
func (c *Connector) Get(key string) ([]byte, error) {
cs := packages_module.NewContentStore()
obj, err := cs.Get(packages_module.BlobHash256Key(key))
if err != nil {
return nil, err
}
return io.ReadAll(obj)
}
// Save contents related to specific arch package.
func (c *Connector) Save(key string, content io.Reader, size int64) error {
cs := packages_module.NewContentStore()
return cs.Save(packages_module.BlobHash256Key(key), content, size)
}
// Join database or package names to prevent collisions with same packages in
// different user spaces. Skips empty strings and returns name joined with
// dots.
func Join(s ...string) string {
rez := ""
for i, v := range s {
if v == "" {
continue
}
if i+1 == len(s) {
rez += v
continue
}
rez += v + "."
}
return rez
}

View File

@ -0,0 +1,77 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package arch
import (
"errors"
"fmt"
"strings"
org "code.gitea.io/gitea/models/organization"
pkg "code.gitea.io/gitea/models/packages"
"code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/json"
"code.gitea.io/gitea/modules/packages/arch"
"code.gitea.io/gitea/modules/timeutil"
)
// This function will create new package in database, if it does not exist it
// will get existing and return it back to user.
func CreateGetPackage(ctx *context.Context, o *org.Organization, name string) (*pkg.Package, error) {
pack, err := pkg.TryInsertPackage(ctx, &pkg.Package{
OwnerID: o.ID,
Type: pkg.TypeArch,
Name: name,
LowerName: strings.ToLower(name),
})
if errors.Is(err, pkg.ErrDuplicatePackage) {
pack, err = pkg.GetPackageByName(ctx, o.ID, pkg.TypeArch, name)
if err != nil {
return nil, fmt.Errorf("unable to get package %s in organization %s", name, o.Name)
}
}
if err != nil {
return nil, err
}
return pack, nil
}
// This function will create new version for package, or find and return existing.
func CreateGetPackageVersion(ctx *context.Context, md *arch.Metadata, p *pkg.Package, u *user.User) (*pkg.PackageVersion, error) {
rawjsonmetadata, err := json.Marshal(&md)
if err != nil {
return nil, err
}
ver, err := pkg.GetOrInsertVersion(ctx, &pkg.PackageVersion{
PackageID: p.ID,
CreatorID: u.ID,
Version: md.Version,
LowerVersion: strings.ToLower(md.Version),
CreatedUnix: timeutil.TimeStampNow(),
MetadataJSON: string(rawjsonmetadata),
})
if err != nil {
if errors.Is(err, pkg.ErrDuplicatePackageVersion) {
return ver, nil
}
return nil, err
}
return ver, nil
}
// Automatically connect repository to pushed package, if package with provided
// with provided name exists in namespace scope.
func RepositoryAutoconnect(ctx *context.Context, owner, repository string, p *pkg.Package) error {
repo, err := repo.GetRepositoryByOwnerAndName(ctx, owner, repository)
if err == nil {
err = pkg.SetRepositoryLink(ctx, p.ID, repo.ID)
if err != nil {
return err
}
}
return nil
}

View File

@ -0,0 +1,190 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package arch
import (
"bytes"
"fmt"
"io"
"os"
"path"
"strings"
"code.gitea.io/gitea/models/db"
pkg_mdl "code.gitea.io/gitea/models/packages"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/packages"
"code.gitea.io/gitea/modules/packages/arch"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/timeutil"
pkg_svc "code.gitea.io/gitea/services/packages"
"github.com/google/uuid"
)
// Save package file to content store for the provided version id and specified distribution.
func SavePackageFile(ctx *context.Context, data []byte, distro, filename string, pkgverid int64) error {
buf, err := packages.CreateHashedBufferFromReader(bytes.NewReader(data))
if err != nil {
return err
}
defer buf.Close()
blob, _, err := pkg_mdl.GetOrInsertBlob(ctx, pkg_svc.NewPackageBlob(buf))
if err != nil {
return err
}
cs := packages.NewContentStore()
err = cs.Save(packages.BlobHash256Key(blob.HashSHA256), buf, blob.Size)
if err != nil {
return err
}
_, err = pkg_mdl.TryInsertFile(ctx, &pkg_mdl.PackageFile{
VersionID: pkgverid,
BlobID: blob.ID,
Name: filename,
LowerName: strings.ToLower(filename),
CompositeKey: distro + "-" + filename,
CreatedUnix: timeutil.TimeStampNow(),
})
return err
}
// Get data related to provided file name and distribution.
func LoadPackageFile(ctx *context.Context, distro, file string) ([]byte, error) {
db := db.GetEngine(ctx)
pkgfile := &pkg_mdl.PackageFile{CompositeKey: distro + "-" + file}
ok, err := db.Get(pkgfile)
if err != nil || !ok {
return nil, fmt.Errorf("%+v %t", err, ok)
}
blob, err := pkg_mdl.GetBlobByID(ctx, pkgfile.BlobID)
if err != nil {
return nil, err
}
cs := packages.NewContentStore()
obj, err := cs.Get(packages.BlobHash256Key(blob.HashSHA256))
if err != nil {
return nil, err
}
return io.ReadAll(obj)
}
// Get data related to pacman database file or symlink.
func LoadPacmanDatabase(ctx *context.Context, owner, distro, architecture, file string) ([]byte, error) {
cs := packages.NewContentStore()
file = strings.TrimPrefix(file, owner+".")
obj, err := cs.Get(packages.BlobHash256Key(arch.Join(owner, distro, architecture, file)))
if err != nil {
return nil, err
}
return io.ReadAll(obj)
}
// This function will update information about package in related pacman databases
// or create them if they do not exist.
func UpdatePacmanDatabases(ctx *context.Context, md *arch.Metadata, distro, owner string) error {
// Create temporary directory for arch database operations.
tmpdir := path.Join(setting.Repository.Upload.TempPath, uuid.New().String())
err := os.MkdirAll(tmpdir, os.ModePerm)
if err != nil {
return err
}
defer os.RemoveAll(tmpdir)
// If architecure is not specified or any, package will be automatically
// saved to databases with most popular architectures.
var architectures = md.Arch
if len(md.Arch) == 0 || md.Arch[0] == "any" {
architectures = []string{
"x86_64", "arm", "i686", "pentium4",
"armv7h", "armv6h", "aarch64", "riscv64",
}
}
cs := packages.NewContentStore()
for _, architecture := range architectures {
var (
db = arch.Join(owner, distro, architecture, setting.Domain, "db.tar.gz")
dbpth = path.Join(tmpdir, db)
dbf = path.Join(tmpdir, db) + ".folder"
sbsl = strings.TrimSuffix(db, ".tar.gz")
slpth = path.Join(tmpdir, sbsl)
)
// Get existing pacman database, or create empty folder for it.
dbdata, err := cs.GetStrBytes(db)
if err == nil {
err = os.WriteFile(dbpth, dbdata, os.ModePerm)
if err != nil {
return err
}
err = arch.UnpackDb(dbpth, dbf)
if err != nil {
return err
}
}
if err != nil {
err = os.MkdirAll(dbf, os.ModePerm)
if err != nil {
return err
}
}
// Update database folder with metadata for new package.
err = md.PutToDb(dbf, os.ModePerm)
if err != nil {
return err
}
// Create database archive and related symlink.
err = arch.PackDb(dbf, dbpth)
if err != nil {
return err
}
// Save database file.
f, err := os.Open(dbpth)
if err != nil {
return err
}
defer f.Close()
dbfi, err := f.Stat()
if err != nil {
return err
}
err = cs.Save(packages.BlobHash256Key(db), f, dbfi.Size())
if err != nil {
return err
}
// Save database symlink file.
f, err = os.Open(slpth)
if err != nil {
return err
}
defer f.Close()
dbarchivefi, err := f.Stat()
if err != nil {
return err
}
err = cs.Save(packages.BlobHash256Key(sbsl), f, dbarchivefi.Size())
if err != nil {
return err
}
}
return nil
}

View File

@ -0,0 +1,110 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package arch
import (
"errors"
"fmt"
"code.gitea.io/gitea/models/asymkey"
"code.gitea.io/gitea/models/db"
org "code.gitea.io/gitea/models/organization"
"code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/context"
"github.com/ProtonMail/gopenpgp/v2/crypto"
)
type IdentidyOwnerParameters struct {
*context.Context
Owner string
Email string
}
// This function will find user related to provided email adress and check if
// he is able to push packages to provided namespace (user/organization/or
// empty namespace allowed for admin users). Function will return user making
// operation, organization or user owning the package.
func IdentifyOwner(ctx *context.Context, owner, email string) (*user.User, *org.Organization, error) {
u, err := user.GetUserByEmail(ctx, email)
if err != nil {
return nil, nil, fmt.Errorf("unable to find user with email %s, %v", email, err)
}
if owner == "" && u.IsAdmin {
return u, (*org.Organization)(u), nil
}
if owner == u.Name {
return u, (*org.Organization)(u), nil
}
if u.Name != owner {
org, err := org.GetOrgByName(ctx, owner)
if err != nil {
return nil, nil, fmt.Errorf("unable to get organization: %s, %v", owner, err)
}
ismember, err := org.IsOrgMember(u.ID)
if err != nil {
return nil, nil, fmt.Errorf("unable to check if user %s belongs to organization %s: %v", u.Name, org.Name, err)
}
if !ismember {
return nil, nil, fmt.Errorf("user %s is not member of organization %s", u.Name, org.Name)
}
return u, org, nil
}
return nil, nil, fmt.Errorf("unknown package owner")
}
// Validate package signature with owner's GnuPG keys stored in gitea's database.
func ValidatePackageSignature(ctx *context.Context, pkg, sign []byte, u *user.User) error {
keys, err := asymkey.ListGPGKeys(ctx, u.ID, db.ListOptions{
ListAll: true,
})
if err != nil {
return errors.New("unable to get public keys")
}
if len(keys) == 0 {
return errors.New("no keys for user with email: " + u.Email)
}
var keyarmors []string
for _, key := range keys {
k, err := asymkey.GetGPGImportByKeyID(key.KeyID)
if err != nil {
return errors.New("unable to import GPG key armor")
}
keyarmors = append(keyarmors, k.Content)
}
var matchedKeyring *crypto.KeyRing
for _, armor := range keyarmors {
pgpkey, err := crypto.NewKeyFromArmored(armor)
if err != nil {
return fmt.Errorf("unable to get keys for %s: %v", u.Name, err)
}
keyring, err := crypto.NewKeyRing(pgpkey)
if err != nil {
return fmt.Errorf("unable to form keyring %s: %v", u.Name, err)
}
for _, idnt := range keyring.GetIdentities() {
if idnt.Email == u.Email {
matchedKeyring = keyring
break
}
}
if matchedKeyring != nil {
break
}
}
if matchedKeyring == nil {
return fmt.Errorf("GPG key related to %s not found", u.Email)
}
var (
pgpmes = crypto.NewPlainMessage(pkg)
pgpsig = crypto.NewPGPSignature(sign)
)
return matchedKeyring.VerifyDetached(pgpmes, pgpsig, crypto.GetUnixTime())
}