mirror of
https://github.com/mainflux/mainflux.git
synced 2025-04-29 13:49:28 +08:00

* fix error package errors Signed-off-by: aryan <aryangodara03@gmail.com> * fix bootstap and bootstrap api Signed-off-by: aryan <aryangodara03@gmail.com> * fix certs Signed-off-by: aryan <aryangodara03@gmail.com> * fix consumers Signed-off-by: aryan <aryangodara03@gmail.com> * fix http Signed-off-by: aryan <aryangodara03@gmail.com> * fix provision Signed-off-by: aryan <aryangodara03@gmail.com> * fix readers Signed-off-by: aryan <aryangodara03@gmail.com> * fix twins Signed-off-by: aryan <aryangodara03@gmail.com> * fix things Signed-off-by: aryan <aryangodara03@gmail.com> * fix users Signed-off-by: aryan <aryangodara03@gmail.com> * fix sdk excpet channel policies users things Signed-off-by: aryan <aryangodara03@gmail.com> * tests passing, but logging not working for things and users Signed-off-by: aryan <aryangodara03@gmail.com> * fix sdk tests, and other failing tests Signed-off-by: aryan <aryangodara03@gmail.com> * fix comment Signed-off-by: aryan <aryangodara03@gmail.com> * fix errors acc to pr review Signed-off-by: aryan <aryangodara03@gmail.com> * fix errror wrapping in consumers api Signed-off-by: aryan <aryangodara03@gmail.com> * all tests running Signed-off-by: aryan <aryangodara03@gmail.com> * fix encodeError Signed-off-by: aryan <aryangodara03@gmail.com> * fix minor issues Signed-off-by: aryan <aryangodara03@gmail.com> * fix failing sdk policy tests Signed-off-by: aryan <aryangodara03@gmail.com> * fix errors in things test sdk Signed-off-by: aryan <aryangodara03@gmail.com> * update things service Signed-off-by: aryan <aryangodara03@gmail.com> * update usrs service Signed-off-by: aryan <aryangodara03@gmail.com> * fix things and users sdk Signed-off-by: aryan <aryangodara03@gmail.com> * fix sdk for channels groups policies things users Signed-off-by: aryan <aryangodara03@gmail.com> * fix remaining services and sdk Signed-off-by: aryan <aryangodara03@gmail.com> * fix bootstrap twins Signed-off-by: aryan <aryangodara03@gmail.com> * resolve conflicts Signed-off-by: aryan <aryangodara03@gmail.com> * Shift errmalformedentity to pkg/errors Signed-off-by: aryan <aryangodara03@gmail.com> * Fix bootstrap service Signed-off-by: aryan <aryangodara03@gmail.com> * Add errors.Unwrap and use in encodeError Signed-off-by: aryan <aryangodara03@gmail.com> * Fix type in print statement for policies_test Signed-off-by: aryan <aryangodara03@gmail.com> * Fix ordering of errvalidation wrapping and encodeError Signed-off-by: aryan <aryangodara03@gmail.com> * Fix failing tests Signed-off-by: aryan <aryangodara03@gmail.com> --------- Signed-off-by: aryan <aryangodara03@gmail.com>
347 lines
9.4 KiB
Go
347 lines
9.4 KiB
Go
// Copyright (c) Mainflux
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
package clients
|
|
|
|
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/clients/postgres"
|
|
tpolicies "github.com/mainflux/mainflux/things/policies"
|
|
upolicies "github.com/mainflux/mainflux/users/policies"
|
|
)
|
|
|
|
const (
|
|
myKey = "mine"
|
|
|
|
thingsObjectKey = "things"
|
|
|
|
addRelationKey = "g_add"
|
|
updateRelationKey = "c_update"
|
|
listRelationKey = "c_list"
|
|
deleteRelationKey = "c_delete"
|
|
|
|
clientEntityType = "client"
|
|
)
|
|
|
|
type service struct {
|
|
uauth upolicies.AuthServiceClient
|
|
policies tpolicies.Service
|
|
clients postgres.Repository
|
|
clientCache Cache
|
|
idProvider mainflux.IDProvider
|
|
grepo mfgroups.Repository
|
|
}
|
|
|
|
// NewService returns a new Clients service implementation.
|
|
func NewService(uauth upolicies.AuthServiceClient, policies tpolicies.Service, c postgres.Repository, grepo mfgroups.Repository, tcache Cache, idp mainflux.IDProvider) Service {
|
|
return service{
|
|
uauth: uauth,
|
|
policies: policies,
|
|
clients: c,
|
|
grepo: grepo,
|
|
clientCache: tcache,
|
|
idProvider: idp,
|
|
}
|
|
}
|
|
|
|
func (svc service) CreateThings(ctx context.Context, token string, clis ...mfclients.Client) ([]mfclients.Client, error) {
|
|
userID, err := svc.identify(ctx, token)
|
|
if err != nil {
|
|
return []mfclients.Client{}, err
|
|
}
|
|
var clients []mfclients.Client
|
|
for _, cli := range clis {
|
|
if cli.ID == "" {
|
|
clientID, err := svc.idProvider.ID()
|
|
if err != nil {
|
|
return []mfclients.Client{}, err
|
|
}
|
|
cli.ID = clientID
|
|
}
|
|
if cli.Credentials.Secret == "" {
|
|
key, err := svc.idProvider.ID()
|
|
if err != nil {
|
|
return []mfclients.Client{}, err
|
|
}
|
|
cli.Credentials.Secret = key
|
|
}
|
|
if cli.Owner == "" {
|
|
cli.Owner = userID
|
|
}
|
|
if cli.Status != mfclients.DisabledStatus && cli.Status != mfclients.EnabledStatus {
|
|
return []mfclients.Client{}, apiutil.ErrInvalidStatus
|
|
}
|
|
cli.CreatedAt = time.Now()
|
|
clients = append(clients, cli)
|
|
}
|
|
|
|
return svc.clients.Save(ctx, clients...)
|
|
}
|
|
|
|
func (svc service) ViewClient(ctx context.Context, token string, id string) (mfclients.Client, error) {
|
|
userID, err := svc.identify(ctx, token)
|
|
if err != nil {
|
|
return mfclients.Client{}, err
|
|
}
|
|
if err := svc.authorize(ctx, userID, id, listRelationKey); err != nil {
|
|
return mfclients.Client{}, errors.Wrap(errors.ErrNotFound, err)
|
|
}
|
|
return svc.clients.RetrieveByID(ctx, id)
|
|
}
|
|
|
|
func (svc service) ListClients(ctx context.Context, token string, pm mfclients.Page) (mfclients.ClientsPage, error) {
|
|
userID, err := svc.identify(ctx, token)
|
|
if err != nil {
|
|
return mfclients.ClientsPage{}, err
|
|
}
|
|
|
|
switch err = svc.checkAdmin(ctx, userID, thingsObjectKey, listRelationKey); err {
|
|
// If the user is admin, fetch all things from database.
|
|
case nil:
|
|
switch {
|
|
// visibility = all
|
|
case pm.SharedBy == myKey && pm.Owner == myKey:
|
|
pm.SharedBy = ""
|
|
pm.Owner = ""
|
|
// visibility = shared
|
|
case pm.SharedBy == myKey && pm.Owner != myKey:
|
|
pm.SharedBy = userID
|
|
pm.Owner = ""
|
|
// visibility = mine
|
|
case pm.Owner == myKey && pm.SharedBy != myKey:
|
|
pm.Owner = userID
|
|
pm.SharedBy = ""
|
|
}
|
|
|
|
default:
|
|
// If the user is not admin, check 'sharedby' parameter from page metadata.
|
|
// If user provides 'sharedby' key, fetch things from policies. Otherwise,
|
|
// fetch things from the database based on thing's 'owner' field.
|
|
switch {
|
|
// visibility = all
|
|
case pm.SharedBy == myKey && pm.Owner == myKey:
|
|
pm.SharedBy = userID
|
|
pm.Owner = userID
|
|
// visibility = shared
|
|
case pm.SharedBy == myKey && pm.Owner != myKey:
|
|
pm.SharedBy = userID
|
|
pm.Owner = ""
|
|
// visibility = mine
|
|
case pm.Owner == myKey && pm.SharedBy != myKey:
|
|
pm.Owner = userID
|
|
pm.SharedBy = ""
|
|
default:
|
|
pm.Owner = userID
|
|
}
|
|
pm.Action = listRelationKey
|
|
}
|
|
|
|
return svc.clients.RetrieveAll(ctx, pm)
|
|
}
|
|
|
|
func (svc service) UpdateClient(ctx context.Context, token string, cli mfclients.Client) (mfclients.Client, error) {
|
|
userID, err := svc.identify(ctx, token)
|
|
if err != nil {
|
|
return mfclients.Client{}, err
|
|
}
|
|
if err := svc.authorize(ctx, userID, cli.ID, updateRelationKey); 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.identify(ctx, token)
|
|
if err != nil {
|
|
return mfclients.Client{}, err
|
|
}
|
|
if err := svc.authorize(ctx, userID, cli.ID, updateRelationKey); 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.identify(ctx, token)
|
|
if err != nil {
|
|
return mfclients.Client{}, err
|
|
}
|
|
if err := svc.authorize(ctx, userID, id, updateRelationKey); 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.identify(ctx, token)
|
|
if err != nil {
|
|
return mfclients.Client{}, err
|
|
}
|
|
if err := svc.authorize(ctx, userID, cli.ID, updateRelationKey); 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) changeClientStatus(ctx context.Context, token string, client mfclients.Client) (mfclients.Client, error) {
|
|
userID, err := svc.identify(ctx, token)
|
|
if err != nil {
|
|
return mfclients.Client{}, err
|
|
}
|
|
if err := svc.authorize(ctx, userID, client.ID, deleteRelationKey); 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) {
|
|
userID, err := svc.identify(ctx, token)
|
|
if err != nil {
|
|
return mfclients.MembersPage{}, err
|
|
}
|
|
// If the user is admin, fetch all things connected to the channel.
|
|
if err := svc.checkAdmin(ctx, userID, thingsObjectKey, listRelationKey); err == nil {
|
|
return svc.clients.Members(ctx, groupID, pm)
|
|
}
|
|
pm.Owner = userID
|
|
|
|
return svc.clients.Members(ctx, groupID, pm)
|
|
}
|
|
|
|
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) {
|
|
req := &upolicies.IdentifyReq{Token: token}
|
|
res, err := svc.uauth.Identify(ctx, req)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return res.GetId(), nil
|
|
}
|
|
|
|
func (svc service) authorize(ctx context.Context, subject, object, action string) error {
|
|
// If the user is admin, skip authorization.
|
|
if err := svc.checkAdmin(ctx, subject, thingsObjectKey, action); err == nil {
|
|
return nil
|
|
}
|
|
|
|
policy := tpolicies.AccessRequest{Subject: subject, Object: object, Action: action, Entity: clientEntityType}
|
|
|
|
_, err := svc.policies.Authorize(ctx, policy)
|
|
return err
|
|
}
|
|
|
|
// TODO : Only accept token as parameter since object and action are irrelevant.
|
|
func (svc service) checkAdmin(ctx context.Context, subject, object, action string) error {
|
|
req := &upolicies.AuthorizeReq{
|
|
Subject: subject,
|
|
Object: object,
|
|
Action: action,
|
|
EntityType: clientEntityType,
|
|
}
|
|
res, err := svc.uauth.Authorize(ctx, req)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if !res.GetAuthorized() {
|
|
return errors.ErrAuthorization
|
|
}
|
|
return nil
|
|
}
|