1
0
mirror of https://github.com/mainflux/mainflux.git synced 2025-04-24 13:48:49 +08:00
Arvindh a91a8f44d3
Fix: user and things authz (#1941)
Signed-off-by: Arvindh <arvindh91@gmail.com>
2023-10-19 19:51:03 +02:00

449 lines
11 KiB
Go

// Copyright (c) Mainflux
// SPDX-License-Identifier: Apache-2.0
package things
import (
"context"
"time"
"github.com/mainflux/mainflux"
"github.com/mainflux/mainflux/internal/apiutil"
mfclients "github.com/mainflux/mainflux/pkg/clients"
"github.com/mainflux/mainflux/pkg/errors"
mfgroups "github.com/mainflux/mainflux/pkg/groups"
"github.com/mainflux/mainflux/things/postgres"
)
const (
ownerRelation = "owner"
groupRelation = "group"
ownerPermission = "delete"
deletePermission = "delete"
editPermission = "edit"
viewPermission = "view"
userType = "user"
tokenKind = "token"
userKind = "users"
thingType = "thing"
groupType = "group"
)
type service struct {
auth mainflux.AuthServiceClient
clients postgres.Repository
clientCache Cache
idProvider mainflux.IDProvider
grepo mfgroups.Repository
}
// NewService returns a new Clients service implementation.
func NewService(uauth mainflux.AuthServiceClient, c postgres.Repository, grepo mfgroups.Repository, tcache Cache, idp mainflux.IDProvider) Service {
return service{
auth: uauth,
clients: c,
grepo: grepo,
clientCache: tcache,
idProvider: idp,
}
}
func (svc service) Authorize(ctx context.Context, req *mainflux.AuthorizeReq) (string, error) {
thingID, err := svc.Identify(ctx, req.GetSubject())
if err != nil {
return "", errors.ErrAuthentication
}
r := &mainflux.AuthorizeReq{
SubjectType: groupType,
Subject: req.GetObject(),
ObjectType: thingType,
Object: thingID,
Permission: req.GetPermission(),
}
resp, err := svc.auth.Authorize(ctx, r)
if err != nil {
return "", err
}
if !resp.GetAuthorized() {
return "", errors.ErrAuthorization
}
return thingID, nil
}
func (svc service) CreateThings(ctx context.Context, token string, cls ...mfclients.Client) ([]mfclients.Client, error) {
user, err := svc.auth.Identify(ctx, &mainflux.IdentityReq{Token: token})
if err != nil {
return []mfclients.Client{}, errors.Wrap(errors.ErrAuthorization, err)
}
var clients []mfclients.Client
for _, c := range cls {
if c.ID == "" {
clientID, err := svc.idProvider.ID()
if err != nil {
return []mfclients.Client{}, err
}
c.ID = clientID
}
if c.Credentials.Secret == "" {
key, err := svc.idProvider.ID()
if err != nil {
return []mfclients.Client{}, err
}
c.Credentials.Secret = key
}
if c.Owner == "" {
c.Owner = user.GetId()
}
if c.Status != mfclients.DisabledStatus && c.Status != mfclients.EnabledStatus {
return []mfclients.Client{}, apiutil.ErrInvalidStatus
}
c.CreatedAt = time.Now()
clients = append(clients, c)
}
saved, err := svc.clients.Save(ctx, clients...)
if err != nil {
return nil, err
}
for _, c := range saved {
policy := mainflux.AddPolicyReq{
SubjectType: userType,
Subject: user.GetId(),
Relation: ownerRelation,
ObjectType: thingType,
Object: c.ID,
}
if _, err := svc.auth.AddPolicy(ctx, &policy); err != nil {
return nil, err
}
}
return saved, nil
}
func (svc service) ViewClient(ctx context.Context, token string, id string) (mfclients.Client, error) {
_, err := svc.authorize(ctx, userType, tokenKind, token, viewPermission, thingType, id)
if err != nil {
return mfclients.Client{}, err
}
return svc.clients.RetrieveByID(ctx, id)
}
func (svc service) ListClients(ctx context.Context, token string, reqUserID string, pm mfclients.Page) (mfclients.ClientsPage, error) {
var ids []string
userID, err := svc.identify(ctx, token)
if err != nil {
return mfclients.ClientsPage{}, err
}
switch {
case (reqUserID != "" && reqUserID != userID):
if _, err := svc.authorize(ctx, userType, userKind, userID, ownerRelation, userType, reqUserID); err != nil {
return mfclients.ClientsPage{}, err
}
rtids, err := svc.listClientIDs(ctx, reqUserID, pm.Permission)
if err != nil {
return mfclients.ClientsPage{}, err
}
ids, err = svc.filterAllowedThingIDs(ctx, userID, pm.Permission, rtids)
if err != nil {
return mfclients.ClientsPage{}, err
}
default:
ids, err = svc.listClientIDs(ctx, userID, pm.Permission)
if err != nil {
return mfclients.ClientsPage{}, err
}
}
if len(ids) == 0 {
return mfclients.ClientsPage{
Page: mfclients.Page{Total: 0, Limit: pm.Limit, Offset: pm.Offset},
}, nil
}
pm.IDs = ids
return svc.clients.RetrieveAllByIDs(ctx, pm)
}
func (svc service) listClientIDs(ctx context.Context, userID, permission string) ([]string, error) {
tids, err := svc.auth.ListAllObjects(ctx, &mainflux.ListObjectsReq{
SubjectType: userType,
Subject: userID,
Permission: permission,
ObjectType: thingType,
})
if err != nil {
return nil, err
}
return tids.Policies, nil
}
func (svc service) filterAllowedThingIDs(ctx context.Context, userID, permission string, thingIDs []string) ([]string, error) {
var ids []string
tids, err := svc.auth.ListAllObjects(ctx, &mainflux.ListObjectsReq{
SubjectType: userType,
Subject: userID,
Permission: permission,
ObjectType: thingType,
})
if err != nil {
return nil, err
}
for _, thingID := range thingIDs {
for _, tid := range tids.Policies {
if thingID == tid {
ids = append(ids, thingID)
}
}
}
return ids, nil
}
func (svc service) UpdateClient(ctx context.Context, token string, cli mfclients.Client) (mfclients.Client, error) {
userID, err := svc.authorize(ctx, userType, tokenKind, token, editPermission, thingType, cli.ID)
if err != nil {
return mfclients.Client{}, err
}
client := mfclients.Client{
ID: cli.ID,
Name: cli.Name,
Metadata: cli.Metadata,
UpdatedAt: time.Now(),
UpdatedBy: userID,
}
return svc.clients.Update(ctx, client)
}
func (svc service) UpdateClientTags(ctx context.Context, token string, cli mfclients.Client) (mfclients.Client, error) {
userID, err := svc.authorize(ctx, userType, tokenKind, token, editPermission, thingType, cli.ID)
if err != nil {
return mfclients.Client{}, err
}
client := mfclients.Client{
ID: cli.ID,
Tags: cli.Tags,
UpdatedAt: time.Now(),
UpdatedBy: userID,
}
return svc.clients.UpdateTags(ctx, client)
}
func (svc service) UpdateClientSecret(ctx context.Context, token, id, key string) (mfclients.Client, error) {
userID, err := svc.authorize(ctx, userType, tokenKind, token, editPermission, thingType, id)
if err != nil {
return mfclients.Client{}, err
}
client := mfclients.Client{
ID: id,
Credentials: mfclients.Credentials{
Secret: key,
},
UpdatedAt: time.Now(),
UpdatedBy: userID,
Status: mfclients.EnabledStatus,
}
return svc.clients.UpdateSecret(ctx, client)
}
func (svc service) UpdateClientOwner(ctx context.Context, token string, cli mfclients.Client) (mfclients.Client, error) {
userID, err := svc.authorize(ctx, userType, tokenKind, token, editPermission, thingType, cli.ID)
if err != nil {
return mfclients.Client{}, err
}
client := mfclients.Client{
ID: cli.ID,
Owner: cli.Owner,
UpdatedAt: time.Now(),
UpdatedBy: userID,
Status: mfclients.EnabledStatus,
}
return svc.clients.UpdateOwner(ctx, client)
}
func (svc service) EnableClient(ctx context.Context, token, id string) (mfclients.Client, error) {
client := mfclients.Client{
ID: id,
Status: mfclients.EnabledStatus,
UpdatedAt: time.Now(),
}
client, err := svc.changeClientStatus(ctx, token, client)
if err != nil {
return mfclients.Client{}, errors.Wrap(mfclients.ErrEnableClient, err)
}
return client, nil
}
func (svc service) DisableClient(ctx context.Context, token, id string) (mfclients.Client, error) {
client := mfclients.Client{
ID: id,
Status: mfclients.DisabledStatus,
UpdatedAt: time.Now(),
}
client, err := svc.changeClientStatus(ctx, token, client)
if err != nil {
return mfclients.Client{}, errors.Wrap(mfclients.ErrDisableClient, err)
}
if err := svc.clientCache.Remove(ctx, client.ID); err != nil {
return client, err
}
return client, nil
}
func (svc service) Share(ctx context.Context, token, id, relation string, userids ...string) error {
_, err := svc.authorize(ctx, userType, tokenKind, token, ownerPermission, thingType, id)
if err != nil {
return err
}
for _, userid := range userids {
addPolicyReq := &mainflux.AddPolicyReq{
SubjectType: userType,
Subject: userid,
Relation: relation,
ObjectType: thingType,
Object: id,
}
res, err := svc.auth.AddPolicy(ctx, addPolicyReq)
if err != nil {
return err
}
if !res.Authorized {
return errors.ErrAuthorization
}
}
return nil
}
func (svc service) Unshare(ctx context.Context, token, id, relation string, userids ...string) error {
_, err := svc.authorize(ctx, userType, tokenKind, token, ownerPermission, thingType, id)
if err != nil {
return err
}
for _, userid := range userids {
delPolicyReq := &mainflux.DeletePolicyReq{
SubjectType: userType,
Subject: userid,
Relation: relation,
ObjectType: thingType,
Object: id,
}
res, err := svc.auth.DeletePolicy(ctx, delPolicyReq)
if err != nil {
return err
}
if !res.Deleted {
return errors.ErrAuthorization
}
}
return nil
}
func (svc service) changeClientStatus(ctx context.Context, token string, client mfclients.Client) (mfclients.Client, error) {
userID, err := svc.authorize(ctx, userType, tokenKind, token, deletePermission, thingType, client.ID)
if err != nil {
return mfclients.Client{}, err
}
dbClient, err := svc.clients.RetrieveByID(ctx, client.ID)
if err != nil {
return mfclients.Client{}, err
}
if dbClient.Status == client.Status {
return mfclients.Client{}, mfclients.ErrStatusAlreadyAssigned
}
client.UpdatedBy = userID
return svc.clients.ChangeStatus(ctx, client)
}
func (svc service) ListClientsByGroup(ctx context.Context, token, groupID string, pm mfclients.Page) (mfclients.MembersPage, error) {
if _, err := svc.authorize(ctx, userType, tokenKind, token, pm.Permission, groupType, groupID); err != nil {
return mfclients.MembersPage{}, err
}
tids, err := svc.auth.ListAllObjects(ctx, &mainflux.ListObjectsReq{
SubjectType: groupType,
Subject: groupID,
Permission: groupRelation,
ObjectType: thingType,
})
if err != nil {
return mfclients.MembersPage{}, err
}
pm.IDs = tids.Policies
cp, err := svc.clients.RetrieveAllByIDs(ctx, pm)
if err != nil {
return mfclients.MembersPage{}, err
}
return mfclients.MembersPage{
Page: cp.Page,
Members: cp.Clients,
}, nil
}
func (svc service) Identify(ctx context.Context, key string) (string, error) {
id, err := svc.clientCache.ID(ctx, key)
if err == nil {
return id, nil
}
client, err := svc.clients.RetrieveBySecret(ctx, key)
if err != nil {
return "", err
}
if err := svc.clientCache.Save(ctx, key, client.ID); err != nil {
return "", err
}
return client.ID, nil
}
func (svc service) identify(ctx context.Context, token string) (string, error) {
user, err := svc.auth.Identify(ctx, &mainflux.IdentityReq{Token: token})
if err != nil {
return "", err
}
return user.GetId(), nil
}
func (svc *service) authorize(ctx context.Context, subjType, subjKind, subj, perm, objType, obj string) (string, error) {
req := &mainflux.AuthorizeReq{
SubjectType: subjType,
SubjectKind: subjKind,
Subject: subj,
Permission: perm,
ObjectType: objType,
Object: obj,
}
res, err := svc.auth.Authorize(ctx, req)
if err != nil {
return "", errors.Wrap(errors.ErrAuthorization, err)
}
if !res.GetAuthorized() {
return "", errors.ErrAuthorization
}
return res.GetId(), nil
}