123 lines
3.6 KiB
Go
123 lines
3.6 KiB
Go
package github
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"slices"
|
|
"strings"
|
|
|
|
"github.com/kandev/kandev/internal/auth/authn"
|
|
)
|
|
|
|
var (
|
|
ErrDeploymentAppOperatorRequired = errors.New("deployment GitHub App operator access is required")
|
|
ErrDeploymentAppIdentityMismatch = errors.New("GitHub App manifest result did not match the requested owner")
|
|
ErrDeploymentAppPolicyMismatch = errors.New("GitHub App manifest result did not match the required policy")
|
|
ErrDeploymentAppRegistrationCancelled = errors.New("GitHub App registration was cancelled")
|
|
)
|
|
|
|
const defaultGitHubAppHost = "github.com"
|
|
|
|
type DeploymentAppManifestConverter interface {
|
|
Convert(context.Context, string) (ManifestConversionResult, error)
|
|
}
|
|
|
|
func requireDeploymentAppOperator(ctx context.Context, userID string) error {
|
|
identity, ok := authn.IdentityFromContext(ctx)
|
|
if !ok {
|
|
// Lifecycle jobs and legacy direct callers are internal. HTTP and WS
|
|
// requests always carry an identity (synthetic when auth is disabled).
|
|
return nil
|
|
}
|
|
if !identity.IsAdmin() || identity.UserID != userID {
|
|
return ErrDeploymentAppOperatorRequired
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func deploymentOwnerType(ownerType ManifestOwnerType) DeploymentAppOwnerType {
|
|
if ownerType == ManifestOwnerOrganization {
|
|
return DeploymentAppOwnerOrganization
|
|
}
|
|
return DeploymentAppOwnerUser
|
|
}
|
|
|
|
func deploymentOwnerTypeFromGitHub(ownerType string) DeploymentAppOwnerType {
|
|
if strings.EqualFold(ownerType, string(DeploymentAppOwnerOrganization)) {
|
|
return DeploymentAppOwnerOrganization
|
|
}
|
|
return DeploymentAppOwnerUser
|
|
}
|
|
|
|
func matchesDeploymentAppOwner(
|
|
flow *DeploymentAppRegistrationFlow,
|
|
converted ManifestConversionResult,
|
|
) bool {
|
|
return flow != nil && strings.EqualFold(flow.OwnerLogin, converted.Owner.Login) &&
|
|
strings.EqualFold(string(flow.OwnerType), converted.Owner.Type)
|
|
}
|
|
|
|
func matchesDeploymentAppPolicy(converted ManifestConversionResult) bool {
|
|
submission, err := BuildDeploymentAppManifest(
|
|
ManifestOwnerUser, "policy", "https://kandev.example",
|
|
)
|
|
if err != nil {
|
|
return false
|
|
}
|
|
if len(converted.Permissions) != len(submission.Manifest.DefaultPermissions) ||
|
|
len(converted.Events) != len(submission.Manifest.DefaultEvents) {
|
|
return false
|
|
}
|
|
for permission, level := range submission.Manifest.DefaultPermissions {
|
|
if converted.Permissions[permission] != level {
|
|
return false
|
|
}
|
|
}
|
|
for _, event := range submission.Manifest.DefaultEvents {
|
|
if !slices.Contains(converted.Events, event) {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
func resolvedDeploymentAppRegistration(
|
|
registration *DeploymentAppRegistration,
|
|
credentials DeploymentAppCredentials,
|
|
) ResolvedDeploymentAppConfig {
|
|
return ResolvedDeploymentAppConfig{
|
|
Source: DeploymentAppSourceManaged, Registration: registration,
|
|
Config: appRegistrationRuntimeConfig(registration, credentials),
|
|
}
|
|
}
|
|
|
|
func cloneDeploymentAppRegistration(value *DeploymentAppRegistration) *DeploymentAppRegistration {
|
|
if value == nil {
|
|
return nil
|
|
}
|
|
cloned := *value
|
|
return &cloned
|
|
}
|
|
|
|
func sanitizeDeploymentAppActivationError(_ error) error {
|
|
return errors.New("generated GitHub App credentials could not be activated")
|
|
}
|
|
|
|
func sanitizeDeploymentAppActivationAndCleanupError(activationErr, cleanupErr error) error {
|
|
activationErr = sanitizeDeploymentAppActivationError(activationErr)
|
|
if cleanupErr == nil {
|
|
return activationErr
|
|
}
|
|
return errors.Join(
|
|
activationErr,
|
|
errors.New("generated GitHub App credentials could not be cleaned up; retry deletion"),
|
|
)
|
|
}
|
|
|
|
func sanitizeDeploymentAppPersistenceError(err error) error {
|
|
if errors.Is(err, ErrDeploymentAppInUse) {
|
|
return ErrDeploymentAppInUse
|
|
}
|
|
return errors.New("generated GitHub App credentials could not be stored")
|
|
}
|