mirror of
https://github.com/paralus/paralus.git
synced 2026-05-07 00:46:52 +00:00
This was done inorder to support transactions which will be done in the next PR. This is the first step towards that.
512 lines
15 KiB
Go
512 lines
15 KiB
Go
package service
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"strconv"
|
|
"time"
|
|
|
|
"github.com/RafaySystems/rcloud-base/internal/dao"
|
|
"github.com/RafaySystems/rcloud-base/internal/models"
|
|
"github.com/RafaySystems/rcloud-base/internal/persistence/provider/pg"
|
|
authzv1 "github.com/RafaySystems/rcloud-base/proto/types/authz"
|
|
v3 "github.com/RafaySystems/rcloud-base/proto/types/commonpb/v3"
|
|
userv3 "github.com/RafaySystems/rcloud-base/proto/types/userpb/v3"
|
|
"github.com/google/uuid"
|
|
bun "github.com/uptrace/bun"
|
|
"google.golang.org/protobuf/types/known/timestamppb"
|
|
)
|
|
|
|
const (
|
|
groupKind = "Group"
|
|
groupListKind = "GroupList"
|
|
)
|
|
|
|
// GroupService is the interface for group operations
|
|
type GroupService interface {
|
|
// create group
|
|
Create(context.Context, *userv3.Group) (*userv3.Group, error)
|
|
// get group by id
|
|
GetByID(context.Context, *userv3.Group) (*userv3.Group, error)
|
|
// get group by name
|
|
GetByName(context.Context, *userv3.Group) (*userv3.Group, error)
|
|
// create or update group
|
|
Update(context.Context, *userv3.Group) (*userv3.Group, error)
|
|
// delete group
|
|
Delete(context.Context, *userv3.Group) (*userv3.Group, error)
|
|
// list groups
|
|
List(context.Context, *userv3.Group) (*userv3.GroupList, error)
|
|
}
|
|
|
|
// groupService implements GroupService
|
|
type groupService struct {
|
|
db *bun.DB
|
|
azc AuthzService
|
|
}
|
|
|
|
// NewGroupService return new group service
|
|
func NewGroupService(db *bun.DB, azc AuthzService) GroupService {
|
|
return &groupService{db: db, azc: azc}
|
|
}
|
|
|
|
func (s *groupService) deleteGroupRoleRelaitons(ctx context.Context, groupId uuid.UUID, group *userv3.Group) (*userv3.Group, error) {
|
|
// delete previous entries
|
|
// TODO: single delete command
|
|
err := pg.DeleteX(ctx, s.db, "group_id", groupId, &models.GroupRole{})
|
|
if err != nil {
|
|
return &userv3.Group{}, err
|
|
}
|
|
err = pg.DeleteX(ctx, s.db, "group_id", groupId, &models.ProjectGroupRole{})
|
|
if err != nil {
|
|
return &userv3.Group{}, err
|
|
}
|
|
err = pg.DeleteX(ctx, s.db, "group_id", groupId, &models.ProjectGroupNamespaceRole{})
|
|
if err != nil {
|
|
return &userv3.Group{}, err
|
|
}
|
|
|
|
_, err = s.azc.DeletePolicies(ctx, &authzv1.Policy{Sub: "g:" + group.GetMetadata().GetName()})
|
|
if err != nil {
|
|
return &userv3.Group{}, fmt.Errorf("unable to delete group-role relations from authz; %v", err)
|
|
}
|
|
return group, nil
|
|
}
|
|
|
|
// Map roles to groups
|
|
func (s *groupService) createGroupRoleRelations(ctx context.Context, group *userv3.Group, ids parsedIds) (*userv3.Group, error) {
|
|
// TODO: add transactions
|
|
projectNamespaceRoles := group.GetSpec().GetProjectNamespaceRoles()
|
|
|
|
var pgnrs []models.ProjectGroupNamespaceRole
|
|
var pgrs []models.ProjectGroupRole
|
|
var grs []models.GroupRole
|
|
var ps []*authzv1.Policy
|
|
for _, pnr := range projectNamespaceRoles {
|
|
role := pnr.GetRole()
|
|
entity, err := pg.GetIdByName(ctx, s.db, role, &models.Role{})
|
|
if err != nil {
|
|
return &userv3.Group{}, fmt.Errorf("unable to find role '%v'", role)
|
|
}
|
|
var roleId uuid.UUID
|
|
if rle, ok := entity.(*models.Role); ok {
|
|
roleId = rle.ID
|
|
} else {
|
|
return &userv3.Group{}, fmt.Errorf("unable to find role '%v'", role)
|
|
}
|
|
|
|
project := pnr.GetProject()
|
|
org := group.GetMetadata().GetOrganization()
|
|
namespaceId := pnr.GetNamespace() // TODO: lookup id from name
|
|
switch {
|
|
case namespaceId != 0:
|
|
projectId, err := pg.GetProjectId(ctx, s.db, project)
|
|
if err != nil {
|
|
return &userv3.Group{}, fmt.Errorf("unable to find project '%v'", project)
|
|
}
|
|
pgnr := models.ProjectGroupNamespaceRole{
|
|
Trash: false,
|
|
RoleId: roleId,
|
|
PartnerId: ids.Partner,
|
|
OrganizationId: ids.Organization,
|
|
GroupId: ids.Id,
|
|
ProjectId: projectId,
|
|
NamespaceId: namespaceId,
|
|
Active: true,
|
|
}
|
|
pgnrs = append(pgnrs, pgnr)
|
|
|
|
ps = append(ps, &authzv1.Policy{
|
|
Sub: "g:" + group.GetMetadata().GetName(),
|
|
Ns: strconv.FormatInt(namespaceId, 10),
|
|
Proj: project,
|
|
Org: org,
|
|
Obj: role,
|
|
Act: "*",
|
|
})
|
|
case project != "":
|
|
projectId, err := pg.GetProjectId(ctx, s.db, project)
|
|
if err != nil {
|
|
return &userv3.Group{}, fmt.Errorf("unable to find project '%v'", project)
|
|
}
|
|
pgr := models.ProjectGroupRole{
|
|
Trash: false,
|
|
RoleId: roleId,
|
|
PartnerId: ids.Partner,
|
|
OrganizationId: ids.Organization,
|
|
GroupId: ids.Id,
|
|
ProjectId: projectId,
|
|
Active: true,
|
|
}
|
|
pgrs = append(pgrs, pgr)
|
|
|
|
ps = append(ps, &authzv1.Policy{
|
|
Sub: "g:" + group.GetMetadata().GetName(),
|
|
Ns: "*",
|
|
Proj: project,
|
|
Org: org,
|
|
Obj: role,
|
|
Act: "*",
|
|
})
|
|
default:
|
|
gr := models.GroupRole{
|
|
Trash: false,
|
|
RoleId: roleId,
|
|
PartnerId: ids.Partner,
|
|
OrganizationId: ids.Organization,
|
|
GroupId: ids.Id,
|
|
Active: true,
|
|
}
|
|
grs = append(grs, gr)
|
|
ps = append(ps, &authzv1.Policy{
|
|
Sub: "g:" + group.GetMetadata().GetName(),
|
|
Ns: "*",
|
|
Proj: "*",
|
|
Org: org,
|
|
Obj: role,
|
|
Act: "*",
|
|
})
|
|
}
|
|
}
|
|
if len(pgnrs) > 0 {
|
|
_, err := pg.Create(ctx, s.db, &pgnrs)
|
|
if err != nil {
|
|
return &userv3.Group{}, err
|
|
}
|
|
}
|
|
if len(pgrs) > 0 {
|
|
_, err := pg.Create(ctx, s.db, &pgrs)
|
|
if err != nil {
|
|
return &userv3.Group{}, err
|
|
}
|
|
}
|
|
if len(grs) > 0 {
|
|
_, err := pg.Create(ctx, s.db, &grs)
|
|
if err != nil {
|
|
return &userv3.Group{}, err
|
|
}
|
|
}
|
|
|
|
if len(ps) > 0 {
|
|
success, err := s.azc.CreatePolicies(ctx, &authzv1.Policies{Policies: ps})
|
|
if err != nil || !success.Res {
|
|
return &userv3.Group{}, fmt.Errorf("unable to create mapping in authz; %v", err)
|
|
}
|
|
}
|
|
|
|
return group, nil
|
|
}
|
|
|
|
func (s *groupService) deleteGroupAccountRelations(ctx context.Context, groupId uuid.UUID, group *userv3.Group) (*userv3.Group, error) {
|
|
err := pg.DeleteX(ctx, s.db, "group_id", groupId, &models.GroupAccount{})
|
|
if err != nil {
|
|
return &userv3.Group{}, fmt.Errorf("unable to delete user; %v", err)
|
|
}
|
|
|
|
_, err = s.azc.DeleteUserGroups(ctx, &authzv1.UserGroup{Grp: "g:" + group.GetMetadata().GetName()})
|
|
if err != nil {
|
|
return &userv3.Group{}, fmt.Errorf("unable to delete group-user relations from authz; %v", err)
|
|
}
|
|
return group, nil
|
|
}
|
|
|
|
// Update the users(account) mapped to each group
|
|
func (s *groupService) createGroupAccountRelations(ctx context.Context, groupId uuid.UUID, group *userv3.Group) (*userv3.Group, error) {
|
|
// TODO: add transactions
|
|
var grpaccs []models.GroupAccount
|
|
var ugs []*authzv1.UserGroup
|
|
for _, account := range unique(group.GetSpec().GetUsers()) {
|
|
// FIXME: do combined lookup
|
|
entity, err := pg.GetIdByTraits(ctx, s.db, account, &models.KratosIdentities{})
|
|
if err != nil {
|
|
return &userv3.Group{}, fmt.Errorf("unable to find user '%v'", account)
|
|
}
|
|
if acc, ok := entity.(*models.KratosIdentities); ok {
|
|
grp := models.GroupAccount{
|
|
CreatedAt: time.Now(),
|
|
ModifiedAt: time.Now(),
|
|
Trash: false,
|
|
AccountId: acc.ID,
|
|
GroupId: groupId,
|
|
Active: true,
|
|
}
|
|
grpaccs = append(grpaccs, grp)
|
|
ugs = append(ugs, &authzv1.UserGroup{
|
|
Grp: "g:" + group.GetMetadata().GetName(),
|
|
User: "u:" + account,
|
|
})
|
|
}
|
|
}
|
|
if len(grpaccs) == 0 {
|
|
return group, nil
|
|
}
|
|
_, err := pg.Create(ctx, s.db, &grpaccs)
|
|
if err != nil {
|
|
return &userv3.Group{}, err
|
|
}
|
|
|
|
// TODO: revert our db inserts if this fails
|
|
// Just FYI, the success can be false if we delete the db directly but casbin has it available internally
|
|
_, err = s.azc.CreateUserGroups(ctx, &authzv1.UserGroups{UserGroups: ugs})
|
|
if err != nil {
|
|
return &userv3.Group{}, fmt.Errorf("unable to create mapping in authz; %v", err)
|
|
}
|
|
|
|
return group, nil
|
|
}
|
|
|
|
func (s *groupService) getPartnerOrganization(ctx context.Context, group *userv3.Group) (uuid.UUID, uuid.UUID, error) {
|
|
partner := group.GetMetadata().GetPartner()
|
|
org := group.GetMetadata().GetOrganization()
|
|
partnerId, err := pg.GetPartnerId(ctx, s.db, partner)
|
|
if err != nil {
|
|
return uuid.Nil, uuid.Nil, err
|
|
}
|
|
organizationId, err := pg.GetOrganizationId(ctx, s.db, org)
|
|
if err != nil {
|
|
return partnerId, uuid.Nil, err
|
|
}
|
|
return partnerId, organizationId, nil
|
|
|
|
}
|
|
|
|
func (s *groupService) Create(ctx context.Context, group *userv3.Group) (*userv3.Group, error) {
|
|
partnerId, organizationId, err := s.getPartnerOrganization(ctx, group)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("unable to get partner and org id")
|
|
}
|
|
g, _ := pg.GetIdByNamePartnerOrg(ctx, s.db, group.GetMetadata().GetName(), uuid.NullUUID{UUID: partnerId, Valid: true}, uuid.NullUUID{UUID: organizationId, Valid: true}, &models.Group{})
|
|
if g != nil {
|
|
return nil, fmt.Errorf("group '%v' already exists", group.GetMetadata().GetName())
|
|
}
|
|
//convert v3 spec to internal models
|
|
grp := models.Group{
|
|
Name: group.GetMetadata().GetName(),
|
|
Description: group.GetMetadata().GetDescription(),
|
|
CreatedAt: time.Now(),
|
|
ModifiedAt: time.Now(),
|
|
Trash: false,
|
|
OrganizationId: organizationId,
|
|
PartnerId: partnerId,
|
|
Type: group.GetSpec().GetType(),
|
|
}
|
|
entity, err := pg.Create(ctx, s.db, &grp)
|
|
if err != nil {
|
|
return &userv3.Group{}, err
|
|
}
|
|
|
|
//update v3 spec
|
|
if grp, ok := entity.(*models.Group); ok {
|
|
// we can get previous group using the id, find users/roles from that and delete those
|
|
group, err = s.createGroupAccountRelations(ctx, grp.ID, group)
|
|
if err != nil {
|
|
return &userv3.Group{}, err
|
|
}
|
|
|
|
group, err = s.createGroupRoleRelations(ctx, group, parsedIds{Id: grp.ID, Partner: partnerId, Organization: organizationId})
|
|
if err != nil {
|
|
return &userv3.Group{}, err
|
|
}
|
|
return group, nil
|
|
}
|
|
return &userv3.Group{}, fmt.Errorf("unable to create group")
|
|
}
|
|
|
|
func (s *groupService) toV3Group(ctx context.Context, group *userv3.Group, grp *models.Group) (*userv3.Group, error) {
|
|
labels := make(map[string]string)
|
|
labels["organization"] = group.GetMetadata().GetOrganization()
|
|
labels["partner"] = group.GetMetadata().GetPartner()
|
|
|
|
group.ApiVersion = apiVersion
|
|
group.Kind = groupKind
|
|
group.Metadata = &v3.Metadata{
|
|
Name: grp.Name,
|
|
Description: grp.Description,
|
|
Organization: group.GetMetadata().GetOrganization(),
|
|
Partner: group.GetMetadata().GetPartner(),
|
|
Labels: labels,
|
|
ModifiedAt: timestamppb.New(grp.ModifiedAt),
|
|
}
|
|
users, err := dao.GetUsers(ctx, s.db, grp.ID)
|
|
if err != nil {
|
|
return &userv3.Group{}, err
|
|
}
|
|
userNames := []string{}
|
|
for _, u := range users {
|
|
userNames = append(userNames, u.Traits["email"].(string))
|
|
}
|
|
|
|
roles, err := dao.GetGroupRoles(ctx, s.db, grp.ID)
|
|
if err != nil {
|
|
return &userv3.Group{}, err
|
|
}
|
|
group.Spec = &userv3.GroupSpec{
|
|
Type: grp.Type,
|
|
Users: userNames,
|
|
ProjectNamespaceRoles: roles,
|
|
}
|
|
return group, nil
|
|
}
|
|
|
|
func (s *groupService) GetByID(ctx context.Context, group *userv3.Group) (*userv3.Group, error) {
|
|
id := group.GetMetadata().GetId()
|
|
uid, err := uuid.Parse(id)
|
|
if err != nil {
|
|
return &userv3.Group{}, err
|
|
}
|
|
entity, err := pg.GetByID(ctx, s.db, uid, &models.Group{})
|
|
if err != nil {
|
|
return &userv3.Group{}, err
|
|
}
|
|
|
|
if grp, ok := entity.(*models.Group); ok {
|
|
return s.toV3Group(ctx, group, grp)
|
|
}
|
|
return group, nil
|
|
|
|
}
|
|
|
|
func (s *groupService) GetByName(ctx context.Context, group *userv3.Group) (*userv3.Group, error) {
|
|
name := group.GetMetadata().GetName()
|
|
partnerId, organizationId, err := s.getPartnerOrganization(ctx, group)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("unable to get partner and org id")
|
|
}
|
|
entity, err := pg.GetByNamePartnerOrg(ctx, s.db, name, uuid.NullUUID{UUID: partnerId, Valid: true}, uuid.NullUUID{UUID: organizationId, Valid: true}, &models.Group{})
|
|
if err != nil {
|
|
return &userv3.Group{}, err
|
|
}
|
|
|
|
if grp, ok := entity.(*models.Group); ok {
|
|
return s.toV3Group(ctx, group, grp)
|
|
}
|
|
return group, nil
|
|
|
|
}
|
|
|
|
func (s *groupService) Update(ctx context.Context, group *userv3.Group) (*userv3.Group, error) {
|
|
// TODO: inform when unchanged
|
|
name := group.GetMetadata().GetName()
|
|
partnerId, organizationId, err := s.getPartnerOrganization(ctx, group)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("unable to get partner and org id")
|
|
}
|
|
entity, err := pg.GetByNamePartnerOrg(ctx, s.db, name, uuid.NullUUID{UUID: partnerId, Valid: true}, uuid.NullUUID{UUID: organizationId, Valid: true}, &models.Group{})
|
|
if err != nil {
|
|
return &userv3.Group{}, fmt.Errorf("no group found with name '%v'", name)
|
|
}
|
|
|
|
if grp, ok := entity.(*models.Group); ok {
|
|
// TODO: are we not letting them update org/partner?
|
|
grp.Name = group.Metadata.Name
|
|
grp.Description = group.Metadata.Description
|
|
grp.Type = group.Spec.Type
|
|
grp.ModifiedAt = time.Now()
|
|
|
|
// update account/role links
|
|
group, err = s.deleteGroupAccountRelations(ctx, grp.ID, group)
|
|
if err != nil {
|
|
return &userv3.Group{}, err
|
|
}
|
|
group, err = s.createGroupAccountRelations(ctx, grp.ID, group)
|
|
if err != nil {
|
|
return &userv3.Group{}, err
|
|
}
|
|
group, err = s.deleteGroupRoleRelaitons(ctx, grp.ID, group)
|
|
if err != nil {
|
|
return &userv3.Group{}, err
|
|
}
|
|
group, err = s.createGroupRoleRelations(ctx, group, parsedIds{Id: grp.ID, Partner: partnerId, Organization: organizationId})
|
|
if err != nil {
|
|
return &userv3.Group{}, err
|
|
}
|
|
|
|
_, err = pg.Update(ctx, s.db, grp.ID, grp)
|
|
if err != nil {
|
|
return &userv3.Group{}, err
|
|
}
|
|
|
|
// update spec and status
|
|
group.Spec = &userv3.GroupSpec{
|
|
Type: grp.Type,
|
|
Users: group.Spec.Users, // TODO: update from db resp or no update?
|
|
ProjectNamespaceRoles: group.Spec.ProjectNamespaceRoles,
|
|
}
|
|
}
|
|
|
|
return group, nil
|
|
}
|
|
|
|
func (s *groupService) Delete(ctx context.Context, group *userv3.Group) (*userv3.Group, error) {
|
|
name := group.GetMetadata().GetName()
|
|
partnerId, organizationId, err := s.getPartnerOrganization(ctx, group)
|
|
if err != nil {
|
|
return &userv3.Group{}, fmt.Errorf("unable to get partner and org id")
|
|
}
|
|
entity, err := pg.GetByNamePartnerOrg(ctx, s.db, name, uuid.NullUUID{UUID: partnerId, Valid: true}, uuid.NullUUID{UUID: organizationId, Valid: true}, &models.Group{})
|
|
if err != nil {
|
|
return &userv3.Group{}, err
|
|
}
|
|
if grp, ok := entity.(*models.Group); ok {
|
|
group, err = s.deleteGroupRoleRelaitons(ctx, grp.ID, group)
|
|
if err != nil {
|
|
return &userv3.Group{}, err
|
|
}
|
|
group, err = s.deleteGroupAccountRelations(ctx, grp.ID, group)
|
|
if err != nil {
|
|
return &userv3.Group{}, err
|
|
}
|
|
err = pg.Delete(ctx, s.db, grp.ID, grp)
|
|
if err != nil {
|
|
return &userv3.Group{}, err
|
|
}
|
|
}
|
|
|
|
return group, nil
|
|
}
|
|
|
|
func (s *groupService) List(ctx context.Context, group *userv3.Group) (*userv3.GroupList, error) {
|
|
var groups []*userv3.Group
|
|
groupList := &userv3.GroupList{
|
|
ApiVersion: apiVersion,
|
|
Kind: groupListKind,
|
|
Metadata: &v3.ListMetadata{
|
|
Count: 0,
|
|
},
|
|
}
|
|
if len(group.Metadata.Organization) > 0 {
|
|
orgId, err := pg.GetOrganizationId(ctx, s.db, group.Metadata.Organization)
|
|
if err != nil {
|
|
return groupList, err
|
|
}
|
|
partId, err := pg.GetPartnerId(ctx, s.db, group.Metadata.Partner)
|
|
if err != nil {
|
|
return groupList, err
|
|
}
|
|
var grps []models.Group
|
|
entities, err := pg.List(ctx, s.db, uuid.NullUUID{UUID: partId, Valid: true}, uuid.NullUUID{UUID: orgId, Valid: true}, &grps)
|
|
if err != nil {
|
|
return groupList, err
|
|
}
|
|
if grps, ok := entities.(*[]models.Group); ok {
|
|
for _, grp := range *grps {
|
|
entry := &userv3.Group{Metadata: group.GetMetadata()}
|
|
entry, err = s.toV3Group(ctx, entry, &grp)
|
|
if err != nil {
|
|
return groupList, err
|
|
}
|
|
groups = append(groups, entry)
|
|
}
|
|
|
|
//update the list metadata and items response
|
|
groupList.Metadata = &v3.ListMetadata{
|
|
Count: int64(len(groups)),
|
|
}
|
|
groupList.Items = groups
|
|
}
|
|
|
|
} else {
|
|
return groupList, fmt.Errorf("missing organization id in metadata")
|
|
}
|
|
return groupList, nil
|
|
}
|