Files
magistrala/channels/middleware/authorization.go
T
Steve Munene 7f03134d8e
Property Based Tests / api-test (push) Has been cancelled
Continuous Delivery / lint-and-build (push) Has been cancelled
Deploy GitHub Pages / swagger-ui (push) Has been cancelled
CI Pipeline / Lint Proto (push) Has been cancelled
CI Pipeline / Detect Changes (push) Has been cancelled
Continuous Delivery / Build and Push Docker Images (push) Has been cancelled
CI Pipeline / lint-and-build (push) Has been cancelled
CI Pipeline / Test ${{ matrix.module }} (push) Has been cancelled
CI Pipeline / Upload Coverage (push) Has been cancelled
NOISSUE - Update bootstrap and provision service (#3476)
Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>
Signed-off-by: JeffMboya <jangina.mboya@gmail.com>
Co-authored-by: JeffMboya <jangina.mboya@gmail.com>
2026-05-08 10:35:00 +02:00

384 lines
14 KiB
Go

// Copyright (c) Abstract Machines
// SPDX-License-Identifier: Apache-2.0
package middleware
import (
"context"
"fmt"
"github.com/absmach/magistrala/auth"
"github.com/absmach/magistrala/channels"
"github.com/absmach/magistrala/channels/operations"
cOperations "github.com/absmach/magistrala/clients/operations"
dOperations "github.com/absmach/magistrala/domains/operations"
gOperations "github.com/absmach/magistrala/groups/operations"
"github.com/absmach/magistrala/pkg/authn"
smqauthz "github.com/absmach/magistrala/pkg/authz"
"github.com/absmach/magistrala/pkg/connections"
"github.com/absmach/magistrala/pkg/errors"
svcerr "github.com/absmach/magistrala/pkg/errors/service"
"github.com/absmach/magistrala/pkg/permissions"
"github.com/absmach/magistrala/pkg/policies"
"github.com/absmach/magistrala/pkg/roles"
rolemgr "github.com/absmach/magistrala/pkg/roles/rolemanager/middleware"
)
var (
errView = errors.New("not authorized to view channel")
errList = errors.New("not authorized to list user channels")
errUpdate = errors.New("not authorized to update channel")
errUpdateTags = errors.New("not authorized to update channel tags")
errEnable = errors.New("not authorized to enable channel")
errDisable = errors.New("not authorized to disable channel")
errDelete = errors.New("not authorized to delete channel")
errConnect = errors.New("not authorized to connect to channel")
errDisconnect = errors.New("not authorized to disconnect from channel")
errSetParentGroup = errors.New("not authorized to set parent group to channel")
errRemoveParentGroup = errors.New("not authorized to remove parent group from channel")
errDomainCreateChannels = errors.New("not authorized to create channel in domain")
errGroupSetChildChannels = errors.New("not authorized to set child channel for group")
errGroupRemoveChildChannels = errors.New("not authorized to remove child channel for group")
errClientDisConnectChannels = errors.New("not authorized to disconnect channel for client")
errClientConnectChannels = errors.New("not authorized to connect channel for client")
)
var _ channels.Service = (*authorizationMiddleware)(nil)
type authorizationMiddleware struct {
svc channels.Service
repo channels.Repository
authz smqauthz.Authorization
entitiesOps permissions.EntitiesOperations[permissions.Operation]
rolemgr.RoleManagerAuthorizationMiddleware
}
// NewAuthorization adds authorization to the channels service.
func NewAuthorization(
entityType string,
svc channels.Service,
authz smqauthz.Authorization,
repo channels.Repository,
entitiesOps permissions.EntitiesOperations[permissions.Operation],
roleOps permissions.Operations[permissions.RoleOperation],
) (channels.Service, error) {
if err := entitiesOps.Validate(); err != nil {
return nil, err
}
ram, err := rolemgr.NewAuthorization(policies.ChannelType, svc, authz, roleOps)
if err != nil {
return nil, err
}
return &authorizationMiddleware{
svc: svc,
authz: authz,
repo: repo,
entitiesOps: entitiesOps,
RoleManagerAuthorizationMiddleware: ram,
}, nil
}
func (am *authorizationMiddleware) CreateChannels(ctx context.Context, session authn.Session, chs ...channels.Channel) ([]channels.Channel, []roles.RoleProvision, error) {
if err := am.authorize(ctx, session, policies.DomainType, dOperations.OpCreateDomainChannels, smqauthz.PolicyReq{
Domain: session.DomainID,
SubjectType: policies.UserType,
Subject: session.DomainUserID,
ObjectType: policies.DomainType,
Object: session.DomainID,
}); err != nil {
return []channels.Channel{}, []roles.RoleProvision{}, errors.Wrap(err, errDomainCreateChannels)
}
for _, ch := range chs {
if ch.ParentGroup != "" {
if err := am.authorize(ctx, session, policies.GroupType, gOperations.OpGroupSetChildChannel, smqauthz.PolicyReq{
Domain: session.DomainID,
SubjectType: policies.UserType,
Subject: session.DomainUserID,
ObjectType: policies.GroupType,
Object: ch.ParentGroup,
}); err != nil {
return []channels.Channel{}, []roles.RoleProvision{}, errors.Wrap(err, errors.Wrap(errGroupSetChildChannels, fmt.Errorf("channel name %s parent group id %s", ch.Name, ch.ParentGroup)))
}
}
}
return am.svc.CreateChannels(ctx, session, chs...)
}
func (am *authorizationMiddleware) ViewChannel(ctx context.Context, session authn.Session, id string, withRoles bool) (channels.Channel, error) {
if err := am.authorize(ctx, session, policies.ChannelType, operations.OpViewChannel, smqauthz.PolicyReq{
Domain: session.DomainID,
SubjectType: policies.UserType,
Subject: session.DomainUserID,
ObjectType: policies.ChannelType,
Object: id,
}); err != nil {
return channels.Channel{}, errors.Wrap(err, errView)
}
return am.svc.ViewChannel(ctx, session, id, withRoles)
}
func (am *authorizationMiddleware) ListChannels(ctx context.Context, session authn.Session, pm channels.Page) (channels.ChannelsPage, error) {
switch err := am.checkSuperAdmin(ctx, session); {
case err == nil:
session.SuperAdmin = true
case errors.Contains(err, svcerr.ErrSuperAdminAction):
default:
return channels.ChannelsPage{}, err
}
return am.svc.ListChannels(ctx, session, pm)
}
func (am *authorizationMiddleware) ListUserChannels(ctx context.Context, session authn.Session, userID string, pm channels.Page) (channels.ChannelsPage, error) {
if err := am.checkSuperAdmin(ctx, session); err != nil {
return channels.ChannelsPage{}, errors.Wrap(err, errList)
}
return am.svc.ListUserChannels(ctx, session, userID, pm)
}
func (am *authorizationMiddleware) UpdateChannel(ctx context.Context, session authn.Session, channel channels.Channel) (channels.Channel, error) {
if err := am.authorize(ctx, session, policies.ChannelType, operations.OpUpdateChannel, smqauthz.PolicyReq{
Domain: session.DomainID,
SubjectType: policies.UserType,
Subject: session.DomainUserID,
ObjectType: policies.ChannelType,
Object: channel.ID,
}); err != nil {
return channels.Channel{}, errors.Wrap(err, errUpdate)
}
return am.svc.UpdateChannel(ctx, session, channel)
}
func (am *authorizationMiddleware) UpdateChannelTags(ctx context.Context, session authn.Session, channel channels.Channel) (channels.Channel, error) {
if err := am.authorize(ctx, session, policies.ChannelType, operations.OpUpdateChannelTags, smqauthz.PolicyReq{
Domain: session.DomainID,
SubjectType: policies.UserType,
Subject: session.DomainUserID,
ObjectType: policies.ChannelType,
Object: channel.ID,
}); err != nil {
return channels.Channel{}, errors.Wrap(err, errUpdateTags)
}
return am.svc.UpdateChannelTags(ctx, session, channel)
}
func (am *authorizationMiddleware) EnableChannel(ctx context.Context, session authn.Session, id string) (channels.Channel, error) {
if err := am.authorize(ctx, session, policies.ChannelType, operations.OpEnableChannel, smqauthz.PolicyReq{
Domain: session.DomainID,
SubjectType: policies.UserType,
Subject: session.DomainUserID,
ObjectType: policies.ChannelType,
Object: id,
}); err != nil {
return channels.Channel{}, errors.Wrap(err, errEnable)
}
return am.svc.EnableChannel(ctx, session, id)
}
func (am *authorizationMiddleware) DisableChannel(ctx context.Context, session authn.Session, id string) (channels.Channel, error) {
if err := am.authorize(ctx, session, policies.ChannelType, operations.OpDisableChannel, smqauthz.PolicyReq{
Domain: session.DomainID,
SubjectType: policies.UserType,
Subject: session.DomainUserID,
ObjectType: policies.ChannelType,
Object: id,
}); err != nil {
return channels.Channel{}, errors.Wrap(err, errDisable)
}
return am.svc.DisableChannel(ctx, session, id)
}
func (am *authorizationMiddleware) RemoveChannel(ctx context.Context, session authn.Session, id string) error {
if err := am.authorize(ctx, session, policies.ChannelType, operations.OpDeleteChannel, smqauthz.PolicyReq{
Domain: session.DomainID,
SubjectType: policies.UserType,
Subject: session.DomainUserID,
ObjectType: policies.ChannelType,
Object: id,
}); err != nil {
return errors.Wrap(err, errDelete)
}
return am.svc.RemoveChannel(ctx, session, id)
}
func (am *authorizationMiddleware) Connect(ctx context.Context, session authn.Session, chIDs, thIDs []string, connTypes []connections.ConnType) error {
for _, chID := range chIDs {
if err := am.authorize(ctx, session, policies.ChannelType, operations.OpConnectClient, smqauthz.PolicyReq{
Domain: session.DomainID,
SubjectType: policies.UserType,
Subject: session.DomainUserID,
ObjectType: policies.ChannelType,
Object: chID,
}); err != nil {
return errors.Wrap(err, errConnect)
}
}
for _, thID := range thIDs {
if err := am.authorize(ctx, session, policies.ClientType, cOperations.OpConnectToChannel, smqauthz.PolicyReq{
Domain: session.DomainID,
SubjectType: policies.UserType,
Subject: session.DomainUserID,
ObjectType: policies.ClientType,
Object: thID,
}); err != nil {
return errors.Wrap(err, errClientConnectChannels)
}
}
return am.svc.Connect(ctx, session, chIDs, thIDs, connTypes)
}
func (am *authorizationMiddleware) Disconnect(ctx context.Context, session authn.Session, chIDs, thIDs []string, connTypes []connections.ConnType) error {
for _, chID := range chIDs {
if err := am.authorize(ctx, session, policies.ChannelType, operations.OpDisconnectClient, smqauthz.PolicyReq{
Domain: session.DomainID,
SubjectType: policies.UserType,
Subject: session.DomainUserID,
ObjectType: policies.ChannelType,
Object: chID,
}); err != nil {
return errors.Wrap(err, errDisconnect)
}
}
for _, thID := range thIDs {
if err := am.authorize(ctx, session, policies.ClientType, cOperations.OpDisconnectFromChannel, smqauthz.PolicyReq{
Domain: session.DomainID,
SubjectType: policies.UserType,
Subject: session.DomainUserID,
ObjectType: policies.ClientType,
Object: thID,
}); err != nil {
return errors.Wrap(err, errClientDisConnectChannels)
}
}
return am.svc.Disconnect(ctx, session, chIDs, thIDs, connTypes)
}
func (am *authorizationMiddleware) SetParentGroup(ctx context.Context, session authn.Session, parentGroupID string, id string) error {
if err := am.authorize(ctx, session, policies.ChannelType, operations.OpSetParentGroup, smqauthz.PolicyReq{
Domain: session.DomainID,
SubjectType: policies.UserType,
Subject: session.DomainUserID,
ObjectType: policies.ChannelType,
Object: id,
}); err != nil {
return errors.Wrap(err, errSetParentGroup)
}
if err := am.authorize(ctx, session, policies.GroupType, gOperations.OpGroupSetChildChannel, smqauthz.PolicyReq{
Domain: session.DomainID,
SubjectType: policies.UserType,
Subject: session.DomainUserID,
ObjectType: policies.GroupType,
Object: parentGroupID,
}); err != nil {
return errors.Wrap(err, errGroupSetChildChannels)
}
return am.svc.SetParentGroup(ctx, session, parentGroupID, id)
}
func (am *authorizationMiddleware) RemoveParentGroup(ctx context.Context, session authn.Session, id string) error {
if err := am.authorize(ctx, session, policies.ChannelType, operations.OpSetParentGroup, smqauthz.PolicyReq{
Domain: session.DomainID,
SubjectType: policies.UserType,
Subject: session.DomainUserID,
ObjectType: policies.ChannelType,
Object: id,
}); err != nil {
return errors.Wrap(err, errRemoveParentGroup)
}
ch, err := am.repo.RetrieveByID(ctx, id)
if err != nil {
return errors.Wrap(svcerr.ErrRemoveEntity, err)
}
if ch.ParentGroup != "" {
if err := am.authorize(ctx, session, policies.GroupType, gOperations.OpGroupRemoveChildChannel, smqauthz.PolicyReq{
Domain: session.DomainID,
SubjectType: policies.UserType,
Subject: session.DomainUserID,
ObjectType: policies.GroupType,
Object: ch.ParentGroup,
}); err != nil {
return errors.Wrap(err, errGroupRemoveChildChannels)
}
return am.svc.RemoveParentGroup(ctx, session, id)
}
return nil
}
func (am *authorizationMiddleware) authorize(ctx context.Context, session authn.Session, entityType string, op permissions.Operation, req smqauthz.PolicyReq) error {
req.Domain = session.DomainID
perm, err := am.entitiesOps.GetPermission(entityType, op)
if err != nil {
return err
}
req.Permission = perm.String()
var pat *smqauthz.PATReq
if session.PatID != "" {
entityID := req.Object
opName := am.entitiesOps.OperationName(entityType, op)
if op == operations.OpListUserChannels || op == dOperations.OpCreateDomainChannels || op == dOperations.OpListDomainChannels {
entityID = auth.AnyIDs
}
pat = &smqauthz.PATReq{
UserID: session.UserID,
PatID: session.PatID,
EntityID: entityID,
EntityType: patEntityType(entityType),
Operation: opName,
Domain: session.DomainID,
}
}
if err := am.authz.Authorize(ctx, req, pat); err != nil {
return err
}
return nil
}
func patEntityType(entityType string) string {
switch entityType {
case policies.ClientType:
return auth.ClientsType.String()
default:
return auth.ChannelsType.String()
}
}
func (am *authorizationMiddleware) checkSuperAdmin(ctx context.Context, session authn.Session) error {
if session.Role != authn.SuperAdminRole {
return svcerr.ErrSuperAdminAction
}
if err := am.authz.Authorize(ctx, smqauthz.PolicyReq{
SubjectType: policies.UserType,
Subject: session.UserID,
Permission: policies.AdminPermission,
ObjectType: policies.PlatformType,
Object: policies.MagistralaObject,
}, nil); err != nil {
return err
}
return nil
}