mirror of
https://github.com/absmach/magistrala.git
synced 2026-06-23 04:10:28 +00:00
61d0427898
Signed-off-by: dusan <borovcanindusan1@gmail.com>
425 lines
13 KiB
Go
425 lines
13 KiB
Go
// Copyright (c) Abstract Machines
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
package domains
|
|
|
|
import (
|
|
"context"
|
|
"time"
|
|
|
|
"github.com/absmach/magistrala"
|
|
"github.com/absmach/magistrala/pkg/authn"
|
|
"github.com/absmach/magistrala/pkg/errors"
|
|
repoerr "github.com/absmach/magistrala/pkg/errors/repository"
|
|
svcerr "github.com/absmach/magistrala/pkg/errors/service"
|
|
"github.com/absmach/magistrala/pkg/policies"
|
|
"github.com/absmach/magistrala/pkg/roles"
|
|
)
|
|
|
|
var (
|
|
errCreateDomainPolicy = errors.New("failed to create domain policy")
|
|
errRollbackRepo = errors.New("failed to rollback repo")
|
|
)
|
|
|
|
type service struct {
|
|
repo Repository
|
|
cache Cache
|
|
policy policies.Service
|
|
idProvider magistrala.IDProvider
|
|
roles.ProvisionManageService
|
|
}
|
|
|
|
var _ Service = (*service)(nil)
|
|
|
|
func New(repo Repository, cache Cache, policy policies.Service, idProvider magistrala.IDProvider, sidProvider magistrala.IDProvider, availableActions []roles.Action, builtInRoles map[roles.BuiltInRoleName][]roles.Action) (Service, error) {
|
|
rpms, err := roles.NewProvisionManageService(policies.DomainType, repo, policy, sidProvider, availableActions, builtInRoles)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &service{
|
|
repo: repo,
|
|
cache: cache,
|
|
policy: policy,
|
|
idProvider: idProvider,
|
|
ProvisionManageService: rpms,
|
|
}, nil
|
|
}
|
|
|
|
func (svc service) CreateDomain(ctx context.Context, session authn.Session, d Domain) (retDo Domain, retRps []roles.RoleProvision, retErr error) {
|
|
d.CreatedBy = session.UserID
|
|
|
|
if d.ID == "" {
|
|
domainID, err := svc.idProvider.ID()
|
|
if err != nil {
|
|
return Domain{}, []roles.RoleProvision{}, errors.Wrap(svcerr.ErrCreateEntity, err)
|
|
}
|
|
d.ID = domainID
|
|
}
|
|
|
|
if d.Status != DisabledStatus && d.Status != EnabledStatus {
|
|
return Domain{}, []roles.RoleProvision{}, svcerr.ErrInvalidStatus
|
|
}
|
|
|
|
d.CreatedAt = time.Now().UTC()
|
|
|
|
// Domain is created in repo first, because Roles table have foreign key relation with Domain ID
|
|
dom, err := svc.repo.SaveDomain(ctx, d)
|
|
if err != nil {
|
|
if errors.Contains(err, errors.ErrRouteNotAvailable) {
|
|
return Domain{}, []roles.RoleProvision{}, errors.ErrRouteNotAvailable
|
|
}
|
|
return Domain{}, []roles.RoleProvision{}, errors.Wrap(svcerr.ErrCreateEntity, err)
|
|
}
|
|
defer func() {
|
|
if retErr != nil {
|
|
if errRollBack := svc.repo.DeleteDomain(ctx, d.ID); errRollBack != nil {
|
|
retErr = errors.Wrap(retErr, errors.Wrap(errRollbackRepo, errRollBack))
|
|
}
|
|
}
|
|
}()
|
|
|
|
newBuiltInRoleMembers := map[roles.BuiltInRoleName][]roles.Member{
|
|
BuiltInRoleAdmin: {roles.Member(session.UserID)},
|
|
}
|
|
|
|
optionalPolicies := []policies.Policy{
|
|
{
|
|
Subject: policies.MagistralaObject,
|
|
SubjectType: policies.PlatformType,
|
|
Relation: "organization",
|
|
Object: d.ID,
|
|
ObjectType: policies.DomainType,
|
|
},
|
|
}
|
|
|
|
rps, err := svc.AddNewEntitiesRoles(ctx, d.ID, session.UserID, []string{d.ID}, optionalPolicies, newBuiltInRoleMembers)
|
|
if err != nil {
|
|
return Domain{}, []roles.RoleProvision{}, errors.Wrap(errCreateDomainPolicy, err)
|
|
}
|
|
|
|
return dom, rps, nil
|
|
}
|
|
|
|
func (svc service) RetrieveDomain(ctx context.Context, session authn.Session, id string, withRoles bool) (Domain, error) {
|
|
var domain Domain
|
|
var err error
|
|
switch session.SuperAdmin {
|
|
case true:
|
|
domain, err = svc.repo.RetrieveDomainByID(ctx, id)
|
|
default:
|
|
switch withRoles {
|
|
case true:
|
|
domain, err = svc.repo.RetrieveDomainByIDWithRoles(ctx, id, session.UserID)
|
|
default:
|
|
domain, err = svc.repo.RetrieveDomainByID(ctx, id)
|
|
}
|
|
}
|
|
if err != nil {
|
|
return Domain{}, errors.Wrap(svcerr.ErrViewEntity, err)
|
|
}
|
|
return domain, nil
|
|
}
|
|
|
|
func (svc service) UpdateDomain(ctx context.Context, session authn.Session, id string, d DomainReq) (Domain, error) {
|
|
updatedAt := time.Now().UTC()
|
|
d.UpdatedAt = &updatedAt
|
|
d.UpdatedBy = &session.UserID
|
|
dom, err := svc.repo.UpdateDomain(ctx, id, d)
|
|
if err != nil {
|
|
return Domain{}, errors.Wrap(svcerr.ErrUpdateEntity, err)
|
|
}
|
|
return dom, nil
|
|
}
|
|
|
|
func (svc service) EnableDomain(ctx context.Context, session authn.Session, id string) (Domain, error) {
|
|
status := EnabledStatus
|
|
updatedAt := time.Now().UTC()
|
|
dom, err := svc.repo.UpdateDomain(ctx, id, DomainReq{Status: &status, UpdatedBy: &session.UserID, UpdatedAt: &updatedAt})
|
|
if err != nil {
|
|
return Domain{}, errors.Wrap(svcerr.ErrUpdateEntity, err)
|
|
}
|
|
if err := svc.cache.RemoveStatus(ctx, id); err != nil {
|
|
return dom, errors.Wrap(svcerr.ErrRemoveEntity, err)
|
|
}
|
|
|
|
return dom, nil
|
|
}
|
|
|
|
func (svc service) DisableDomain(ctx context.Context, session authn.Session, id string) (Domain, error) {
|
|
status := DisabledStatus
|
|
updatedAt := time.Now().UTC()
|
|
dom, err := svc.repo.UpdateDomain(ctx, id, DomainReq{Status: &status, UpdatedBy: &session.UserID, UpdatedAt: &updatedAt})
|
|
if err != nil {
|
|
return Domain{}, errors.Wrap(svcerr.ErrUpdateEntity, err)
|
|
}
|
|
if err := svc.cache.RemoveStatus(ctx, id); err != nil {
|
|
return dom, errors.Wrap(svcerr.ErrRemoveEntity, err)
|
|
}
|
|
|
|
return dom, nil
|
|
}
|
|
|
|
// Only SuperAdmin can freeze the domain.
|
|
func (svc service) FreezeDomain(ctx context.Context, session authn.Session, id string) (Domain, error) {
|
|
status := FreezeStatus
|
|
updatedAt := time.Now().UTC()
|
|
dom, err := svc.repo.UpdateDomain(ctx, id, DomainReq{Status: &status, UpdatedBy: &session.UserID, UpdatedAt: &updatedAt})
|
|
if err != nil {
|
|
return Domain{}, errors.Wrap(svcerr.ErrUpdateEntity, err)
|
|
}
|
|
if err := svc.cache.RemoveStatus(ctx, id); err != nil {
|
|
return dom, errors.Wrap(svcerr.ErrRemoveEntity, err)
|
|
}
|
|
|
|
return dom, nil
|
|
}
|
|
|
|
func (svc service) ListDomains(ctx context.Context, session authn.Session, p Page) (DomainsPage, error) {
|
|
p.UserID = session.UserID
|
|
if session.SuperAdmin {
|
|
p.UserID = ""
|
|
}
|
|
|
|
dp, err := svc.repo.ListDomains(ctx, p)
|
|
if err != nil {
|
|
return DomainsPage{}, errors.Wrap(svcerr.ErrViewEntity, err)
|
|
}
|
|
return dp, nil
|
|
}
|
|
|
|
func (svc *service) SendInvitation(ctx context.Context, session authn.Session, invitation Invitation) (Invitation, error) {
|
|
role, err := svc.repo.RetrieveRole(ctx, invitation.RoleID)
|
|
if err != nil {
|
|
return Invitation{}, errors.Wrap(svcerr.ErrInvalidRole, err)
|
|
}
|
|
invitation.RoleName = role.Name
|
|
|
|
// Retrieve domain to get domain name
|
|
domain, err := svc.repo.RetrieveDomainByID(ctx, invitation.DomainID)
|
|
if err != nil {
|
|
return Invitation{}, errors.Wrap(svcerr.ErrViewEntity, err)
|
|
}
|
|
invitation.DomainName = domain.Name
|
|
|
|
invitation.InvitedBy = session.UserID
|
|
invitation.CreatedAt = time.Now().UTC()
|
|
|
|
if invitation.Resend {
|
|
if err := svc.resendInvitation(ctx, invitation); err != nil {
|
|
return Invitation{}, err
|
|
}
|
|
return invitation, nil
|
|
}
|
|
|
|
if err := svc.repo.SaveInvitation(ctx, invitation); err != nil {
|
|
return Invitation{}, errors.Wrap(svcerr.ErrCreateEntity, err)
|
|
}
|
|
return invitation, nil
|
|
}
|
|
|
|
func (svc *service) resendInvitation(ctx context.Context, invitation Invitation) error {
|
|
inv, err := svc.repo.RetrieveInvitation(ctx, invitation.InviteeUserID, invitation.DomainID)
|
|
if err != nil {
|
|
return errors.Wrap(svcerr.ErrViewEntity, err)
|
|
}
|
|
if !inv.ConfirmedAt.IsZero() {
|
|
return svcerr.ErrInvitationAlreadyAccepted
|
|
}
|
|
if !inv.RejectedAt.IsZero() {
|
|
invitation.RejectedAt = time.Time{}
|
|
invitation.UpdatedAt = time.Now().UTC()
|
|
if err := svc.repo.UpdateRejection(ctx, invitation); err != nil {
|
|
return errors.Wrap(svcerr.ErrUpdateEntity, err)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (svc *service) ListInvitations(ctx context.Context, session authn.Session, page InvitationPageMeta) (invitations InvitationPage, err error) {
|
|
page.InviteeUserID = session.UserID
|
|
ip, err := svc.repo.RetrieveAllInvitations(ctx, page)
|
|
if err != nil {
|
|
return InvitationPage{}, errors.Wrap(svcerr.ErrViewEntity, err)
|
|
}
|
|
return ip, nil
|
|
}
|
|
|
|
func (svc *service) ListDomainInvitations(ctx context.Context, session authn.Session, page InvitationPageMeta) (invitations InvitationPage, err error) {
|
|
page.DomainID = session.DomainID
|
|
ip, err := svc.repo.RetrieveAllInvitations(ctx, page)
|
|
if err != nil {
|
|
return InvitationPage{}, errors.Wrap(svcerr.ErrViewEntity, err)
|
|
}
|
|
return ip, nil
|
|
}
|
|
|
|
func (svc *service) AcceptInvitation(ctx context.Context, session authn.Session, domainID string) (invitation Invitation, err error) {
|
|
inv, err := svc.repo.RetrieveInvitation(ctx, session.UserID, domainID)
|
|
if err != nil {
|
|
return Invitation{}, errors.Wrap(svcerr.ErrViewEntity, err)
|
|
}
|
|
|
|
if inv.InviteeUserID != session.UserID {
|
|
return Invitation{}, svcerr.ErrAuthorization
|
|
}
|
|
|
|
if !inv.ConfirmedAt.IsZero() {
|
|
return Invitation{}, svcerr.ErrInvitationAlreadyAccepted
|
|
}
|
|
|
|
if !inv.RejectedAt.IsZero() {
|
|
return Invitation{}, svcerr.ErrInvitationAlreadyRejected
|
|
}
|
|
|
|
inv, err = svc.populateDetails(ctx, inv, domainID)
|
|
if err != nil {
|
|
return Invitation{}, err
|
|
}
|
|
|
|
session.DomainID = domainID
|
|
|
|
if _, err := svc.RoleAddMembers(ctx, session, domainID, inv.RoleID, []string{session.UserID}); err != nil {
|
|
return Invitation{}, errors.Wrap(svcerr.ErrUpdateEntity, err)
|
|
}
|
|
|
|
inv.ConfirmedAt = time.Now().UTC()
|
|
inv.UpdatedAt = inv.ConfirmedAt
|
|
|
|
if err := svc.repo.UpdateConfirmation(ctx, inv); err != nil {
|
|
return Invitation{}, errors.Wrap(svcerr.ErrUpdateEntity, err)
|
|
}
|
|
|
|
return inv, nil
|
|
}
|
|
|
|
func (svc *service) RejectInvitation(ctx context.Context, session authn.Session, domainID string) (Invitation, error) {
|
|
inv, err := svc.repo.RetrieveInvitation(ctx, session.UserID, domainID)
|
|
if err != nil {
|
|
return Invitation{}, errors.Wrap(svcerr.ErrViewEntity, err)
|
|
}
|
|
|
|
if inv.InviteeUserID != session.UserID {
|
|
return Invitation{}, svcerr.ErrAuthorization
|
|
}
|
|
|
|
if !inv.ConfirmedAt.IsZero() {
|
|
return Invitation{}, svcerr.ErrInvitationAlreadyAccepted
|
|
}
|
|
|
|
if !inv.RejectedAt.IsZero() {
|
|
return Invitation{}, svcerr.ErrInvitationAlreadyRejected
|
|
}
|
|
|
|
inv, err = svc.populateDetails(ctx, inv, domainID)
|
|
if err != nil {
|
|
return Invitation{}, err
|
|
}
|
|
|
|
inv.RejectedAt = time.Now().UTC()
|
|
inv.UpdatedAt = inv.RejectedAt
|
|
|
|
if err := svc.repo.UpdateRejection(ctx, inv); err != nil {
|
|
return Invitation{}, errors.Wrap(svcerr.ErrUpdateEntity, err)
|
|
}
|
|
|
|
return inv, nil
|
|
}
|
|
|
|
func (svc *service) DeleteInvitation(ctx context.Context, session authn.Session, inviteeUserID, domainID string) error {
|
|
if session.UserID == inviteeUserID {
|
|
if err := svc.repo.DeleteUsersInvitations(ctx, domainID, inviteeUserID); err != nil {
|
|
return errors.Wrap(svcerr.ErrRemoveEntity, err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
inv, err := svc.repo.RetrieveInvitation(ctx, inviteeUserID, domainID)
|
|
if err != nil {
|
|
return errors.Wrap(svcerr.ErrRemoveEntity, err)
|
|
}
|
|
|
|
if !inv.ConfirmedAt.IsZero() {
|
|
return errors.Wrap(svcerr.ErrRemoveEntity, svcerr.ErrInvitationAlreadyAccepted)
|
|
}
|
|
|
|
if !inv.RejectedAt.IsZero() {
|
|
return errors.Wrap(svcerr.ErrRemoveEntity, svcerr.ErrInvitationAlreadyRejected)
|
|
}
|
|
|
|
if err := svc.repo.DeleteUsersInvitations(ctx, domainID, inviteeUserID); err != nil {
|
|
return errors.Wrap(svcerr.ErrRemoveEntity, err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Add domain and role names for an invitation if they are not already set.
|
|
func (svc *service) populateDetails(ctx context.Context, inv Invitation, domainID string) (Invitation, error) {
|
|
// Populate domain name if not already set
|
|
if inv.DomainName == "" {
|
|
domain, err := svc.repo.RetrieveDomainByID(ctx, domainID)
|
|
if err != nil {
|
|
return Invitation{}, errors.Wrap(svcerr.ErrViewEntity, err)
|
|
}
|
|
inv.DomainName = domain.Name
|
|
}
|
|
|
|
// Populate role name if not already set
|
|
if inv.RoleName == "" {
|
|
role, err := svc.repo.RetrieveRole(ctx, inv.RoleID)
|
|
if err != nil {
|
|
return Invitation{}, errors.Wrap(svcerr.ErrViewEntity, err)
|
|
}
|
|
inv.RoleName = role.Name
|
|
}
|
|
|
|
return inv, nil
|
|
}
|
|
|
|
// Add addition removal of user from invitations.
|
|
func (svc *service) RemoveEntityMembers(ctx context.Context, session authn.Session, entityID string, members []string) error {
|
|
if err := svc.repo.DeleteUsersInvitations(ctx, entityID, members...); err != nil && err != repoerr.ErrNotFound {
|
|
return err
|
|
}
|
|
|
|
return svc.ProvisionManageService.RemoveEntityMembers(ctx, session, entityID, members)
|
|
}
|
|
|
|
func (svc *service) RoleRemoveMembers(ctx context.Context, session authn.Session, entityID, roleID string, members []string) error {
|
|
ro, err := svc.repo.RetrieveEntityRole(ctx, entityID, roleID)
|
|
if err != nil {
|
|
return errors.Wrap(svcerr.ErrViewEntity, err)
|
|
}
|
|
|
|
if _, err := svc.ProvisionManageService.BuiltInRoleActions(roles.BuiltInRoleName(ro.Name)); err == nil {
|
|
membersPage, err := svc.repo.RoleListMembers(ctx, ro.ID, 0, 0)
|
|
if err != nil {
|
|
return errors.Wrap(svcerr.ErrViewEntity, err)
|
|
}
|
|
if membersPage.Total <= uint64(len(members)) {
|
|
return svcerr.ErrRetainOneMember
|
|
}
|
|
}
|
|
|
|
if err := svc.repo.DeleteUsersInvitations(ctx, entityID, members...); err != nil && err != repoerr.ErrNotFound {
|
|
return err
|
|
}
|
|
|
|
return svc.ProvisionManageService.RoleRemoveMembers(ctx, session, entityID, roleID, members)
|
|
}
|
|
|
|
func (svc *service) RoleRemoveAllMembers(ctx context.Context, session authn.Session, entityID, roleID string) error {
|
|
ro, err := svc.repo.RetrieveEntityRole(ctx, entityID, roleID)
|
|
if err != nil {
|
|
return errors.Wrap(svcerr.ErrViewEntity, err)
|
|
}
|
|
|
|
if _, err := svc.ProvisionManageService.BuiltInRoleActions(roles.BuiltInRoleName(ro.Name)); err == nil {
|
|
return svcerr.ErrRetainOneMember
|
|
}
|
|
|
|
return svc.ProvisionManageService.RoleRemoveAllMembers(ctx, session, entityID, roleID)
|
|
}
|