package policy import ( "errors" "fmt" "net/http" "github.com/porter-dev/porter/api/server/shared/apierrors" "github.com/porter-dev/porter/api/types" "github.com/porter-dev/porter/internal/models" "github.com/porter-dev/porter/internal/repository" "gorm.io/gorm" ) type PolicyLoaderOpts struct { ProjectID, UserID uint ProjectToken *models.APIToken } type PolicyDocumentLoader interface { LoadPolicyDocuments(opts *PolicyLoaderOpts) ([]*types.PolicyDocument, apierrors.RequestError) } // RepoPolicyDocumentLoader loads policy documents by reading from the repository database type RepoPolicyDocumentLoader struct { projRepo repository.ProjectRepository policyRepo repository.PolicyRepository } func NewBasicPolicyDocumentLoader(projRepo repository.ProjectRepository, policyRepo repository.PolicyRepository) *RepoPolicyDocumentLoader { return &RepoPolicyDocumentLoader{projRepo, policyRepo} } func (b *RepoPolicyDocumentLoader) LoadPolicyDocuments( opts *PolicyLoaderOpts, ) ([]*types.PolicyDocument, apierrors.RequestError) { if opts.ProjectToken != nil { // check that the token belongs to the project, in this case it's solely project-scoped if opts.ProjectID == 0 || opts.ProjectToken.ProjectID == 0 || opts.ProjectID != opts.ProjectToken.ProjectID { return nil, apierrors.NewErrForbidden(fmt.Errorf("project id %d does not match token id %d", opts.ProjectID, opts.ProjectToken.ProjectID)) } // load the policy apiPolicy, reqErr := GetAPIPolicyFromUID(b.policyRepo, opts.ProjectToken.ProjectID, opts.ProjectToken.PolicyUID) if reqErr != nil { return nil, reqErr } return apiPolicy.Policy, nil } else if opts.ProjectID != 0 && opts.UserID != 0 { userID := opts.UserID projectID := opts.ProjectID // read role and case on role "kind" role, err := b.projRepo.ReadProjectRole(projectID, userID) if err != nil && err == gorm.ErrRecordNotFound { return nil, apierrors.NewErrForbidden( fmt.Errorf("user %d does not have a role in project %d", userID, projectID), ) } else if err != nil { return nil, apierrors.NewErrInternal(err) } // load role based on role kind switch role.Kind { case types.RoleAdmin: return types.AdminPolicy, nil case types.RoleDeveloper: return types.DeveloperPolicy, nil case types.RoleViewer: return types.ViewerPolicy, nil default: return nil, apierrors.NewErrForbidden( fmt.Errorf("%s role not supported for user %d, project %d", string(role.Kind), userID, projectID), ) } } return nil, apierrors.NewErrForbidden( fmt.Errorf("policy loader called with invalid arguments"), ) } func GetAPIPolicyFromUID(policyRepo repository.PolicyRepository, projectID uint, uid string) (*types.APIPolicy, apierrors.RequestError) { switch uid { case "admin": return &types.APIPolicy{ APIPolicyMeta: &types.APIPolicyMeta{ Name: "admin", UID: "admin", }, Policy: types.AdminPolicy, }, nil case "developer": return &types.APIPolicy{ APIPolicyMeta: &types.APIPolicyMeta{ Name: "developer", UID: "developer", }, Policy: types.DeveloperPolicy, }, nil case "viewer": return &types.APIPolicy{ APIPolicyMeta: &types.APIPolicyMeta{ Name: "viewer", UID: "viewer", }, Policy: types.ViewerPolicy, }, nil default: // look up the policy and make sure it exists policyModel, err := policyRepo.ReadPolicy(projectID, uid) if err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return nil, apierrors.NewErrPassThroughToClient( fmt.Errorf("policy not found in project"), http.StatusBadRequest, ) } return nil, apierrors.NewErrInternal(err) } apiPolicy, err := policyModel.ToAPIPolicyType() if err != nil { return nil, apierrors.NewErrInternal(err) } return apiPolicy, nil } }