mirror of https://github.com/harness/drone.git
245 lines
8.3 KiB
Go
245 lines
8.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 generic
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"net/http"
|
|
"regexp"
|
|
"strings"
|
|
|
|
usercontroller "github.com/harness/gitness/app/api/controller/user"
|
|
"github.com/harness/gitness/app/auth/authn"
|
|
"github.com/harness/gitness/app/auth/authz"
|
|
corestore "github.com/harness/gitness/app/store"
|
|
urlprovider "github.com/harness/gitness/app/url"
|
|
"github.com/harness/gitness/registry/app/api/controller/metadata"
|
|
"github.com/harness/gitness/registry/app/api/handler/utils"
|
|
artifact2 "github.com/harness/gitness/registry/app/api/openapi/contracts/artifact"
|
|
"github.com/harness/gitness/registry/app/dist_temp/errcode"
|
|
"github.com/harness/gitness/registry/app/pkg"
|
|
"github.com/harness/gitness/registry/app/pkg/commons"
|
|
"github.com/harness/gitness/registry/app/pkg/generic"
|
|
|
|
"github.com/rs/zerolog/log"
|
|
)
|
|
|
|
const (
|
|
packageNameRegex = `^[a-zA-Z0-9][a-zA-Z0-9._-]*[a-zA-Z0-9]$`
|
|
versionRegex = `^[a-z0-9][a-z0-9.-]*[a-z0-9]$`
|
|
filenameRegex = `^[a-zA-Z0-9][a-zA-Z0-9._~@,/-]*[a-zA-Z0-9]$`
|
|
// Add other route types here.
|
|
)
|
|
|
|
func NewGenericArtifactHandler(
|
|
spaceStore corestore.SpaceStore, controller *generic.Controller, tokenStore corestore.TokenStore,
|
|
userCtrl *usercontroller.Controller, authenticator authn.Authenticator, urlProvider urlprovider.Provider,
|
|
authorizer authz.Authorizer,
|
|
) *Handler {
|
|
return &Handler{
|
|
Controller: controller,
|
|
SpaceStore: spaceStore,
|
|
TokenStore: tokenStore,
|
|
UserCtrl: userCtrl,
|
|
Authenticator: authenticator,
|
|
URLProvider: urlProvider,
|
|
Authorizer: authorizer,
|
|
}
|
|
}
|
|
|
|
type Handler struct {
|
|
Controller *generic.Controller
|
|
SpaceStore corestore.SpaceStore
|
|
TokenStore corestore.TokenStore
|
|
UserCtrl *usercontroller.Controller
|
|
Authenticator authn.Authenticator
|
|
URLProvider urlprovider.Provider
|
|
Authorizer authz.Authorizer
|
|
}
|
|
|
|
func (h *Handler) GetArtifactInfo(r *http.Request) (pkg.GenericArtifactInfo, errcode.Error) {
|
|
ctx := r.Context()
|
|
path := r.URL.Path
|
|
rootIdentifier, registryIdentifier, artifact, tag, fileName, description, err := ExtractPathVars(r)
|
|
|
|
if err != nil {
|
|
return pkg.GenericArtifactInfo{}, errcode.ErrCodeInvalidRequest.WithDetail(err)
|
|
}
|
|
|
|
if err := metadata.ValidateIdentifier(registryIdentifier); err != nil {
|
|
return pkg.GenericArtifactInfo{}, errcode.ErrCodeInvalidRequest.WithDetail(err)
|
|
}
|
|
|
|
if err := validatePackageVersionAndFileName(artifact, tag, fileName); err != nil {
|
|
return pkg.GenericArtifactInfo{}, errcode.ErrCodeInvalidRequest.WithDetail(err)
|
|
}
|
|
|
|
rootSpace, err := h.SpaceStore.FindByRefCaseInsensitive(ctx, rootIdentifier)
|
|
if err != nil {
|
|
log.Ctx(ctx).Error().Msgf("Root space not found: %s", rootIdentifier)
|
|
return pkg.GenericArtifactInfo{}, errcode.ErrCodeRootNotFound.WithDetail(err)
|
|
}
|
|
|
|
registry, err := h.Controller.DBStore.RegistryDao.GetByRootParentIDAndName(ctx, rootSpace.ID, registryIdentifier)
|
|
if err != nil {
|
|
log.Ctx(ctx).Error().Msgf(
|
|
"registry %s not found for root: %s. Reason: %s", registryIdentifier, rootSpace.Identifier, err,
|
|
)
|
|
return pkg.GenericArtifactInfo{}, errcode.ErrCodeRegNotFound.WithDetail(err)
|
|
}
|
|
|
|
if registry.PackageType != artifact2.PackageTypeGENERIC {
|
|
log.Ctx(ctx).Error().Msgf(
|
|
"registry %s is not a generic artifact registry for root: %s", registryIdentifier, rootSpace.Identifier,
|
|
)
|
|
return pkg.GenericArtifactInfo{}, errcode.ErrCodeInvalidRequest.WithDetail(fmt.Errorf("registry %s is"+
|
|
" not a generic artifact registry", registryIdentifier))
|
|
}
|
|
|
|
_, err = h.SpaceStore.Find(r.Context(), registry.ParentID)
|
|
if err != nil {
|
|
log.Ctx(ctx).Error().Msgf("Parent space not found: %d", registry.ParentID)
|
|
return pkg.GenericArtifactInfo{}, errcode.ErrCodeParentNotFound.WithDetail(err)
|
|
}
|
|
|
|
info := &pkg.GenericArtifactInfo{
|
|
ArtifactInfo: &pkg.ArtifactInfo{
|
|
BaseInfo: &pkg.BaseInfo{
|
|
RootIdentifier: rootIdentifier,
|
|
RootParentID: rootSpace.ID,
|
|
ParentID: registry.ParentID,
|
|
},
|
|
RegIdentifier: registryIdentifier,
|
|
Image: artifact,
|
|
},
|
|
RegistryID: registry.ID,
|
|
Version: tag,
|
|
FileName: fileName,
|
|
Description: description,
|
|
}
|
|
|
|
log.Ctx(ctx).Info().Msgf("Dispatch: URI: %s", path)
|
|
if commons.IsEmpty(rootSpace.Identifier) {
|
|
log.Ctx(ctx).Error().Msgf("ParentRef not found in context")
|
|
return pkg.GenericArtifactInfo{}, errcode.ErrCodeParentNotFound.WithDetail(err)
|
|
}
|
|
|
|
if commons.IsEmpty(registryIdentifier) {
|
|
log.Ctx(ctx).Warn().Msgf("registry not found in context")
|
|
return pkg.GenericArtifactInfo{}, errcode.ErrCodeRegNotFound.WithDetail(err)
|
|
}
|
|
|
|
if !commons.IsEmpty(info.Image) && !commons.IsEmpty(info.Version) && !commons.IsEmpty(info.FileName) {
|
|
flag, err2 := utils.IsPatternAllowed(registry.AllowedPattern, registry.BlockedPattern,
|
|
info.Image+":"+info.Version+":"+info.FileName)
|
|
if !flag || err2 != nil {
|
|
return pkg.GenericArtifactInfo{}, errcode.ErrCodeInvalidRequest.WithDetail(err2)
|
|
}
|
|
}
|
|
|
|
return *info, errcode.Error{}
|
|
}
|
|
|
|
// ExtractPathVars extracts registry,image, reference, digest and tag from the path
|
|
// Path format: /generic/:rootSpace/:registry/:image/:tag (for ex:
|
|
// /generic/myRootSpace/reg1/alpine/v1).
|
|
func ExtractPathVars(r *http.Request) (
|
|
rootIdentifier, registry, artifact,
|
|
tag, fileName string, description string, err error,
|
|
) {
|
|
path := r.URL.Path
|
|
|
|
// Ensure the path starts with "/generic/"
|
|
if !strings.HasPrefix(path, "/generic/") {
|
|
return "", "", "", "", "", "", fmt.Errorf("invalid path: must start with /generic/")
|
|
}
|
|
|
|
trimmedPath := strings.TrimPrefix(path, "/generic/")
|
|
firstSlashIndex := strings.Index(trimmedPath, "/")
|
|
if firstSlashIndex == -1 {
|
|
return "", "", "", "", "", "", fmt.Errorf("invalid path format: missing rootIdentifier or registry")
|
|
}
|
|
rootIdentifier = trimmedPath[:firstSlashIndex]
|
|
|
|
remainingPath := trimmedPath[firstSlashIndex+1:]
|
|
secondSlashIndex := strings.Index(remainingPath, "/")
|
|
if secondSlashIndex == -1 {
|
|
return "", "", "", "", "", "", fmt.Errorf("invalid path format: missing registry")
|
|
}
|
|
registry = remainingPath[:secondSlashIndex]
|
|
|
|
// Extract the artifact and tag from the remaining path
|
|
artifactPath := remainingPath[secondSlashIndex+1:]
|
|
|
|
// Check if the artifactPath contains a ":" for tag and filename
|
|
if strings.Contains(artifactPath, ":") {
|
|
segments := strings.SplitN(artifactPath, ":", 3)
|
|
if len(segments) < 3 {
|
|
return "", "", "", "", "", "", fmt.Errorf("invalid artifact format: %s", artifactPath)
|
|
}
|
|
artifact = segments[0]
|
|
tag = segments[1]
|
|
fileName = segments[2]
|
|
} else {
|
|
segments := strings.SplitN(artifactPath, "/", 2)
|
|
if len(segments) < 2 {
|
|
return "", "", "", "", "", "", fmt.Errorf("invalid artifact format: %s", artifactPath)
|
|
}
|
|
artifact = segments[0]
|
|
tag = segments[1]
|
|
|
|
fileName = r.FormValue("filename")
|
|
if fileName == "" {
|
|
return "", "", "", "", "", "", fmt.Errorf("filename not provided in path or form parameter")
|
|
}
|
|
}
|
|
description = r.FormValue("description")
|
|
|
|
return rootIdentifier, registry, artifact, tag, fileName, description, nil
|
|
}
|
|
|
|
func handleErrors(ctx context.Context, err errcode.Error, w http.ResponseWriter) {
|
|
if !commons.IsEmptyError(err) {
|
|
w.WriteHeader(err.Code.Descriptor().HTTPStatusCode)
|
|
_ = errcode.ServeJSON(w, err)
|
|
log.Ctx(ctx).Error().Msgf("Error occurred while performing generic artifact action: %s", err.Message)
|
|
}
|
|
}
|
|
|
|
func validatePackageVersionAndFileName(packageName, version, filename string) error {
|
|
// Compile the regular expressions
|
|
packageNameRe := regexp.MustCompile(packageNameRegex)
|
|
versionRe := regexp.MustCompile(versionRegex)
|
|
filenameRe := regexp.MustCompile(filenameRegex)
|
|
|
|
// Validate package name
|
|
if !packageNameRe.MatchString(packageName) {
|
|
return fmt.Errorf("invalid package name: %s", packageName)
|
|
}
|
|
|
|
// Validate version
|
|
if !versionRe.MatchString(version) {
|
|
return fmt.Errorf("invalid version: %s", version)
|
|
}
|
|
|
|
// Validate filename
|
|
if !filenameRe.MatchString(filename) {
|
|
return fmt.Errorf("invalid filename: %s", filename)
|
|
}
|
|
|
|
return nil
|
|
}
|