mirror of
https://github.com/paralus/paralus.git
synced 2026-05-06 08:26:53 +00:00
591 lines
16 KiB
Go
591 lines
16 KiB
Go
package service
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
"fmt"
|
|
"time"
|
|
|
|
"github.com/RafayLabs/rcloud-base/internal/dao"
|
|
"github.com/RafayLabs/rcloud-base/internal/models"
|
|
authzv1 "github.com/RafayLabs/rcloud-base/proto/types/authz"
|
|
v3 "github.com/RafayLabs/rcloud-base/proto/types/commonpb/v3"
|
|
systemv3 "github.com/RafayLabs/rcloud-base/proto/types/systempb/v3"
|
|
"github.com/google/uuid"
|
|
bun "github.com/uptrace/bun"
|
|
"go.uber.org/zap"
|
|
"google.golang.org/protobuf/types/known/timestamppb"
|
|
)
|
|
|
|
const (
|
|
projectKind = "Project"
|
|
projectListKind = "ProjectList"
|
|
)
|
|
|
|
// ProjectService is the interface for project operations
|
|
type ProjectService interface {
|
|
// create project
|
|
Create(ctx context.Context, project *systemv3.Project) (*systemv3.Project, error)
|
|
// get project by id
|
|
GetByID(ctx context.Context, id string) (*systemv3.Project, error)
|
|
// get project by name
|
|
GetByName(ctx context.Context, name string) (*systemv3.Project, error)
|
|
// create or update project
|
|
Update(ctx context.Context, project *systemv3.Project) (*systemv3.Project, error)
|
|
// delete project
|
|
Delete(ctx context.Context, project *systemv3.Project) (*systemv3.Project, error)
|
|
// list projects
|
|
List(ctx context.Context, project *systemv3.Project) (*systemv3.ProjectList, error)
|
|
//TODO Associate project with groups, user, roles
|
|
}
|
|
|
|
// projectService implements ProjectService
|
|
type projectService struct {
|
|
db *bun.DB
|
|
azc AuthzService
|
|
al *zap.Logger
|
|
dev bool
|
|
}
|
|
|
|
// NewProjectService return new project service
|
|
func NewProjectService(db *bun.DB, azc AuthzService, al *zap.Logger, dev bool) ProjectService {
|
|
return &projectService{db: db, azc: azc, al: al, dev: dev}
|
|
}
|
|
|
|
func (s *projectService) Create(ctx context.Context, project *systemv3.Project) (*systemv3.Project, error) {
|
|
|
|
if project.Metadata.Organization == "" {
|
|
return nil, fmt.Errorf("missing organization in metadata")
|
|
}
|
|
|
|
var org models.Organization
|
|
_, err := dao.GetByName(ctx, s.db, project.Metadata.Organization, &org)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
//convert v3 spec to internal models
|
|
proj := models.Project{
|
|
Name: project.GetMetadata().GetName(),
|
|
Description: project.GetMetadata().GetDescription(),
|
|
CreatedAt: time.Now(),
|
|
ModifiedAt: time.Now(),
|
|
Trash: false,
|
|
OrganizationId: org.ID,
|
|
PartnerId: org.PartnerId,
|
|
Default: project.GetSpec().GetDefault(),
|
|
}
|
|
|
|
tx, err := s.db.BeginTx(ctx, &sql.TxOptions{})
|
|
if err != nil {
|
|
return &systemv3.Project{}, err
|
|
}
|
|
|
|
entity, err := dao.Create(ctx, tx, &proj)
|
|
if err != nil {
|
|
tx.Rollback()
|
|
return &systemv3.Project{}, err
|
|
}
|
|
|
|
//update v3 spec
|
|
if createdProject, ok := entity.(*models.Project); ok {
|
|
|
|
project, err = s.createGroupRoleRelations(ctx, tx, project, parsedIds{Id: createdProject.ID, Partner: createdProject.PartnerId, Organization: createdProject.OrganizationId})
|
|
if err != nil {
|
|
tx.Rollback()
|
|
return &systemv3.Project{}, err
|
|
}
|
|
|
|
project, err = s.createProjectAccountRelations(ctx, tx, createdProject.ID, project)
|
|
if err != nil {
|
|
tx.Rollback()
|
|
return &systemv3.Project{}, err
|
|
}
|
|
|
|
project.Metadata.Id = createdProject.ID.String()
|
|
project.Spec = &systemv3.ProjectSpec{
|
|
Default: createdProject.Default,
|
|
}
|
|
|
|
CreateProjectAuditEvent(ctx, s.al, AuditActionCreate, project.GetMetadata().GetName(), createdProject.ID)
|
|
}
|
|
err = tx.Commit()
|
|
if err != nil {
|
|
tx.Rollback()
|
|
_log.Warn("unable to commit changes", err)
|
|
}
|
|
|
|
return project, nil
|
|
|
|
}
|
|
|
|
func (s *projectService) GetByID(ctx context.Context, id string) (*systemv3.Project, error) {
|
|
|
|
project := &systemv3.Project{
|
|
ApiVersion: apiVersion,
|
|
Kind: projectKind,
|
|
Metadata: &v3.Metadata{
|
|
Id: id,
|
|
},
|
|
}
|
|
|
|
uid, err := uuid.Parse(id)
|
|
if err != nil {
|
|
return &systemv3.Project{}, err
|
|
}
|
|
entity, err := dao.GetByID(ctx, s.db, uid, &models.Project{})
|
|
if err != nil {
|
|
return &systemv3.Project{}, err
|
|
}
|
|
|
|
if proj, ok := entity.(*models.Project); ok {
|
|
|
|
project.Metadata = &v3.Metadata{
|
|
Name: proj.Name,
|
|
Description: proj.Description,
|
|
Id: proj.ID.String(),
|
|
Organization: proj.OrganizationId.String(),
|
|
Partner: proj.PartnerId.String(),
|
|
ModifiedAt: timestamppb.New(proj.ModifiedAt),
|
|
}
|
|
project.Spec = &systemv3.ProjectSpec{
|
|
Default: proj.Default,
|
|
}
|
|
|
|
return project, nil
|
|
}
|
|
return project, nil
|
|
|
|
}
|
|
|
|
func (s *projectService) GetByName(ctx context.Context, name string) (*systemv3.Project, error) {
|
|
|
|
project := &systemv3.Project{
|
|
ApiVersion: apiVersion,
|
|
Kind: projectKind,
|
|
Metadata: &v3.Metadata{
|
|
Name: name,
|
|
},
|
|
}
|
|
|
|
entity, err := dao.GetByName(ctx, s.db, name, &models.Project{})
|
|
if err != nil {
|
|
return &systemv3.Project{}, err
|
|
}
|
|
|
|
if proj, ok := entity.(*models.Project); ok {
|
|
|
|
var org models.Organization
|
|
_, err := dao.GetByID(ctx, s.db, proj.OrganizationId, &org)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var partner models.Partner
|
|
_, err = dao.GetByID(ctx, s.db, proj.PartnerId, &partner)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
pnr, err := dao.GetProjectGroupRoles(ctx, s.db, proj.ID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
ur, err := dao.GetProjectUserRoles(ctx, s.db, proj.ID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
project.Metadata = &v3.Metadata{
|
|
Name: proj.Name,
|
|
Description: proj.Description,
|
|
Id: proj.ID.String(),
|
|
Organization: org.Name,
|
|
Partner: partner.Name,
|
|
ModifiedAt: timestamppb.New(proj.ModifiedAt),
|
|
}
|
|
project.Spec = &systemv3.ProjectSpec{
|
|
Default: proj.Default,
|
|
ProjectNamespaceRoles: pnr,
|
|
UserRoles: ur,
|
|
}
|
|
|
|
return project, nil
|
|
}
|
|
return project, nil
|
|
|
|
}
|
|
|
|
func (s *projectService) Update(ctx context.Context, project *systemv3.Project) (*systemv3.Project, error) {
|
|
|
|
entity, err := dao.GetByName(ctx, s.db, project.Metadata.Name, &models.Project{})
|
|
if err != nil {
|
|
return &systemv3.Project{}, err
|
|
}
|
|
|
|
if proj, ok := entity.(*models.Project); ok {
|
|
|
|
tx, err := s.db.BeginTx(ctx, &sql.TxOptions{})
|
|
if err != nil {
|
|
return &systemv3.Project{}, err
|
|
}
|
|
|
|
//update project details
|
|
proj.Description = project.Metadata.Description
|
|
proj.Default = project.Spec.Default
|
|
proj.ModifiedAt = time.Now()
|
|
|
|
project, err = s.deleteGroupRoleRelations(ctx, tx, proj.ID, project)
|
|
if err != nil {
|
|
tx.Rollback()
|
|
return &systemv3.Project{}, err
|
|
}
|
|
project, err = s.createGroupRoleRelations(ctx, tx, project, parsedIds{Id: proj.ID, Partner: proj.PartnerId, Organization: proj.OrganizationId})
|
|
if err != nil {
|
|
tx.Rollback()
|
|
return &systemv3.Project{}, err
|
|
}
|
|
|
|
project, err = s.deleteProjectAccountRelations(ctx, tx, proj.ID, project)
|
|
if err != nil {
|
|
tx.Rollback()
|
|
return &systemv3.Project{}, err
|
|
}
|
|
project, err = s.createProjectAccountRelations(ctx, tx, proj.ID, project)
|
|
if err != nil {
|
|
tx.Rollback()
|
|
return &systemv3.Project{}, err
|
|
}
|
|
|
|
_, err = dao.Update(ctx, tx, proj.ID, proj)
|
|
if err != nil {
|
|
tx.Rollback()
|
|
return &systemv3.Project{}, err
|
|
}
|
|
|
|
pnr, err := dao.GetProjectGroupRoles(ctx, tx, proj.ID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
ur, err := dao.GetProjectUserRoles(ctx, tx, proj.ID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
//update spec and status
|
|
project.Spec = &systemv3.ProjectSpec{
|
|
Default: proj.Default,
|
|
ProjectNamespaceRoles: pnr,
|
|
UserRoles: ur,
|
|
}
|
|
|
|
err = tx.Commit()
|
|
if err != nil {
|
|
tx.Rollback()
|
|
_log.Warn("unable to commit changes", err)
|
|
}
|
|
|
|
CreateProjectAuditEvent(ctx, s.al, AuditActionUpdate, project.GetMetadata().GetName(), proj.ID)
|
|
}
|
|
|
|
return project, nil
|
|
}
|
|
|
|
func (s *projectService) Delete(ctx context.Context, project *systemv3.Project) (*systemv3.Project, error) {
|
|
entity, err := dao.GetByName(ctx, s.db, project.Metadata.Name, &models.Project{})
|
|
if err != nil {
|
|
return &systemv3.Project{}, err
|
|
}
|
|
if proj, ok := entity.(*models.Project); ok {
|
|
|
|
tx, err := s.db.BeginTx(ctx, &sql.TxOptions{})
|
|
if err != nil {
|
|
return &systemv3.Project{}, err
|
|
}
|
|
|
|
project, err = s.deleteGroupRoleRelations(ctx, tx, proj.ID, project)
|
|
if err != nil {
|
|
tx.Rollback()
|
|
return &systemv3.Project{}, err
|
|
}
|
|
|
|
project, err = s.deleteProjectAccountRelations(ctx, tx, proj.ID, project)
|
|
if err != nil {
|
|
tx.Rollback()
|
|
return &systemv3.Project{}, err
|
|
}
|
|
|
|
err = dao.Delete(ctx, tx, proj.ID, proj)
|
|
if err != nil {
|
|
tx.Rollback()
|
|
return &systemv3.Project{}, err
|
|
}
|
|
//update v3 spec
|
|
project.Metadata.Id = proj.ID.String()
|
|
project.Metadata.Name = proj.Name
|
|
|
|
err = tx.Commit()
|
|
if err != nil {
|
|
tx.Rollback()
|
|
_log.Warn("unable to commit changes", err)
|
|
return &systemv3.Project{}, err
|
|
}
|
|
|
|
CreateProjectAuditEvent(ctx, s.al, AuditActionDelete, project.GetMetadata().GetName(), proj.ID)
|
|
}
|
|
|
|
return project, nil
|
|
}
|
|
|
|
func (s *projectService) List(ctx context.Context, project *systemv3.Project) (*systemv3.ProjectList, error) {
|
|
|
|
username := ""
|
|
if !s.dev {
|
|
sd, ok := GetSessionDataFromContext(ctx)
|
|
if !ok {
|
|
return &systemv3.ProjectList{}, fmt.Errorf("cannot perform project listing without auth")
|
|
}
|
|
username = sd.Username
|
|
}
|
|
|
|
var projects []*systemv3.Project
|
|
projectList := &systemv3.ProjectList{
|
|
ApiVersion: apiVersion,
|
|
Kind: projectListKind,
|
|
Metadata: &v3.ListMetadata{
|
|
Count: 0,
|
|
},
|
|
}
|
|
if len(project.Metadata.Organization) > 0 {
|
|
var org models.Organization
|
|
_, err := dao.GetByName(ctx, s.db, project.Metadata.Organization, &org)
|
|
if err != nil {
|
|
return &systemv3.ProjectList{}, err
|
|
}
|
|
var part models.Partner
|
|
_, err = dao.GetByName(ctx, s.db, project.Metadata.Partner, &part)
|
|
if err != nil {
|
|
return &systemv3.ProjectList{}, err
|
|
}
|
|
|
|
var projs []models.Project
|
|
if !s.dev {
|
|
entity, err := dao.GetByTraits(ctx, s.db, username, &models.KratosIdentities{})
|
|
if err != nil {
|
|
return &systemv3.ProjectList{}, err
|
|
}
|
|
|
|
if usr, ok := entity.(*models.KratosIdentities); ok {
|
|
projs, err = dao.GetFileteredProjects(ctx, s.db, usr.ID, part.ID, org.ID)
|
|
if err != nil {
|
|
return &systemv3.ProjectList{}, err
|
|
}
|
|
}
|
|
|
|
} else {
|
|
_, err = dao.List(ctx, s.db, uuid.NullUUID{UUID: part.ID, Valid: true}, uuid.NullUUID{UUID: org.ID, Valid: true}, &projs)
|
|
if err != nil {
|
|
return &systemv3.ProjectList{}, err
|
|
}
|
|
}
|
|
|
|
for _, proj := range projs {
|
|
labels := make(map[string]string)
|
|
labels["organization"] = proj.OrganizationId.String()
|
|
labels["partner"] = proj.PartnerId.String()
|
|
|
|
pnr, err := dao.GetProjectGroupRoles(ctx, s.db, proj.ID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
ur, err := dao.GetProjectUserRoles(ctx, s.db, proj.ID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
project := &systemv3.Project{
|
|
Metadata: &v3.Metadata{
|
|
Name: proj.Name,
|
|
Description: proj.Description,
|
|
Id: proj.ID.String(),
|
|
Organization: proj.OrganizationId.String(),
|
|
Partner: proj.PartnerId.String(),
|
|
Labels: labels,
|
|
ModifiedAt: timestamppb.New(proj.ModifiedAt),
|
|
},
|
|
Spec: &systemv3.ProjectSpec{
|
|
Default: proj.Default,
|
|
ProjectNamespaceRoles: pnr,
|
|
UserRoles: ur,
|
|
},
|
|
}
|
|
projects = append(projects, project)
|
|
}
|
|
|
|
//update the list metadata and items response
|
|
projectList.Metadata = &v3.ListMetadata{
|
|
Count: int64(len(projects)),
|
|
}
|
|
projectList.Items = projects
|
|
return projectList, nil
|
|
|
|
}
|
|
return projectList, fmt.Errorf("missing organization id in metadata")
|
|
}
|
|
|
|
// Map roles to groups
|
|
func (s *projectService) createGroupRoleRelations(ctx context.Context, db bun.IDB, project *systemv3.Project, ids parsedIds) (*systemv3.Project, error) {
|
|
projectNamespaceRoles := project.GetSpec().GetProjectNamespaceRoles()
|
|
|
|
var pgrs []models.ProjectGroupRole
|
|
var ps []*authzv1.Policy
|
|
for _, pnr := range projectNamespaceRoles {
|
|
role := pnr.GetRole()
|
|
entity, err := dao.GetIdByName(ctx, db, role, &models.Role{})
|
|
if err != nil {
|
|
return &systemv3.Project{}, fmt.Errorf("unable to find role '%v'", role)
|
|
}
|
|
var roleId uuid.UUID
|
|
if rle, ok := entity.(*models.Role); ok {
|
|
roleId = rle.ID
|
|
} else {
|
|
return &systemv3.Project{}, fmt.Errorf("unable to find role '%v'", role)
|
|
}
|
|
|
|
grp := pnr.Group
|
|
entity, err = dao.GetIdByName(ctx, s.db, *grp, &models.Group{})
|
|
if err != nil {
|
|
return &systemv3.Project{}, fmt.Errorf("unable to find group '%v'", grp)
|
|
}
|
|
var grpId uuid.UUID
|
|
var grpName string
|
|
if g, ok := entity.(*models.Group); ok {
|
|
grpId = g.ID
|
|
grpName = g.Name
|
|
} else {
|
|
return &systemv3.Project{}, fmt.Errorf("unable to find group '%v'", grp)
|
|
}
|
|
|
|
org := project.Metadata.Organization
|
|
pgr := models.ProjectGroupRole{
|
|
Trash: false,
|
|
RoleId: roleId,
|
|
PartnerId: ids.Partner,
|
|
OrganizationId: ids.Organization,
|
|
GroupId: grpId,
|
|
ProjectId: ids.Id,
|
|
Active: true,
|
|
}
|
|
pgrs = append(pgrs, pgr)
|
|
|
|
ps = append(ps, &authzv1.Policy{
|
|
Sub: "g:" + grpName,
|
|
Ns: "*",
|
|
Proj: project.Metadata.Name,
|
|
Org: org,
|
|
Obj: role,
|
|
})
|
|
}
|
|
if len(pgrs) > 0 {
|
|
_, err := dao.Create(ctx, db, &pgrs)
|
|
if err != nil {
|
|
return &systemv3.Project{}, err
|
|
}
|
|
}
|
|
|
|
if len(ps) > 0 {
|
|
success, err := s.azc.CreatePolicies(ctx, &authzv1.Policies{Policies: ps})
|
|
if err != nil || !success.Res {
|
|
return &systemv3.Project{}, fmt.Errorf("unable to create mapping in authz; %v", err)
|
|
}
|
|
}
|
|
|
|
return project, nil
|
|
}
|
|
|
|
func (s *projectService) deleteGroupRoleRelations(ctx context.Context, db bun.IDB, projectId uuid.UUID, project *systemv3.Project) (*systemv3.Project, error) {
|
|
// delete previous entries
|
|
err := dao.DeleteX(ctx, db, "project_id", projectId, &models.ProjectGroupRole{})
|
|
if err != nil {
|
|
return &systemv3.Project{}, err
|
|
}
|
|
|
|
_, err = s.azc.DeletePolicies(ctx, &authzv1.Policy{Proj: project.GetMetadata().GetName()})
|
|
if err != nil {
|
|
return &systemv3.Project{}, fmt.Errorf("unable to delete project group-role relations from authz; %v", err)
|
|
}
|
|
return project, nil
|
|
}
|
|
|
|
func (s *projectService) deleteProjectAccountRelations(ctx context.Context, db bun.IDB, projectId uuid.UUID, project *systemv3.Project) (*systemv3.Project, error) {
|
|
err := dao.DeleteX(ctx, db, "project_id", projectId, &models.ProjectAccountResourcerole{})
|
|
if err != nil {
|
|
return &systemv3.Project{}, fmt.Errorf("unable to delete project; %v", err)
|
|
}
|
|
|
|
_, err = s.azc.DeletePolicies(ctx, &authzv1.Policy{Proj: project.GetMetadata().GetName()})
|
|
if err != nil {
|
|
return &systemv3.Project{}, fmt.Errorf("unable to delete project user-role relations from authz; %v", err)
|
|
}
|
|
return project, nil
|
|
}
|
|
|
|
// Update the users(account) mapped to each project
|
|
func (s *projectService) createProjectAccountRelations(ctx context.Context, db bun.IDB, projectId uuid.UUID, project *systemv3.Project) (*systemv3.Project, error) {
|
|
var parrs []models.ProjectAccountResourcerole
|
|
var ugs []*authzv1.Policy
|
|
|
|
for _, ur := range project.GetSpec().GetUserRoles() {
|
|
// FIXME: do combined lookup
|
|
entity, err := dao.GetIdByTraits(ctx, db, ur.User, &models.KratosIdentities{})
|
|
if err != nil {
|
|
return &systemv3.Project{}, fmt.Errorf("unable to find user '%v'", ur.User)
|
|
}
|
|
rentity, err := dao.GetByName(ctx, db, ur.Role, &models.Role{})
|
|
if err != nil {
|
|
return &systemv3.Project{}, fmt.Errorf("unable to find user '%v'", ur.User)
|
|
}
|
|
|
|
if acc, ok := entity.(*models.KratosIdentities); ok {
|
|
if role, ok := rentity.(*models.Role); ok {
|
|
parr := models.ProjectAccountResourcerole{
|
|
CreatedAt: time.Now(),
|
|
ModifiedAt: time.Now(),
|
|
Trash: false,
|
|
AccountId: acc.ID,
|
|
ProjectId: projectId,
|
|
RoleId: role.ID,
|
|
OrganizationId: role.OrganizationId,
|
|
PartnerId: role.PartnerId,
|
|
Active: true,
|
|
}
|
|
parrs = append(parrs, parr)
|
|
ugs = append(ugs, &authzv1.Policy{
|
|
Sub: "u:" + ur.User,
|
|
Proj: project.Metadata.Name,
|
|
Org: project.Metadata.Organization,
|
|
Ns: "*",
|
|
Obj: role.Name,
|
|
})
|
|
}
|
|
}
|
|
}
|
|
if len(parrs) == 0 {
|
|
return project, nil
|
|
}
|
|
_, err := dao.Create(ctx, db, &parrs)
|
|
if err != nil {
|
|
return &systemv3.Project{}, err
|
|
}
|
|
|
|
// TODO: revert our db inserts if this fails
|
|
// Just FYI, the succcess can be false if we delete the db directly but casbin has it available internally
|
|
_, err = s.azc.CreatePolicies(ctx, &authzv1.Policies{Policies: ugs})
|
|
if err != nil {
|
|
return &systemv3.Project{}, fmt.Errorf("unable to create mapping in authz; %v", err)
|
|
}
|
|
|
|
return project, nil
|
|
}
|