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>
407 lines
12 KiB
Go
407 lines
12 KiB
Go
// Copyright (c) Abstract Machines
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
package clients
|
|
|
|
import (
|
|
"context"
|
|
"time"
|
|
|
|
mg "github.com/absmach/magistrala"
|
|
grpcChannelsV1 "github.com/absmach/magistrala/api/grpc/channels/v1"
|
|
grpcCommonV1 "github.com/absmach/magistrala/api/grpc/common/v1"
|
|
grpcGroupsV1 "github.com/absmach/magistrala/api/grpc/groups/v1"
|
|
apiutil "github.com/absmach/magistrala/api/http/util"
|
|
"github.com/absmach/magistrala/pkg/authn"
|
|
"github.com/absmach/magistrala/pkg/errors"
|
|
svcerr "github.com/absmach/magistrala/pkg/errors/service"
|
|
"github.com/absmach/magistrala/pkg/policies"
|
|
"github.com/absmach/magistrala/pkg/roles"
|
|
)
|
|
|
|
var (
|
|
errRollbackRepo = errors.New("failed to rollback repo")
|
|
errSetParentGroup = errors.NewRequestError("client already have parent")
|
|
errSetSameParentGroup = errors.NewRequestError("client already assigned to the parent group")
|
|
errParentGroupDomainID = errors.NewRequestError("parent group has invalid domain id")
|
|
errParentGroupDisabled = errors.NewRequestError("parent group is not enabled")
|
|
)
|
|
var _ Service = (*service)(nil)
|
|
|
|
type service struct {
|
|
repo Repository
|
|
policy policies.Service
|
|
channels grpcChannelsV1.ChannelsServiceClient
|
|
groups grpcGroupsV1.GroupsServiceClient
|
|
cache Cache
|
|
idProvider mg.IDProvider
|
|
roles.ProvisionManageService
|
|
}
|
|
|
|
// NewService returns a new Clients service implementation.
|
|
func NewService(repo Repository, policy policies.Service, cache Cache, channels grpcChannelsV1.ChannelsServiceClient, groups grpcGroupsV1.GroupsServiceClient, idProvider mg.IDProvider, sIDProvider mg.IDProvider, availableActions []roles.Action, builtInRoles map[roles.BuiltInRoleName][]roles.Action) (Service, error) {
|
|
rpms, err := roles.NewProvisionManageService(policies.ClientType, repo, policy, sIDProvider, availableActions, builtInRoles)
|
|
if err != nil {
|
|
return service{}, err
|
|
}
|
|
return service{
|
|
repo: repo,
|
|
policy: policy,
|
|
channels: channels,
|
|
groups: groups,
|
|
cache: cache,
|
|
idProvider: idProvider,
|
|
ProvisionManageService: rpms,
|
|
}, nil
|
|
}
|
|
|
|
func (svc service) CreateClients(ctx context.Context, session authn.Session, cls ...Client) (retClients []Client, retRps []roles.RoleProvision, retErr error) {
|
|
var clients []Client
|
|
for _, c := range cls {
|
|
if c.ID == "" {
|
|
clientID, err := svc.idProvider.ID()
|
|
if err != nil {
|
|
return []Client{}, []roles.RoleProvision{}, errors.Wrap(svcerr.ErrIssueProviderID, err)
|
|
}
|
|
c.ID = clientID
|
|
}
|
|
if c.Credentials.Secret == "" {
|
|
key, err := svc.idProvider.ID()
|
|
if err != nil {
|
|
return []Client{}, []roles.RoleProvision{}, errors.Wrap(svcerr.ErrIssueProviderID, err)
|
|
}
|
|
c.Credentials.Secret = key
|
|
}
|
|
if c.Status != DisabledStatus && c.Status != EnabledStatus {
|
|
return []Client{}, []roles.RoleProvision{}, svcerr.ErrInvalidStatus
|
|
}
|
|
c.Domain = session.DomainID
|
|
c.CreatedAt = time.Now().UTC()
|
|
clients = append(clients, c)
|
|
}
|
|
|
|
newClients, err := svc.repo.Save(ctx, clients...)
|
|
if err != nil {
|
|
return []Client{}, []roles.RoleProvision{}, errors.Wrap(svcerr.ErrCreateEntity, err)
|
|
}
|
|
newClientIDs := []string{}
|
|
for _, newClient := range newClients {
|
|
newClientIDs = append(newClientIDs, newClient.ID)
|
|
}
|
|
|
|
defer func() {
|
|
if retErr != nil {
|
|
if errRollBack := svc.repo.Delete(ctx, newClientIDs...); errRollBack != nil {
|
|
retErr = errors.Wrap(retErr, errors.Wrap(errRollbackRepo, errRollBack))
|
|
}
|
|
}
|
|
}()
|
|
|
|
newBuiltInRoleMembers := map[roles.BuiltInRoleName][]roles.Member{
|
|
BuiltInRoleAdmin: {roles.Member(session.UserID)},
|
|
}
|
|
|
|
optionalPolicies := []policies.Policy{}
|
|
|
|
for _, newClientID := range newClientIDs {
|
|
optionalPolicies = append(optionalPolicies,
|
|
policies.Policy{
|
|
Domain: session.DomainID,
|
|
SubjectType: policies.DomainType,
|
|
Subject: session.DomainID,
|
|
Relation: policies.DomainRelation,
|
|
ObjectType: policies.ClientType,
|
|
Object: newClientID,
|
|
},
|
|
)
|
|
}
|
|
|
|
rp, err := svc.AddNewEntitiesRoles(ctx, session.DomainID, session.UserID, newClientIDs, optionalPolicies, newBuiltInRoleMembers)
|
|
if err != nil {
|
|
return []Client{}, []roles.RoleProvision{}, errors.Wrap(svcerr.ErrAddPolicies, err)
|
|
}
|
|
|
|
return newClients, rp, nil
|
|
}
|
|
|
|
func (svc service) View(ctx context.Context, session authn.Session, id string, withRoles bool) (Client, error) {
|
|
var client Client
|
|
var err error
|
|
switch withRoles {
|
|
case true:
|
|
client, err = svc.repo.RetrieveByIDWithRoles(ctx, id, session.UserID)
|
|
default:
|
|
client, err = svc.repo.RetrieveByID(ctx, id)
|
|
}
|
|
if err != nil {
|
|
return Client{}, errors.Wrap(svcerr.ErrViewEntity, err)
|
|
}
|
|
return client, nil
|
|
}
|
|
|
|
func (svc service) ListClients(ctx context.Context, session authn.Session, pm Page) (ClientsPage, error) {
|
|
switch session.SuperAdmin {
|
|
case true:
|
|
pm.Domain = session.DomainID
|
|
cp, err := svc.repo.RetrieveAll(ctx, pm)
|
|
if err != nil {
|
|
return ClientsPage{}, errors.Wrap(svcerr.ErrViewEntity, err)
|
|
}
|
|
return cp, nil
|
|
default:
|
|
cp, err := svc.repo.RetrieveUserClients(ctx, session.DomainID, session.UserID, pm)
|
|
if err != nil {
|
|
return ClientsPage{}, errors.Wrap(svcerr.ErrViewEntity, err)
|
|
}
|
|
return cp, nil
|
|
}
|
|
}
|
|
|
|
func (svc service) ListUserClients(ctx context.Context, session authn.Session, userID string, pm Page) (ClientsPage, error) {
|
|
cp, err := svc.repo.RetrieveUserClients(ctx, session.DomainID, userID, pm)
|
|
if err != nil {
|
|
return ClientsPage{}, errors.Wrap(svcerr.ErrViewEntity, err)
|
|
}
|
|
return cp, nil
|
|
}
|
|
|
|
func (svc service) Update(ctx context.Context, session authn.Session, cli Client) (Client, error) {
|
|
client := Client{
|
|
ID: cli.ID,
|
|
Name: cli.Name,
|
|
Metadata: cli.Metadata,
|
|
PrivateMetadata: cli.PrivateMetadata,
|
|
UpdatedAt: time.Now().UTC(),
|
|
UpdatedBy: session.UserID,
|
|
}
|
|
client, err := svc.repo.Update(ctx, client)
|
|
if err != nil {
|
|
return Client{}, errors.Wrap(svcerr.ErrUpdateEntity, err)
|
|
}
|
|
return client, nil
|
|
}
|
|
|
|
func (svc service) UpdateTags(ctx context.Context, session authn.Session, cli Client) (Client, error) {
|
|
client := Client{
|
|
ID: cli.ID,
|
|
Tags: cli.Tags,
|
|
UpdatedAt: time.Now().UTC(),
|
|
UpdatedBy: session.UserID,
|
|
}
|
|
client, err := svc.repo.UpdateTags(ctx, client)
|
|
if err != nil {
|
|
return Client{}, errors.Wrap(svcerr.ErrUpdateEntity, err)
|
|
}
|
|
return client, nil
|
|
}
|
|
|
|
func (svc service) UpdateSecret(ctx context.Context, session authn.Session, id, key string) (Client, error) {
|
|
client := Client{
|
|
ID: id,
|
|
Credentials: Credentials{
|
|
Secret: key,
|
|
},
|
|
UpdatedAt: time.Now().UTC(),
|
|
UpdatedBy: session.UserID,
|
|
Status: EnabledStatus,
|
|
}
|
|
client, err := svc.repo.UpdateSecret(ctx, client)
|
|
if err != nil {
|
|
return Client{}, errors.Wrap(svcerr.ErrUpdateEntity, err)
|
|
}
|
|
if err := svc.cache.Remove(ctx, client.ID); err != nil {
|
|
return client, errors.Wrap(svcerr.ErrRemoveEntity, err)
|
|
}
|
|
return client, nil
|
|
}
|
|
|
|
func (svc service) Enable(ctx context.Context, session authn.Session, id string) (Client, error) {
|
|
client := Client{
|
|
ID: id,
|
|
Status: EnabledStatus,
|
|
UpdatedAt: time.Now().UTC(),
|
|
}
|
|
client, err := svc.changeClientStatus(ctx, session, client)
|
|
if err != nil {
|
|
return Client{}, errors.Wrap(ErrEnableClient, err)
|
|
}
|
|
|
|
return client, nil
|
|
}
|
|
|
|
func (svc service) Disable(ctx context.Context, session authn.Session, id string) (Client, error) {
|
|
client := Client{
|
|
ID: id,
|
|
Status: DisabledStatus,
|
|
UpdatedAt: time.Now().UTC(),
|
|
}
|
|
client, err := svc.changeClientStatus(ctx, session, client)
|
|
if err != nil {
|
|
return Client{}, errors.Wrap(ErrDisableClient, err)
|
|
}
|
|
|
|
if err := svc.cache.Remove(ctx, client.ID); err != nil {
|
|
return client, errors.Wrap(svcerr.ErrRemoveEntity, err)
|
|
}
|
|
|
|
return client, nil
|
|
}
|
|
|
|
func (svc service) SetParentGroup(ctx context.Context, session authn.Session, parentGroupID string, id string) (retErr error) {
|
|
cli, err := svc.repo.RetrieveByID(ctx, id)
|
|
if err != nil {
|
|
return errors.Wrap(svcerr.ErrUpdateEntity, err)
|
|
}
|
|
switch cli.ParentGroup {
|
|
case parentGroupID:
|
|
return errors.Wrap(svcerr.ErrConflict, errSetSameParentGroup)
|
|
case "":
|
|
// No action needed, proceed to next code after switch
|
|
default:
|
|
return errors.Wrap(svcerr.ErrConflict, errSetParentGroup)
|
|
}
|
|
|
|
resp, err := svc.groups.RetrieveEntity(ctx, &grpcCommonV1.RetrieveEntityReq{Id: parentGroupID})
|
|
if err != nil {
|
|
return errors.Wrap(svcerr.ErrUpdateEntity, err)
|
|
}
|
|
if resp.GetEntity().GetDomainId() != session.DomainID {
|
|
return errors.Wrap(svcerr.ErrUpdateEntity, errParentGroupDomainID)
|
|
}
|
|
if resp.GetEntity().GetStatus() != uint32(EnabledStatus) {
|
|
return errors.Wrap(svcerr.ErrUpdateEntity, errParentGroupDisabled)
|
|
}
|
|
|
|
var pols []policies.Policy
|
|
|
|
pols = append(pols, policies.Policy{
|
|
Domain: session.DomainID,
|
|
SubjectType: policies.GroupType,
|
|
Subject: parentGroupID,
|
|
Relation: policies.ParentGroupRelation,
|
|
ObjectType: policies.ClientType,
|
|
Object: id,
|
|
})
|
|
|
|
if err := svc.policy.AddPolicies(ctx, pols); err != nil {
|
|
return errors.Wrap(svcerr.ErrAddPolicies, err)
|
|
}
|
|
defer func() {
|
|
if retErr != nil {
|
|
if errRollback := svc.policy.DeletePolicies(ctx, pols); errRollback != nil {
|
|
retErr = errors.Wrap(retErr, errors.Wrap(apiutil.ErrRollbackTx, errRollback))
|
|
}
|
|
}
|
|
}()
|
|
cli = Client{ID: id, ParentGroup: parentGroupID, UpdatedBy: session.UserID, UpdatedAt: time.Now().UTC()}
|
|
|
|
if err := svc.repo.SetParentGroup(ctx, cli); err != nil {
|
|
return errors.Wrap(svcerr.ErrUpdateEntity, err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (svc service) RemoveParentGroup(ctx context.Context, session authn.Session, id string) (retErr error) {
|
|
cli, err := svc.repo.RetrieveByID(ctx, id)
|
|
if err != nil {
|
|
return errors.Wrap(svcerr.ErrViewEntity, err)
|
|
}
|
|
|
|
if cli.ParentGroup != "" {
|
|
var pols []policies.Policy
|
|
pols = append(pols, policies.Policy{
|
|
Domain: session.DomainID,
|
|
SubjectType: policies.GroupType,
|
|
Subject: cli.ParentGroup,
|
|
Relation: policies.ParentGroupRelation,
|
|
ObjectType: policies.ClientType,
|
|
Object: id,
|
|
})
|
|
|
|
if err := svc.policy.DeletePolicies(ctx, pols); err != nil {
|
|
return errors.Wrap(svcerr.ErrDeletePolicies, err)
|
|
}
|
|
defer func() {
|
|
if retErr != nil {
|
|
if errRollback := svc.policy.AddPolicies(ctx, pols); errRollback != nil {
|
|
retErr = errors.Wrap(retErr, errors.Wrap(apiutil.ErrRollbackTx, errRollback))
|
|
}
|
|
}
|
|
}()
|
|
|
|
cli := Client{ID: id, UpdatedBy: session.UserID, UpdatedAt: time.Now().UTC()}
|
|
|
|
if err := svc.repo.RemoveParentGroup(ctx, cli); err != nil {
|
|
return errors.Wrap(svcerr.ErrUpdateEntity, err)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (svc service) Delete(ctx context.Context, session authn.Session, id string) error {
|
|
ok, err := svc.repo.DoesClientHaveConnections(ctx, id)
|
|
if err != nil {
|
|
return errors.Wrap(svcerr.ErrRemoveEntity, err)
|
|
}
|
|
if ok {
|
|
if _, err := svc.channels.RemoveClientConnections(ctx, &grpcChannelsV1.RemoveClientConnectionsReq{ClientId: id}); err != nil {
|
|
return errors.Wrap(svcerr.ErrRemoveEntity, err)
|
|
}
|
|
}
|
|
|
|
if _, err := svc.repo.ChangeStatus(ctx, Client{ID: id, Status: DeletedStatus}); err != nil {
|
|
return errors.Wrap(svcerr.ErrRemoveEntity, err)
|
|
}
|
|
|
|
if err := svc.cache.Remove(ctx, id); err != nil {
|
|
return errors.Wrap(svcerr.ErrRemoveEntity, err)
|
|
}
|
|
|
|
filterDeletePolicies := []policies.Policy{
|
|
{
|
|
SubjectType: policies.ClientType,
|
|
Subject: id,
|
|
},
|
|
{
|
|
ObjectType: policies.ClientType,
|
|
Object: id,
|
|
},
|
|
}
|
|
deletePolicies := []policies.Policy{
|
|
{
|
|
SubjectType: policies.DomainType,
|
|
Subject: session.DomainID,
|
|
Relation: policies.DomainRelation,
|
|
ObjectType: policies.ClientType,
|
|
Object: id,
|
|
},
|
|
}
|
|
|
|
if err := svc.RemoveEntitiesRoles(ctx, session.DomainID, session.DomainUserID, []string{id}, filterDeletePolicies, deletePolicies); err != nil {
|
|
return errors.Wrap(svcerr.ErrDeletePolicies, err)
|
|
}
|
|
|
|
if err := svc.repo.Delete(ctx, id); err != nil {
|
|
return errors.Wrap(svcerr.ErrRemoveEntity, err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (svc service) changeClientStatus(ctx context.Context, session authn.Session, client Client) (Client, error) {
|
|
dbClient, err := svc.repo.RetrieveByID(ctx, client.ID)
|
|
if err != nil {
|
|
return Client{}, errors.Wrap(svcerr.ErrViewEntity, err)
|
|
}
|
|
if dbClient.Status == client.Status {
|
|
return Client{}, svcerr.ErrStatusAlreadyAssigned
|
|
}
|
|
|
|
client.UpdatedBy = session.UserID
|
|
|
|
client, err = svc.repo.ChangeStatus(ctx, client)
|
|
if err != nil {
|
|
return Client{}, errors.Wrap(svcerr.ErrUpdateEntity, err)
|
|
}
|
|
return client, nil
|
|
}
|