diff --git a/Makefile b/Makefile index 2dfb7e3db87..e8b7f80ebbf 100644 --- a/Makefile +++ b/Makefile @@ -3,7 +3,7 @@ MF_DOCKER_IMAGE_NAME_PREFIX ?= mainflux BUILD_DIR = build -SERVICES = clients users things http coap ws lora influxdb-writer influxdb-reader mongodb-writer \ +SERVICES = users things http coap ws lora influxdb-writer influxdb-reader mongodb-writer \ mongodb-reader cassandra-writer cassandra-reader postgres-writer postgres-reader timescale-writer timescale-reader cli \ bootstrap opcua twins mqtt provision certs smtp-notifier smpp-notifier DOCKERS = $(addprefix docker_,$(SERVICES)) diff --git a/bootstrap/mocks/users.go b/bootstrap/mocks/users.go index 75bb2d3777f..ad1ba89f237 100644 --- a/bootstrap/mocks/users.go +++ b/bootstrap/mocks/users.go @@ -1,4 +1,4 @@ -// Copyright (c) Mainflux +s// Copyright (c) Mainflux // SPDX-License-Identifier: Apache-2.0 package mocks diff --git a/certs/service.go b/certs/service.go index 6f09766e006..555127fadca 100644 --- a/certs/service.go +++ b/certs/service.go @@ -8,6 +8,7 @@ import ( "time" "github.com/mainflux/mainflux/certs/pki" + "github.com/mainflux/mainflux/users/policies" "github.com/mainflux/mainflux/pkg/errors" mfsdk "github.com/mainflux/mainflux/pkg/sdk/go" "github.com/mainflux/mainflux/users/policies" diff --git a/cli/users.go b/cli/users.go index 966b76f5862..226bde249ca 100644 --- a/cli/users.go +++ b/cli/users.go @@ -159,7 +159,7 @@ var cmdUsers = []cobra.Command{ Short: "Update user tags", Long: `Update user tags`, Run: func(cmd *cobra.Command, args []string) { - if len(args) != 3 { + if len(args) != 4 { logUsage(cmd.Use) return } diff --git a/clients/README.md b/clients/README.md deleted file mode 100644 index 3458839cfb1..00000000000 --- a/clients/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# Clients - -Repository for Clients service for handling generic clients diff --git a/clients/clients/api/endpoints.go b/clients/clients/api/endpoints.go deleted file mode 100644 index c0ac11e3110..00000000000 --- a/clients/clients/api/endpoints.go +++ /dev/null @@ -1,264 +0,0 @@ -package api - -import ( - "context" - - "github.com/go-kit/kit/endpoint" - "github.com/mainflux/mainflux/clients/clients" -) - -func registrationEndpoint(svc clients.Service) endpoint.Endpoint { - return func(ctx context.Context, request interface{}) (interface{}, error) { - req := request.(createClientReq) - if err := req.validate(); err != nil { - return createClientRes{}, err - } - client, err := svc.RegisterClient(ctx, req.token, req.client) - if err != nil { - return createClientRes{}, err - } - ucr := createClientRes{ - Client: client, - created: true, - } - - return ucr, nil - } -} - -func viewClientEndpoint(svc clients.Service) endpoint.Endpoint { - return func(ctx context.Context, request interface{}) (interface{}, error) { - req := request.(viewClientReq) - if err := req.validate(); err != nil { - return nil, err - } - - c, err := svc.ViewClient(ctx, req.token, req.id) - if err != nil { - return nil, err - } - return viewClientRes{Client: c}, nil - } -} - -func listClientsEndpoint(svc clients.Service) endpoint.Endpoint { - return func(ctx context.Context, request interface{}) (interface{}, error) { - req := request.(listClientsReq) - if err := req.validate(); err != nil { - return clients.ClientsPage{}, err - } - - pm := clients.Page{ - SharedBy: req.sharedBy, - Status: req.status, - Offset: req.offset, - Limit: req.limit, - OwnerID: req.owner, - Name: req.name, - Tag: req.tag, - Metadata: req.metadata, - } - page, err := svc.ListClients(ctx, req.token, pm) - if err != nil { - return clients.ClientsPage{}, err - } - - res := clientsPageRes{ - pageRes: pageRes{ - Total: page.Total, - Offset: page.Offset, - Limit: page.Limit, - }, - Clients: []viewClientRes{}, - } - for _, c := range page.Clients { - res.Clients = append(res.Clients, viewClientRes{Client: c}) - } - - return res, nil - } -} - -func listMembersEndpoint(svc clients.Service) endpoint.Endpoint { - return func(ctx context.Context, request interface{}) (interface{}, error) { - req := request.(listMembersReq) - if err := req.validate(); err != nil { - return memberPageRes{}, err - } - page, err := svc.ListMembers(ctx, req.token, req.groupID, req.Page) - if err != nil { - return memberPageRes{}, err - } - return buildMembersResponse(page), nil - } -} - -func updateClientEndpoint(svc clients.Service) endpoint.Endpoint { - return func(ctx context.Context, request interface{}) (interface{}, error) { - req := request.(updateClientReq) - if err := req.validate(); err != nil { - return nil, err - } - - cli := clients.Client{ - ID: req.id, - Name: req.Name, - Metadata: req.Metadata, - } - client, err := svc.UpdateClient(ctx, req.token, cli) - if err != nil { - return nil, err - } - return updateClientRes{Client: client}, nil - } -} - -func updateClientTagsEndpoint(svc clients.Service) endpoint.Endpoint { - return func(ctx context.Context, request interface{}) (interface{}, error) { - req := request.(updateClientTagsReq) - if err := req.validate(); err != nil { - return nil, err - } - - cli := clients.Client{ - ID: req.id, - Tags: req.Tags, - } - client, err := svc.UpdateClientTags(ctx, req.token, cli) - if err != nil { - return nil, err - } - return updateClientRes{Client: client}, nil - } -} - -func updateClientIdentityEndpoint(svc clients.Service) endpoint.Endpoint { - return func(ctx context.Context, request interface{}) (interface{}, error) { - req := request.(updateClientCredentialsReq) - if err := req.validate(); err != nil { - return nil, err - } - client, err := svc.UpdateClientIdentity(ctx, req.token, req.id, req.Identity) - if err != nil { - return nil, err - } - return updateClientRes{Client: client}, nil - } -} - -func updateClientSecretEndpoint(svc clients.Service) endpoint.Endpoint { - return func(ctx context.Context, request interface{}) (interface{}, error) { - req := request.(updateClientCredentialsReq) - if err := req.validate(); err != nil { - return nil, err - } - client, err := svc.UpdateClientSecret(ctx, req.token, req.OldSecret, req.NewSecret) - if err != nil { - return nil, err - } - return updateClientRes{Client: client}, nil - } -} - -func updateClientOwnerEndpoint(svc clients.Service) endpoint.Endpoint { - return func(ctx context.Context, request interface{}) (interface{}, error) { - req := request.(updateClientOwnerReq) - if err := req.validate(); err != nil { - return nil, err - } - - cli := clients.Client{ - ID: req.id, - Owner: req.Owner, - } - - client, err := svc.UpdateClientOwner(ctx, req.token, cli) - if err != nil { - return nil, err - } - return updateClientRes{Client: client}, nil - } -} - -func issueTokenEndpoint(svc clients.Service) endpoint.Endpoint { - return func(ctx context.Context, request interface{}) (interface{}, error) { - req := request.(loginClientReq) - if err := req.validate(); err != nil { - return nil, err - } - - token, err := svc.IssueToken(ctx, req.Identity, req.Secret) - if err != nil { - return nil, err - } - - return tokenRes{ - AccessToken: token.AccessToken, - RefreshToken: token.RefreshToken, - AccessType: token.AccessType, - }, nil - } -} - -func refreshTokenEndpoint(svc clients.Service) endpoint.Endpoint { - return func(ctx context.Context, request interface{}) (interface{}, error) { - req := request.(tokenReq) - if err := req.validate(); err != nil { - return nil, err - } - - token, err := svc.RefreshToken(ctx, req.RefreshToken) - if err != nil { - return nil, err - } - - return tokenRes{ - AccessToken: token.AccessToken, - RefreshToken: token.RefreshToken, - AccessType: token.AccessType, - }, nil - } -} - -func enableClientEndpoint(svc clients.Service) endpoint.Endpoint { - return func(ctx context.Context, request interface{}) (interface{}, error) { - req := request.(changeClientStatusReq) - if err := req.validate(); err != nil { - return nil, err - } - client, err := svc.EnableClient(ctx, req.token, req.id) - if err != nil { - return nil, err - } - return deleteClientRes{Client: client}, nil - } -} - -func disableClientEndpoint(svc clients.Service) endpoint.Endpoint { - return func(ctx context.Context, request interface{}) (interface{}, error) { - req := request.(changeClientStatusReq) - if err := req.validate(); err != nil { - return nil, err - } - client, err := svc.DisableClient(ctx, req.token, req.id) - if err != nil { - return nil, err - } - return deleteClientRes{Client: client}, nil - } -} - -func buildMembersResponse(cp clients.MembersPage) memberPageRes { - res := memberPageRes{ - pageRes: pageRes{ - Total: cp.Total, - Offset: cp.Offset, - Limit: cp.Limit, - }, - Members: []viewMembersRes{}, - } - for _, c := range cp.Members { - res.Members = append(res.Members, viewMembersRes{Client: c}) - } - return res -} diff --git a/clients/clients/api/logging.go b/clients/clients/api/logging.go deleted file mode 100644 index 14d33669502..00000000000 --- a/clients/clients/api/logging.go +++ /dev/null @@ -1,177 +0,0 @@ -package api - -import ( - "context" - "fmt" - "time" - - "github.com/mainflux/mainflux/clients/clients" - "github.com/mainflux/mainflux/clients/jwt" - log "github.com/mainflux/mainflux/logger" -) - -var _ clients.Service = (*loggingMiddleware)(nil) - -type loggingMiddleware struct { - logger log.Logger - svc clients.Service -} - -func LoggingMiddleware(svc clients.Service, logger log.Logger) clients.Service { - return &loggingMiddleware{logger, svc} -} - -func (lm *loggingMiddleware) RegisterClient(ctx context.Context, token string, client clients.Client) (c clients.Client, err error) { - defer func(begin time.Time) { - message := fmt.Sprintf("Method register_client of identity %s with token %s took %s to complete", c.Credentials.Identity, token, time.Since(begin)) - if err != nil { - lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err)) - return - } - lm.logger.Info(fmt.Sprintf("%s without errors.", message)) - }(time.Now()) - return lm.svc.RegisterClient(ctx, token, client) -} - -func (lm *loggingMiddleware) IssueToken(ctx context.Context, identity, secret string) (token jwt.Token, err error) { - defer func(begin time.Time) { - message := fmt.Sprintf("Method issue_token for client %s took %s to complete", identity, time.Since(begin)) - if err != nil { - lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err)) - return - } - lm.logger.Info(fmt.Sprintf("%s without errors.", message)) - }(time.Now()) - return lm.svc.IssueToken(ctx, identity, secret) -} - -func (lm *loggingMiddleware) RefreshToken(ctx context.Context, accessToken string) (token jwt.Token, err error) { - defer func(begin time.Time) { - message := fmt.Sprintf("Method refresh_token for token %s took %s to complete", accessToken, time.Since(begin)) - if err != nil { - lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err)) - return - } - lm.logger.Info(fmt.Sprintf("%s without errors.", message)) - }(time.Now()) - return lm.svc.RefreshToken(ctx, accessToken) -} - -func (lm *loggingMiddleware) ViewClient(ctx context.Context, token, id string) (c clients.Client, err error) { - defer func(begin time.Time) { - message := fmt.Sprintf("Method view_client for client %s took %s to complete", c.Credentials.Identity, time.Since(begin)) - if err != nil { - lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err)) - return - } - lm.logger.Info(fmt.Sprintf("%s without errors.", message)) - }(time.Now()) - return lm.svc.ViewClient(ctx, token, id) -} - -func (lm *loggingMiddleware) ListClients(ctx context.Context, token string, pm clients.Page) (cp clients.ClientsPage, err error) { - defer func(begin time.Time) { - message := fmt.Sprintf("Method list_clients for token %s took %s to complete", token, time.Since(begin)) - if err != nil { - lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err)) - return - } - lm.logger.Info(fmt.Sprintf("%s without errors.", message)) - }(time.Now()) - return lm.svc.ListClients(ctx, token, pm) -} - -func (lm *loggingMiddleware) UpdateClient(ctx context.Context, token string, client clients.Client) (c clients.Client, err error) { - defer func(begin time.Time) { - message := fmt.Sprintf("Method update_client_name_and_metadata for token %s took %s to complete", token, time.Since(begin)) - if err != nil { - lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err)) - return - } - lm.logger.Info(fmt.Sprintf("%s without errors.", message)) - }(time.Now()) - return lm.svc.UpdateClient(ctx, token, client) -} - -func (lm *loggingMiddleware) UpdateClientTags(ctx context.Context, token string, client clients.Client) (c clients.Client, err error) { - defer func(begin time.Time) { - message := fmt.Sprintf("Method update_client_tags for token %s took %s to complete", token, time.Since(begin)) - if err != nil { - lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err)) - return - } - lm.logger.Info(fmt.Sprintf("%s without errors.", message)) - }(time.Now()) - return lm.svc.UpdateClientTags(ctx, token, client) -} -func (lm *loggingMiddleware) UpdateClientIdentity(ctx context.Context, token, id, identity string) (c clients.Client, err error) { - defer func(begin time.Time) { - message := fmt.Sprintf("Method update_client_identity for token %s and identity %s took %s to complete", token, identity, time.Since(begin)) - if err != nil { - lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err)) - return - } - lm.logger.Info(fmt.Sprintf("%s without errors.", message)) - }(time.Now()) - return lm.svc.UpdateClientIdentity(ctx, token, id, identity) -} - -func (lm *loggingMiddleware) UpdateClientSecret(ctx context.Context, token, oldSecret, newSecret string) (c clients.Client, err error) { - defer func(begin time.Time) { - message := fmt.Sprintf("Method update_client_secret for token %s took %s to complete", token, time.Since(begin)) - if err != nil { - lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err)) - return - } - lm.logger.Info(fmt.Sprintf("%s without errors.", message)) - }(time.Now()) - return lm.svc.UpdateClientSecret(ctx, token, oldSecret, newSecret) -} - -func (lm *loggingMiddleware) UpdateClientOwner(ctx context.Context, token string, client clients.Client) (c clients.Client, err error) { - defer func(begin time.Time) { - message := fmt.Sprintf("Method update_client_owner for token %s took %s to complete", token, time.Since(begin)) - if err != nil { - lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err)) - return - } - lm.logger.Info(fmt.Sprintf("%s without errors.", message)) - }(time.Now()) - return lm.svc.UpdateClientOwner(ctx, token, client) -} - -func (lm *loggingMiddleware) EnableClient(ctx context.Context, token string, id string) (c clients.Client, err error) { - defer func(begin time.Time) { - message := fmt.Sprintf("Method enable_client for client %s took %s to complete", id, time.Since(begin)) - if err != nil { - lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err)) - return - } - lm.logger.Info(fmt.Sprintf("%s without errors.", message)) - }(time.Now()) - return lm.svc.EnableClient(ctx, token, id) -} - -func (lm *loggingMiddleware) DisableClient(ctx context.Context, token string, id string) (c clients.Client, err error) { - defer func(begin time.Time) { - message := fmt.Sprintf("Method disable_client for client %s took %s to complete", id, time.Since(begin)) - if err != nil { - lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err)) - return - } - lm.logger.Info(fmt.Sprintf("%s without errors.", message)) - }(time.Now()) - return lm.svc.DisableClient(ctx, token, id) -} - -func (lm *loggingMiddleware) ListMembers(ctx context.Context, token, groupID string, cp clients.Page) (mp clients.MembersPage, err error) { - defer func(begin time.Time) { - message := fmt.Sprintf("Method list_members for group %s and token %s took %s to complete", groupID, token, time.Since(begin)) - if err != nil { - lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err)) - return - } - lm.logger.Info(fmt.Sprintf("%s without errors.", message)) - }(time.Now()) - return lm.svc.ListMembers(ctx, token, groupID, cp) -} diff --git a/clients/clients/api/metrics.go b/clients/clients/api/metrics.go deleted file mode 100644 index c694a7f070e..00000000000 --- a/clients/clients/api/metrics.go +++ /dev/null @@ -1,131 +0,0 @@ -package api - -import ( - "context" - "time" - - "github.com/go-kit/kit/metrics" - "github.com/mainflux/mainflux/clients/clients" - "github.com/mainflux/mainflux/clients/jwt" -) - -var _ clients.Service = (*metricsMiddleware)(nil) - -type metricsMiddleware struct { - counter metrics.Counter - latency metrics.Histogram - svc clients.Service -} - -// MetricsMiddleware returns a new metrics middleware wrapper. -func MetricsMiddleware(svc clients.Service, counter metrics.Counter, latency metrics.Histogram) clients.Service { - return &metricsMiddleware{ - counter: counter, - latency: latency, - svc: svc, - } -} - -func (ms *metricsMiddleware) RegisterClient(ctx context.Context, token string, client clients.Client) (clients.Client, error) { - defer func(begin time.Time) { - ms.counter.With("method", "register_client").Add(1) - ms.latency.With("method", "register_client").Observe(time.Since(begin).Seconds()) - }(time.Now()) - return ms.svc.RegisterClient(ctx, token, client) -} - -func (ms *metricsMiddleware) IssueToken(ctx context.Context, identity, secret string) (jwt.Token, error) { - defer func(begin time.Time) { - ms.counter.With("method", "issue_token").Add(1) - ms.latency.With("method", "issue_token").Observe(time.Since(begin).Seconds()) - }(time.Now()) - return ms.svc.IssueToken(ctx, identity, secret) -} - -func (ms *metricsMiddleware) RefreshToken(ctx context.Context, accessToken string) (token jwt.Token, err error) { - defer func(begin time.Time) { - ms.counter.With("method", "refresh_token").Add(1) - ms.latency.With("method", "refresh_token").Observe(time.Since(begin).Seconds()) - }(time.Now()) - return ms.svc.RefreshToken(ctx, accessToken) -} - -func (ms *metricsMiddleware) ViewClient(ctx context.Context, token, id string) (clients.Client, error) { - defer func(begin time.Time) { - ms.counter.With("method", "view_client").Add(1) - ms.latency.With("method", "view_client").Observe(time.Since(begin).Seconds()) - }(time.Now()) - return ms.svc.ViewClient(ctx, token, id) -} - -func (ms *metricsMiddleware) ListClients(ctx context.Context, token string, pm clients.Page) (clients.ClientsPage, error) { - defer func(begin time.Time) { - ms.counter.With("method", "list_clients").Add(1) - ms.latency.With("method", "list_clients").Observe(time.Since(begin).Seconds()) - }(time.Now()) - return ms.svc.ListClients(ctx, token, pm) -} - -func (ms *metricsMiddleware) UpdateClient(ctx context.Context, token string, client clients.Client) (clients.Client, error) { - defer func(begin time.Time) { - ms.counter.With("method", "update_client_name_and_metadata").Add(1) - ms.latency.With("method", "update_client_name_and_metadata").Observe(time.Since(begin).Seconds()) - }(time.Now()) - return ms.svc.UpdateClient(ctx, token, client) -} - -func (ms *metricsMiddleware) UpdateClientTags(ctx context.Context, token string, client clients.Client) (clients.Client, error) { - defer func(begin time.Time) { - ms.counter.With("method", "update_client_tags").Add(1) - ms.latency.With("method", "update_client_tags").Observe(time.Since(begin).Seconds()) - }(time.Now()) - return ms.svc.UpdateClientTags(ctx, token, client) -} - -func (ms *metricsMiddleware) UpdateClientIdentity(ctx context.Context, token, id, identity string) (clients.Client, error) { - defer func(begin time.Time) { - ms.counter.With("method", "update_client_identity").Add(1) - ms.latency.With("method", "update_client_identity").Observe(time.Since(begin).Seconds()) - }(time.Now()) - return ms.svc.UpdateClientIdentity(ctx, token, id, identity) -} - -func (ms *metricsMiddleware) UpdateClientSecret(ctx context.Context, token, oldSecret, newSecret string) (clients.Client, error) { - defer func(begin time.Time) { - ms.counter.With("method", "update_client_secret").Add(1) - ms.latency.With("method", "update_client_secret").Observe(time.Since(begin).Seconds()) - }(time.Now()) - return ms.svc.UpdateClientSecret(ctx, token, oldSecret, newSecret) -} - -func (ms *metricsMiddleware) UpdateClientOwner(ctx context.Context, token string, client clients.Client) (clients.Client, error) { - defer func(begin time.Time) { - ms.counter.With("method", "update_client_owner").Add(1) - ms.latency.With("method", "update_client_owner").Observe(time.Since(begin).Seconds()) - }(time.Now()) - return ms.svc.UpdateClientOwner(ctx, token, client) -} - -func (ms *metricsMiddleware) EnableClient(ctx context.Context, token string, id string) (clients.Client, error) { - defer func(begin time.Time) { - ms.counter.With("method", "enable_client").Add(1) - ms.latency.With("method", "enable_client").Observe(time.Since(begin).Seconds()) - }(time.Now()) - return ms.svc.EnableClient(ctx, token, id) -} - -func (ms *metricsMiddleware) DisableClient(ctx context.Context, token string, id string) (clients.Client, error) { - defer func(begin time.Time) { - ms.counter.With("method", "disable_client").Add(1) - ms.latency.With("method", "disable_client").Observe(time.Since(begin).Seconds()) - }(time.Now()) - return ms.svc.DisableClient(ctx, token, id) -} - -func (ms *metricsMiddleware) ListMembers(ctx context.Context, token, groupID string, pm clients.Page) (mp clients.MembersPage, err error) { - defer func(begin time.Time) { - ms.counter.With("method", "list_members").Add(1) - ms.latency.With("method", "list_members").Observe(time.Since(begin).Seconds()) - }(time.Now()) - return ms.svc.ListMembers(ctx, token, groupID, pm) -} diff --git a/clients/clients/api/requests.go b/clients/clients/api/requests.go deleted file mode 100644 index 8d8927c4ab8..00000000000 --- a/clients/clients/api/requests.go +++ /dev/null @@ -1,183 +0,0 @@ -package api - -import ( - "github.com/mainflux/mainflux/clients/clients" - "github.com/mainflux/mainflux/internal/api" - "github.com/mainflux/mainflux/internal/apiutil" -) - -const maxLimitSize = 100 - -type createClientReq struct { - client clients.Client - token string -} - -func (req createClientReq) validate() error { - if len(req.client.Name) > api.MaxNameSize { - return apiutil.ErrNameSize - } - return nil -} - -type viewClientReq struct { - token string - id string -} - -func (req viewClientReq) validate() error { - return nil -} - -type listClientsReq struct { - token string - status clients.Status - offset uint64 - limit uint64 - name string - tag string - owner string - sharedBy string - visibility string - metadata clients.Metadata -} - -func (req listClientsReq) validate() error { - if req.limit > maxLimitSize || req.limit < 1 { - return apiutil.ErrLimitSize - } - if req.visibility != "" && - req.visibility != api.AllVisibility && - req.visibility != api.MyVisibility && - req.visibility != api.SharedVisibility { - return apiutil.ErrInvalidVisibilityType - } - return nil -} - -type listMembersReq struct { - clients.Page - token string - groupID string -} - -func (req listMembersReq) validate() error { - if req.token == "" { - return apiutil.ErrBearerToken - } - - if req.groupID == "" { - return apiutil.ErrMissingID - } - - return nil -} - -type updateClientReq struct { - token string - id string - Name string `json:"name,omitempty"` - Metadata map[string]interface{} `json:"metadata,omitempty"` - Tags []string `json:"tags,omitempty"` -} - -func (req updateClientReq) validate() error { - if req.token == "" { - return apiutil.ErrBearerToken - } - - if req.id == "" { - return apiutil.ErrMissingID - } - - return nil -} - -type updateClientTagsReq struct { - id string - token string - Tags []string `json:"tags,omitempty"` -} - -func (req updateClientTagsReq) validate() error { - if req.token == "" { - return apiutil.ErrBearerToken - } - - if req.id == "" { - return apiutil.ErrMissingID - } - return nil -} - -type updateClientOwnerReq struct { - id string - token string - Owner string `json:"owner,omitempty"` -} - -func (req updateClientOwnerReq) validate() error { - if req.token == "" { - return apiutil.ErrBearerToken - } - if req.id == "" { - return apiutil.ErrMissingID - } - if req.Owner == "" { - return apiutil.ErrMissingOwner - } - return nil -} - -type updateClientCredentialsReq struct { - token string - id string - Identity string `json:"identity,omitempty"` - OldSecret string `json:"old_secret,omitempty"` - NewSecret string `json:"new_secret,omitempty"` -} - -func (req updateClientCredentialsReq) validate() error { - if req.token == "" { - return apiutil.ErrBearerToken - } - return nil -} - -type changeClientStatusReq struct { - token string - id string -} - -func (req changeClientStatusReq) validate() error { - if req.id == "" { - return apiutil.ErrMissingID - } - return nil -} - -type loginClientReq struct { - Identity string `json:"identity,omitempty"` - Secret string `json:"secret,omitempty"` -} - -func (req loginClientReq) validate() error { - if req.Identity == "" { - return apiutil.ErrMissingIdentity - } - if req.Secret == "" { - return apiutil.ErrMissingSecret - } - return nil -} - -type tokenReq struct { - RefreshToken string `json:"refresh_token,omitempty"` -} - -func (req tokenReq) validate() error { - if req.RefreshToken == "" { - return apiutil.ErrBearerToken - } - return nil -} diff --git a/clients/clients/api/responses.go b/clients/clients/api/responses.go deleted file mode 100644 index 1343fec3479..00000000000 --- a/clients/clients/api/responses.go +++ /dev/null @@ -1,170 +0,0 @@ -package api - -import ( - "fmt" - "net/http" - - "github.com/mainflux/mainflux" - "github.com/mainflux/mainflux/clients/clients" -) - -var ( - _ mainflux.Response = (*tokenRes)(nil) - _ mainflux.Response = (*viewClientRes)(nil) - _ mainflux.Response = (*createClientRes)(nil) - _ mainflux.Response = (*deleteClientRes)(nil) - _ mainflux.Response = (*clientsPageRes)(nil) - _ mainflux.Response = (*viewMembersRes)(nil) - _ mainflux.Response = (*memberPageRes)(nil) -) - -type pageRes struct { - Limit uint64 `json:"limit,omitempty"` - Offset uint64 `json:"offset,omitempty"` - Total uint64 `json:"total"` - Level uint64 `json:"level"` - Name string `json:"name"` -} - -type createClientRes struct { - clients.Client - created bool -} - -func (res createClientRes) Code() int { - if res.created { - return http.StatusCreated - } - - return http.StatusOK -} - -func (res createClientRes) Headers() map[string]string { - if res.created { - return map[string]string{ - "Location": fmt.Sprintf("/clients/%s", res.ID), - } - } - - return map[string]string{} -} - -func (res createClientRes) Empty() bool { - return false -} - -type tokenRes struct { - AccessToken string `json:"access_token,omitempty"` - RefreshToken string `json:"refresh_token,omitempty"` - AccessType string `json:"access_type,omitempty"` -} - -func (res tokenRes) Code() int { - return http.StatusCreated -} - -func (res tokenRes) Headers() map[string]string { - return map[string]string{} -} - -func (res tokenRes) Empty() bool { - return res.AccessToken == "" || res.RefreshToken == "" -} - -type updateClientRes struct { - clients.Client -} - -func (res updateClientRes) Code() int { - return http.StatusOK -} - -func (res updateClientRes) Headers() map[string]string { - return map[string]string{} -} - -func (res updateClientRes) Empty() bool { - return false -} - -type viewClientRes struct { - clients.Client -} - -func (res viewClientRes) Code() int { - return http.StatusOK -} - -func (res viewClientRes) Headers() map[string]string { - return map[string]string{} -} - -func (res viewClientRes) Empty() bool { - return false -} - -type clientsPageRes struct { - pageRes - Clients []viewClientRes `json:"clients"` -} - -func (res clientsPageRes) Code() int { - return http.StatusOK -} - -func (res clientsPageRes) Headers() map[string]string { - return map[string]string{} -} - -func (res clientsPageRes) Empty() bool { - return false -} - -type viewMembersRes struct { - clients.Client -} - -func (res viewMembersRes) Code() int { - return http.StatusOK -} - -func (res viewMembersRes) Headers() map[string]string { - return map[string]string{} -} - -func (res viewMembersRes) Empty() bool { - return false -} - -type memberPageRes struct { - pageRes - Members []viewMembersRes `json:"members"` -} - -func (res memberPageRes) Code() int { - return http.StatusOK -} - -func (res memberPageRes) Headers() map[string]string { - return map[string]string{} -} - -func (res memberPageRes) Empty() bool { - return false -} - -type deleteClientRes struct { - clients.Client -} - -func (res deleteClientRes) Code() int { - return http.StatusOK -} - -func (res deleteClientRes) Headers() map[string]string { - return map[string]string{} -} - -func (res deleteClientRes) Empty() bool { - return false -} diff --git a/clients/clients/api/transport.go b/clients/clients/api/transport.go deleted file mode 100644 index ecd5270f08c..00000000000 --- a/clients/clients/api/transport.go +++ /dev/null @@ -1,316 +0,0 @@ -package api - -import ( - "context" - "encoding/json" - "net/http" - "strings" - - kithttp "github.com/go-kit/kit/transport/http" - "github.com/go-zoo/bone" - "github.com/mainflux/mainflux" - "github.com/mainflux/mainflux/clients/clients" - "github.com/mainflux/mainflux/internal/api" - "github.com/mainflux/mainflux/internal/apiutil" - "github.com/mainflux/mainflux/logger" - "github.com/mainflux/mainflux/pkg/errors" - "github.com/prometheus/client_golang/prometheus/promhttp" - "go.opentelemetry.io/contrib/instrumentation/github.com/go-kit/kit/otelkit" -) - -// MakeClientsHandler returns a HTTP handler for API endpoints. -func MakeClientsHandler(svc clients.Service, mux *bone.Mux, logger logger.Logger) { - opts := []kithttp.ServerOption{ - kithttp.ServerErrorEncoder(apiutil.LoggingErrorEncoder(logger, api.EncodeError)), - } - - mux.Post("/clients", kithttp.NewServer( - otelkit.EndpointMiddleware(otelkit.WithOperation("register_client"))(registrationEndpoint(svc)), - decodeCreateClientReq, - api.EncodeResponse, - opts..., - )) - - mux.Get("/clients/:id", kithttp.NewServer( - otelkit.EndpointMiddleware(otelkit.WithOperation("view_client"))(viewClientEndpoint(svc)), - decodeViewClient, - api.EncodeResponse, - opts..., - )) - - mux.Get("/clients", kithttp.NewServer( - otelkit.EndpointMiddleware(otelkit.WithOperation("list_clients"))(listClientsEndpoint(svc)), - decodeListClients, - api.EncodeResponse, - opts..., - )) - - mux.Get("/clients/groups/:groupID/members", kithttp.NewServer( - otelkit.EndpointMiddleware(otelkit.WithOperation("list_members"))(listMembersEndpoint(svc)), - decodeListMembersRequest, - api.EncodeResponse, - opts..., - )) - - mux.Patch("/clients/:id", kithttp.NewServer( - otelkit.EndpointMiddleware(otelkit.WithOperation("update_client_name_and_metadata"))(updateClientEndpoint(svc)), - decodeUpdateClient, - api.EncodeResponse, - opts..., - )) - - mux.Patch("/clients/:id/tags", kithttp.NewServer( - otelkit.EndpointMiddleware(otelkit.WithOperation("update_client_tags"))(updateClientTagsEndpoint(svc)), - decodeUpdateClientTags, - api.EncodeResponse, - opts..., - )) - - mux.Patch("/clients/:id/identity", kithttp.NewServer( - otelkit.EndpointMiddleware(otelkit.WithOperation("update_client_identity"))(updateClientIdentityEndpoint(svc)), - decodeUpdateClientCredentials, - api.EncodeResponse, - opts..., - )) - - mux.Patch("/clients/:id/secret", kithttp.NewServer( - otelkit.EndpointMiddleware(otelkit.WithOperation("update_client_secret"))(updateClientSecretEndpoint(svc)), - decodeUpdateClientCredentials, - api.EncodeResponse, - opts..., - )) - - mux.Patch("/clients/:id/owner", kithttp.NewServer( - otelkit.EndpointMiddleware(otelkit.WithOperation("update_client_owner"))(updateClientOwnerEndpoint(svc)), - decodeUpdateClientOwner, - api.EncodeResponse, - opts..., - )) - - mux.Post("/clients/tokens/issue", kithttp.NewServer( - otelkit.EndpointMiddleware(otelkit.WithOperation("issue_token"))(issueTokenEndpoint(svc)), - decodeCredentials, - api.EncodeResponse, - opts..., - )) - - mux.Post("/clients/tokens/refresh", kithttp.NewServer( - otelkit.EndpointMiddleware(otelkit.WithOperation("refresh_token"))(refreshTokenEndpoint(svc)), - decodeRefreshToken, - api.EncodeResponse, - opts..., - )) - - mux.Post("/clients/:id/enable", kithttp.NewServer( - otelkit.EndpointMiddleware(otelkit.WithOperation("enable_client"))(enableClientEndpoint(svc)), - decodeChangeClientStatus, - api.EncodeResponse, - opts..., - )) - - mux.Post("/clients/:id/disable", kithttp.NewServer( - otelkit.EndpointMiddleware(otelkit.WithOperation("disable_client"))(disableClientEndpoint(svc)), - decodeChangeClientStatus, - api.EncodeResponse, - opts..., - )) - - mux.GetFunc("/health", mainflux.Health("clients")) - mux.Handle("/metrics", promhttp.Handler()) -} - -func decodeViewClient(_ context.Context, r *http.Request) (interface{}, error) { - req := viewClientReq{ - token: apiutil.ExtractBearerToken(r), - id: bone.GetValue(r, "id"), - } - - return req, nil -} - -func decodeListClients(_ context.Context, r *http.Request) (interface{}, error) { - var sid string - s, err := apiutil.ReadStringQuery(r, api.StatusKey, api.DefClientStatus) - if err != nil { - return nil, err - } - o, err := apiutil.ReadNumQuery[uint64](r, api.OffsetKey, api.DefOffset) - if err != nil { - return nil, err - } - l, err := apiutil.ReadNumQuery[uint64](r, api.LimitKey, api.DefLimit) - if err != nil { - return nil, err - } - m, err := apiutil.ReadMetadataQuery(r, api.MetadataKey, nil) - if err != nil { - return nil, err - } - - n, err := apiutil.ReadStringQuery(r, api.NameKey, "") - if err != nil { - return nil, err - } - t, err := apiutil.ReadStringQuery(r, api.TagKey, "") - if err != nil { - return nil, err - } - oid, err := apiutil.ReadStringQuery(r, api.OwnerKey, "") - if err != nil { - return nil, err - } - visibility, err := apiutil.ReadStringQuery(r, api.VisibilityKey, api.MyVisibility) - if err != nil { - return nil, err - } - switch visibility { - case api.MyVisibility: - oid = api.MyVisibility - case api.SharedVisibility: - sid = api.MyVisibility - case api.AllVisibility: - sid = api.MyVisibility - oid = api.MyVisibility - } - st, err := clients.ToStatus(s) - if err != nil { - return nil, err - } - req := listClientsReq{ - token: apiutil.ExtractBearerToken(r), - status: st, - offset: o, - limit: l, - metadata: m, - name: n, - tag: t, - sharedBy: sid, - owner: oid, - } - return req, nil -} - -func decodeUpdateClient(_ context.Context, r *http.Request) (interface{}, error) { - req := updateClientReq{ - token: apiutil.ExtractBearerToken(r), - id: bone.GetValue(r, "id"), - } - if err := json.NewDecoder(r.Body).Decode(&req); err != nil { - return nil, errors.Wrap(errors.ErrMalformedEntity, err) - } - - return req, nil -} - -func decodeUpdateClientTags(_ context.Context, r *http.Request) (interface{}, error) { - req := updateClientTagsReq{ - token: apiutil.ExtractBearerToken(r), - id: bone.GetValue(r, "id"), - } - if err := json.NewDecoder(r.Body).Decode(&req); err != nil { - return nil, errors.Wrap(errors.ErrMalformedEntity, err) - } - - return req, nil -} - -func decodeUpdateClientCredentials(_ context.Context, r *http.Request) (interface{}, error) { - req := updateClientCredentialsReq{ - token: apiutil.ExtractBearerToken(r), - id: bone.GetValue(r, "id"), - } - if err := json.NewDecoder(r.Body).Decode(&req); err != nil { - return nil, errors.Wrap(errors.ErrMalformedEntity, err) - } - - return req, nil -} - -func decodeUpdateClientOwner(_ context.Context, r *http.Request) (interface{}, error) { - req := updateClientOwnerReq{ - token: apiutil.ExtractBearerToken(r), - id: bone.GetValue(r, "id"), - } - if err := json.NewDecoder(r.Body).Decode(&req); err != nil { - return nil, errors.Wrap(errors.ErrMalformedEntity, err) - } - - return req, nil -} - -func decodeCredentials(_ context.Context, r *http.Request) (interface{}, error) { - if !strings.Contains(r.Header.Get("Content-Type"), api.ContentType) { - return nil, errors.ErrUnsupportedContentType - } - req := loginClientReq{} - if err := json.NewDecoder(r.Body).Decode(&req); err != nil { - return nil, errors.Wrap(errors.ErrMalformedEntity, err) - } - - return req, nil -} - -func decodeRefreshToken(_ context.Context, r *http.Request) (interface{}, error) { - req := tokenReq{RefreshToken: apiutil.ExtractBearerToken(r)} - - return req, nil -} -func decodeCreateClientReq(_ context.Context, r *http.Request) (interface{}, error) { - if !strings.Contains(r.Header.Get("Content-Type"), api.ContentType) { - return nil, errors.ErrUnsupportedContentType - } - - var c clients.Client - if err := json.NewDecoder(r.Body).Decode(&c); err != nil { - return nil, errors.Wrap(errors.ErrMalformedEntity, err) - } - req := createClientReq{ - client: c, - token: apiutil.ExtractBearerToken(r), - } - - return req, nil -} - -func decodeChangeClientStatus(_ context.Context, r *http.Request) (interface{}, error) { - req := changeClientStatusReq{ - token: apiutil.ExtractBearerToken(r), - id: bone.GetValue(r, "id"), - } - - return req, nil -} - -func decodeListMembersRequest(_ context.Context, r *http.Request) (interface{}, error) { - s, err := apiutil.ReadStringQuery(r, api.StatusKey, api.DefClientStatus) - if err != nil { - return nil, err - } - o, err := apiutil.ReadNumQuery[uint64](r, api.OffsetKey, api.DefOffset) - if err != nil { - return nil, err - } - l, err := apiutil.ReadNumQuery[uint64](r, api.LimitKey, api.DefLimit) - if err != nil { - return nil, err - } - m, err := apiutil.ReadMetadataQuery(r, api.MetadataKey, nil) - if err != nil { - return nil, err - } - st, err := clients.ToStatus(s) - if err != nil { - return nil, err - } - req := listMembersReq{ - token: apiutil.ExtractBearerToken(r), - Page: clients.Page{ - Status: st, - Offset: o, - Limit: l, - Metadata: m, - }, - groupID: bone.GetValue(r, "groupID"), - } - return req, nil -} diff --git a/clients/clients/clients.go b/clients/clients/clients.go deleted file mode 100644 index d9de1c50c28..00000000000 --- a/clients/clients/clients.go +++ /dev/null @@ -1,135 +0,0 @@ -package clients - -import ( - "context" - "encoding/json" - "strings" - "time" -) - -// Credentials represent client credentials: its -// "identity" which can be a username, email, generated name; -// and "secret" which can be a password or access token. -type Credentials struct { - Identity string `json:"identity"` // username or generated login ID - Secret string `json:"secret"` // password or token -} - -// Client represents generic Client. -type Client struct { - ID string `json:"id"` - Name string `json:"name,omitempty"` - Tags []string `json:"tags,omitempty"` - Owner string `json:"owner,omitempty"` // nullable - Credentials Credentials `json:"credentials"` - Metadata Metadata `json:"metadata,omitempty"` - CreatedAt time.Time `json:"created_at"` - UpdatedAt time.Time `json:"updated_at"` - Status Status `json:"status"` // 1 for enabled, 0 for disabled -} - -// ClientsPage contains page related metadata as well as list -// of Clients that belong to the page. -type ClientsPage struct { - Page - Clients []Client -} - -// MembersPage contains page related metadata as well as list of members that -// belong to this page. -type MembersPage struct { - Page - Members []Client -} - -// ClientRepository specifies an account persistence API. -type ClientRepository interface { - // Save persists the client account. A non-nil error is returned to indicate - // operation failure. - Save(ctx context.Context, client Client) (Client, error) - - // RetrieveByID retrieves client by its unique ID. - RetrieveByID(ctx context.Context, id string) (Client, error) - - // RetrieveByIdentity retrieves client by its unique credentials - RetrieveByIdentity(ctx context.Context, identity string) (Client, error) - - // RetrieveAll retrieves all clients. - RetrieveAll(ctx context.Context, pm Page) (ClientsPage, error) - - // Members retrieves everything that is assigned to a group identified by groupID. - Members(ctx context.Context, groupID string, pm Page) (MembersPage, error) - - // Update updates the client name and metadata. - Update(ctx context.Context, client Client) (Client, error) - - // UpdateTags updates the client tags. - UpdateTags(ctx context.Context, client Client) (Client, error) - - // UpdateIdentity updates identity for client with given id. - UpdateIdentity(ctx context.Context, client Client) (Client, error) - - // UpdateSecret updates secret for client with given identity. - UpdateSecret(ctx context.Context, client Client) (Client, error) - - // UpdateOwner updates owner for client with given id. - UpdateOwner(ctx context.Context, client Client) (Client, error) - - // ChangeStatus changes client status to enabled or disabled - ChangeStatus(ctx context.Context, id string, status Status) (Client, error) -} - -// ClientService specifies an API that must be fullfiled by the domain service -// implementation, and all of its decorators (e.g. logging & metrics). -type ClientService interface { - // RegisterClient creates new client. In case of the failed registration, a - // non-nil error value is returned. - RegisterClient(ctx context.Context, token string, client Client) (Client, error) - - // LoginClient authenticates the client given its credentials. Successful - // authentication generates new access token. Failed invocations are - // identified by the non-nil error values in the response. - - // ViewClient retrieves client info for a given client ID and an authorized token. - ViewClient(ctx context.Context, token, id string) (Client, error) - - // ListClients retrieves clients list for a valid auth token. - ListClients(ctx context.Context, token string, pm Page) (ClientsPage, error) - - // ListMembers retrieves everything that is assigned to a group identified by groupID. - ListMembers(ctx context.Context, token, groupID string, pm Page) (MembersPage, error) - - // UpdateClient updates the client's name and metadata. - UpdateClient(ctx context.Context, token string, client Client) (Client, error) - - // UpdateClientTags updates the client's tags. - UpdateClientTags(ctx context.Context, token string, client Client) (Client, error) - - // UpdateClientIdentity updates the client's identity - UpdateClientIdentity(ctx context.Context, token, id, identity string) (Client, error) - - // UpdateClientSecret updates the client's secret - UpdateClientSecret(ctx context.Context, token, oldSecret, newSecret string) (Client, error) - - // UpdateClientOwner updates the client's owner. - UpdateClientOwner(ctx context.Context, token string, client Client) (Client, error) - - // EnableClient logically enableds the client identified with the provided ID - EnableClient(ctx context.Context, token, id string) (Client, error) - - // DisableClient logically disables the client identified with the provided ID - DisableClient(ctx context.Context, token, id string) (Client, error) -} - -// Custom Marshaller for Client -func (s Status) MarshalJSON() ([]byte, error) { - return json.Marshal(s.String()) -} - -// Custom Unmarshaller for Client -func (s *Status) UnmarshalJSON(data []byte) error { - str := strings.Trim(string(data), "\"") - val, err := ToStatus(str) - *s = val - return err -} diff --git a/clients/clients/hasher.go b/clients/clients/hasher.go deleted file mode 100644 index fd5dfff3147..00000000000 --- a/clients/clients/hasher.go +++ /dev/null @@ -1,12 +0,0 @@ -package clients - -// Hasher specifies an API for generating hashes of an arbitrary textual -// content. -type Hasher interface { - // Hash generates the hashed string from plain-text. - Hash(string) (string, error) - - // Compare compares plain-text version to the hashed one. An error should - // indicate failed comparison. - Compare(string, string) error -} diff --git a/clients/clients/mocks/clients.go b/clients/clients/mocks/clients.go deleted file mode 100644 index 9333272327c..00000000000 --- a/clients/clients/mocks/clients.go +++ /dev/null @@ -1,133 +0,0 @@ -package mocks - -import ( - "context" - - "github.com/mainflux/mainflux/clients/clients" - "github.com/mainflux/mainflux/pkg/errors" - "github.com/stretchr/testify/mock" -) - -const WrongID = "wrongID" - -var _ clients.ClientRepository = (*ClientRepository)(nil) - -type ClientRepository struct { - mock.Mock -} - -func (m *ClientRepository) ChangeStatus(ctx context.Context, id string, status clients.Status) (clients.Client, error) { - ret := m.Called(ctx, id, status) - - if id == WrongID { - return clients.Client{}, errors.ErrNotFound - } - - if status != clients.EnabledStatus && status != clients.DisabledStatus { - return clients.Client{}, errors.ErrMalformedEntity - } - - return ret.Get(0).(clients.Client), ret.Error(1) -} - -func (m *ClientRepository) Members(ctx context.Context, groupID string, pm clients.Page) (clients.MembersPage, error) { - ret := m.Called(ctx, groupID, pm) - if groupID == WrongID { - return clients.MembersPage{}, errors.ErrNotFound - } - - return ret.Get(0).(clients.MembersPage), ret.Error(1) -} - -func (m *ClientRepository) RetrieveAll(ctx context.Context, pm clients.Page) (clients.ClientsPage, error) { - ret := m.Called(ctx, pm) - - return ret.Get(0).(clients.ClientsPage), ret.Error(1) -} - -func (m *ClientRepository) RetrieveByID(ctx context.Context, id string) (clients.Client, error) { - ret := m.Called(ctx, id) - - if id == WrongID { - return clients.Client{}, errors.ErrNotFound - } - - return ret.Get(0).(clients.Client), ret.Error(1) -} - -func (m *ClientRepository) RetrieveByIdentity(ctx context.Context, identity string) (clients.Client, error) { - ret := m.Called(ctx, identity) - - if identity == "" { - return clients.Client{}, errors.ErrMalformedEntity - } - - return ret.Get(0).(clients.Client), ret.Error(1) -} - -func (m *ClientRepository) Save(ctx context.Context, client clients.Client) (clients.Client, error) { - ret := m.Called(ctx, client) - if client.Owner == WrongID { - return clients.Client{}, errors.ErrMalformedEntity - } - if client.Credentials.Secret == "" { - return clients.Client{}, errors.ErrMalformedEntity - } - - return client, ret.Error(1) -} - -func (m *ClientRepository) Update(ctx context.Context, client clients.Client) (clients.Client, error) { - ret := m.Called(ctx, client) - - if client.ID == WrongID { - return clients.Client{}, errors.ErrNotFound - } - return ret.Get(0).(clients.Client), ret.Error(1) -} - -func (m *ClientRepository) UpdateIdentity(ctx context.Context, client clients.Client) (clients.Client, error) { - ret := m.Called(ctx, client) - - if client.ID == WrongID { - return clients.Client{}, errors.ErrNotFound - } - if client.Credentials.Identity == "" { - return clients.Client{}, errors.ErrMalformedEntity - } - - return ret.Get(0).(clients.Client), ret.Error(1) -} - -func (m *ClientRepository) UpdateSecret(ctx context.Context, client clients.Client) (clients.Client, error) { - ret := m.Called(ctx, client) - - if client.ID == WrongID { - return clients.Client{}, errors.ErrNotFound - } - if client.Credentials.Secret == "" { - return clients.Client{}, errors.ErrMalformedEntity - } - - return ret.Get(0).(clients.Client), ret.Error(1) -} - -func (m *ClientRepository) UpdateTags(ctx context.Context, client clients.Client) (clients.Client, error) { - ret := m.Called(ctx, client) - - if client.ID == WrongID { - return clients.Client{}, errors.ErrNotFound - } - - return ret.Get(0).(clients.Client), ret.Error(1) -} - -func (m *ClientRepository) UpdateOwner(ctx context.Context, client clients.Client) (clients.Client, error) { - ret := m.Called(ctx, client) - - if client.ID == WrongID { - return clients.Client{}, errors.ErrNotFound - } - - return ret.Get(0).(clients.Client), ret.Error(1) -} diff --git a/clients/clients/page.go b/clients/clients/page.go deleted file mode 100644 index 27072864326..00000000000 --- a/clients/clients/page.go +++ /dev/null @@ -1,19 +0,0 @@ -package clients - -// Metadata represents arbitrary JSON. -type Metadata map[string]interface{} - -// Page contains page metadata that helps navigation. -type Page struct { - Total uint64 - Offset uint64 - Limit uint64 - Name string - OwnerID string - Tag string - Metadata Metadata - SharedBy string - Status Status - Action string - Subject string -} diff --git a/clients/clients/postgres/clients.go b/clients/clients/postgres/clients.go deleted file mode 100644 index b9032790425..00000000000 --- a/clients/clients/postgres/clients.go +++ /dev/null @@ -1,517 +0,0 @@ -package postgres - -import ( - "context" - "database/sql" - "encoding/json" - "fmt" - "strings" - "time" - - "github.com/jackc/pgtype" // required for SQL access - "github.com/mainflux/mainflux/clients/clients" - "github.com/mainflux/mainflux/clients/groups" - "github.com/mainflux/mainflux/clients/postgres" - "github.com/mainflux/mainflux/pkg/errors" -) - -var _ clients.ClientRepository = (*clientRepo)(nil) - -type clientRepo struct { - db postgres.Database -} - -// NewClientRepo instantiates a PostgreSQL -// implementation of Clients repository. -func NewClientRepo(db postgres.Database) clients.ClientRepository { - return &clientRepo{ - db: db, - } -} - -func (repo clientRepo) Save(ctx context.Context, c clients.Client) (clients.Client, error) { - q := `INSERT INTO clients (id, name, tags, owner, identity, secret, metadata, created_at, updated_at, status) - VALUES (:id, :name, :tags, :owner, :identity, :secret, :metadata, :created_at, :updated_at, :status) - RETURNING id, name, tags, identity, metadata, COALESCE(owner, '') AS owner, status, created_at, updated_at` - if c.Owner == "" { - q = `INSERT INTO clients (id, name, tags, identity, secret, metadata, created_at, updated_at, status) - VALUES (:id, :name, :tags, :identity, :secret, :metadata, :created_at, :updated_at, :status) - RETURNING id, name, tags, identity, metadata, COALESCE(owner, '') AS owner, status, created_at, updated_at` - } - dbc, err := toDBClient(c) - if err != nil { - return clients.Client{}, errors.Wrap(errors.ErrCreateEntity, err) - } - - row, err := repo.db.NamedQueryContext(ctx, q, dbc) - if err != nil { - return clients.Client{}, postgres.HandleError(err, errors.ErrCreateEntity) - } - - defer row.Close() - row.Next() - var rClient dbClient - if err := row.StructScan(&rClient); err != nil { - return clients.Client{}, err - } - - return toClient(rClient) -} - -func (repo clientRepo) RetrieveByID(ctx context.Context, id string) (clients.Client, error) { - q := `SELECT id, name, tags, COALESCE(owner, '') AS owner, identity, secret, metadata, created_at, updated_at, status - FROM clients - WHERE id = $1` - - dbc := dbClient{ - ID: id, - } - - if err := repo.db.QueryRowxContext(ctx, q, id).StructScan(&dbc); err != nil { - if err == sql.ErrNoRows { - return clients.Client{}, errors.Wrap(errors.ErrNotFound, err) - - } - return clients.Client{}, errors.Wrap(errors.ErrViewEntity, err) - } - - return toClient(dbc) -} - -func (repo clientRepo) RetrieveByIdentity(ctx context.Context, identity string) (clients.Client, error) { - q := fmt.Sprintf(`SELECT id, name, tags, COALESCE(owner, '') AS owner, identity, secret, metadata, created_at, updated_at, status - FROM clients - WHERE identity = $1 AND status = %d`, clients.EnabledStatus) - - dbc := dbClient{ - Identity: identity, - } - - if err := repo.db.QueryRowxContext(ctx, q, identity).StructScan(&dbc); err != nil { - if err == sql.ErrNoRows { - return clients.Client{}, errors.Wrap(errors.ErrNotFound, err) - - } - return clients.Client{}, errors.Wrap(errors.ErrViewEntity, err) - } - - return toClient(dbc) -} - -func (repo clientRepo) RetrieveAll(ctx context.Context, pm clients.Page) (clients.ClientsPage, error) { - query, err := pageQuery(pm) - if err != nil { - return clients.ClientsPage{}, errors.Wrap(errors.ErrViewEntity, err) - } - - q := fmt.Sprintf(`SELECT c.id, c.name, c.tags, c.identity, c.metadata, COALESCE(c.owner, '') AS owner, c.status, c.created_at - FROM clients c %s ORDER BY c.created_at LIMIT :limit OFFSET :offset;`, query) - - dbPage, err := toDBClientsPage(pm) - if err != nil { - return clients.ClientsPage{}, errors.Wrap(postgres.ErrFailedToRetrieveAll, err) - } - rows, err := repo.db.NamedQueryContext(ctx, q, dbPage) - if err != nil { - return clients.ClientsPage{}, errors.Wrap(postgres.ErrFailedToRetrieveAll, err) - } - defer rows.Close() - - var items []clients.Client - for rows.Next() { - dbc := dbClient{} - if err := rows.StructScan(&dbc); err != nil { - return clients.ClientsPage{}, errors.Wrap(errors.ErrViewEntity, err) - } - - c, err := toClient(dbc) - if err != nil { - return clients.ClientsPage{}, err - } - - items = append(items, c) - } - cq := fmt.Sprintf(`SELECT COUNT(*) FROM clients c %s;`, query) - - total, err := postgres.Total(ctx, repo.db, cq, dbPage) - if err != nil { - return clients.ClientsPage{}, errors.Wrap(errors.ErrViewEntity, err) - } - - page := clients.ClientsPage{ - Clients: items, - Page: clients.Page{ - Total: total, - Offset: pm.Offset, - Limit: pm.Limit, - }, - } - - return page, nil -} - -func (repo clientRepo) Members(ctx context.Context, groupID string, pm clients.Page) (clients.MembersPage, error) { - emq, err := pageQuery(pm) - if err != nil { - return clients.MembersPage{}, err - } - - q := fmt.Sprintf(`SELECT c.id, c.name, c.tags, c.metadata, c.identity, c.status, c.created_at FROM clients c - INNER JOIN policies ON c.id=policies.subject %s AND policies.object = :group_id - AND EXISTS (SELECT 1 FROM policies WHERE policies.subject = '%s' AND '%s'=ANY(actions)) - ORDER BY c.created_at LIMIT :limit OFFSET :offset;`, emq, pm.Subject, pm.Action) - dbPage, err := toDBClientsPage(pm) - if err != nil { - return clients.MembersPage{}, errors.Wrap(postgres.ErrFailedToRetrieveAll, err) - } - dbPage.GroupID = groupID - rows, err := repo.db.NamedQueryContext(ctx, q, dbPage) - if err != nil { - return clients.MembersPage{}, errors.Wrap(postgres.ErrFailedToRetrieveMembers, err) - } - defer rows.Close() - - var items []clients.Client - for rows.Next() { - dbc := dbClient{} - if err := rows.StructScan(&dbc); err != nil { - return clients.MembersPage{}, errors.Wrap(postgres.ErrFailedToRetrieveMembers, err) - } - - c, err := toClient(dbc) - if err != nil { - return clients.MembersPage{}, err - } - - items = append(items, c) - } - cq := fmt.Sprintf(`SELECT COUNT(*) FROM clients c INNER JOIN policies ON c.id=policies.subject %s AND policies.object = :group_id;`, emq) - - total, err := postgres.Total(ctx, repo.db, cq, dbPage) - if err != nil { - return clients.MembersPage{}, errors.Wrap(postgres.ErrFailedToRetrieveMembers, err) - } - - page := clients.MembersPage{ - Members: items, - Page: clients.Page{ - Total: total, - Offset: pm.Offset, - Limit: pm.Limit, - }, - } - return page, nil -} - -func (repo clientRepo) Update(ctx context.Context, client clients.Client) (clients.Client, error) { - var query []string - var upq string - if client.Name != "" { - query = append(query, "name = :name,") - } - if client.Metadata != nil { - query = append(query, "metadata = :metadata,") - } - if len(query) > 0 { - upq = strings.Join(query, " ") - } - q := fmt.Sprintf(`UPDATE clients SET %s updated_at = :updated_at - WHERE id = :id AND status = %d - RETURNING id, name, tags, identity, metadata, COALESCE(owner, '') AS owner, status, created_at, updated_at`, - upq, clients.EnabledStatus) - - dbu, err := toDBClient(client) - if err != nil { - return clients.Client{}, errors.Wrap(errors.ErrUpdateEntity, err) - } - - row, err := repo.db.NamedQueryContext(ctx, q, dbu) - if err != nil { - return clients.Client{}, postgres.HandleError(err, errors.ErrCreateEntity) - } - - defer row.Close() - if ok := row.Next(); !ok { - return clients.Client{}, errors.Wrap(errors.ErrNotFound, row.Err()) - } - var rClient dbClient - if err := row.StructScan(&rClient); err != nil { - return clients.Client{}, err - } - - return toClient(rClient) -} - -func (repo clientRepo) UpdateTags(ctx context.Context, client clients.Client) (clients.Client, error) { - q := fmt.Sprintf(`UPDATE clients SET tags = :tags, updated_at = :updated_at - WHERE id = :id AND status = %d - RETURNING id, name, tags, identity, metadata, COALESCE(owner, '') AS owner, status, created_at, updated_at`, - clients.EnabledStatus) - - dbu, err := toDBClient(client) - if err != nil { - return clients.Client{}, errors.Wrap(errors.ErrUpdateEntity, err) - } - row, err := repo.db.NamedQueryContext(ctx, q, dbu) - if err != nil { - return clients.Client{}, postgres.HandleError(err, errors.ErrUpdateEntity) - } - - defer row.Close() - if ok := row.Next(); !ok { - return clients.Client{}, errors.Wrap(errors.ErrNotFound, row.Err()) - } - var rClient dbClient - if err := row.StructScan(&rClient); err != nil { - return clients.Client{}, err - } - - return toClient(rClient) -} - -func (repo clientRepo) UpdateIdentity(ctx context.Context, client clients.Client) (clients.Client, error) { - q := fmt.Sprintf(`UPDATE clients SET identity = :identity, updated_at = :updated_at - WHERE id = :id AND status = %d - RETURNING id, name, tags, identity, metadata, COALESCE(owner, '') AS owner, status, created_at, updated_at`, - clients.EnabledStatus) - - dbc, err := toDBClient(client) - if err != nil { - return clients.Client{}, errors.Wrap(errors.ErrUpdateEntity, err) - } - row, err := repo.db.NamedQueryContext(ctx, q, dbc) - if err != nil { - return clients.Client{}, postgres.HandleError(err, errors.ErrUpdateEntity) - } - - defer row.Close() - if ok := row.Next(); !ok { - return clients.Client{}, errors.Wrap(errors.ErrNotFound, row.Err()) - } - var rClient dbClient - if err := row.StructScan(&rClient); err != nil { - return clients.Client{}, err - } - - return toClient(rClient) -} - -func (repo clientRepo) UpdateSecret(ctx context.Context, client clients.Client) (clients.Client, error) { - q := fmt.Sprintf(`UPDATE clients SET secret = :secret, updated_at = :updated_at - WHERE identity = :identity AND status = %d - RETURNING id, name, tags, identity, metadata, COALESCE(owner, '') AS owner, status, created_at, updated_at`, - clients.EnabledStatus) - - dbc, err := toDBClient(client) - if err != nil { - return clients.Client{}, errors.Wrap(errors.ErrUpdateEntity, err) - } - row, err := repo.db.NamedQueryContext(ctx, q, dbc) - if err != nil { - return clients.Client{}, postgres.HandleError(err, errors.ErrUpdateEntity) - } - - defer row.Close() - if ok := row.Next(); !ok { - return clients.Client{}, errors.Wrap(errors.ErrNotFound, row.Err()) - } - var rClient dbClient - if err := row.StructScan(&rClient); err != nil { - return clients.Client{}, err - } - - return toClient(rClient) -} - -func (repo clientRepo) UpdateOwner(ctx context.Context, client clients.Client) (clients.Client, error) { - q := fmt.Sprintf(`UPDATE clients SET owner = :owner, updated_at = :updated_at - WHERE id = :id AND status = %d - RETURNING id, name, tags, identity, metadata, COALESCE(owner, '') AS owner, status, created_at, updated_at`, - clients.EnabledStatus) - - dbc, err := toDBClient(client) - if err != nil { - return clients.Client{}, errors.Wrap(errors.ErrUpdateEntity, err) - } - row, err := repo.db.NamedQueryContext(ctx, q, dbc) - if err != nil { - return clients.Client{}, postgres.HandleError(err, errors.ErrUpdateEntity) - } - - defer row.Close() - if ok := row.Next(); !ok { - return clients.Client{}, errors.Wrap(errors.ErrNotFound, row.Err()) - } - var rClient dbClient - if err := row.StructScan(&rClient); err != nil { - return clients.Client{}, err - } - - return toClient(rClient) -} - -func (repo clientRepo) ChangeStatus(ctx context.Context, id string, status clients.Status) (clients.Client, error) { - q := fmt.Sprintf(`UPDATE clients SET status = %d WHERE id = :id - RETURNING id, name, tags, identity, metadata, COALESCE(owner, '') AS owner, status, created_at, updated_at`, status) - - dbc := dbClient{ - ID: id, - } - row, err := repo.db.NamedQueryContext(ctx, q, dbc) - if err != nil { - return clients.Client{}, postgres.HandleError(err, errors.ErrUpdateEntity) - } - - defer row.Close() - if ok := row.Next(); !ok { - return clients.Client{}, errors.Wrap(errors.ErrNotFound, row.Err()) - } - var rClient dbClient - if err := row.StructScan(&rClient); err != nil { - return clients.Client{}, errors.Wrap(errors.ErrUpdateEntity, err) - } - - return toClient(rClient) -} - -type dbClient struct { - ID string `db:"id"` - Name string `db:"name,omitempty"` - Tags pgtype.TextArray `db:"tags,omitempty"` - Identity string `db:"identity"` - Owner string `db:"owner,omitempty"` // nullable - Secret string `db:"secret"` - Metadata []byte `db:"metadata,omitempty"` - CreatedAt time.Time `db:"created_at"` - UpdatedAt time.Time `db:"updated_at"` - Groups []groups.Group `db:"groups"` - Status clients.Status `db:"status"` -} - -func toDBClient(c clients.Client) (dbClient, error) { - data := []byte("{}") - if len(c.Metadata) > 0 { - b, err := json.Marshal(c.Metadata) - if err != nil { - return dbClient{}, errors.Wrap(errors.ErrMalformedEntity, err) - } - data = b - } - var tags pgtype.TextArray - if err := tags.Set(c.Tags); err != nil { - return dbClient{}, err - } - - return dbClient{ - ID: c.ID, - Name: c.Name, - Tags: tags, - Owner: c.Owner, - Identity: c.Credentials.Identity, - Secret: c.Credentials.Secret, - Metadata: data, - CreatedAt: c.CreatedAt, - UpdatedAt: c.UpdatedAt, - Status: c.Status, - }, nil -} - -func toClient(c dbClient) (clients.Client, error) { - var metadata clients.Metadata - if c.Metadata != nil { - if err := json.Unmarshal([]byte(c.Metadata), &metadata); err != nil { - return clients.Client{}, errors.Wrap(errors.ErrMalformedEntity, err) - } - } - var tags []string - for _, e := range c.Tags.Elements { - tags = append(tags, e.String) - } - - return clients.Client{ - ID: c.ID, - Name: c.Name, - Tags: tags, - Owner: c.Owner, - Credentials: clients.Credentials{ - Identity: c.Identity, - Secret: c.Secret, - }, - Metadata: metadata, - CreatedAt: c.CreatedAt, - UpdatedAt: c.UpdatedAt, - Status: c.Status, - }, nil -} - -func pageQuery(pm clients.Page) (string, error) { - mq, _, err := postgres.CreateMetadataQuery("", pm.Metadata) - if err != nil { - return "", errors.Wrap(errors.ErrViewEntity, err) - } - var query []string - var emq string - if mq != "" { - query = append(query, mq) - } - if pm.Name != "" { - query = append(query, fmt.Sprintf("c.name = '%s'", pm.Name)) - } - if pm.Tag != "" { - query = append(query, fmt.Sprintf("'%s' = ANY(c.tags)", pm.Tag)) - } - if pm.Status != clients.AllStatus { - query = append(query, fmt.Sprintf("c.status = %d", pm.Status)) - } - // For listing clients that the specified client owns but not sharedby - if pm.OwnerID != "" && pm.SharedBy == "" { - query = append(query, fmt.Sprintf("c.owner = '%s'", pm.OwnerID)) - } - - // For listing clients that the specified client owns and that are shared with the specified client - if pm.OwnerID != "" && pm.SharedBy != "" { - query = append(query, fmt.Sprintf("(c.owner = '%s' OR policies.object IN (SELECT object FROM policies WHERE subject = '%s' AND '%s'=ANY(actions)))", pm.OwnerID, pm.SharedBy, pm.Action)) - } - // For listing clients that the specified client is shared with - if pm.SharedBy != "" && pm.OwnerID == "" { - query = append(query, fmt.Sprintf("c.owner != '%s' AND (policies.object IN (SELECT object FROM policies WHERE subject = '%s' AND '%s'=ANY(actions)))", pm.SharedBy, pm.SharedBy, pm.Action)) - } - if len(query) > 0 { - emq = fmt.Sprintf("WHERE %s", strings.Join(query, " AND ")) - if strings.Contains(emq, "policies") { - emq = fmt.Sprintf("JOIN policies ON policies.subject = c.id %s", emq) - } - } - return emq, nil - -} - -func toDBClientsPage(pm clients.Page) (dbClientsPage, error) { - _, data, err := postgres.CreateMetadataQuery("", pm.Metadata) - if err != nil { - return dbClientsPage{}, errors.Wrap(errors.ErrViewEntity, err) - } - return dbClientsPage{ - Name: pm.Name, - Metadata: data, - Owner: pm.OwnerID, - Total: pm.Total, - Offset: pm.Offset, - Limit: pm.Limit, - Status: pm.Status, - Tag: pm.Tag, - }, nil -} - -type dbClientsPage struct { - GroupID string `db:"group_id"` - Name string `db:"name"` - Owner string `db:"owner"` - Identity string `db:"identity"` - Metadata []byte `db:"metadata"` - Tag string `db:"tag"` - Status clients.Status `db:"status"` - Total uint64 `db:"total"` - Limit uint64 `db:"limit"` - Offset uint64 `db:"offset"` -} diff --git a/clients/clients/postgres/clients_test.go b/clients/clients/postgres/clients_test.go deleted file mode 100644 index 24f2a9024f3..00000000000 --- a/clients/clients/postgres/clients_test.go +++ /dev/null @@ -1,1048 +0,0 @@ -package postgres_test - -import ( - "context" - "fmt" - "strings" - "testing" - - "github.com/mainflux/mainflux/clients/clients" - cpostgres "github.com/mainflux/mainflux/clients/clients/postgres" - "github.com/mainflux/mainflux/clients/groups" - gpostgres "github.com/mainflux/mainflux/clients/groups/postgres" - "github.com/mainflux/mainflux/clients/policies" - ppostgres "github.com/mainflux/mainflux/clients/policies/postgres" - "github.com/mainflux/mainflux/clients/postgres" - "github.com/mainflux/mainflux/internal/testsutil" - "github.com/mainflux/mainflux/pkg/errors" - "github.com/mainflux/mainflux/pkg/uuid" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -const ( - maxNameSize = 254 -) - -var ( - idProvider = uuid.New() - invalidName = strings.Repeat("m", maxNameSize+10) - password = "$tr0ngPassw0rd" - clientIdentity = "client-identity@example.com" - clientName = "client name" - wrongName = "wrong-name" - wrongID = "wrong-id" -) - -func TestClientsSave(t *testing.T) { - t.Cleanup(func() { testsutil.CleanUpDB(t, db) }) - postgres.NewDatabase(db, tracer) - repo := cpostgres.NewClientRepo(database) - - uid := testsutil.GenerateUUID(t, idProvider) - - cases := []struct { - desc string - client clients.Client - err error - }{ - { - desc: "add new client successfully", - client: clients.Client{ - ID: uid, - Name: clientName, - Credentials: clients.Credentials{ - Identity: clientIdentity, - Secret: password, - }, - Metadata: clients.Metadata{}, - Status: clients.EnabledStatus, - }, - err: nil, - }, - { - desc: "add new client with an owner", - client: clients.Client{ - ID: testsutil.GenerateUUID(t, idProvider), - Owner: uid, - Name: clientName, - Credentials: clients.Credentials{ - Identity: "withowner-client@example.com", - Secret: password, - }, - Metadata: clients.Metadata{}, - Status: clients.EnabledStatus, - }, - err: nil, - }, - { - desc: "add client with duplicate client identity", - client: clients.Client{ - ID: testsutil.GenerateUUID(t, idProvider), - Name: clientName, - Credentials: clients.Credentials{ - Identity: clientIdentity, - Secret: password, - }, - Metadata: clients.Metadata{}, - Status: clients.EnabledStatus, - }, - err: errors.ErrConflict, - }, - { - desc: "add client with invalid client id", - client: clients.Client{ - ID: invalidName, - Name: clientName, - Credentials: clients.Credentials{ - Identity: "invalidid-client@example.com", - Secret: password, - }, - Metadata: clients.Metadata{}, - Status: clients.EnabledStatus, - }, - err: errors.ErrMalformedEntity, - }, - { - desc: "add client with invalid client name", - client: clients.Client{ - ID: testsutil.GenerateUUID(t, idProvider), - Name: invalidName, - Credentials: clients.Credentials{ - Identity: "invalidname-client@example.com", - Secret: password, - }, - Metadata: clients.Metadata{}, - Status: clients.EnabledStatus, - }, - err: errors.ErrMalformedEntity, - }, - { - desc: "add client with invalid client owner", - client: clients.Client{ - ID: testsutil.GenerateUUID(t, idProvider), - Owner: invalidName, - Credentials: clients.Credentials{ - Identity: "invalidowner-client@example.com", - Secret: password, - }, - Metadata: clients.Metadata{}, - Status: clients.EnabledStatus, - }, - err: errors.ErrMalformedEntity, - }, - { - desc: "add client with invalid client identity", - client: clients.Client{ - ID: testsutil.GenerateUUID(t, idProvider), - Name: clientName, - Credentials: clients.Credentials{ - Identity: invalidName, - Secret: password, - }, - Metadata: clients.Metadata{}, - Status: clients.EnabledStatus, - }, - err: errors.ErrMalformedEntity, - }, - { - desc: "add client with a missing client identity", - client: clients.Client{ - ID: testsutil.GenerateUUID(t, idProvider), - Credentials: clients.Credentials{ - Identity: "", - Secret: password, - }, - Metadata: clients.Metadata{}, - }, - err: nil, - }, - { - desc: "add client with a missing client secret", - client: clients.Client{ - ID: testsutil.GenerateUUID(t, idProvider), - Credentials: clients.Credentials{ - Identity: "missing-client-secret@example.com", - Secret: "", - }, - Metadata: clients.Metadata{}, - }, - err: nil, - }, - } - for _, tc := range cases { - rClient, err := repo.Save(context.Background(), tc.client) - assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) - rClient.Credentials.Secret = tc.client.Credentials.Secret - if err == nil { - assert.Equal(t, tc.client, rClient, fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.client, rClient)) - } - } -} - -func TestClientsRetrieveByID(t *testing.T) { - t.Cleanup(func() { testsutil.CleanUpDB(t, db) }) - postgres.NewDatabase(db, tracer) - repo := cpostgres.NewClientRepo(database) - - client := clients.Client{ - ID: testsutil.GenerateUUID(t, idProvider), - Name: clientName, - Credentials: clients.Credentials{ - Identity: clientIdentity, - Secret: password, - }, - Status: clients.EnabledStatus, - } - - client, err := repo.Save(context.Background(), client) - require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err)) - - cases := map[string]struct { - ID string - err error - }{ - "retrieve existing client": {client.ID, nil}, - "retrieve non-existing client": {wrongID, errors.ErrNotFound}, - } - - for desc, tc := range cases { - cli, err := repo.RetrieveByID(context.Background(), tc.ID) - assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", desc, tc.err, err)) - if err == nil { - assert.Equal(t, client.ID, cli.ID, fmt.Sprintf("retrieve client by ID : client ID : expected %s got %s\n", client.ID, cli.ID)) - assert.Equal(t, client.Name, cli.Name, fmt.Sprintf("retrieve client by ID : client Name : expected %s got %s\n", client.Name, cli.Name)) - assert.Equal(t, client.Credentials.Identity, cli.Credentials.Identity, fmt.Sprintf("retrieve client by ID : client Identity : expected %s got %s\n", client.Credentials.Identity, cli.Credentials.Identity)) - assert.Equal(t, client.Status, cli.Status, fmt.Sprintf("retrieve client by ID : client Status : expected %d got %d\n", client.Status, cli.Status)) - } - } -} - -func TestClientsRetrieveByIdentity(t *testing.T) { - t.Cleanup(func() { testsutil.CleanUpDB(t, db) }) - postgres.NewDatabase(db, tracer) - repo := cpostgres.NewClientRepo(database) - - client := clients.Client{ - ID: testsutil.GenerateUUID(t, idProvider), - Name: clientName, - Credentials: clients.Credentials{ - Identity: clientIdentity, - Secret: password, - }, - Status: clients.EnabledStatus, - } - - _, err := repo.Save(context.Background(), client) - require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err)) - - cases := map[string]struct { - identity string - err error - }{ - "retrieve existing client": {clientIdentity, nil}, - "retrieve non-existing client": {wrongID, errors.ErrNotFound}, - } - - for desc, tc := range cases { - _, err := repo.RetrieveByIdentity(context.Background(), tc.identity) - assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", desc, tc.err, err)) - } -} - -func TestClientsRetrieveAll(t *testing.T) { - t.Cleanup(func() { testsutil.CleanUpDB(t, db) }) - postgres.NewDatabase(db, tracer) - repo := cpostgres.NewClientRepo(database) - grepo := gpostgres.NewGroupRepo(database) - prepo := ppostgres.NewPolicyRepo(database) - - var nClients = uint64(200) - var ownerID string - - meta := clients.Metadata{ - "admin": "true", - } - wrongMeta := clients.Metadata{ - "admin": "false", - } - var expectedClients = []clients.Client{} - - var sharedGroup = groups.Group{ - ID: testsutil.GenerateUUID(t, idProvider), - Name: "shared-group", - } - _, err := grepo.Save(context.Background(), sharedGroup) - require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err)) - - for i := uint64(0); i < nClients; i++ { - identity := fmt.Sprintf("TestRetrieveAll%d@example.com", i) - client := clients.Client{ - ID: testsutil.GenerateUUID(t, idProvider), - Name: identity, - Credentials: clients.Credentials{ - Identity: identity, - Secret: password, - }, - Metadata: clients.Metadata{}, - Status: clients.EnabledStatus, - } - if i == 1 { - ownerID = client.ID - } - if i%10 == 0 { - client.Owner = ownerID - client.Metadata = meta - client.Tags = []string{"Test"} - } - if i%50 == 0 { - client.Status = clients.DisabledStatus - } - _, err := repo.Save(context.Background(), client) - require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err)) - client.Credentials.Secret = "" - expectedClients = append(expectedClients, client) - var policy = policies.Policy{ - Subject: client.ID, - Object: sharedGroup.ID, - Actions: []string{"c_list"}, - } - err = prepo.Save(context.Background(), policy) - require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err)) - } - - cases := map[string]struct { - size uint64 - pm clients.Page - response []clients.Client - }{ - "retrieve all clients empty page": { - pm: clients.Page{}, - response: []clients.Client{}, - size: 0, - }, - "retrieve all clients": { - pm: clients.Page{ - Offset: 0, - Limit: nClients, - Status: clients.AllStatus, - }, - response: expectedClients, - size: 200, - }, - "retrieve all clients with limit": { - pm: clients.Page{ - Offset: 0, - Limit: 50, - Status: clients.AllStatus, - }, - response: expectedClients[0:50], - size: 50, - }, - "retrieve all clients with offset": { - pm: clients.Page{ - Offset: 50, - Limit: nClients, - Status: clients.AllStatus, - }, - response: expectedClients[50:200], - size: 150, - }, - "retrieve all clients with limit and offset": { - pm: clients.Page{ - Offset: 50, - Limit: 50, - Status: clients.AllStatus, - }, - response: expectedClients[50:100], - size: 50, - }, - "retrieve all clients with limit and offset not full": { - pm: clients.Page{ - Offset: 170, - Limit: 50, - Status: clients.AllStatus, - }, - response: expectedClients[170:200], - size: 30, - }, - "retrieve all clients by metadata": { - pm: clients.Page{ - Offset: 0, - Limit: nClients, - Total: nClients, - Metadata: meta, - Status: clients.AllStatus, - }, - response: []clients.Client{expectedClients[0], expectedClients[10], expectedClients[20], expectedClients[30], expectedClients[40], expectedClients[50], expectedClients[60], - expectedClients[70], expectedClients[80], expectedClients[90], expectedClients[100], expectedClients[110], expectedClients[120], expectedClients[130], - expectedClients[140], expectedClients[150], expectedClients[160], expectedClients[170], expectedClients[180], expectedClients[190], - }, - size: 20, - }, - "retrieve clients by wrong metadata": { - pm: clients.Page{ - Offset: 0, - Limit: nClients, - Total: nClients, - Metadata: wrongMeta, - Status: clients.AllStatus, - }, - response: []clients.Client{}, - size: 0, - }, - "retrieve all clients by name": { - pm: clients.Page{ - Offset: 0, - Limit: nClients, - Total: nClients, - Name: "TestRetrieveAll3@example.com", - Status: clients.AllStatus, - }, - response: []clients.Client{expectedClients[3]}, - size: 1, - }, - "retrieve clients by wrong name": { - pm: clients.Page{ - Offset: 0, - Limit: nClients, - Total: nClients, - Name: wrongName, - Status: clients.AllStatus, - }, - response: []clients.Client{}, - size: 0, - }, - "retrieve all clients by owner": { - pm: clients.Page{ - Offset: 0, - Limit: nClients, - Total: nClients, - OwnerID: ownerID, - Status: clients.AllStatus, - }, - response: []clients.Client{expectedClients[10], expectedClients[20], expectedClients[30], expectedClients[40], expectedClients[50], expectedClients[60], - expectedClients[70], expectedClients[80], expectedClients[90], expectedClients[100], expectedClients[110], expectedClients[120], expectedClients[130], - expectedClients[140], expectedClients[150], expectedClients[160], expectedClients[170], expectedClients[180], expectedClients[190], - }, - size: 19, - }, - "retrieve clients by wrong owner": { - pm: clients.Page{ - Offset: 0, - Limit: nClients, - Total: nClients, - OwnerID: wrongID, - Status: clients.AllStatus, - }, - response: []clients.Client{}, - size: 0, - }, - "retrieve all clients by disabled status": { - pm: clients.Page{ - Offset: 0, - Limit: nClients, - Total: nClients, - Status: clients.DisabledStatus, - }, - response: []clients.Client{expectedClients[0], expectedClients[50], expectedClients[100], expectedClients[150]}, - size: 4, - }, - "retrieve all clients by combined status": { - pm: clients.Page{ - Offset: 0, - Limit: nClients, - Total: nClients, - Status: clients.AllStatus, - }, - response: expectedClients, - size: 200, - }, - "retrieve clients by the wrong status": { - pm: clients.Page{ - Offset: 0, - Limit: nClients, - Total: nClients, - Status: 10, - }, - response: []clients.Client{}, - size: 0, - }, - "retrieve all clients by tags": { - pm: clients.Page{ - Offset: 0, - Limit: nClients, - Total: nClients, - Tag: "Test", - Status: clients.AllStatus, - }, - response: []clients.Client{expectedClients[0], expectedClients[10], expectedClients[20], expectedClients[30], expectedClients[40], expectedClients[50], expectedClients[60], - expectedClients[70], expectedClients[80], expectedClients[90], expectedClients[100], expectedClients[110], expectedClients[120], expectedClients[130], - expectedClients[140], expectedClients[150], expectedClients[160], expectedClients[170], expectedClients[180], expectedClients[190], - }, - size: 20, - }, - "retrieve clients by wrong tags": { - pm: clients.Page{ - Offset: 0, - Limit: nClients, - Total: nClients, - Tag: "wrongTags", - Status: clients.AllStatus, - }, - response: []clients.Client{}, - size: 0, - }, - "retrieve all clients by sharedby": { - pm: clients.Page{ - Offset: 0, - Limit: nClients, - Total: nClients, - SharedBy: expectedClients[0].ID, - Status: clients.AllStatus, - Action: "c_list", - }, - response: []clients.Client{expectedClients[10], expectedClients[20], expectedClients[30], expectedClients[40], expectedClients[50], expectedClients[60], - expectedClients[70], expectedClients[80], expectedClients[90], expectedClients[100], expectedClients[110], expectedClients[120], expectedClients[130], - expectedClients[140], expectedClients[150], expectedClients[160], expectedClients[170], expectedClients[180], expectedClients[190], - }, - size: 19, - }, - } - for desc, tc := range cases { - page, err := repo.RetrieveAll(context.Background(), tc.pm) - size := uint64(len(page.Clients)) - assert.ElementsMatch(t, page.Clients, tc.response, fmt.Sprintf("%s: expected %v got %v\n", desc, tc.response, page.Clients)) - assert.Equal(t, tc.size, size, fmt.Sprintf("%s: expected size %d got %d\n", desc, tc.size, size)) - assert.Nil(t, err, fmt.Sprintf("%s: expected no error got %d\n", desc, err)) - } -} - -func TestClientsUpdateMetadata(t *testing.T) { - t.Cleanup(func() { testsutil.CleanUpDB(t, db) }) - postgres.NewDatabase(db, tracer) - repo := cpostgres.NewClientRepo(database) - - client1 := clients.Client{ - ID: testsutil.GenerateUUID(t, idProvider), - Name: "enabled-client", - Credentials: clients.Credentials{ - Identity: "client1-update@example.com", - Secret: password, - }, - Metadata: clients.Metadata{ - "name": "enabled-client", - }, - Tags: []string{"enabled", "tag1"}, - Status: clients.EnabledStatus, - } - - client2 := clients.Client{ - ID: testsutil.GenerateUUID(t, idProvider), - Name: "disabled-client", - Credentials: clients.Credentials{ - Identity: "client2-update@example.com", - Secret: password, - }, - Metadata: clients.Metadata{ - "name": "disabled-client", - }, - Tags: []string{"disabled", "tag1"}, - Status: clients.DisabledStatus, - } - - client1, err := repo.Save(context.Background(), client1) - assert.True(t, errors.Contains(err, nil), fmt.Sprintf("add new client with metadata: expected %v got %s\n", nil, err)) - client2, err = repo.Save(context.Background(), client2) - assert.True(t, errors.Contains(err, nil), fmt.Sprintf("add new disabled client: expected %v got %s\n", nil, err)) - - ucases := []struct { - desc string - update string - client clients.Client - err error - }{ - { - desc: "update metadata for enabled client", - update: "metadata", - client: clients.Client{ - ID: client1.ID, - Metadata: clients.Metadata{ - "update": "metadata", - }, - }, - err: nil, - }, - { - desc: "update metadata for disabled client", - update: "metadata", - client: clients.Client{ - ID: client2.ID, - Metadata: clients.Metadata{ - "update": "metadata", - }, - }, - err: errors.ErrNotFound, - }, - { - desc: "update name for enabled client", - update: "name", - client: clients.Client{ - ID: client1.ID, - Name: "updated name", - }, - err: nil, - }, - { - desc: "update name for disabled client", - update: "name", - client: clients.Client{ - ID: client2.ID, - Name: "updated name", - }, - err: errors.ErrNotFound, - }, - { - desc: "update name and metadata for enabled client", - update: "both", - client: clients.Client{ - ID: client1.ID, - Name: "updated name and metadata", - Metadata: clients.Metadata{ - "update": "name and metadata", - }, - }, - err: nil, - }, - { - desc: "update name and metadata for a disabled client", - update: "both", - client: clients.Client{ - ID: client2.ID, - Name: "updated name and metadata", - Metadata: clients.Metadata{ - "update": "name and metadata", - }, - }, - err: errors.ErrNotFound, - }, - { - desc: "update metadata for invalid client", - update: "metadata", - client: clients.Client{ - ID: wrongID, - Metadata: clients.Metadata{ - "update": "metadata", - }, - }, - err: errors.ErrNotFound, - }, - { - desc: "update name for invalid client", - update: "name", - client: clients.Client{ - ID: wrongID, - Name: "updated name", - }, - err: errors.ErrNotFound, - }, - { - desc: "update name and metadata for invalid client", - update: "both", - client: clients.Client{ - ID: client2.ID, - Name: "updated name and metadata", - Metadata: clients.Metadata{ - "update": "name and metadata", - }, - }, - err: errors.ErrNotFound, - }, - } - for _, tc := range ucases { - expected, err := repo.Update(context.Background(), tc.client) - assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) - if err == nil { - if tc.client.Name != "" { - assert.Equal(t, expected.Name, tc.client.Name, fmt.Sprintf("%s: expected %v got %v\n", tc.desc, expected.Name, tc.client.Name)) - } - if tc.client.Metadata != nil { - assert.Equal(t, expected.Metadata, tc.client.Metadata, fmt.Sprintf("%s: expected %v got %v\n", tc.desc, expected.Metadata, tc.client.Metadata)) - } - - } - } -} - -func TestClientsUpdateTags(t *testing.T) { - t.Cleanup(func() { testsutil.CleanUpDB(t, db) }) - postgres.NewDatabase(db, tracer) - repo := cpostgres.NewClientRepo(database) - - client1 := clients.Client{ - ID: testsutil.GenerateUUID(t, idProvider), - Name: "enabled-client-with-tags", - Credentials: clients.Credentials{ - Identity: "client1-update-tags@example.com", - Secret: password, - }, - Tags: []string{"test", "enabled"}, - Status: clients.EnabledStatus, - } - client2 := clients.Client{ - ID: testsutil.GenerateUUID(t, idProvider), - Name: "disabled-client-with-tags", - Credentials: clients.Credentials{ - Identity: "client2-update-tags@example.com", - Secret: password, - }, - Tags: []string{"test", "disabled"}, - Status: clients.DisabledStatus, - } - - client1, err := repo.Save(context.Background(), client1) - assert.True(t, errors.Contains(err, nil), fmt.Sprintf("add new client with tags: expected %v got %s\n", nil, err)) - if err == nil { - assert.Equal(t, client1.ID, client1.ID, fmt.Sprintf("add new client with tags: expected %v got %s\n", nil, err)) - } - client2, err = repo.Save(context.Background(), client2) - assert.True(t, errors.Contains(err, nil), fmt.Sprintf("add new disabled client with tags: expected %v got %s\n", nil, err)) - if err == nil { - assert.Equal(t, client2.ID, client2.ID, fmt.Sprintf("add new disabled client with tags: expected %v got %s\n", nil, err)) - } - ucases := []struct { - desc string - client clients.Client - err error - }{ - { - desc: "update tags for enabled client", - client: clients.Client{ - ID: client1.ID, - Tags: []string{"updated"}, - }, - err: nil, - }, - { - desc: "update tags for disabled client", - client: clients.Client{ - ID: client2.ID, - Tags: []string{"updated"}, - }, - err: errors.ErrNotFound, - }, - { - desc: "update tags for invalid client", - client: clients.Client{ - ID: wrongID, - Tags: []string{"updated"}, - }, - err: errors.ErrNotFound, - }, - } - for _, tc := range ucases { - expected, err := repo.UpdateTags(context.Background(), tc.client) - assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) - if err == nil { - assert.Equal(t, tc.client.Tags, expected.Tags, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.client.Tags, expected.Tags)) - } - } -} - -func TestClientsUpdateSecret(t *testing.T) { - t.Cleanup(func() { testsutil.CleanUpDB(t, db) }) - postgres.NewDatabase(db, tracer) - repo := cpostgres.NewClientRepo(database) - - client1 := clients.Client{ - ID: testsutil.GenerateUUID(t, idProvider), - Name: "enabled-client", - Credentials: clients.Credentials{ - Identity: "client1-update@example.com", - Secret: password, - }, - Status: clients.EnabledStatus, - } - client2 := clients.Client{ - ID: testsutil.GenerateUUID(t, idProvider), - Name: "disabled-client", - Credentials: clients.Credentials{ - Identity: "client2-update@example.com", - Secret: password, - }, - Status: clients.DisabledStatus, - } - - rClient1, err := repo.Save(context.Background(), client1) - assert.True(t, errors.Contains(err, nil), fmt.Sprintf("add new client: expected %v got %s\n", nil, err)) - if err == nil { - assert.Equal(t, client1.ID, rClient1.ID, fmt.Sprintf("add new client: expected %v got %s\n", nil, err)) - } - rClient2, err := repo.Save(context.Background(), client2) - assert.True(t, errors.Contains(err, nil), fmt.Sprintf("add new disabled client: expected %v got %s\n", nil, err)) - if err == nil { - assert.Equal(t, client2.ID, rClient2.ID, fmt.Sprintf("add new disabled client: expected %v got %s\n", nil, err)) - } - - ucases := []struct { - desc string - client clients.Client - err error - }{ - { - desc: "update secret for enabled client", - client: clients.Client{ - ID: client1.ID, - Credentials: clients.Credentials{ - Identity: "client1-update@example.com", - Secret: "newpassword", - }, - }, - err: nil, - }, - { - desc: "update secret for disabled client", - client: clients.Client{ - ID: client2.ID, - Credentials: clients.Credentials{ - Identity: "client2-update@example.com", - Secret: "newpassword", - }, - }, - err: errors.ErrNotFound, - }, - { - desc: "update secret for invalid client", - client: clients.Client{ - ID: wrongID, - Credentials: clients.Credentials{ - Identity: "client3-update@example.com", - Secret: "newpassword", - }, - }, - err: errors.ErrNotFound, - }, - } - for _, tc := range ucases { - _, err := repo.UpdateSecret(context.Background(), tc.client) - assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) - if err == nil { - c, err := repo.RetrieveByIdentity(context.Background(), tc.client.Credentials.Identity) - require.Nil(t, err, fmt.Sprintf("retrieve client by id during update of secret unexpected error: %s", err)) - assert.Equal(t, tc.client.Credentials.Secret, c.Credentials.Secret, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.client.Credentials.Secret, c.Credentials.Secret)) - } - } -} - -func TestClientsUpdateIdentity(t *testing.T) { - t.Cleanup(func() { testsutil.CleanUpDB(t, db) }) - postgres.NewDatabase(db, tracer) - repo := cpostgres.NewClientRepo(database) - - client1 := clients.Client{ - ID: testsutil.GenerateUUID(t, idProvider), - Name: "enabled-client", - Credentials: clients.Credentials{ - Identity: "client1-update@example.com", - Secret: password, - }, - Status: clients.EnabledStatus, - } - client2 := clients.Client{ - ID: testsutil.GenerateUUID(t, idProvider), - Name: "disabled-client", - Credentials: clients.Credentials{ - Identity: "client2-update@example.com", - Secret: password, - }, - Status: clients.DisabledStatus, - } - - rClient1, err := repo.Save(context.Background(), client1) - assert.True(t, errors.Contains(err, nil), fmt.Sprintf("add new client: expected %v got %s\n", nil, err)) - if err == nil { - assert.Equal(t, client1.ID, rClient1.ID, fmt.Sprintf("add new client: expected %v got %s\n", nil, err)) - } - rClient2, err := repo.Save(context.Background(), client2) - assert.True(t, errors.Contains(err, nil), fmt.Sprintf("add new disabled client: expected %v got %s\n", nil, err)) - if err == nil { - assert.Equal(t, client2.ID, rClient2.ID, fmt.Sprintf("add new disabled client: expected %v got %s\n", nil, err)) - } - - ucases := []struct { - desc string - client clients.Client - err error - }{ - { - desc: "update identity for enabled client", - client: clients.Client{ - ID: client1.ID, - Credentials: clients.Credentials{ - Identity: "client1-updated@example.com", - }, - }, - err: nil, - }, - { - desc: "update identity for disabled client", - client: clients.Client{ - ID: client2.ID, - Credentials: clients.Credentials{ - Identity: "client2-updated@example.com", - }, - }, - err: errors.ErrNotFound, - }, - { - desc: "update identity for invalid client", - client: clients.Client{ - ID: wrongID, - Credentials: clients.Credentials{ - Identity: "client3-updated@example.com", - }, - }, - err: errors.ErrNotFound, - }, - } - for _, tc := range ucases { - expected, err := repo.UpdateIdentity(context.Background(), tc.client) - assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) - if err == nil { - assert.Equal(t, tc.client.Credentials.Identity, expected.Credentials.Identity, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.client.Credentials.Identity, expected.Credentials.Identity)) - } - } -} - -func TestClientsUpdateOwner(t *testing.T) { - t.Cleanup(func() { testsutil.CleanUpDB(t, db) }) - postgres.NewDatabase(db, tracer) - repo := cpostgres.NewClientRepo(database) - - client1 := clients.Client{ - ID: testsutil.GenerateUUID(t, idProvider), - Name: "enabled-client-with-owner", - Credentials: clients.Credentials{ - Identity: "client1-update-owner@example.com", - Secret: password, - }, - Owner: testsutil.GenerateUUID(t, idProvider), - Status: clients.EnabledStatus, - } - client2 := clients.Client{ - ID: testsutil.GenerateUUID(t, idProvider), - Name: "disabled-client-with-owner", - Credentials: clients.Credentials{ - Identity: "client2-update-owner@example.com", - Secret: password, - }, - Owner: testsutil.GenerateUUID(t, idProvider), - Status: clients.DisabledStatus, - } - - client1, err := repo.Save(context.Background(), client1) - assert.True(t, errors.Contains(err, nil), fmt.Sprintf("add new client with owner: expected %v got %s\n", nil, err)) - if err == nil { - assert.Equal(t, client1.ID, client1.ID, fmt.Sprintf("add new client with owner: expected %v got %s\n", nil, err)) - } - client2, err = repo.Save(context.Background(), client2) - assert.True(t, errors.Contains(err, nil), fmt.Sprintf("add new disabled client with owner: expected %v got %s\n", nil, err)) - if err == nil { - assert.Equal(t, client2.ID, client2.ID, fmt.Sprintf("add new disabled client with owner: expected %v got %s\n", nil, err)) - } - ucases := []struct { - desc string - client clients.Client - err error - }{ - { - desc: "update owner for enabled client", - client: clients.Client{ - ID: client1.ID, - Owner: testsutil.GenerateUUID(t, idProvider), - }, - err: nil, - }, - { - desc: "update owner for disabled client", - client: clients.Client{ - ID: client2.ID, - Owner: testsutil.GenerateUUID(t, idProvider), - }, - err: errors.ErrNotFound, - }, - { - desc: "update owner for invalid client", - client: clients.Client{ - ID: wrongID, - Owner: testsutil.GenerateUUID(t, idProvider), - }, - err: errors.ErrNotFound, - }, - } - for _, tc := range ucases { - expected, err := repo.UpdateOwner(context.Background(), tc.client) - assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) - if err == nil { - assert.Equal(t, tc.client.Owner, expected.Owner, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.client.Owner, expected.Owner)) - } - } -} - -func TestClientsChangeStatus(t *testing.T) { - t.Cleanup(func() { testsutil.CleanUpDB(t, db) }) - postgres.NewDatabase(db, tracer) - repo := cpostgres.NewClientRepo(database) - - client1 := clients.Client{ - ID: testsutil.GenerateUUID(t, idProvider), - Name: "enabled-client", - Credentials: clients.Credentials{ - Identity: "client1-update@example.com", - Secret: password, - }, - Status: clients.EnabledStatus, - } - - client1, err := repo.Save(context.Background(), client1) - assert.True(t, errors.Contains(err, nil), fmt.Sprintf("add new client: expected %v got %s\n", nil, err)) - - ucases := []struct { - desc string - client clients.Client - err error - }{ - { - desc: "change client status for an enabled client", - client: clients.Client{ - ID: client1.ID, - Status: 0, - }, - err: nil, - }, - { - desc: "change client status for a disabled client", - client: clients.Client{ - ID: client1.ID, - Status: 1, - }, - err: nil, - }, - { - desc: "change client status for non-existing client", - client: clients.Client{ - ID: "invalid", - Status: 2, - }, - err: errors.ErrNotFound, - }, - } - - for _, tc := range ucases { - expected, err := repo.ChangeStatus(context.Background(), tc.client.ID, tc.client.Status) - assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) - if err == nil { - assert.Equal(t, tc.client.Status, expected.Status, fmt.Sprintf("%s: expected %d got %d\n", tc.desc, tc.client.Status, expected.Status)) - } - } -} diff --git a/clients/clients/postgres/doc.go b/clients/clients/postgres/doc.go deleted file mode 100644 index bf560bea285..00000000000 --- a/clients/clients/postgres/doc.go +++ /dev/null @@ -1 +0,0 @@ -package postgres diff --git a/clients/clients/postgres/setup_test.go b/clients/clients/postgres/setup_test.go deleted file mode 100644 index 9cb273dbc56..00000000000 --- a/clients/clients/postgres/setup_test.go +++ /dev/null @@ -1,90 +0,0 @@ -// Package postgres_test contains tests for PostgreSQL repository -// implementations. -package postgres_test - -import ( - "database/sql" - "fmt" - "log" - "os" - "testing" - "time" - - "github.com/jmoiron/sqlx" - "github.com/mainflux/mainflux/clients/postgres" - dockertest "github.com/ory/dockertest/v3" - "github.com/ory/dockertest/v3/docker" - "go.opentelemetry.io/otel" -) - -var ( - db *sqlx.DB - database postgres.Database - tracer = otel.Tracer("repo_tests") -) - -func TestMain(m *testing.M) { - pool, err := dockertest.NewPool("") - if err != nil { - log.Fatalf("Could not connect to docker: %s", err) - } - - container, err := pool.RunWithOptions(&dockertest.RunOptions{ - Repository: "postgres", - Tag: "15.1-alpine", - Env: []string{ - "POSTGRES_USER=test", - "POSTGRES_PASSWORD=test", - "POSTGRES_DB=test", - "listen_addresses = '*'", - }, - }, func(config *docker.HostConfig) { - config.AutoRemove = true - config.RestartPolicy = docker.RestartPolicy{Name: "no"} - }) - if err != nil { - log.Fatalf("Could not start container: %s", err) - } - - port := container.GetPort("5432/tcp") - - // exponential backoff-retry, because the application in the container might not be ready to accept connections yet - pool.MaxWait = 120 * time.Second - if err := pool.Retry(func() error { - url := fmt.Sprintf("host=localhost port=%s user=test dbname=test password=test sslmode=disable", port) - db, err := sql.Open("pgx", url) - if err != nil { - return err - } - return db.Ping() - }); err != nil { - log.Fatalf("Could not connect to docker: %s", err) - } - - dbConfig := postgres.Config{ - Host: "localhost", - Port: port, - User: "test", - Pass: "test", - Name: "test", - SSLMode: "disable", - SSLCert: "", - SSLKey: "", - SSLRootCert: "", - } - - if db, err = postgres.Connect(dbConfig); err != nil { - log.Fatalf("Could not setup test DB connection: %s", err) - } - database = postgres.NewDatabase(db, tracer) - - code := m.Run() - - // Defers will not be run when using os.Exit - db.Close() - if err := pool.Purge(container); err != nil { - log.Fatalf("Could not purge container: %s", err) - } - - os.Exit(code) -} diff --git a/clients/clients/service.go b/clients/clients/service.go deleted file mode 100644 index b7643bbb32f..00000000000 --- a/clients/clients/service.go +++ /dev/null @@ -1,316 +0,0 @@ -package clients - -import ( - "context" - "time" - - "github.com/mainflux/mainflux" - "github.com/mainflux/mainflux/clients/jwt" - "github.com/mainflux/mainflux/clients/policies" - "github.com/mainflux/mainflux/internal/apiutil" - "github.com/mainflux/mainflux/pkg/errors" -) - -const MyKey = "mine" - -var ( - // ErrInvalidStatus indicates invalid status. - ErrInvalidStatus = errors.New("invalid client status") - - // ErrEnableClient indicates error in enabling client. - ErrEnableClient = errors.New("failed to enable client") - - // ErrDisableClient indicates error in disabling client. - ErrDisableClient = errors.New("failed to disable client") - - // ErrStatusAlreadyAssigned indicated that the client or group has already been assigned the status. - ErrStatusAlreadyAssigned = errors.New("status already assigned") -) - -// Service unites Clients and Group services. -type Service interface { - ClientService - jwt.TokenService -} - -type service struct { - clients ClientRepository - policies policies.PolicyRepository - idProvider mainflux.IDProvider - hasher Hasher - tokens jwt.TokenRepository -} - -// NewService returns a new Clients service implementation. -func NewService(c ClientRepository, p policies.PolicyRepository, t jwt.TokenRepository, h Hasher, idp mainflux.IDProvider) Service { - return service{ - clients: c, - policies: p, - hasher: h, - tokens: t, - idProvider: idp, - } -} - -func (svc service) RegisterClient(ctx context.Context, token string, cli Client) (Client, error) { - clientID, err := svc.idProvider.ID() - if err != nil { - return Client{}, err - } - - // We don't check the error currently since we can register client with empty token - ownerID, _ := svc.identify(ctx, token) - if ownerID != "" && cli.Owner == "" { - cli.Owner = ownerID - } - if cli.Credentials.Secret == "" { - return Client{}, apiutil.ErrMissingSecret - } - hash, err := svc.hasher.Hash(cli.Credentials.Secret) - if err != nil { - return Client{}, errors.Wrap(errors.ErrMalformedEntity, err) - } - cli.Credentials.Secret = hash - if cli.Status != DisabledStatus && cli.Status != EnabledStatus { - return Client{}, apiutil.ErrInvalidStatus - } - - cli.ID = clientID - cli.CreatedAt = time.Now() - cli.UpdatedAt = cli.CreatedAt - - return svc.clients.Save(ctx, cli) -} - -func (svc service) IssueToken(ctx context.Context, identity, secret string) (jwt.Token, error) { - dbUser, err := svc.clients.RetrieveByIdentity(ctx, identity) - if err != nil { - return jwt.Token{}, errors.Wrap(errors.ErrAuthentication, err) - } - if err := svc.hasher.Compare(secret, dbUser.Credentials.Secret); err != nil { - return jwt.Token{}, errors.Wrap(errors.ErrAuthentication, err) - } - - claims := jwt.Claims{ - ClientID: dbUser.ID, - } - - return svc.tokens.Issue(ctx, claims) -} - -func (svc service) RefreshToken(ctx context.Context, accessToken string) (jwt.Token, error) { - claims, err := svc.tokens.Parse(ctx, accessToken) - if err != nil { - return jwt.Token{}, errors.Wrap(errors.ErrAuthentication, err) - } - if claims.Type != jwt.RefreshToken { - return jwt.Token{}, errors.Wrap(errors.ErrAuthentication, err) - } - if _, err := svc.clients.RetrieveByID(ctx, claims.ClientID); err != nil { - return jwt.Token{}, errors.Wrap(errors.ErrAuthentication, err) - } - - return svc.tokens.Issue(ctx, claims) -} - -func (svc service) ViewClient(ctx context.Context, token string, id string) (Client, error) { - subject, err := svc.identify(ctx, token) - if err != nil { - return Client{}, err - } - if subject == id { - return svc.clients.RetrieveByID(ctx, id) - } - - if err := svc.policies.Evaluate(ctx, "client", policies.Policy{Subject: subject, Object: id, Actions: []string{"c_list"}}); err != nil { - return Client{}, err - } - - return svc.clients.RetrieveByID(ctx, id) -} - -func (svc service) ListClients(ctx context.Context, token string, pm Page) (ClientsPage, error) { - id, err := svc.identify(ctx, token) - if err != nil { - return ClientsPage{}, err - } - if pm.SharedBy == MyKey { - pm.SharedBy = id - } - if pm.OwnerID == MyKey { - pm.OwnerID = id - } - pm.Action = "c_list" - clients, err := svc.clients.RetrieveAll(ctx, pm) - if err != nil { - return ClientsPage{}, err - } - for i, client := range clients.Clients { - if client.ID == id { - clients.Clients = append(clients.Clients[:i], clients.Clients[i+1:]...) - if clients.Total != 0 { - clients.Total = clients.Total - 1 - } - } - } - - return clients, nil -} - -func (svc service) UpdateClient(ctx context.Context, token string, cli Client) (Client, error) { - if err := svc.authorize(ctx, "client", policies.Policy{Subject: token, Object: cli.ID, Actions: []string{"c_update"}}); err != nil { - return Client{}, err - } - - client := Client{ - ID: cli.ID, - Name: cli.Name, - Metadata: cli.Metadata, - UpdatedAt: time.Now(), - } - - return svc.clients.Update(ctx, client) -} - -func (svc service) UpdateClientTags(ctx context.Context, token string, cli Client) (Client, error) { - if err := svc.authorize(ctx, "client", policies.Policy{Subject: token, Object: cli.ID, Actions: []string{"c_update"}}); err != nil { - return Client{}, err - } - - client := Client{ - ID: cli.ID, - Tags: cli.Tags, - UpdatedAt: time.Now(), - } - - return svc.clients.UpdateTags(ctx, client) -} - -func (svc service) UpdateClientIdentity(ctx context.Context, token, id, identity string) (Client, error) { - if err := svc.authorize(ctx, "client", policies.Policy{Subject: token, Object: id, Actions: []string{"c_update"}}); err != nil { - return Client{}, err - } - - cli := Client{ - ID: id, - Credentials: Credentials{ - Identity: identity, - }, - } - return svc.clients.UpdateIdentity(ctx, cli) -} - -func (svc service) UpdateClientSecret(ctx context.Context, token, oldSecret, newSecret string) (Client, error) { - id, err := svc.identify(ctx, token) - if err != nil { - return Client{}, err - } - dbClient, err := svc.clients.RetrieveByID(ctx, id) - if err != nil { - return Client{}, err - } - if err := svc.hasher.Compare(oldSecret, dbClient.Credentials.Secret); err != nil { - return Client{}, errors.Wrap(errors.ErrAuthentication, err) - } - - c := Client{ - Credentials: Credentials{ - Identity: dbClient.Credentials.Identity, - Secret: oldSecret, - }, - } - if _, err := svc.IssueToken(ctx, c.Credentials.Identity, c.Credentials.Secret); err != nil { - return Client{}, errors.ErrAuthentication - } - newSecret, err = svc.hasher.Hash(newSecret) - if err != nil { - return Client{}, err - } - dbClient.Credentials.Secret = newSecret - return svc.clients.UpdateSecret(ctx, dbClient) -} - -func (svc service) UpdateClientOwner(ctx context.Context, token string, cli Client) (Client, error) { - if err := svc.authorize(ctx, "client", policies.Policy{Subject: token, Object: cli.ID, Actions: []string{"c_update"}}); err != nil { - return Client{}, err - } - - client := Client{ - ID: cli.ID, - Owner: cli.Owner, - UpdatedAt: time.Now(), - } - - return svc.clients.UpdateOwner(ctx, client) -} - -func (svc service) EnableClient(ctx context.Context, token, id string) (Client, error) { - if err := svc.authorize(ctx, "client", policies.Policy{Subject: token, Object: id, Actions: []string{"c_delete"}}); err != nil { - return Client{}, err - } - client, err := svc.changeClientStatus(ctx, id, EnabledStatus) - if err != nil { - return Client{}, errors.Wrap(ErrEnableClient, err) - } - - return client, nil -} - -func (svc service) DisableClient(ctx context.Context, token, id string) (Client, error) { - if err := svc.authorize(ctx, "client", policies.Policy{Subject: token, Object: id, Actions: []string{"c_delete"}}); err != nil { - return Client{}, err - } - client, err := svc.changeClientStatus(ctx, id, DisabledStatus) - if err != nil { - return Client{}, errors.Wrap(ErrDisableClient, err) - } - - return client, nil -} - -func (svc service) ListMembers(ctx context.Context, token, groupID string, pm Page) (MembersPage, error) { - id, err := svc.identify(ctx, token) - if err != nil { - return MembersPage{}, err - } - pm.Subject = id - pm.Action = "g_list" - - return svc.clients.Members(ctx, groupID, pm) -} - -func (svc service) changeClientStatus(ctx context.Context, id string, status Status) (Client, error) { - dbClient, err := svc.clients.RetrieveByID(ctx, id) - if err != nil { - return Client{}, err - } - if dbClient.Status == status { - return Client{}, ErrStatusAlreadyAssigned - } - - return svc.clients.ChangeStatus(ctx, id, status) -} - -func (svc service) authorize(ctx context.Context, entityType string, p policies.Policy) error { - if err := p.Validate(); err != nil { - return err - } - id, err := svc.identify(ctx, p.Subject) - if err != nil { - return err - } - p.Subject = id - return svc.policies.Evaluate(ctx, entityType, p) -} - -func (svc service) identify(ctx context.Context, tkn string) (string, error) { - claims, err := svc.tokens.Parse(ctx, tkn) - if err != nil { - return "", errors.Wrap(errors.ErrAuthentication, err) - } - if claims.Type != jwt.AccessToken { - return "", errors.ErrAuthentication - } - - return claims.ClientID, nil -} diff --git a/clients/clients/service_test.go b/clients/clients/service_test.go deleted file mode 100644 index 0c42f043569..00000000000 --- a/clients/clients/service_test.go +++ /dev/null @@ -1,1361 +0,0 @@ -package clients_test - -import ( - context "context" - fmt "fmt" - "testing" - "time" - - "github.com/mainflux/mainflux/clients/clients" - "github.com/mainflux/mainflux/clients/clients/mocks" - cmocks "github.com/mainflux/mainflux/clients/clients/mocks" - "github.com/mainflux/mainflux/clients/hasher" - "github.com/mainflux/mainflux/clients/jwt" - pmocks "github.com/mainflux/mainflux/clients/policies/mocks" - "github.com/mainflux/mainflux/internal/apiutil" - "github.com/mainflux/mainflux/internal/testsutil" - "github.com/mainflux/mainflux/pkg/errors" - "github.com/mainflux/mainflux/pkg/uuid" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" - "github.com/stretchr/testify/require" -) - -var ( - idProvider = uuid.New() - phasher = hasher.New() - secret = "strongsecret" - validCMetadata = clients.Metadata{"role": "client"} - client = clients.Client{ - ID: testsutil.GenerateUUID(&testing.T{}, idProvider), - Name: "clientname", - Tags: []string{"tag1", "tag2"}, - Credentials: clients.Credentials{Identity: "clientidentity", Secret: secret}, - Metadata: validCMetadata, - Status: clients.EnabledStatus, - } - inValidToken = "invalidToken" - withinDuration = 5 * time.Second -) - -func generateValidToken(t *testing.T, clientID string, svc clients.Service, cRepo *mocks.ClientRepository) string { - client := clients.Client{ - ID: clientID, - Name: "validtoken", - Credentials: clients.Credentials{ - Identity: "validtoken", - Secret: secret, - }, - Status: clients.EnabledStatus, - } - rClient := client - rClient.Credentials.Secret, _ = phasher.Hash(client.Credentials.Secret) - - repoCall := cRepo.On("RetrieveByIdentity", context.Background(), mock.Anything).Return(rClient, nil) - token, err := svc.IssueToken(context.Background(), client.Credentials.Identity, client.Credentials.Secret) - assert.True(t, errors.Contains(err, nil), fmt.Sprintf("Create token expected nil got %s\n", err)) - repoCall.Unset() - return token.AccessToken -} - -func TestRegisterClient(t *testing.T) { - cRepo := new(cmocks.ClientRepository) - pRepo := new(pmocks.PolicyRepository) - tokenizer := jwt.NewTokenRepo([]byte(secret)) - svc := clients.NewService(cRepo, pRepo, tokenizer, phasher, idProvider) - - cases := []struct { - desc string - client clients.Client - token string - err error - }{ - { - desc: "register new client", - client: client, - token: generateValidToken(t, testsutil.GenerateUUID(t, idProvider), svc, cRepo), - err: nil, - }, - { - desc: "register existing client", - client: client, - token: generateValidToken(t, testsutil.GenerateUUID(t, idProvider), svc, cRepo), - err: errors.ErrConflict, - }, - { - desc: "register a new enabled client with name", - client: clients.Client{ - Name: "clientWithName", - Credentials: clients.Credentials{ - Identity: "newclientwithname@example.com", - Secret: secret, - }, - Status: clients.EnabledStatus, - }, - err: nil, - token: generateValidToken(t, testsutil.GenerateUUID(t, idProvider), svc, cRepo), - }, - { - desc: "register a new disabled client with name", - client: clients.Client{ - Name: "clientWithName", - Credentials: clients.Credentials{ - Identity: "newclientwithname@example.com", - Secret: secret, - }, - }, - err: nil, - token: generateValidToken(t, testsutil.GenerateUUID(t, idProvider), svc, cRepo), - }, - { - desc: "register a new enabled client with tags", - client: clients.Client{ - Tags: []string{"tag1", "tag2"}, - Credentials: clients.Credentials{ - Identity: "newclientwithtags@example.com", - Secret: secret, - }, - Status: clients.EnabledStatus, - }, - err: nil, - token: generateValidToken(t, testsutil.GenerateUUID(t, idProvider), svc, cRepo), - }, - { - desc: "register a new disabled client with tags", - client: clients.Client{ - Tags: []string{"tag1", "tag2"}, - Credentials: clients.Credentials{ - Identity: "newclientwithtags@example.com", - Secret: secret, - }, - Status: clients.DisabledStatus, - }, - err: nil, - token: generateValidToken(t, testsutil.GenerateUUID(t, idProvider), svc, cRepo), - }, - { - desc: "register a new enabled client with metadata", - client: clients.Client{ - Credentials: clients.Credentials{ - Identity: "newclientwithmetadata@example.com", - Secret: secret, - }, - Metadata: validCMetadata, - Status: clients.EnabledStatus, - }, - err: nil, - token: generateValidToken(t, testsutil.GenerateUUID(t, idProvider), svc, cRepo), - }, - { - desc: "register a new disabled client with metadata", - client: clients.Client{ - Credentials: clients.Credentials{ - Identity: "newclientwithmetadata@example.com", - Secret: secret, - }, - Metadata: validCMetadata, - }, - err: nil, - token: generateValidToken(t, testsutil.GenerateUUID(t, idProvider), svc, cRepo), - }, - { - desc: "register a new disabled client", - client: clients.Client{ - Credentials: clients.Credentials{ - Identity: "newclientwithvalidstatus@example.com", - Secret: secret, - }, - }, - err: nil, - token: generateValidToken(t, testsutil.GenerateUUID(t, idProvider), svc, cRepo), - }, - { - desc: "register a new client with valid disabled status", - client: clients.Client{ - Credentials: clients.Credentials{ - Identity: "newclientwithvalidstatus@example.com", - Secret: secret, - }, - Status: clients.DisabledStatus, - }, - err: nil, - token: generateValidToken(t, testsutil.GenerateUUID(t, idProvider), svc, cRepo), - }, - { - desc: "register a new client with all fields", - client: clients.Client{ - Name: "newclientwithallfields", - Tags: []string{"tag1", "tag2"}, - Credentials: clients.Credentials{ - Identity: "newclientwithallfields@example.com", - Secret: secret, - }, - Metadata: clients.Metadata{ - "name": "newclientwithallfields", - }, - Status: clients.EnabledStatus, - }, - err: nil, - token: generateValidToken(t, testsutil.GenerateUUID(t, idProvider), svc, cRepo), - }, - { - desc: "register a new client with missing identity", - client: clients.Client{ - Name: "clientWithMissingIdentity", - Credentials: clients.Credentials{ - Secret: secret, - }, - }, - err: errors.ErrMalformedEntity, - token: generateValidToken(t, testsutil.GenerateUUID(t, idProvider), svc, cRepo), - }, - { - desc: "register a new client with invalid owner", - client: clients.Client{ - Owner: mocks.WrongID, - Credentials: clients.Credentials{ - Identity: "newclientwithinvalidowner@example.com", - Secret: secret, - }, - }, - err: errors.ErrMalformedEntity, - token: generateValidToken(t, testsutil.GenerateUUID(t, idProvider), svc, cRepo), - }, - { - desc: "register a new client with empty secret", - client: clients.Client{ - Owner: testsutil.GenerateUUID(t, idProvider), - Credentials: clients.Credentials{ - Identity: "newclientwithemptysecret@example.com", - }, - }, - err: apiutil.ErrMissingSecret, - token: generateValidToken(t, testsutil.GenerateUUID(t, idProvider), svc, cRepo), - }, - { - desc: "register a new client with invalid status", - client: clients.Client{ - Credentials: clients.Credentials{ - Identity: "newclientwithinvalidstatus@example.com", - Secret: secret, - }, - Status: clients.AllStatus, - }, - err: apiutil.ErrInvalidStatus, - token: generateValidToken(t, testsutil.GenerateUUID(t, idProvider), svc, cRepo), - }, - } - - for _, tc := range cases { - repoCall := cRepo.On("Save", context.Background(), mock.Anything).Return(&clients.Client{}, tc.err) - registerTime := time.Now() - expected, err := svc.RegisterClient(context.Background(), tc.token, tc.client) - assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) - if err == nil { - assert.NotEmpty(t, expected.ID, fmt.Sprintf("%s: expected %s not to be empty\n", tc.desc, expected.ID)) - assert.WithinDuration(t, expected.CreatedAt, registerTime, withinDuration, fmt.Sprintf("%s: expected %v got %v\n", tc.desc, expected.CreatedAt, registerTime)) - tc.client.ID = expected.ID - tc.client.CreatedAt = expected.CreatedAt - tc.client.UpdatedAt = expected.UpdatedAt - tc.client.Credentials.Secret = expected.Credentials.Secret - tc.client.Owner = expected.Owner - assert.Equal(t, tc.client, expected, fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.client, expected)) - } - repoCall.Unset() - } -} - -func TestViewClient(t *testing.T) { - cRepo := new(cmocks.ClientRepository) - pRepo := new(pmocks.PolicyRepository) - tokenizer := jwt.NewTokenRepo([]byte(secret)) - svc := clients.NewService(cRepo, pRepo, tokenizer, phasher, idProvider) - - cases := []struct { - desc string - token string - clientID string - response clients.Client - err error - }{ - { - desc: "view client successfully", - response: client, - token: generateValidToken(t, testsutil.GenerateUUID(t, idProvider), svc, cRepo), - clientID: client.ID, - err: nil, - }, - { - desc: "view client with an invalid token", - response: clients.Client{}, - token: inValidToken, - clientID: "", - err: errors.ErrAuthentication, - }, - { - desc: "view client with valid token and invalid client id", - response: clients.Client{}, - token: generateValidToken(t, testsutil.GenerateUUID(t, idProvider), svc, cRepo), - clientID: mocks.WrongID, - err: errors.ErrNotFound, - }, - { - desc: "view client with an invalid token and invalid client id", - response: clients.Client{}, - token: inValidToken, - clientID: mocks.WrongID, - err: errors.ErrAuthentication, - }, - } - - for _, tc := range cases { - repoCall := pRepo.On("Evaluate", context.Background(), "client", mock.Anything).Return(nil) - repoCall1 := cRepo.On("RetrieveByID", context.Background(), mock.Anything).Return(tc.response, tc.err) - rClient, err := svc.ViewClient(context.Background(), tc.token, tc.clientID) - assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) - assert.Equal(t, tc.response, rClient, fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.response, rClient)) - repoCall.Unset() - repoCall1.Unset() - } -} - -func TestListClients(t *testing.T) { - cRepo := new(cmocks.ClientRepository) - pRepo := new(pmocks.PolicyRepository) - tokenizer := jwt.NewTokenRepo([]byte(secret)) - svc := clients.NewService(cRepo, pRepo, tokenizer, phasher, idProvider) - - var nClients = uint64(200) - var aClients = []clients.Client{} - var OwnerID = testsutil.GenerateUUID(t, idProvider) - for i := uint64(1); i < nClients; i++ { - identity := fmt.Sprintf("TestListClients_%d@example.com", i) - client := clients.Client{ - Name: identity, - Credentials: clients.Credentials{ - Identity: identity, - Secret: "password", - }, - Tags: []string{"tag1", "tag2"}, - Metadata: clients.Metadata{"role": "client"}, - } - if i%50 == 0 { - client.Owner = OwnerID - client.Owner = testsutil.GenerateUUID(t, idProvider) - } - aClients = append(aClients, client) - } - - cases := []struct { - desc string - token string - page clients.Page - response clients.ClientsPage - size uint64 - err error - }{ - { - desc: "list clients with authorized token", - token: generateValidToken(t, testsutil.GenerateUUID(t, idProvider), svc, cRepo), - - page: clients.Page{ - Status: clients.AllStatus, - }, - size: 0, - response: clients.ClientsPage{ - Page: clients.Page{ - Total: 0, - Offset: 0, - Limit: 0, - }, - Clients: []clients.Client{}, - }, - err: nil, - }, - { - desc: "list clients with an invalid token", - token: inValidToken, - page: clients.Page{ - Status: clients.AllStatus, - }, - size: 0, - response: clients.ClientsPage{ - Page: clients.Page{ - Total: 0, - Offset: 0, - Limit: 0, - }, - }, - err: errors.ErrAuthentication, - }, - { - desc: "list clients that are shared with me", - token: generateValidToken(t, testsutil.GenerateUUID(t, idProvider), svc, cRepo), - page: clients.Page{ - Offset: 6, - Limit: nClients, - SharedBy: clients.MyKey, - Status: clients.EnabledStatus, - }, - response: clients.ClientsPage{ - Page: clients.Page{ - Total: 4, - Offset: 0, - Limit: 0, - }, - Clients: []clients.Client{aClients[0], aClients[50], aClients[100], aClients[150]}, - }, - size: 4, - }, - { - desc: "list clients that are shared with me with a specific name", - token: generateValidToken(t, testsutil.GenerateUUID(t, idProvider), svc, cRepo), - page: clients.Page{ - Offset: 6, - Limit: nClients, - SharedBy: clients.MyKey, - Name: "TestListClients3", - Status: clients.EnabledStatus, - }, - response: clients.ClientsPage{ - Page: clients.Page{ - Total: 4, - Offset: 0, - Limit: 0, - }, - Clients: []clients.Client{aClients[0], aClients[50], aClients[100], aClients[150]}, - }, - size: 4, - }, - { - desc: "list clients that are shared with me with an invalid name", - token: generateValidToken(t, testsutil.GenerateUUID(t, idProvider), svc, cRepo), - page: clients.Page{ - Offset: 6, - Limit: nClients, - SharedBy: clients.MyKey, - Name: "notpresentclient", - Status: clients.EnabledStatus, - }, - response: clients.ClientsPage{ - Page: clients.Page{ - Total: 0, - Offset: 0, - Limit: 0, - }, - Clients: []clients.Client{}, - }, - size: 0, - }, - { - desc: "list clients that I own", - token: generateValidToken(t, testsutil.GenerateUUID(t, idProvider), svc, cRepo), - page: clients.Page{ - Offset: 6, - Limit: nClients, - OwnerID: clients.MyKey, - Status: clients.EnabledStatus, - }, - response: clients.ClientsPage{ - Page: clients.Page{ - Total: 4, - Offset: 0, - Limit: 0, - }, - Clients: []clients.Client{aClients[0], aClients[50], aClients[100], aClients[150]}, - }, - size: 4, - }, - { - desc: "list clients that I own with a specific name", - token: generateValidToken(t, testsutil.GenerateUUID(t, idProvider), svc, cRepo), - page: clients.Page{ - Offset: 6, - Limit: nClients, - OwnerID: clients.MyKey, - Name: "TestListClients3", - Status: clients.AllStatus, - }, - response: clients.ClientsPage{ - Page: clients.Page{ - Total: 4, - Offset: 0, - Limit: 0, - }, - Clients: []clients.Client{aClients[0], aClients[50], aClients[100], aClients[150]}, - }, - size: 4, - }, - { - desc: "list clients that I own with an invalid name", - token: generateValidToken(t, testsutil.GenerateUUID(t, idProvider), svc, cRepo), - page: clients.Page{ - Offset: 6, - Limit: nClients, - OwnerID: clients.MyKey, - Name: "notpresentclient", - Status: clients.AllStatus, - }, - response: clients.ClientsPage{ - Page: clients.Page{ - Total: 0, - Offset: 0, - Limit: 0, - }, - Clients: []clients.Client{}, - }, - size: 0, - }, - { - desc: "list clients that I own and are shared with me", - token: generateValidToken(t, testsutil.GenerateUUID(t, idProvider), svc, cRepo), - page: clients.Page{ - Offset: 6, - Limit: nClients, - OwnerID: clients.MyKey, - SharedBy: clients.MyKey, - Status: clients.AllStatus, - }, - response: clients.ClientsPage{ - Page: clients.Page{ - Total: 4, - Offset: 0, - Limit: 0, - }, - Clients: []clients.Client{aClients[0], aClients[50], aClients[100], aClients[150]}, - }, - size: 4, - }, - { - desc: "list clients that I own and are shared with me with a specific name", - token: generateValidToken(t, testsutil.GenerateUUID(t, idProvider), svc, cRepo), - page: clients.Page{ - Offset: 6, - Limit: nClients, - SharedBy: clients.MyKey, - OwnerID: clients.MyKey, - Name: "TestListClients3", - Status: clients.AllStatus, - }, - response: clients.ClientsPage{ - Page: clients.Page{ - Total: 4, - Offset: 0, - Limit: 0, - }, - Clients: []clients.Client{aClients[0], aClients[50], aClients[100], aClients[150]}, - }, - size: 4, - }, - { - desc: "list clients that I own and are shared with me with an invalid name", - token: generateValidToken(t, testsutil.GenerateUUID(t, idProvider), svc, cRepo), - page: clients.Page{ - Offset: 6, - Limit: nClients, - SharedBy: clients.MyKey, - OwnerID: clients.MyKey, - Name: "notpresentclient", - Status: clients.AllStatus, - }, - response: clients.ClientsPage{ - Page: clients.Page{ - Total: 0, - Offset: 0, - Limit: 0, - }, - Clients: []clients.Client{}, - }, - size: 0, - }, - { - desc: "list clients with offset and limit", - token: generateValidToken(t, testsutil.GenerateUUID(t, idProvider), svc, cRepo), - - page: clients.Page{ - Offset: 6, - Limit: nClients, - Status: clients.AllStatus, - }, - response: clients.ClientsPage{ - Page: clients.Page{ - Total: nClients - 6, - Offset: 0, - Limit: 0, - }, - Clients: aClients[6:nClients], - }, - size: nClients - 6, - }, - } - - for _, tc := range cases { - repoCall := pRepo.On("Evaluate", context.Background(), "group", mock.Anything).Return(nil) - repoCall1 := cRepo.On("RetrieveAll", context.Background(), mock.Anything).Return(tc.response, tc.err) - page, err := svc.ListClients(context.Background(), tc.token, tc.page) - assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) - assert.Equal(t, tc.response, page, fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.response, page)) - repoCall.Unset() - repoCall1.Unset() - } -} - -func TestUpdateClient(t *testing.T) { - cRepo := new(cmocks.ClientRepository) - pRepo := new(pmocks.PolicyRepository) - tokenizer := jwt.NewTokenRepo([]byte(secret)) - svc := clients.NewService(cRepo, pRepo, tokenizer, phasher, idProvider) - - client1 := client - client2 := client - client1.Name = "Updated client" - client2.Metadata = clients.Metadata{"role": "test"} - - cases := []struct { - desc string - client clients.Client - response clients.Client - token string - err error - }{ - { - desc: "update client name with valid token", - client: client1, - response: client1, - token: generateValidToken(t, testsutil.GenerateUUID(t, idProvider), svc, cRepo), - err: nil, - }, - { - desc: "update client name with invalid token", - client: client1, - response: clients.Client{}, - token: "non-existent", - err: errors.ErrAuthentication, - }, - { - desc: "update client name with invalid ID", - client: clients.Client{ - ID: mocks.WrongID, - Name: "Updated Client", - }, - response: clients.Client{}, - token: "non-existent", - err: errors.ErrAuthentication, - }, - { - desc: "update client metadata with valid token", - client: client2, - response: client2, - token: generateValidToken(t, testsutil.GenerateUUID(t, idProvider), svc, cRepo), - err: nil, - }, - { - desc: "update client metadata with invalid token", - client: client2, - response: clients.Client{}, - token: "non-existent", - err: errors.ErrAuthentication, - }, - } - - for _, tc := range cases { - repoCall := pRepo.On("Evaluate", context.Background(), "client", mock.Anything).Return(nil) - repoCall1 := cRepo.On("Update", context.Background(), mock.Anything).Return(tc.response, tc.err) - updatedClient, err := svc.UpdateClient(context.Background(), tc.token, tc.client) - assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) - assert.Equal(t, tc.response, updatedClient, fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.response, updatedClient)) - repoCall.Unset() - repoCall1.Unset() - } -} - -func TestUpdateClientTags(t *testing.T) { - cRepo := new(cmocks.ClientRepository) - pRepo := new(pmocks.PolicyRepository) - tokenizer := jwt.NewTokenRepo([]byte(secret)) - svc := clients.NewService(cRepo, pRepo, tokenizer, phasher, idProvider) - - client.Tags = []string{"updated"} - - cases := []struct { - desc string - client clients.Client - response clients.Client - token string - err error - }{ - { - desc: "update client tags with valid token", - client: client, - token: generateValidToken(t, testsutil.GenerateUUID(t, idProvider), svc, cRepo), - response: client, - err: nil, - }, - { - desc: "update client tags with invalid token", - client: client, - token: "non-existent", - response: clients.Client{}, - err: errors.ErrAuthentication, - }, - { - desc: "update client name with invalid ID", - client: clients.Client{ - ID: mocks.WrongID, - Name: "Updated name", - }, - response: clients.Client{}, - token: "non-existent", - err: errors.ErrAuthentication, - }, - } - - for _, tc := range cases { - repoCall := pRepo.On("Evaluate", context.Background(), "client", mock.Anything).Return(nil) - repoCall1 := cRepo.On("UpdateTags", context.Background(), mock.Anything).Return(tc.response, tc.err) - updatedClient, err := svc.UpdateClientTags(context.Background(), tc.token, tc.client) - assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) - assert.Equal(t, tc.response, updatedClient, fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.response, updatedClient)) - repoCall.Unset() - repoCall1.Unset() - } -} - -func TestUpdateClientIdentity(t *testing.T) { - cRepo := new(cmocks.ClientRepository) - pRepo := new(pmocks.PolicyRepository) - tokenizer := jwt.NewTokenRepo([]byte(secret)) - svc := clients.NewService(cRepo, pRepo, tokenizer, phasher, idProvider) - - client2 := client - client2.Credentials.Identity = "updated@example.com" - - cases := []struct { - desc string - identity string - response clients.Client - token string - id string - err error - }{ - { - desc: "update client identity with valid token", - identity: "updated@example.com", - token: generateValidToken(t, testsutil.GenerateUUID(t, idProvider), svc, cRepo), - id: client.ID, - response: client2, - err: nil, - }, - { - desc: "update client identity with invalid id", - identity: "updated@example.com", - token: generateValidToken(t, testsutil.GenerateUUID(t, idProvider), svc, cRepo), - id: mocks.WrongID, - response: clients.Client{}, - err: errors.ErrNotFound, - }, - { - desc: "update client identity with invalid token", - identity: "updated@example.com", - token: "non-existent", - id: client2.ID, - response: clients.Client{}, - err: errors.ErrAuthentication, - }, - } - - for _, tc := range cases { - repoCall := pRepo.On("Evaluate", context.Background(), "client", mock.Anything).Return(nil) - repo1Call := cRepo.On("RetrieveByID", context.Background(), mock.Anything).Return(tc.response, tc.err) - repo2Call := cRepo.On("UpdateIdentity", context.Background(), mock.Anything).Return(tc.response, tc.err) - updatedClient, err := svc.UpdateClientIdentity(context.Background(), tc.token, tc.id, tc.identity) - assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) - assert.Equal(t, tc.response, updatedClient, fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.response, updatedClient)) - repoCall.Unset() - repo1Call.Unset() - repo2Call.Unset() - } -} - -func TestUpdateClientOwner(t *testing.T) { - cRepo := new(cmocks.ClientRepository) - pRepo := new(pmocks.PolicyRepository) - tokenizer := jwt.NewTokenRepo([]byte(secret)) - svc := clients.NewService(cRepo, pRepo, tokenizer, phasher, idProvider) - - client.Owner = "newowner@mail.com" - - cases := []struct { - desc string - client clients.Client - response clients.Client - token string - err error - }{ - { - desc: "update client owner with valid token", - client: client, - token: generateValidToken(t, testsutil.GenerateUUID(t, idProvider), svc, cRepo), - response: client, - err: nil, - }, - { - desc: "update client owner with invalid token", - client: client, - token: "non-existent", - response: clients.Client{}, - err: errors.ErrAuthentication, - }, - { - desc: "update client owner with invalid ID", - client: clients.Client{ - ID: mocks.WrongID, - Owner: "updatedowner@mail.com", - }, - response: clients.Client{}, - token: "non-existent", - err: errors.ErrAuthentication, - }, - } - - for _, tc := range cases { - repoCall := pRepo.On("Evaluate", context.Background(), "client", mock.Anything).Return(nil) - repoCall1 := cRepo.On("UpdateOwner", context.Background(), mock.Anything).Return(tc.response, tc.err) - updatedClient, err := svc.UpdateClientOwner(context.Background(), tc.token, tc.client) - assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) - assert.Equal(t, tc.response, updatedClient, fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.response, updatedClient)) - repoCall.Unset() - repoCall1.Unset() - } -} - -func TestUpdateClientSecret(t *testing.T) { - cRepo := new(cmocks.ClientRepository) - pRepo := new(pmocks.PolicyRepository) - tokenizer := jwt.NewTokenRepo([]byte(secret)) - svc := clients.NewService(cRepo, pRepo, tokenizer, phasher, idProvider) - - rClient := client - rClient.Credentials.Secret, _ = phasher.Hash(client.Credentials.Secret) - - repoCall := cRepo.On("RetrieveByIdentity", context.Background(), mock.Anything).Return(rClient, nil) - token, err := svc.IssueToken(context.Background(), client.Credentials.Identity, client.Credentials.Secret) - assert.Nil(t, err, fmt.Sprintf("Issue token expected nil got %s\n", err)) - repoCall.Unset() - - cases := []struct { - desc string - oldSecret string - newSecret string - token string - response clients.Client - err error - }{ - { - desc: "update client secret with valid token", - oldSecret: client.Credentials.Secret, - newSecret: "newSecret", - token: token.AccessToken, - response: rClient, - err: nil, - }, - { - desc: "update client secret with invalid token", - oldSecret: client.Credentials.Secret, - newSecret: "newPassword", - token: "non-existent", - response: clients.Client{}, - err: errors.ErrAuthentication, - }, - { - desc: "update client secret with wrong old secret", - oldSecret: "oldSecret", - newSecret: "newSecret", - token: token.AccessToken, - response: clients.Client{}, - err: apiutil.ErrInvalidSecret, - }, - } - - for _, tc := range cases { - repoCall1 := cRepo.On("RetrieveByID", context.Background(), mock.Anything).Return(tc.response, tc.err) - repoCall2 := cRepo.On("RetrieveByIdentity", context.Background(), mock.Anything).Return(tc.response, tc.err) - repoCall3 := cRepo.On("UpdateSecret", context.Background(), mock.Anything).Return(tc.response, tc.err) - updatedClient, err := svc.UpdateClientSecret(context.Background(), tc.token, tc.oldSecret, tc.newSecret) - assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) - assert.Equal(t, tc.response, updatedClient, fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.response, updatedClient)) - repoCall1.Unset() - repoCall2.Unset() - repoCall3.Unset() - } -} - -func TestEnableClient(t *testing.T) { - cRepo := new(cmocks.ClientRepository) - pRepo := new(pmocks.PolicyRepository) - tokenizer := jwt.NewTokenRepo([]byte(secret)) - svc := clients.NewService(cRepo, pRepo, tokenizer, phasher, idProvider) - - enabledClient1 := clients.Client{ID: testsutil.GenerateUUID(t, idProvider), Credentials: clients.Credentials{Identity: "client1@example.com", Secret: "password"}, Status: clients.EnabledStatus} - disabledClient1 := clients.Client{ID: testsutil.GenerateUUID(t, idProvider), Credentials: clients.Credentials{Identity: "client3@example.com", Secret: "password"}, Status: clients.DisabledStatus} - endisabledClient1 := disabledClient1 - endisabledClient1.Status = clients.EnabledStatus - - cases := []struct { - desc string - id string - token string - client clients.Client - response clients.Client - err error - }{ - { - desc: "enable disabled client", - id: disabledClient1.ID, - token: generateValidToken(t, testsutil.GenerateUUID(t, idProvider), svc, cRepo), - client: disabledClient1, - response: endisabledClient1, - err: nil, - }, - { - desc: "enable enabled client", - id: enabledClient1.ID, - token: generateValidToken(t, testsutil.GenerateUUID(t, idProvider), svc, cRepo), - client: enabledClient1, - response: enabledClient1, - err: clients.ErrStatusAlreadyAssigned, - }, - { - desc: "enable non-existing client", - id: mocks.WrongID, - token: generateValidToken(t, testsutil.GenerateUUID(t, idProvider), svc, cRepo), - client: clients.Client{}, - response: clients.Client{}, - err: errors.ErrNotFound, - }, - } - - for _, tc := range cases { - repoCall := pRepo.On("Evaluate", context.Background(), "client", mock.Anything).Return(nil) - repoCall1 := cRepo.On("RetrieveByID", context.Background(), mock.Anything).Return(tc.client, tc.err) - repoCall2 := cRepo.On("ChangeStatus", context.Background(), mock.Anything, mock.Anything).Return(tc.response, tc.err) - _, err := svc.EnableClient(context.Background(), tc.token, tc.id) - assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) - repoCall.Unset() - repoCall1.Unset() - repoCall2.Unset() - } - - cases2 := []struct { - desc string - status clients.Status - size uint64 - response clients.ClientsPage - }{ - { - desc: "list enabled clients", - status: clients.EnabledStatus, - size: 2, - response: clients.ClientsPage{ - Page: clients.Page{ - Total: 2, - Offset: 0, - Limit: 100, - }, - Clients: []clients.Client{enabledClient1, endisabledClient1}, - }, - }, - { - desc: "list disabled clients", - status: clients.DisabledStatus, - size: 1, - response: clients.ClientsPage{ - Page: clients.Page{ - Total: 1, - Offset: 0, - Limit: 100, - }, - Clients: []clients.Client{disabledClient1}, - }, - }, - { - desc: "list enabled and disabled clients", - status: clients.AllStatus, - size: 3, - response: clients.ClientsPage{ - Page: clients.Page{ - Total: 3, - Offset: 0, - Limit: 100, - }, - Clients: []clients.Client{enabledClient1, disabledClient1, endisabledClient1}, - }, - }, - } - - for _, tc := range cases2 { - pm := clients.Page{ - Offset: 0, - Limit: 100, - Status: tc.status, - } - repoCall := pRepo.On("Evaluate", context.Background(), "group", mock.Anything).Return(nil) - repoCall1 := cRepo.On("RetrieveAll", context.Background(), mock.Anything).Return(tc.response, nil) - page, err := svc.ListClients(context.Background(), generateValidToken(t, testsutil.GenerateUUID(t, idProvider), svc, cRepo), pm) - require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err)) - size := uint64(len(page.Clients)) - assert.Equal(t, tc.size, size, fmt.Sprintf("%s: expected size %d got %d\n", tc.desc, tc.size, size)) - repoCall.Unset() - repoCall1.Unset() - } -} - -func TestDisableClient(t *testing.T) { - cRepo := new(cmocks.ClientRepository) - pRepo := new(pmocks.PolicyRepository) - tokenizer := jwt.NewTokenRepo([]byte(secret)) - svc := clients.NewService(cRepo, pRepo, tokenizer, phasher, idProvider) - - enabledClient1 := clients.Client{ID: testsutil.GenerateUUID(t, idProvider), Credentials: clients.Credentials{Identity: "client1@example.com", Secret: "password"}, Status: clients.EnabledStatus} - disabledClient1 := clients.Client{ID: testsutil.GenerateUUID(t, idProvider), Credentials: clients.Credentials{Identity: "client3@example.com", Secret: "password"}, Status: clients.DisabledStatus} - disenabledClient1 := enabledClient1 - disenabledClient1.Status = clients.DisabledStatus - - cases := []struct { - desc string - id string - token string - client clients.Client - response clients.Client - err error - }{ - { - desc: "disable enabled client", - id: enabledClient1.ID, - token: generateValidToken(t, testsutil.GenerateUUID(t, idProvider), svc, cRepo), - client: enabledClient1, - response: disenabledClient1, - err: nil, - }, - { - desc: "disable disabled client", - id: disabledClient1.ID, - token: generateValidToken(t, testsutil.GenerateUUID(t, idProvider), svc, cRepo), - client: disabledClient1, - response: clients.Client{}, - err: clients.ErrStatusAlreadyAssigned, - }, - { - desc: "disable non-existing client", - id: mocks.WrongID, - client: clients.Client{}, - token: generateValidToken(t, testsutil.GenerateUUID(t, idProvider), svc, cRepo), - response: clients.Client{}, - err: errors.ErrNotFound, - }, - } - - for _, tc := range cases { - repoCall := pRepo.On("Evaluate", context.Background(), "client", mock.Anything).Return(nil) - _ = cRepo.On("RetrieveByID", context.Background(), mock.Anything).Return(tc.client, tc.err) - repoCall1 := cRepo.On("ChangeStatus", context.Background(), mock.Anything, mock.Anything).Return(tc.response, tc.err) - _, err := svc.DisableClient(context.Background(), tc.token, tc.id) - assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) - repoCall.Unset() - repoCall1.Unset() - } - - cases2 := []struct { - desc string - status clients.Status - size uint64 - response clients.ClientsPage - }{ - { - desc: "list enabled clients", - status: clients.EnabledStatus, - size: 1, - response: clients.ClientsPage{ - Page: clients.Page{ - Total: 1, - Offset: 0, - Limit: 100, - }, - Clients: []clients.Client{enabledClient1}, - }, - }, - { - desc: "list disabled clients", - status: clients.DisabledStatus, - size: 2, - response: clients.ClientsPage{ - Page: clients.Page{ - Total: 2, - Offset: 0, - Limit: 100, - }, - Clients: []clients.Client{disenabledClient1, disabledClient1}, - }, - }, - { - desc: "list enabled and disabled clients", - status: clients.AllStatus, - size: 3, - response: clients.ClientsPage{ - Page: clients.Page{ - Total: 3, - Offset: 0, - Limit: 100, - }, - Clients: []clients.Client{enabledClient1, disabledClient1, disenabledClient1}, - }, - }, - } - - for _, tc := range cases2 { - pm := clients.Page{ - Offset: 0, - Limit: 100, - Status: tc.status, - } - repoCall := pRepo.On("Evaluate", context.Background(), "group", mock.Anything).Return(nil) - repoCall1 := cRepo.On("RetrieveAll", context.Background(), mock.Anything).Return(tc.response, nil) - page, err := svc.ListClients(context.Background(), generateValidToken(t, testsutil.GenerateUUID(t, idProvider), svc, cRepo), pm) - require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err)) - size := uint64(len(page.Clients)) - assert.Equal(t, tc.size, size, fmt.Sprintf("%s: expected size %d got %d\n", tc.desc, tc.size, size)) - repoCall.Unset() - repoCall1.Unset() - } -} - -func TestListMembers(t *testing.T) { - cRepo := new(cmocks.ClientRepository) - pRepo := new(pmocks.PolicyRepository) - tokenizer := jwt.NewTokenRepo([]byte(secret)) - svc := clients.NewService(cRepo, pRepo, tokenizer, phasher, idProvider) - - var nClients = uint64(10) - var aClients = []clients.Client{} - for i := uint64(1); i < nClients; i++ { - identity := fmt.Sprintf("member_%d@example.com", i) - client := clients.Client{ - ID: testsutil.GenerateUUID(t, idProvider), - Name: identity, - Credentials: clients.Credentials{ - Identity: identity, - Secret: "password", - }, - Tags: []string{"tag1", "tag2"}, - Metadata: clients.Metadata{"role": "client"}, - } - aClients = append(aClients, client) - } - validID := testsutil.GenerateUUID(t, idProvider) - validToken := generateValidToken(t, validID, svc, cRepo) - - cases := []struct { - desc string - token string - groupID string - page clients.Page - response clients.MembersPage - err error - }{ - { - desc: "list clients with authorized token", - token: validToken, - groupID: testsutil.GenerateUUID(t, idProvider), - page: clients.Page{ - Subject: validID, - Action: "g_list", - }, - response: clients.MembersPage{ - Page: clients.Page{ - Total: 0, - Offset: 0, - Limit: 0, - }, - Members: []clients.Client{}, - }, - err: nil, - }, - { - desc: "list clients with offset and limit", - token: validToken, - groupID: testsutil.GenerateUUID(t, idProvider), - page: clients.Page{ - Offset: 6, - Limit: nClients, - Status: clients.AllStatus, - Subject: validID, - Action: "g_list", - }, - response: clients.MembersPage{ - Page: clients.Page{ - Total: nClients - 6 - 1, - }, - Members: aClients[6 : nClients-1], - }, - }, - { - desc: "list clients with an invalid token", - token: inValidToken, - groupID: testsutil.GenerateUUID(t, idProvider), - page: clients.Page{ - Subject: validID, - Action: "g_list", - }, - response: clients.MembersPage{ - Page: clients.Page{ - Total: 0, - Offset: 0, - Limit: 0, - }, - }, - err: errors.ErrAuthentication, - }, - { - desc: "list clients with an invalid id", - token: validToken, - groupID: mocks.WrongID, - page: clients.Page{ - Subject: validID, - Action: "g_list", - }, - response: clients.MembersPage{ - Page: clients.Page{ - Total: 0, - Offset: 0, - Limit: 0, - }, - }, - err: errors.ErrNotFound, - }, - } - - for _, tc := range cases { - repoCall := cRepo.On("Members", context.Background(), tc.groupID, tc.page).Return(tc.response, tc.err) - page, err := svc.ListMembers(context.Background(), tc.token, tc.groupID, tc.page) - assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) - assert.Equal(t, tc.response, page, fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.response, page)) - repoCall.Unset() - } -} - -func TestIssueToken(t *testing.T) { - cRepo := new(cmocks.ClientRepository) - pRepo := new(pmocks.PolicyRepository) - tokenizer := jwt.NewTokenRepo([]byte(secret)) - svc := clients.NewService(cRepo, pRepo, tokenizer, phasher, idProvider) - - rClient := client - rClient2 := client - rClient3 := client - rClient.Credentials.Secret, _ = phasher.Hash(client.Credentials.Secret) - rClient2.Credentials.Secret = "wrongsecret" - rClient3.Credentials.Secret, _ = phasher.Hash("wrongsecret") - - cases := []struct { - desc string - client clients.Client - rClient clients.Client - err error - }{ - { - desc: "issue token for an existing client", - client: client, - rClient: rClient, - err: nil, - }, - { - desc: "issue token for a non-existing client", - client: client, - rClient: clients.Client{}, - err: errors.ErrAuthentication, - }, - { - desc: "issue token for a client with wrong secret", - client: rClient2, - rClient: rClient3, - err: errors.ErrAuthentication, - }, - } - - for _, tc := range cases { - repoCall := cRepo.On("RetrieveByIdentity", context.Background(), mock.Anything).Return(tc.rClient, tc.err) - token, err := svc.IssueToken(context.Background(), tc.client.Credentials.Identity, tc.client.Credentials.Secret) - assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) - if err == nil { - assert.NotEmpty(t, token.AccessToken, fmt.Sprintf("%s: expected %s not to be empty\n", tc.desc, token.AccessToken)) - assert.NotEmpty(t, token.RefreshToken, fmt.Sprintf("%s: expected %s not to be empty\n", tc.desc, token.RefreshToken)) - } - repoCall.Unset() - } -} - -func TestRefreshToken(t *testing.T) { - cRepo := new(cmocks.ClientRepository) - pRepo := new(pmocks.PolicyRepository) - tokenizer := jwt.NewTokenRepo([]byte(secret)) - svc := clients.NewService(cRepo, pRepo, tokenizer, phasher, idProvider) - - rClient := client - rClient.Credentials.Secret, _ = phasher.Hash(client.Credentials.Secret) - - repoCall := cRepo.On("RetrieveByIdentity", context.Background(), mock.Anything).Return(rClient, nil) - token, err := svc.IssueToken(context.Background(), client.Credentials.Identity, client.Credentials.Secret) - assert.Nil(t, err, fmt.Sprintf("Issue token expected nil got %s\n", err)) - repoCall.Unset() - - cases := []struct { - desc string - token string - client clients.Client - err error - }{ - { - desc: "refresh token with refresh token for an existing client", - token: token.RefreshToken, - client: client, - err: nil, - }, - { - desc: "refresh token with refresh token for a non-existing client", - token: token.RefreshToken, - client: clients.Client{}, - err: errors.ErrAuthentication, - }, - { - desc: "refresh token with access token for an existing client", - token: token.AccessToken, - client: client, - err: errors.ErrAuthentication, - }, - { - desc: "refresh token with access token for a non-existing client", - token: token.AccessToken, - client: clients.Client{}, - err: errors.ErrAuthentication, - }, - { - desc: "refresh token with invalid token for an existing client", - token: generateValidToken(t, testsutil.GenerateUUID(t, idProvider), svc, cRepo), - client: client, - err: errors.ErrAuthentication, - }, - } - - for _, tc := range cases { - repoCall1 := cRepo.On("RetrieveByIdentity", context.Background(), mock.Anything).Return(tc.client, nil) - repoCall2 := cRepo.On("RetrieveByID", context.Background(), mock.Anything).Return(tc.client, tc.err) - token, err := svc.RefreshToken(context.Background(), tc.token) - assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) - if err == nil { - assert.NotEmpty(t, token.AccessToken, fmt.Sprintf("%s: expected %s not to be empty\n", tc.desc, token.AccessToken)) - assert.NotEmpty(t, token.RefreshToken, fmt.Sprintf("%s: expected %s not to be empty\n", tc.desc, token.RefreshToken)) - } - repoCall1.Unset() - repoCall2.Unset() - } -} diff --git a/clients/clients/status.go b/clients/clients/status.go deleted file mode 100644 index cb8cf493dcc..00000000000 --- a/clients/clients/status.go +++ /dev/null @@ -1,53 +0,0 @@ -package clients - -import "github.com/mainflux/mainflux/internal/apiutil" - -// Status represents Client status. -type Status uint8 - -// Possible Client status values -const ( - DisabledStatus Status = iota - EnabledStatus - - // AllStatus is used for querying purposes to list clients irrespective - // of their status - both enabled and disabled. It is never stored in the - // database as the actual Client status and should always be the largest - // value in this enumeration. - AllStatus -) - -// String representation of the possible status values. -const ( - Disabled = "disabled" - Enabled = "enabled" - All = "all" - Unknown = "unknown" -) - -// String converts client status to string literal. -func (s Status) String() string { - switch s { - case DisabledStatus: - return Disabled - case EnabledStatus: - return Enabled - case AllStatus: - return All - default: - return Unknown - } -} - -// ToClientStatus converts string value to a valid Client status. -func ToStatus(status string) (Status, error) { - switch status { - case "", Enabled: - return EnabledStatus, nil - case Disabled: - return DisabledStatus, nil - case All: - return AllStatus, nil - } - return Status(0), apiutil.ErrInvalidStatus -} diff --git a/clients/clients/tracing/tracing.go b/clients/clients/tracing/tracing.go deleted file mode 100644 index 3afbcc7fb42..00000000000 --- a/clients/clients/tracing/tracing.go +++ /dev/null @@ -1,111 +0,0 @@ -package tracing - -import ( - "context" - - "github.com/mainflux/mainflux/clients/clients" - "github.com/mainflux/mainflux/clients/jwt" - "go.opentelemetry.io/otel/attribute" - "go.opentelemetry.io/otel/trace" -) - -var _ clients.Service = (*tracingMiddleware)(nil) - -type tracingMiddleware struct { - tracer trace.Tracer - svc clients.Service -} - -func TracingMiddleware(svc clients.Service, tracer trace.Tracer) clients.Service { - return &tracingMiddleware{tracer, svc} -} - -func (tm *tracingMiddleware) RegisterClient(ctx context.Context, token string, client clients.Client) (clients.Client, error) { - ctx, span := tm.tracer.Start(ctx, "svc_register_client", trace.WithAttributes(attribute.String("identity", client.Credentials.Identity))) - defer span.End() - - return tm.svc.RegisterClient(ctx, token, client) -} - -func (tm *tracingMiddleware) IssueToken(ctx context.Context, identity, secret string) (jwt.Token, error) { - ctx, span := tm.tracer.Start(ctx, "svc_issue_token", trace.WithAttributes(attribute.String("identity", identity))) - defer span.End() - - return tm.svc.IssueToken(ctx, identity, secret) -} - -func (tm *tracingMiddleware) RefreshToken(ctx context.Context, accessToken string) (jwt.Token, error) { - ctx, span := tm.tracer.Start(ctx, "svc_refresh_token", trace.WithAttributes(attribute.String("access_token", accessToken))) - defer span.End() - - return tm.svc.RefreshToken(ctx, accessToken) -} -func (tm *tracingMiddleware) ViewClient(ctx context.Context, token string, id string) (clients.Client, error) { - ctx, span := tm.tracer.Start(ctx, "svc_view_client", trace.WithAttributes(attribute.String("ID", id))) - defer span.End() - return tm.svc.ViewClient(ctx, token, id) -} - -func (tm *tracingMiddleware) ListClients(ctx context.Context, token string, pm clients.Page) (clients.ClientsPage, error) { - ctx, span := tm.tracer.Start(ctx, "svc_list_clients") - defer span.End() - return tm.svc.ListClients(ctx, token, pm) -} - -func (tm *tracingMiddleware) UpdateClient(ctx context.Context, token string, cli clients.Client) (clients.Client, error) { - ctx, span := tm.tracer.Start(ctx, "svc_update_client_name_and_metadata", trace.WithAttributes(attribute.String("Name", cli.Name))) - defer span.End() - - return tm.svc.UpdateClient(ctx, token, cli) -} - -func (tm *tracingMiddleware) UpdateClientTags(ctx context.Context, token string, cli clients.Client) (clients.Client, error) { - ctx, span := tm.tracer.Start(ctx, "svc_update_client_tags", trace.WithAttributes(attribute.StringSlice("Tags", cli.Tags))) - defer span.End() - - return tm.svc.UpdateClientTags(ctx, token, cli) -} -func (tm *tracingMiddleware) UpdateClientIdentity(ctx context.Context, token, id, identity string) (clients.Client, error) { - ctx, span := tm.tracer.Start(ctx, "svc_update_client_identity", trace.WithAttributes(attribute.String("Identity", identity))) - defer span.End() - - return tm.svc.UpdateClientIdentity(ctx, token, id, identity) - -} - -func (tm *tracingMiddleware) UpdateClientSecret(ctx context.Context, token, oldSecret, newSecret string) (clients.Client, error) { - ctx, span := tm.tracer.Start(ctx, "svc_update_client_secret") - defer span.End() - - return tm.svc.UpdateClientSecret(ctx, token, oldSecret, newSecret) - -} - -func (tm *tracingMiddleware) UpdateClientOwner(ctx context.Context, token string, cli clients.Client) (clients.Client, error) { - ctx, span := tm.tracer.Start(ctx, "svc_update_client_owner", trace.WithAttributes(attribute.StringSlice("Tags", cli.Tags))) - defer span.End() - - return tm.svc.UpdateClientOwner(ctx, token, cli) -} - -func (tm *tracingMiddleware) EnableClient(ctx context.Context, token, id string) (clients.Client, error) { - ctx, span := tm.tracer.Start(ctx, "svc_enable_client", trace.WithAttributes(attribute.String("ID", id))) - defer span.End() - - return tm.svc.EnableClient(ctx, token, id) -} - -func (tm *tracingMiddleware) DisableClient(ctx context.Context, token, id string) (clients.Client, error) { - ctx, span := tm.tracer.Start(ctx, "svc_disable_client", trace.WithAttributes(attribute.String("ID", id))) - defer span.End() - - return tm.svc.DisableClient(ctx, token, id) -} - -func (tm *tracingMiddleware) ListMembers(ctx context.Context, token, groupID string, pm clients.Page) (clients.MembersPage, error) { - ctx, span := tm.tracer.Start(ctx, "svc_list_members") - defer span.End() - - return tm.svc.ListMembers(ctx, token, groupID, pm) - -} diff --git a/clients/groups/api/endpoints.go b/clients/groups/api/endpoints.go deleted file mode 100644 index 3998e0819c8..00000000000 --- a/clients/groups/api/endpoints.go +++ /dev/null @@ -1,209 +0,0 @@ -package api - -import ( - "context" - - "github.com/go-kit/kit/endpoint" - "github.com/mainflux/mainflux/clients/groups" -) - -func createGroupEndpoint(svc groups.Service) endpoint.Endpoint { - return func(ctx context.Context, request interface{}) (interface{}, error) { - req := request.(createGroupReq) - if err := req.validate(); err != nil { - return createGroupRes{}, err - } - - group, err := svc.CreateGroup(ctx, req.token, req.Group) - if err != nil { - return createGroupRes{}, err - } - - return createGroupRes{created: true, Group: group}, nil - } -} - -func viewGroupEndpoint(svc groups.Service) endpoint.Endpoint { - return func(ctx context.Context, request interface{}) (interface{}, error) { - req := request.(groupReq) - if err := req.validate(); err != nil { - return viewGroupRes{}, err - } - - group, err := svc.ViewGroup(ctx, req.token, req.id) - if err != nil { - return viewGroupRes{}, err - } - - return viewGroupRes{Group: group}, nil - } -} - -func updateGroupEndpoint(svc groups.Service) endpoint.Endpoint { - return func(ctx context.Context, request interface{}) (interface{}, error) { - req := request.(updateGroupReq) - if err := req.validate(); err != nil { - return updateGroupRes{}, err - } - - group := groups.Group{ - ID: req.id, - Name: req.Name, - Description: req.Description, - Metadata: req.Metadata, - } - - group, err := svc.UpdateGroup(ctx, req.token, group) - if err != nil { - return updateGroupRes{}, err - } - - return updateGroupRes{Group: group}, nil - } -} - -func enableGroupEndpoint(svc groups.Service) endpoint.Endpoint { - return func(ctx context.Context, request interface{}) (interface{}, error) { - req := request.(changeGroupStatusReq) - if err := req.validate(); err != nil { - return nil, err - } - group, err := svc.EnableGroup(ctx, req.token, req.id) - if err != nil { - return nil, err - } - return changeStatusRes{Group: group}, nil - } -} - -func disableGroupEndpoint(svc groups.Service) endpoint.Endpoint { - return func(ctx context.Context, request interface{}) (interface{}, error) { - req := request.(changeGroupStatusReq) - if err := req.validate(); err != nil { - return nil, err - } - group, err := svc.DisableGroup(ctx, req.token, req.id) - if err != nil { - return nil, err - } - return changeStatusRes{Group: group}, nil - } -} - -func listGroupsEndpoint(svc groups.Service) endpoint.Endpoint { - return func(ctx context.Context, request interface{}) (interface{}, error) { - req := request.(listGroupsReq) - if err := req.validate(); err != nil { - return groupPageRes{}, err - } - page, err := svc.ListGroups(ctx, req.token, req.GroupsPage) - if err != nil { - return groupPageRes{}, err - } - - if req.tree { - return buildGroupsResponseTree(page), nil - } - - return buildGroupsResponse(page), nil - } -} - -func listMembershipsEndpoint(svc groups.Service) endpoint.Endpoint { - return func(ctx context.Context, request interface{}) (interface{}, error) { - req := request.(listMembershipReq) - if err := req.validate(); err != nil { - return membershipPageRes{}, err - } - - page, err := svc.ListMemberships(ctx, req.token, req.clientID, req.GroupsPage) - if err != nil { - return membershipPageRes{}, err - } - - res := membershipPageRes{ - pageRes: pageRes{ - Total: page.Total, - Offset: page.Offset, - Limit: page.Limit, - }, - Memberships: []viewMembershipRes{}, - } - for _, g := range page.Memberships { - res.Memberships = append(res.Memberships, viewMembershipRes{Group: g}) - } - - return res, nil - } -} - -func buildGroupsResponseTree(page groups.GroupsPage) groupPageRes { - groupsMap := map[string]*groups.Group{} - // Parents' map keeps its array of children. - parentsMap := map[string][]*groups.Group{} - for i := range page.Groups { - if _, ok := groupsMap[page.Groups[i].ID]; !ok { - groupsMap[page.Groups[i].ID] = &page.Groups[i] - parentsMap[page.Groups[i].ID] = make([]*groups.Group, 0) - } - } - - for _, group := range groupsMap { - if children, ok := parentsMap[group.ParentID]; ok { - children = append(children, group) - parentsMap[group.ParentID] = children - } - } - - res := groupPageRes{ - pageRes: pageRes{ - Limit: page.Limit, - Offset: page.Offset, - Total: page.Total, - Level: page.Level, - }, - Groups: []viewGroupRes{}, - } - - for _, group := range groupsMap { - if children, ok := parentsMap[group.ID]; ok { - group.Children = children - } - - } - - for _, group := range groupsMap { - view := toViewGroupRes(*group) - if children, ok := parentsMap[group.ParentID]; len(children) == 0 || !ok { - res.Groups = append(res.Groups, view) - } - } - - return res -} - -func toViewGroupRes(group groups.Group) viewGroupRes { - view := viewGroupRes{ - Group: group, - } - return view -} - -func buildGroupsResponse(gp groups.GroupsPage) groupPageRes { - res := groupPageRes{ - pageRes: pageRes{ - Total: gp.Total, - Level: gp.Level, - }, - Groups: []viewGroupRes{}, - } - - for _, group := range gp.Groups { - view := viewGroupRes{ - Group: group, - } - res.Groups = append(res.Groups, view) - } - - return res -} diff --git a/clients/groups/api/logging.go b/clients/groups/api/logging.go deleted file mode 100644 index 91e659d80e3..00000000000 --- a/clients/groups/api/logging.go +++ /dev/null @@ -1,105 +0,0 @@ -package api - -import ( - "context" - "fmt" - "time" - - "github.com/mainflux/mainflux/clients/groups" - log "github.com/mainflux/mainflux/logger" -) - -var _ groups.Service = (*loggingMiddleware)(nil) - -type loggingMiddleware struct { - logger log.Logger - svc groups.Service -} - -func LoggingMiddleware(svc groups.Service, logger log.Logger) groups.Service { - return &loggingMiddleware{logger, svc} -} - -func (lm *loggingMiddleware) CreateGroup(ctx context.Context, token string, group groups.Group) (rGroup groups.Group, err error) { - defer func(begin time.Time) { - message := fmt.Sprintf("Method create_group for group %s and token %s took %s to complete", group.Name, token, time.Since(begin)) - if err != nil { - lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err)) - return - } - lm.logger.Info(fmt.Sprintf("%s without errors.", message)) - }(time.Now()) - return lm.svc.CreateGroup(ctx, token, group) -} - -func (lm *loggingMiddleware) UpdateGroup(ctx context.Context, token string, group groups.Group) (rGroup groups.Group, err error) { - defer func(begin time.Time) { - message := fmt.Sprintf("Method update_group for group %s and token %s took %s to complete", group.Name, token, time.Since(begin)) - if err != nil { - lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err)) - return - } - lm.logger.Info(fmt.Sprintf("%s without errors.", message)) - }(time.Now()) - return lm.svc.UpdateGroup(ctx, token, group) -} - -func (lm *loggingMiddleware) ViewGroup(ctx context.Context, token, id string) (g groups.Group, err error) { - defer func(begin time.Time) { - message := fmt.Sprintf("Method view_group for group %s and token %s took %s to complete", g.Name, token, time.Since(begin)) - if err != nil { - lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err)) - return - } - lm.logger.Info(fmt.Sprintf("%s without errors.", message)) - }(time.Now()) - return lm.svc.ViewGroup(ctx, token, id) -} - -func (lm *loggingMiddleware) ListGroups(ctx context.Context, token string, gp groups.GroupsPage) (cg groups.GroupsPage, err error) { - defer func(begin time.Time) { - message := fmt.Sprintf("Method list_groups for token %s took %s to complete", token, time.Since(begin)) - if err != nil { - lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err)) - return - } - lm.logger.Info(fmt.Sprintf("%s without errors.", message)) - }(time.Now()) - return lm.svc.ListGroups(ctx, token, gp) -} - -func (lm *loggingMiddleware) EnableGroup(ctx context.Context, token string, id string) (g groups.Group, err error) { - defer func(begin time.Time) { - message := fmt.Sprintf("Method enable_group for client %s took %s to complete", id, time.Since(begin)) - if err != nil { - lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err)) - return - } - lm.logger.Info(fmt.Sprintf("%s without errors.", message)) - }(time.Now()) - return lm.svc.EnableGroup(ctx, token, id) -} - -func (lm *loggingMiddleware) DisableGroup(ctx context.Context, token string, id string) (g groups.Group, err error) { - defer func(begin time.Time) { - message := fmt.Sprintf("Method disable_group for client %s took %s to complete", id, time.Since(begin)) - if err != nil { - lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err)) - return - } - lm.logger.Info(fmt.Sprintf("%s without errors.", message)) - }(time.Now()) - return lm.svc.DisableGroup(ctx, token, id) -} - -func (lm *loggingMiddleware) ListMemberships(ctx context.Context, token, clientID string, cp groups.GroupsPage) (mp groups.MembershipsPage, err error) { - defer func(begin time.Time) { - message := fmt.Sprintf("Method list_memberships for group %s and token %s took %s to complete", clientID, token, time.Since(begin)) - if err != nil { - lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err)) - return - } - lm.logger.Info(fmt.Sprintf("%s without errors.", message)) - }(time.Now()) - return lm.svc.ListMemberships(ctx, token, clientID, cp) -} diff --git a/clients/groups/api/metrics.go b/clients/groups/api/metrics.go deleted file mode 100644 index 06fbccdd400..00000000000 --- a/clients/groups/api/metrics.go +++ /dev/null @@ -1,82 +0,0 @@ -package api - -import ( - "context" - "time" - - "github.com/go-kit/kit/metrics" - "github.com/mainflux/mainflux/clients/groups" -) - -var _ groups.Service = (*metricsMiddleware)(nil) - -type metricsMiddleware struct { - counter metrics.Counter - latency metrics.Histogram - svc groups.Service -} - -// MetricsMiddleware returns a new metrics middleware wrapper. -func MetricsMiddleware(svc groups.Service, counter metrics.Counter, latency metrics.Histogram) groups.Service { - return &metricsMiddleware{ - counter: counter, - latency: latency, - svc: svc, - } -} - -func (ms *metricsMiddleware) CreateGroup(ctx context.Context, token string, g groups.Group) (groups.Group, error) { - defer func(begin time.Time) { - ms.counter.With("method", "create_group").Add(1) - ms.latency.With("method", "create_group").Observe(time.Since(begin).Seconds()) - }(time.Now()) - return ms.svc.CreateGroup(ctx, token, g) -} - -func (ms *metricsMiddleware) UpdateGroup(ctx context.Context, token string, group groups.Group) (rGroup groups.Group, err error) { - defer func(begin time.Time) { - ms.counter.With("method", "update_group").Add(1) - ms.latency.With("method", "update_group").Observe(time.Since(begin).Seconds()) - }(time.Now()) - return ms.svc.UpdateGroup(ctx, token, group) -} - -func (ms *metricsMiddleware) ViewGroup(ctx context.Context, token, id string) (g groups.Group, err error) { - defer func(begin time.Time) { - ms.counter.With("method", "view_group").Add(1) - ms.latency.With("method", "view_group").Observe(time.Since(begin).Seconds()) - }(time.Now()) - return ms.svc.ViewGroup(ctx, token, id) -} - -func (ms *metricsMiddleware) ListGroups(ctx context.Context, token string, gp groups.GroupsPage) (cg groups.GroupsPage, err error) { - defer func(begin time.Time) { - ms.counter.With("method", "list_groups").Add(1) - ms.latency.With("method", "list_groups").Observe(time.Since(begin).Seconds()) - }(time.Now()) - return ms.svc.ListGroups(ctx, token, gp) -} - -func (ms *metricsMiddleware) EnableGroup(ctx context.Context, token string, id string) (g groups.Group, err error) { - defer func(begin time.Time) { - ms.counter.With("method", "enable_group").Add(1) - ms.latency.With("method", "enable_group").Observe(time.Since(begin).Seconds()) - }(time.Now()) - return ms.svc.EnableGroup(ctx, token, id) -} - -func (ms *metricsMiddleware) DisableGroup(ctx context.Context, token string, id string) (g groups.Group, err error) { - defer func(begin time.Time) { - ms.counter.With("method", "disable_group").Add(1) - ms.latency.With("method", "disable_group").Observe(time.Since(begin).Seconds()) - }(time.Now()) - return ms.svc.DisableGroup(ctx, token, id) -} - -func (ms *metricsMiddleware) ListMemberships(ctx context.Context, token, clientID string, gp groups.GroupsPage) (mp groups.MembershipsPage, err error) { - defer func(begin time.Time) { - ms.counter.With("method", "list_memberships").Add(1) - ms.latency.With("method", "list_memberships").Observe(time.Since(begin).Seconds()) - }(time.Now()) - return ms.svc.ListMemberships(ctx, token, clientID, gp) -} diff --git a/clients/groups/api/requests.go b/clients/groups/api/requests.go deleted file mode 100644 index 4cd4fa698c7..00000000000 --- a/clients/groups/api/requests.go +++ /dev/null @@ -1,99 +0,0 @@ -package api - -import ( - "github.com/mainflux/mainflux/clients/groups" - "github.com/mainflux/mainflux/internal/api" - "github.com/mainflux/mainflux/internal/apiutil" -) - -type createGroupReq struct { - groups.Group - token string -} - -func (req createGroupReq) validate() error { - if len(req.Name) > api.MaxNameSize || req.Name == "" { - return apiutil.ErrNameSize - } - - return nil -} - -type updateGroupReq struct { - token string - id string - Name string `json:"name,omitempty"` - Description string `json:"description,omitempty"` - Metadata map[string]interface{} `json:"metadata,omitempty"` -} - -func (req updateGroupReq) validate() error { - if req.token == "" { - return apiutil.ErrBearerToken - } - - if req.id == "" { - return apiutil.ErrMissingID - } - - return nil -} - -type listGroupsReq struct { - groups.GroupsPage - token string - // - `true` - result is JSON tree representing groups hierarchy, - // - `false` - result is JSON array of groups. - tree bool -} - -func (req listGroupsReq) validate() error { - if req.Level < groups.MinLevel || req.Level > groups.MaxLevel { - return apiutil.ErrInvalidLevel - } - - return nil -} - -type listMembershipReq struct { - groups.GroupsPage - token string - clientID string -} - -func (req listMembershipReq) validate() error { - if req.token == "" { - return apiutil.ErrBearerToken - } - - if req.clientID == "" { - return apiutil.ErrMissingID - } - - return nil -} - -type groupReq struct { - token string - id string -} - -func (req groupReq) validate() error { - if req.id == "" { - return apiutil.ErrMissingID - } - - return nil -} - -type changeGroupStatusReq struct { - token string - id string -} - -func (req changeGroupStatusReq) validate() error { - if req.id == "" { - return apiutil.ErrMissingID - } - return nil -} diff --git a/clients/groups/api/responses.go b/clients/groups/api/responses.go deleted file mode 100644 index 860b340a611..00000000000 --- a/clients/groups/api/responses.go +++ /dev/null @@ -1,152 +0,0 @@ -package api - -import ( - "fmt" - "net/http" - - "github.com/mainflux/mainflux" - "github.com/mainflux/mainflux/clients/groups" -) - -var ( - _ mainflux.Response = (*viewMembershipRes)(nil) - _ mainflux.Response = (*membershipPageRes)(nil) - _ mainflux.Response = (*createGroupRes)(nil) - _ mainflux.Response = (*groupPageRes)(nil) - _ mainflux.Response = (*changeStatusRes)(nil) - _ mainflux.Response = (*viewGroupRes)(nil) - _ mainflux.Response = (*updateGroupRes)(nil) -) - -type viewMembershipRes struct { - groups.Group -} - -func (res viewMembershipRes) Code() int { - return http.StatusOK -} - -func (res viewMembershipRes) Headers() map[string]string { - return map[string]string{} -} - -func (res viewMembershipRes) Empty() bool { - return false -} - -type membershipPageRes struct { - pageRes - Memberships []viewMembershipRes `json:"memberships"` -} - -func (res membershipPageRes) Code() int { - return http.StatusOK -} - -func (res membershipPageRes) Headers() map[string]string { - return map[string]string{} -} - -func (res membershipPageRes) Empty() bool { - return false -} - -type viewGroupRes struct { - groups.Group -} - -func (res viewGroupRes) Code() int { - return http.StatusOK -} - -func (res viewGroupRes) Headers() map[string]string { - return map[string]string{} -} - -func (res viewGroupRes) Empty() bool { - return false -} - -type createGroupRes struct { - groups.Group - created bool -} - -func (res createGroupRes) Code() int { - if res.created { - return http.StatusCreated - } - - return http.StatusOK -} - -func (res createGroupRes) Headers() map[string]string { - if res.created { - return map[string]string{ - "Location": fmt.Sprintf("/groups/%s", res.ID), - } - } - - return map[string]string{} -} - -func (res createGroupRes) Empty() bool { - return false -} - -type groupPageRes struct { - pageRes - Groups []viewGroupRes `json:"groups"` -} - -type pageRes struct { - Limit uint64 `json:"limit,omitempty"` - Offset uint64 `json:"offset,omitempty"` - Total uint64 `json:"total"` - Level uint64 `json:"level"` - Name string `json:"name"` -} - -func (res groupPageRes) Code() int { - return http.StatusOK -} - -func (res groupPageRes) Headers() map[string]string { - return map[string]string{} -} - -func (res groupPageRes) Empty() bool { - return false -} - -type updateGroupRes struct { - groups.Group -} - -func (res updateGroupRes) Code() int { - return http.StatusOK -} - -func (res updateGroupRes) Headers() map[string]string { - return map[string]string{} -} - -func (res updateGroupRes) Empty() bool { - return false -} - -type changeStatusRes struct { - groups.Group -} - -func (res changeStatusRes) Code() int { - return http.StatusOK -} - -func (res changeStatusRes) Headers() map[string]string { - return map[string]string{} -} - -func (res changeStatusRes) Empty() bool { - return false -} diff --git a/clients/groups/api/transport.go b/clients/groups/api/transport.go deleted file mode 100644 index 9269a7f809b..00000000000 --- a/clients/groups/api/transport.go +++ /dev/null @@ -1,245 +0,0 @@ -package api - -import ( - "context" - "encoding/json" - "net/http" - "strings" - - kithttp "github.com/go-kit/kit/transport/http" - "github.com/go-zoo/bone" - "github.com/mainflux/mainflux/clients/groups" - "github.com/mainflux/mainflux/internal/api" - "github.com/mainflux/mainflux/internal/apiutil" - "github.com/mainflux/mainflux/logger" - "github.com/mainflux/mainflux/pkg/errors" - "go.opentelemetry.io/contrib/instrumentation/github.com/go-kit/kit/otelkit" -) - -// MakeGroupsHandler returns a HTTP handler for API endpoints. -func MakeGroupsHandler(svc groups.Service, mux *bone.Mux, logger logger.Logger) { - opts := []kithttp.ServerOption{ - kithttp.ServerErrorEncoder(apiutil.LoggingErrorEncoder(logger, api.EncodeError)), - } - mux.Post("/groups", kithttp.NewServer( - otelkit.EndpointMiddleware(otelkit.WithOperation("create_group"))(createGroupEndpoint(svc)), - decodeGroupCreate, - api.EncodeResponse, - opts..., - )) - - mux.Get("/groups/:id", kithttp.NewServer( - otelkit.EndpointMiddleware(otelkit.WithOperation("view_group"))(viewGroupEndpoint(svc)), - decodeGroupRequest, - api.EncodeResponse, - opts..., - )) - - mux.Put("/groups/:id", kithttp.NewServer( - otelkit.EndpointMiddleware(otelkit.WithOperation("update_group"))(updateGroupEndpoint(svc)), - decodeGroupUpdate, - api.EncodeResponse, - opts..., - )) - - mux.Get("/:clientID/memberships", kithttp.NewServer( - otelkit.EndpointMiddleware(otelkit.WithOperation("list_memberships"))(listMembershipsEndpoint(svc)), - decodeListMembershipRequest, - api.EncodeResponse, - opts..., - )) - - mux.Get("/groups", kithttp.NewServer( - otelkit.EndpointMiddleware(otelkit.WithOperation("list_groups"))(listGroupsEndpoint(svc)), - decodeListGroupsRequest, - api.EncodeResponse, - opts..., - )) - mux.Post("/groups/:id/enable", kithttp.NewServer( - otelkit.EndpointMiddleware(otelkit.WithOperation("enable_group"))(enableGroupEndpoint(svc)), - decodeChangeGroupStatus, - api.EncodeResponse, - opts..., - )) - - mux.Post("/groups/:id/disable", kithttp.NewServer( - otelkit.EndpointMiddleware(otelkit.WithOperation("disable_group"))(disableGroupEndpoint(svc)), - decodeChangeGroupStatus, - api.EncodeResponse, - opts..., - )) -} - -func decodeListMembershipRequest(_ context.Context, r *http.Request) (interface{}, error) { - s, err := apiutil.ReadStringQuery(r, api.StatusKey, api.DefGroupStatus) - if err != nil { - return nil, err - } - level, err := apiutil.ReadNumQuery[uint64](r, api.LevelKey, api.DefLevel) - if err != nil { - return nil, err - } - offset, err := apiutil.ReadNumQuery[uint64](r, api.OffsetKey, api.DefOffset) - if err != nil { - return nil, err - } - limit, err := apiutil.ReadNumQuery[uint64](r, api.LimitKey, api.DefLimit) - if err != nil { - return nil, err - } - parentID, err := apiutil.ReadStringQuery(r, api.ParentKey, "") - if err != nil { - return nil, err - } - ownerID, err := apiutil.ReadStringQuery(r, api.OwnerKey, "") - if err != nil { - return nil, err - } - name, err := apiutil.ReadStringQuery(r, api.NameKey, "") - if err != nil { - return nil, err - } - meta, err := apiutil.ReadMetadataQuery(r, api.MetadataKey, nil) - if err != nil { - return nil, err - } - dir, err := apiutil.ReadNumQuery[int64](r, api.DirKey, -1) - if err != nil { - return nil, err - } - st, err := groups.ToStatus(s) - if err != nil { - return nil, err - } - req := listMembershipReq{ - token: apiutil.ExtractBearerToken(r), - clientID: bone.GetValue(r, "clientID"), - GroupsPage: groups.GroupsPage{ - Level: level, - ID: parentID, - Page: groups.Page{ - Offset: offset, - Limit: limit, - OwnerID: ownerID, - Name: name, - Metadata: meta, - Status: st, - }, - Direction: dir, - }, - } - return req, nil - -} - -func decodeListGroupsRequest(_ context.Context, r *http.Request) (interface{}, error) { - s, err := apiutil.ReadStringQuery(r, api.StatusKey, api.DefGroupStatus) - if err != nil { - return nil, err - } - level, err := apiutil.ReadNumQuery[uint64](r, api.LevelKey, api.DefLevel) - if err != nil { - return nil, err - } - offset, err := apiutil.ReadNumQuery[uint64](r, api.OffsetKey, api.DefOffset) - if err != nil { - return nil, err - } - limit, err := apiutil.ReadNumQuery[uint64](r, api.LimitKey, api.DefLimit) - if err != nil { - return nil, err - } - parentID, err := apiutil.ReadStringQuery(r, api.ParentKey, "") - if err != nil { - return nil, err - } - ownerID, err := apiutil.ReadStringQuery(r, api.OwnerKey, "") - if err != nil { - return nil, err - } - name, err := apiutil.ReadStringQuery(r, api.NameKey, "") - if err != nil { - return nil, err - } - meta, err := apiutil.ReadMetadataQuery(r, api.MetadataKey, nil) - if err != nil { - return nil, err - } - tree, err := apiutil.ReadBoolQuery(r, api.TreeKey, false) - if err != nil { - return nil, err - } - dir, err := apiutil.ReadNumQuery[int64](r, api.DirKey, -1) - if err != nil { - return nil, err - } - st, err := groups.ToStatus(s) - if err != nil { - return nil, err - } - req := listGroupsReq{ - token: apiutil.ExtractBearerToken(r), - tree: tree, - GroupsPage: groups.GroupsPage{ - Level: level, - ID: parentID, - Page: groups.Page{ - Offset: offset, - Limit: limit, - OwnerID: ownerID, - Name: name, - Metadata: meta, - Status: st, - }, - Direction: dir, - }, - } - return req, nil -} - -func decodeGroupCreate(_ context.Context, r *http.Request) (interface{}, error) { - if !strings.Contains(r.Header.Get("Content-Type"), api.ContentType) { - return nil, errors.ErrUnsupportedContentType - } - var g groups.Group - if err := json.NewDecoder(r.Body).Decode(&g); err != nil { - return nil, errors.Wrap(errors.ErrMalformedEntity, err) - } - req := createGroupReq{ - Group: g, - token: apiutil.ExtractBearerToken(r), - } - - return req, nil -} - -func decodeGroupUpdate(_ context.Context, r *http.Request) (interface{}, error) { - if !strings.Contains(r.Header.Get("Content-Type"), api.ContentType) { - return nil, errors.ErrUnsupportedContentType - } - req := updateGroupReq{ - id: bone.GetValue(r, "id"), - token: apiutil.ExtractBearerToken(r), - } - if err := json.NewDecoder(r.Body).Decode(&req); err != nil { - return nil, errors.Wrap(errors.ErrMalformedEntity, err) - } - return req, nil -} - -func decodeGroupRequest(_ context.Context, r *http.Request) (interface{}, error) { - req := groupReq{ - token: apiutil.ExtractBearerToken(r), - id: bone.GetValue(r, "id"), - } - return req, nil -} - -func decodeChangeGroupStatus(_ context.Context, r *http.Request) (interface{}, error) { - req := changeGroupStatusReq{ - token: apiutil.ExtractBearerToken(r), - id: bone.GetValue(r, "id"), - } - - return req, nil -} diff --git a/clients/groups/groups.go b/clients/groups/groups.go deleted file mode 100644 index d33a6c01320..00000000000 --- a/clients/groups/groups.go +++ /dev/null @@ -1,111 +0,0 @@ -package groups - -import ( - "context" - "encoding/json" - "strings" - "time" -) - -const ( - // MaxLevel represents the maximum group hierarchy level. - MaxLevel = uint64(5) - // MinLevel represents the minimum group hierarchy level. - MinLevel = uint64(0) -) - -// MembershipsPage contains page related metadata as well as list of memberships that -// belong to this page. -type MembershipsPage struct { - Page - Memberships []Group -} - -// GroupsPage contains page related metadata as well as list -// of Groups that belong to the page. -type GroupsPage struct { - Page - Path string - Level uint64 - ID string - Direction int64 // ancestors (-1) or descendants (+1) - Groups []Group -} - -// Group represents the group of Clients. -// Indicates a level in tree hierarchy. Root node is level 1. -// Path in a tree consisting of group IDs -// Paths are unique per owner. -type Group struct { - ID string `json:"id"` - OwnerID string `json:"owner_id"` - ParentID string `json:"parent_id,omitempty"` - Name string `json:"name"` - Description string `json:"description,omitempty"` - Metadata Metadata `json:"metadata,omitempty"` - Level int `json:"level"` - Path string `json:"path"` - Children []*Group `json:"children,omitempty"` - CreatedAt time.Time `json:"created_at"` - UpdatedAt time.Time `json:"updated_at"` - Status Status `json:"status"` -} - -// GroupRepository specifies a group persistence API. -type GroupRepository interface { - // Save group. - Save(ctx context.Context, g Group) (Group, error) - - // Update a group. - Update(ctx context.Context, g Group) (Group, error) - - // RetrieveByID retrieves group by its id. - RetrieveByID(ctx context.Context, id string) (Group, error) - - // RetrieveAll retrieves all groups. - RetrieveAll(ctx context.Context, gm GroupsPage) (GroupsPage, error) - - // Memberships retrieves everything that is assigned to a group identified by clientID. - Memberships(ctx context.Context, clientID string, gm GroupsPage) (MembershipsPage, error) - - // ChangeStatus changes groups status to active or inactive - ChangeStatus(ctx context.Context, id string, status Status) (Group, error) -} - -// GroupService specifies an API that must be fulfilled by the domain service -// implementation, and all of its decorators (e.g. logging & metrics). -type GroupService interface { - // CreateGroup creates new group. - CreateGroup(ctx context.Context, token string, g Group) (Group, error) - - // UpdateGroup updates the group identified by the provided ID. - UpdateGroup(ctx context.Context, token string, g Group) (Group, error) - - // ViewGroup retrieves data about the group identified by ID. - ViewGroup(ctx context.Context, token, id string) (Group, error) - - // ListGroups retrieves groups. - ListGroups(ctx context.Context, token string, gm GroupsPage) (GroupsPage, error) - - // ListMemberships retrieves everything that is assigned to a group identified by clientID. - ListMemberships(ctx context.Context, token, clientID string, gm GroupsPage) (MembershipsPage, error) - - // EnableGroup logically enables the group identified with the provided ID. - EnableGroup(ctx context.Context, token, id string) (Group, error) - - // DisableGroup logically disables the group identified with the provided ID. - DisableGroup(ctx context.Context, token, id string) (Group, error) -} - -// Custom Marshaller for Group -func (s Status) MarshalJSON() ([]byte, error) { - return json.Marshal(s.String()) -} - -// Custom Unmarshaller for Group -func (s *Status) UnmarshalJSON(data []byte) error { - str := strings.Trim(string(data), "\"") - val, err := ToStatus(str) - *s = val - return err -} diff --git a/clients/groups/mocks/groups.go b/clients/groups/mocks/groups.go deleted file mode 100644 index 41e327cf654..00000000000 --- a/clients/groups/mocks/groups.go +++ /dev/null @@ -1,85 +0,0 @@ -package mocks - -import ( - "context" - - "github.com/mainflux/mainflux/clients/groups" - "github.com/mainflux/mainflux/pkg/errors" - "github.com/stretchr/testify/mock" -) - -const WrongID = "wrongID" - -var _ groups.GroupRepository = (*GroupRepository)(nil) - -type GroupRepository struct { - mock.Mock -} - -func (m *GroupRepository) Delete(ctx context.Context, id string) error { - ret := m.Called(ctx, id) - if id == WrongID { - return errors.ErrNotFound - } - - return ret.Error(0) -} - -func (m *GroupRepository) ChangeStatus(ctx context.Context, id string, status groups.Status) (groups.Group, error) { - ret := m.Called(ctx, id, status) - - if id == WrongID { - return groups.Group{}, errors.ErrNotFound - } - if status != groups.EnabledStatus && status != groups.DisabledStatus { - return groups.Group{}, errors.ErrMalformedEntity - } - - return ret.Get(0).(groups.Group), ret.Error(1) -} - -func (m *GroupRepository) Memberships(ctx context.Context, clientID string, gm groups.GroupsPage) (groups.MembershipsPage, error) { - ret := m.Called(ctx, clientID, gm) - - if clientID == WrongID { - return groups.MembershipsPage{}, errors.ErrNotFound - } - - return ret.Get(0).(groups.MembershipsPage), ret.Error(1) -} - -func (m *GroupRepository) RetrieveAll(ctx context.Context, gm groups.GroupsPage) (groups.GroupsPage, error) { - ret := m.Called(ctx, gm) - - return ret.Get(0).(groups.GroupsPage), ret.Error(1) -} - -func (m *GroupRepository) RetrieveByID(ctx context.Context, id string) (groups.Group, error) { - ret := m.Called(ctx, id) - if id == WrongID { - return groups.Group{}, errors.ErrNotFound - } - - return ret.Get(0).(groups.Group), ret.Error(1) -} - -func (m *GroupRepository) Save(ctx context.Context, g groups.Group) (groups.Group, error) { - ret := m.Called(ctx, g) - if g.ParentID == WrongID { - return groups.Group{}, errors.ErrCreateEntity - } - if g.OwnerID == WrongID { - return groups.Group{}, errors.ErrCreateEntity - } - - return g, ret.Error(1) -} - -func (m *GroupRepository) Update(ctx context.Context, g groups.Group) (groups.Group, error) { - ret := m.Called(ctx, g) - if g.ID == WrongID { - return groups.Group{}, errors.ErrNotFound - } - - return ret.Get(0).(groups.Group), ret.Error(1) -} diff --git a/clients/groups/page.go b/clients/groups/page.go deleted file mode 100644 index aa64304ebea..00000000000 --- a/clients/groups/page.go +++ /dev/null @@ -1,19 +0,0 @@ -package groups - -// Metadata represents arbitrary JSON. -type Metadata map[string]interface{} - -// Page contains page metadata that helps navigation. -type Page struct { - Total uint64 - Offset uint64 - Limit uint64 - Name string - OwnerID string - Tag string - Metadata Metadata - SharedBy string - Status Status - Subject string - Action string -} diff --git a/clients/groups/postgres/doc.go b/clients/groups/postgres/doc.go deleted file mode 100644 index bf560bea285..00000000000 --- a/clients/groups/postgres/doc.go +++ /dev/null @@ -1 +0,0 @@ -package postgres diff --git a/clients/groups/postgres/groups.go b/clients/groups/postgres/groups.go deleted file mode 100644 index 24d4203da49..00000000000 --- a/clients/groups/postgres/groups.go +++ /dev/null @@ -1,400 +0,0 @@ -package postgres - -import ( - "context" - "database/sql" - "encoding/json" - "fmt" - "strings" - "time" - - "github.com/jmoiron/sqlx" - "github.com/mainflux/mainflux/clients/groups" - "github.com/mainflux/mainflux/clients/postgres" - "github.com/mainflux/mainflux/pkg/errors" -) - -var _ groups.GroupRepository = (*groupRepository)(nil) - -type groupRepository struct { - db postgres.Database -} - -// NewGroupRepo instantiates a PostgreSQL implementation of group -// repository. -func NewGroupRepo(db postgres.Database) groups.GroupRepository { - return &groupRepository{ - db: db, - } -} - -// TODO - check parent group write access. -func (repo groupRepository) Save(ctx context.Context, g groups.Group) (groups.Group, error) { - q := `INSERT INTO groups (name, description, id, owner_id, metadata, created_at, updated_at, status) - VALUES (:name, :description, :id, :owner_id, :metadata, :created_at, :updated_at, :status) - RETURNING id, name, description, owner_id, COALESCE(parent_id, '') AS parent_id, metadata, created_at, updated_at, status;` - if g.ParentID != "" { - q = `INSERT INTO groups (name, description, id, owner_id, parent_id, metadata, created_at, updated_at, status) - VALUES (:name, :description, :id, :owner_id, :parent_id, :metadata, :created_at, :updated_at, :status) - RETURNING id, name, description, owner_id, COALESCE(parent_id, '') AS parent_id, metadata, created_at, updated_at, status;` - } - dbg, err := toDBGroup(g) - if err != nil { - return groups.Group{}, err - } - row, err := repo.db.NamedQueryContext(ctx, q, dbg) - if err != nil { - return groups.Group{}, postgres.HandleError(err, errors.ErrCreateEntity) - } - - defer row.Close() - row.Next() - dbg = dbGroup{} - if err := row.StructScan(&dbg); err != nil { - return groups.Group{}, err - } - - return toGroup(dbg) -} - -func (repo groupRepository) RetrieveByID(ctx context.Context, id string) (groups.Group, error) { - dbu := dbGroup{ - ID: id, - } - q := `SELECT id, name, owner_id, COALESCE(parent_id, '') AS parent_id, description, metadata, created_at, updated_at, status FROM groups - WHERE id = $1` - if err := repo.db.QueryRowxContext(ctx, q, dbu.ID).StructScan(&dbu); err != nil { - if err == sql.ErrNoRows { - return groups.Group{}, errors.Wrap(errors.ErrNotFound, err) - - } - return groups.Group{}, errors.Wrap(errors.ErrViewEntity, err) - } - return toGroup(dbu) -} - -func (repo groupRepository) RetrieveAll(ctx context.Context, gm groups.GroupsPage) (groups.GroupsPage, error) { - var q string - query, err := buildQuery(gm) - if err != nil { - return groups.GroupsPage{}, err - } - - if gm.ID != "" { - q = buildHierachy(gm) - } - if gm.ID == "" { - q = `SELECT DISTINCT g.id, g.owner_id, COALESCE(g.parent_id, '') AS parent_id, g.name, g.description, - g.metadata, g.created_at, g.updated_at, g.status FROM groups g` - } - q = fmt.Sprintf("%s %s ORDER BY g.updated_at LIMIT :limit OFFSET :offset;", q, query) - - dbPage, err := toDBGroupPage(gm) - if err != nil { - return groups.GroupsPage{}, errors.Wrap(postgres.ErrFailedToRetrieveAll, err) - } - rows, err := repo.db.NamedQueryContext(ctx, q, dbPage) - if err != nil { - return groups.GroupsPage{}, errors.Wrap(postgres.ErrFailedToRetrieveAll, err) - } - defer rows.Close() - - items, err := repo.processRows(rows) - if err != nil { - return groups.GroupsPage{}, errors.Wrap(postgres.ErrFailedToRetrieveAll, err) - } - - cq := "SELECT COUNT(*) FROM groups g" - if query != "" { - cq = fmt.Sprintf(" %s %s", cq, query) - } - - total, err := postgres.Total(ctx, repo.db, cq, dbPage) - if err != nil { - return groups.GroupsPage{}, errors.Wrap(postgres.ErrFailedToRetrieveAll, err) - } - - page := gm - page.Groups = items - page.Total = total - - return page, nil -} - -func (repo groupRepository) Memberships(ctx context.Context, clientID string, gm groups.GroupsPage) (groups.MembershipsPage, error) { - var q string - query, err := buildQuery(gm) - if err != nil { - return groups.MembershipsPage{}, err - } - if gm.ID != "" { - q = buildHierachy(gm) - } - if gm.ID == "" { - q = `SELECT DISTINCT g.id, g.owner_id, COALESCE(g.parent_id, '') AS parent_id, g.name, g.description, - g.metadata, g.created_at, g.updated_at, g.status FROM groups g` - } - q = fmt.Sprintf(`%s INNER JOIN policies ON g.id=policies.object %s AND policies.subject = :client_id - AND policies.object IN (SELECT object FROM policies WHERE subject = '%s' AND '%s'=ANY(actions)) - ORDER BY g.updated_at LIMIT :limit OFFSET :offset;`, q, query, gm.Subject, gm.Action) - - dbPage, err := toDBGroupPage(gm) - if err != nil { - return groups.MembershipsPage{}, errors.Wrap(postgres.ErrFailedToRetrieveMembership, err) - } - dbPage.ClientID = clientID - rows, err := repo.db.NamedQueryContext(ctx, q, dbPage) - if err != nil { - return groups.MembershipsPage{}, errors.Wrap(postgres.ErrFailedToRetrieveMembership, err) - } - defer rows.Close() - - var items []groups.Group - for rows.Next() { - dbg := dbGroup{} - if err := rows.StructScan(&dbg); err != nil { - return groups.MembershipsPage{}, errors.Wrap(postgres.ErrFailedToRetrieveMembership, err) - } - group, err := toGroup(dbg) - if err != nil { - return groups.MembershipsPage{}, errors.Wrap(postgres.ErrFailedToRetrieveMembership, err) - } - items = append(items, group) - } - - cq := fmt.Sprintf(`SELECT COUNT(*) FROM groups g INNER JOIN policies - ON g.id=policies.object %s AND policies.subject = :client_id`, query) - - total, err := postgres.Total(ctx, repo.db, cq, dbPage) - if err != nil { - return groups.MembershipsPage{}, errors.Wrap(postgres.ErrFailedToRetrieveMembership, err) - } - page := groups.MembershipsPage{ - Memberships: items, - Page: groups.Page{ - Total: total, - }, - } - - return page, nil -} - -func (repo groupRepository) Update(ctx context.Context, g groups.Group) (groups.Group, error) { - var query []string - var upq string - if g.Name != "" { - query = append(query, "name = :name,") - } - if g.Description != "" { - query = append(query, "description = :description,") - } - if g.Metadata != nil { - query = append(query, "metadata = :metadata,") - } - if len(query) > 0 { - upq = strings.Join(query, " ") - } - q := fmt.Sprintf(`UPDATE groups SET %s updated_at = :updated_at - WHERE id = :id AND status = %d - RETURNING id, name, description, owner_id, COALESCE(parent_id, '') AS parent_id, metadata, created_at, updated_at, status`, upq, groups.EnabledStatus) - - dbu, err := toDBGroup(g) - if err != nil { - return groups.Group{}, errors.Wrap(errors.ErrUpdateEntity, err) - } - - row, err := repo.db.NamedQueryContext(ctx, q, dbu) - if err != nil { - return groups.Group{}, postgres.HandleError(err, errors.ErrUpdateEntity) - } - - defer row.Close() - if ok := row.Next(); !ok { - return groups.Group{}, errors.Wrap(errors.ErrNotFound, row.Err()) - } - dbu = dbGroup{} - if err := row.StructScan(&dbu); err != nil { - return groups.Group{}, errors.Wrap(err, errors.ErrUpdateEntity) - } - return toGroup(dbu) -} - -func (repo groupRepository) ChangeStatus(ctx context.Context, id string, status groups.Status) (groups.Group, error) { - qc := fmt.Sprintf(`UPDATE groups SET status = %d WHERE id = :id RETURNING id, name, description, owner_id, COALESCE(parent_id, '') AS parent_id, metadata, created_at, updated_at, status`, status) - - dbg := dbGroup{ - ID: id, - } - row, err := repo.db.NamedQueryContext(ctx, qc, dbg) - if err != nil { - return groups.Group{}, postgres.HandleError(err, errors.ErrUpdateEntity) - } - - defer row.Close() - if ok := row.Next(); !ok { - return groups.Group{}, errors.Wrap(errors.ErrNotFound, row.Err()) - } - dbg = dbGroup{} - if err := row.StructScan(&dbg); err != nil { - return groups.Group{}, errors.Wrap(err, errors.ErrUpdateEntity) - } - - return toGroup(dbg) -} - -func buildHierachy(gm groups.GroupsPage) string { - query := "" - switch { - case gm.Direction >= 0: // ancestors - query = `WITH RECURSIVE groups_cte as ( - SELECT id, COALESCE(parent_id, '') AS parent_id, owner_id, name, description, metadata, created_at, updated_at, status, 1 as level from groups WHERE id = :id - UNION SELECT x.id, COALESCE(x.parent_id, '') AS parent_id, x.owner_id, x.name, x.description, x.metadata, x.created_at, x.updated_at, x.status, level - 1 from groups x - INNER JOIN groups_cte a ON a.parent_id = x.id - ) SELECT * FROM groups_cte g` - - case gm.Direction < 0: // descendants - query = `WITH RECURSIVE groups_cte as ( - SELECT id, COALESCE(parent_id, '') AS parent_id, owner_id, name, description, metadata, created_at, updated_at, status, 1 as level, CONCAT('', '', id) as path from groups WHERE id = :id - UNION SELECT x.id, COALESCE(x.parent_id, '') AS parent_id, x.owner_id, x.name, x.description, x.metadata, x.created_at, x.updated_at, x.status, level + 1, CONCAT(path, '.', x.id) as path from groups x - INNER JOIN groups_cte d ON d.id = x.parent_id - ) SELECT * FROM groups_cte g` - } - return query -} -func buildQuery(gm groups.GroupsPage) (string, error) { - queries := []string{} - - if gm.Name != "" { - queries = append(queries, "g.name = :name") - } - if gm.Status != groups.AllStatus { - queries = append(queries, fmt.Sprintf("g.status = %d", gm.Status)) - } - - if gm.Subject != "" { - queries = append(queries, fmt.Sprintf("(g.owner_id = '%s' OR id IN (SELECT object as id FROM policies WHERE subject = '%s' AND '%s'=ANY(actions)))", gm.OwnerID, gm.Subject, gm.Action)) - } - if len(gm.Metadata) > 0 { - queries = append(queries, "'g.metadata @> :metadata'") - } - if len(queries) > 0 { - return fmt.Sprintf("WHERE %s", strings.Join(queries, " AND ")), nil - } - return "", nil -} - -type dbGroup struct { - ID string `db:"id"` - ParentID string `db:"parent_id"` - OwnerID string `db:"owner_id"` - Name string `db:"name"` - Description string `db:"description"` - Level int `db:"level"` - Path string `db:"path,omitempty"` - Metadata []byte `db:"metadata"` - CreatedAt time.Time `db:"created_at"` - UpdatedAt time.Time `db:"updated_at"` - Status groups.Status `db:"status"` -} - -func toDBGroup(g groups.Group) (dbGroup, error) { - data := []byte("{}") - if len(g.Metadata) > 0 { - b, err := json.Marshal(g.Metadata) - if err != nil { - return dbGroup{}, errors.Wrap(errors.ErrMalformedEntity, err) - } - data = b - } - return dbGroup{ - ID: g.ID, - Name: g.Name, - ParentID: g.ParentID, - OwnerID: g.OwnerID, - Description: g.Description, - Metadata: data, - Path: g.Path, - CreatedAt: g.CreatedAt, - UpdatedAt: g.UpdatedAt, - Status: g.Status, - }, nil -} - -func toGroup(g dbGroup) (groups.Group, error) { - var metadata groups.Metadata - if g.Metadata != nil { - if err := json.Unmarshal([]byte(g.Metadata), &metadata); err != nil { - return groups.Group{}, errors.Wrap(errors.ErrMalformedEntity, err) - } - } - return groups.Group{ - ID: g.ID, - Name: g.Name, - ParentID: g.ParentID, - OwnerID: g.OwnerID, - Description: g.Description, - Metadata: metadata, - Level: g.Level, - Path: g.Path, - UpdatedAt: g.UpdatedAt, - CreatedAt: g.CreatedAt, - Status: g.Status, - }, nil -} - -func (gr groupRepository) processRows(rows *sqlx.Rows) ([]groups.Group, error) { - var items []groups.Group - for rows.Next() { - dbg := dbGroup{} - if err := rows.StructScan(&dbg); err != nil { - return items, err - } - group, err := toGroup(dbg) - if err != nil { - return items, err - } - items = append(items, group) - } - return items, nil -} - -func toDBGroupPage(pm groups.GroupsPage) (dbGroupPage, error) { - level := groups.MaxLevel - if pm.Level < groups.MaxLevel { - level = pm.Level - } - data := []byte("{}") - if len(pm.Metadata) > 0 { - b, err := json.Marshal(pm.Metadata) - if err != nil { - return dbGroupPage{}, errors.Wrap(errors.ErrMalformedEntity, err) - } - data = b - } - return dbGroupPage{ - ID: pm.ID, - Name: pm.Name, - Metadata: data, - Path: pm.Path, - Level: level, - Total: pm.Total, - Offset: pm.Offset, - Limit: pm.Limit, - ParentID: pm.ID, - OwnerID: pm.OwnerID, - }, nil -} - -type dbGroupPage struct { - ClientID string `db:"client_id"` - ID string `db:"id"` - Name string `db:"name"` - ParentID string `db:"parent_id"` - OwnerID string `db:"owner_id"` - Metadata []byte `db:"metadata"` - Path string `db:"path"` - Level uint64 `db:"level"` - Total uint64 `db:"total"` - Limit uint64 `db:"limit"` - Offset uint64 `db:"offset"` -} diff --git a/clients/groups/postgres/groups_test.go b/clients/groups/postgres/groups_test.go deleted file mode 100644 index 6186fbdfc55..00000000000 --- a/clients/groups/postgres/groups_test.go +++ /dev/null @@ -1,595 +0,0 @@ -package postgres_test - -import ( - "context" - "fmt" - "strings" - "testing" - "time" - - "github.com/mainflux/mainflux/clients/clients" - cpostgres "github.com/mainflux/mainflux/clients/clients/postgres" - "github.com/mainflux/mainflux/clients/groups" - gpostgres "github.com/mainflux/mainflux/clients/groups/postgres" - "github.com/mainflux/mainflux/clients/policies" - ppostgres "github.com/mainflux/mainflux/clients/policies/postgres" - "github.com/mainflux/mainflux/clients/postgres" - "github.com/mainflux/mainflux/internal/testsutil" - "github.com/mainflux/mainflux/pkg/errors" - "github.com/mainflux/mainflux/pkg/uuid" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -const ( - maxNameSize = 254 - maxDescSize = 1024 - maxLevel = uint64(5) - groupName = "group" - description = "description" -) - -var ( - wrongID = "wrong-id" - invalidName = strings.Repeat("m", maxNameSize+10) - validDesc = strings.Repeat("m", 100) - invalidDesc = strings.Repeat("m", maxDescSize+1) - metadata = groups.Metadata{ - "admin": "true", - } - password = "$tr0ngPassw0rd" - idProvider = uuid.New() -) - -func TestGroupSave(t *testing.T) { - t.Cleanup(func() { testsutil.CleanUpDB(t, db) }) - groupRepo := gpostgres.NewGroupRepo(database) - - usrID := testsutil.GenerateUUID(t, idProvider) - grpID := testsutil.GenerateUUID(t, idProvider) - - cases := []struct { - desc string - group groups.Group - err error - }{ - { - desc: "create new group successfully", - group: groups.Group{ - ID: grpID, - Name: groupName, - Status: groups.EnabledStatus, - }, - err: nil, - }, - { - desc: "create a new group with an existing name", - group: groups.Group{ - ID: grpID, - Name: groupName, - Status: groups.EnabledStatus, - }, - err: errors.ErrConflict, - }, - { - desc: "create group with an invalid name", - group: groups.Group{ - ID: testsutil.GenerateUUID(t, idProvider), - Name: invalidName, - Status: groups.EnabledStatus, - }, - err: errors.ErrMalformedEntity, - }, - { - desc: "create a group with invalid ID", - group: groups.Group{ - ID: usrID, - Name: "withInvalidDescription", - Description: invalidDesc, - Status: groups.EnabledStatus, - }, - err: errors.ErrMalformedEntity, - }, - { - desc: "create group with description", - group: groups.Group{ - ID: testsutil.GenerateUUID(t, idProvider), - Name: "withDescription", - Description: validDesc, - Status: groups.EnabledStatus, - }, - err: nil, - }, - { - desc: "create group with invalid description", - group: groups.Group{ - ID: testsutil.GenerateUUID(t, idProvider), - Name: "withInvalidDescription", - Description: invalidDesc, - Status: groups.EnabledStatus, - }, - err: errors.ErrMalformedEntity, - }, - { - desc: "create group with parent", - group: groups.Group{ - ID: testsutil.GenerateUUID(t, idProvider), - ParentID: grpID, - Name: "withParent", - Status: groups.EnabledStatus, - }, - err: nil, - }, - { - desc: "create a group with an invalid parent", - group: groups.Group{ - ID: testsutil.GenerateUUID(t, idProvider), - ParentID: invalidName, - Name: "withInvalidParent", - Status: groups.EnabledStatus, - }, - err: errors.ErrMalformedEntity, - }, - { - desc: "create a group with an owner", - group: groups.Group{ - ID: testsutil.GenerateUUID(t, idProvider), - OwnerID: usrID, - Name: "withOwner", - Status: groups.EnabledStatus, - }, - err: nil, - }, - { - desc: "create a group with an invalid owner", - group: groups.Group{ - ID: testsutil.GenerateUUID(t, idProvider), - OwnerID: invalidName, - Name: "withInvalidOwner", - Status: groups.EnabledStatus, - }, - err: errors.ErrMalformedEntity, - }, - { - desc: "create a group with metadata", - group: groups.Group{ - ID: testsutil.GenerateUUID(t, idProvider), - Name: "withMetadata", - Metadata: metadata, - Status: groups.EnabledStatus, - }, - err: nil, - }, - } - - for _, tc := range cases { - _, err := groupRepo.Save(context.Background(), tc.group) - assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) - } - -} - -func TestGroupRetrieveByID(t *testing.T) { - t.Cleanup(func() { testsutil.CleanUpDB(t, db) }) - groupRepo := gpostgres.NewGroupRepo(database) - - uid := testsutil.GenerateUUID(t, idProvider) - group1 := groups.Group{ - ID: testsutil.GenerateUUID(t, idProvider), - Name: groupName + "TestGroupRetrieveByID1", - OwnerID: uid, - Status: groups.EnabledStatus, - } - - _, err := groupRepo.Save(context.Background(), group1) - require.Nil(t, err, fmt.Sprintf("got unexpected error: %s", err)) - - retrieved, err := groupRepo.RetrieveByID(context.Background(), group1.ID) - require.Nil(t, err, fmt.Sprintf("got unexpected error: %s", err)) - assert.True(t, retrieved.ID == group1.ID, fmt.Sprintf("Save group, ID: expected %s got %s\n", group1.ID, retrieved.ID)) - - // Round to milliseconds as otherwise saving and retrieving from DB - // adds rounding error. - creationTime := time.Now().UTC().Round(time.Millisecond) - group2 := groups.Group{ - ID: testsutil.GenerateUUID(t, idProvider), - Name: groupName + "TestGroupRetrieveByID", - OwnerID: uid, - ParentID: group1.ID, - CreatedAt: creationTime, - UpdatedAt: creationTime, - Description: description, - Metadata: metadata, - Status: groups.EnabledStatus, - } - - _, err = groupRepo.Save(context.Background(), group2) - require.Nil(t, err, fmt.Sprintf("got unexpected error: %s", err)) - - retrieved, err = groupRepo.RetrieveByID(context.Background(), group2.ID) - require.Nil(t, err, fmt.Sprintf("got unexpected error: %s", err)) - assert.True(t, retrieved.ID == group2.ID, fmt.Sprintf("Save group, ID: expected %s got %s\n", group2.ID, retrieved.ID)) - assert.True(t, retrieved.CreatedAt.Equal(creationTime), fmt.Sprintf("Save group, CreatedAt: expected %s got %s\n", creationTime, retrieved.CreatedAt)) - assert.True(t, retrieved.UpdatedAt.Equal(creationTime), fmt.Sprintf("Save group, UpdatedAt: expected %s got %s\n", creationTime, retrieved.UpdatedAt)) - assert.True(t, retrieved.ParentID == group1.ID, fmt.Sprintf("Save group, Level: expected %s got %s\n", group1.ID, retrieved.ParentID)) - assert.True(t, retrieved.Description == description, fmt.Sprintf("Save group, Description: expected %v got %v\n", retrieved.Description, description)) - - retrieved, err = groupRepo.RetrieveByID(context.Background(), testsutil.GenerateUUID(t, idProvider)) - assert.True(t, errors.Contains(err, errors.ErrNotFound), fmt.Sprintf("Retrieve group: expected %s got %s\n", errors.ErrNotFound, err)) -} - -func TestGroupRetrieveAll(t *testing.T) { - t.Cleanup(func() { testsutil.CleanUpDB(t, db) }) - groupRepo := gpostgres.NewGroupRepo(database) - - var nGroups = uint64(200) - var ownerID = testsutil.GenerateUUID(t, idProvider) - var parentID string - for i := uint64(0); i < nGroups; i++ { - creationTime := time.Now().UTC() - group := groups.Group{ - ID: testsutil.GenerateUUID(t, idProvider), - Name: fmt.Sprintf("%s-%d", groupName, i), - Description: fmt.Sprintf("%s-description-%d", groupName, i), - CreatedAt: creationTime, - UpdatedAt: creationTime, - Status: groups.EnabledStatus, - } - if i == 1 { - parentID = group.ID - } - if i%10 == 0 { - group.OwnerID = ownerID - group.ParentID = parentID - } - if i%50 == 0 { - group.Status = groups.DisabledStatus - } - _, err := groupRepo.Save(context.Background(), group) - require.Nil(t, err, fmt.Sprintf("unexpected error: %s\n", err)) - parentID = group.ID - } - - cases := map[string]struct { - Size uint64 - Metadata groups.GroupsPage - }{ - "retrieve all groups": { - Metadata: groups.GroupsPage{ - Page: groups.Page{ - Total: nGroups, - Limit: nGroups, - Status: groups.AllStatus, - }, - Level: maxLevel, - }, - Size: nGroups, - }, - "retrieve all groups with offset": { - Metadata: groups.GroupsPage{ - Page: groups.Page{ - Total: nGroups, - Offset: 50, - Limit: nGroups, - Status: groups.AllStatus, - }, - Level: maxLevel, - }, - Size: nGroups - 50, - }, - "retrieve all groups with limit": { - Metadata: groups.GroupsPage{ - Page: groups.Page{ - Total: nGroups, - Offset: 0, - Limit: 50, - Status: groups.AllStatus, - }, - Level: maxLevel, - }, - Size: 50, - }, - "retrieve all groups with offset and limit": { - Metadata: groups.GroupsPage{ - Page: groups.Page{ - Total: nGroups, - Offset: 50, - Limit: 50, - Status: groups.AllStatus, - }, - Level: maxLevel, - }, - Size: 50, - }, - "retrieve all groups with offset greater than limit": { - Metadata: groups.GroupsPage{ - Page: groups.Page{ - Total: nGroups, - Offset: 250, - Limit: nGroups, - Status: groups.AllStatus, - }, - Level: maxLevel, - }, - Size: 0, - }, - "retrieve all groups with owner id": { - Metadata: groups.GroupsPage{ - Page: groups.Page{ - Total: nGroups, - Limit: nGroups, - Subject: ownerID, - OwnerID: ownerID, - Status: groups.AllStatus, - }, - Level: maxLevel, - }, - Size: 20, - }, - } - - for desc, tc := range cases { - page, err := groupRepo.RetrieveAll(context.Background(), tc.Metadata) - size := len(page.Groups) - assert.Equal(t, tc.Size, uint64(size), fmt.Sprintf("%s: expected size %d got %d\n", desc, tc.Size, size)) - assert.Nil(t, err, fmt.Sprintf("%s: expected no error got %d\n", desc, err)) - } -} - -func TestGroupUpdate(t *testing.T) { - t.Cleanup(func() { testsutil.CleanUpDB(t, db) }) - groupRepo := gpostgres.NewGroupRepo(database) - - uid := testsutil.GenerateUUID(t, idProvider) - - creationTime := time.Now().UTC() - updateTime := time.Now().UTC() - groupID := testsutil.GenerateUUID(t, idProvider) - - group := groups.Group{ - ID: groupID, - Name: groupName + "TestGroupUpdate", - OwnerID: uid, - CreatedAt: creationTime, - UpdatedAt: creationTime, - Description: description, - Metadata: metadata, - Status: groups.EnabledStatus, - } - updatedName := groupName + "Updated" - updatedMetadata := groups.Metadata{"admin": "false"} - updatedDescription := description + "updated" - _, err := groupRepo.Save(context.Background(), group) - require.Nil(t, err, fmt.Sprintf("group save got unexpected error: %s", err)) - - retrieved, err := groupRepo.RetrieveByID(context.Background(), group.ID) - require.Nil(t, err, fmt.Sprintf("group save got unexpected error: %s", err)) - - cases := []struct { - desc string - groupUpdate groups.Group - groupExpected groups.Group - err error - }{ - { - desc: "update group name for existing id", - groupUpdate: groups.Group{ - ID: groupID, - Name: updatedName, - UpdatedAt: updateTime, - }, - groupExpected: groups.Group{ - Name: updatedName, - Metadata: retrieved.Metadata, - Description: retrieved.Description, - }, - err: nil, - }, - { - desc: "update group metadata for existing id", - groupUpdate: groups.Group{ - ID: groupID, - UpdatedAt: updateTime, - Metadata: updatedMetadata, - }, - groupExpected: groups.Group{ - Name: updatedName, - UpdatedAt: updateTime, - Metadata: updatedMetadata, - Description: retrieved.Description, - }, - err: nil, - }, - { - desc: "update group description for existing id", - groupUpdate: groups.Group{ - ID: groupID, - UpdatedAt: updateTime, - Description: updatedDescription, - }, - groupExpected: groups.Group{ - Name: updatedName, - Description: updatedDescription, - UpdatedAt: updateTime, - Metadata: updatedMetadata, - }, - err: nil, - }, - { - desc: "update group name and metadata for existing id", - groupUpdate: groups.Group{ - ID: groupID, - Name: updatedName, - UpdatedAt: updateTime, - Metadata: updatedMetadata, - }, - groupExpected: groups.Group{ - Name: updatedName, - UpdatedAt: updateTime, - Metadata: updatedMetadata, - Description: updatedDescription, - }, - err: nil, - }, - { - desc: "update group for invalid name", - groupUpdate: groups.Group{ - ID: groupID, - Name: invalidName, - }, - err: errors.ErrMalformedEntity, - }, - { - desc: "update group for invalid description", - groupUpdate: groups.Group{ - ID: groupID, - Description: invalidDesc, - }, - err: errors.ErrMalformedEntity, - }, - } - - for _, tc := range cases { - updated, err := groupRepo.Update(context.Background(), tc.groupUpdate) - assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) - if err == nil { - assert.True(t, updated.Name == tc.groupExpected.Name, fmt.Sprintf("%s:Name: expected %s got %s\n", tc.desc, tc.groupExpected.Name, updated.Name)) - assert.True(t, updated.Description == tc.groupExpected.Description, fmt.Sprintf("%s:Description: expected %s got %s\n", tc.desc, tc.groupExpected.Description, updated.Description)) - assert.True(t, updated.Metadata["admin"] == tc.groupExpected.Metadata["admin"], fmt.Sprintf("%s:Metadata: expected %d got %d\n", tc.desc, tc.groupExpected.Metadata["admin"], updated.Metadata["admin"])) - } - } -} - -func TestClientsMemberships(t *testing.T) { - t.Cleanup(func() { testsutil.CleanUpDB(t, db) }) - postgres.NewDatabase(db, tracer) - crepo := cpostgres.NewClientRepo(database) - grepo := gpostgres.NewGroupRepo(database) - prepo := ppostgres.NewPolicyRepo(database) - - clientA := clients.Client{ - ID: testsutil.GenerateUUID(t, idProvider), - Name: "client-memberships", - Credentials: clients.Credentials{ - Identity: "client-memberships1@example.com", - Secret: password, - }, - Metadata: clients.Metadata{}, - Status: clients.EnabledStatus, - } - clientB := clients.Client{ - ID: testsutil.GenerateUUID(t, idProvider), - Name: "client-memberships", - Credentials: clients.Credentials{ - Identity: "client-memberships2@example.com", - Secret: password, - }, - Metadata: clients.Metadata{}, - Status: clients.EnabledStatus, - } - group := groups.Group{ - ID: testsutil.GenerateUUID(t, idProvider), - Name: "group-membership", - Metadata: groups.Metadata{}, - Status: groups.EnabledStatus, - } - - policyA := policies.Policy{ - Subject: clientA.ID, - Object: group.ID, - Actions: []string{"g_list"}, - } - policyB := policies.Policy{ - Subject: clientB.ID, - Object: group.ID, - Actions: []string{"g_list"}, - } - - _, err := crepo.Save(context.Background(), clientA) - assert.True(t, errors.Contains(err, nil), fmt.Sprintf("save client: expected %v got %s\n", nil, err)) - _, err = crepo.Save(context.Background(), clientB) - assert.True(t, errors.Contains(err, nil), fmt.Sprintf("save client: expected %v got %s\n", nil, err)) - _, err = grepo.Save(context.Background(), group) - assert.True(t, errors.Contains(err, nil), fmt.Sprintf("save group: expected %v got %s\n", nil, err)) - err = prepo.Save(context.Background(), policyA) - assert.True(t, errors.Contains(err, nil), fmt.Sprintf("save policy: expected %v got %s\n", nil, err)) - err = prepo.Save(context.Background(), policyB) - assert.True(t, errors.Contains(err, nil), fmt.Sprintf("save policy: expected %v got %s\n", nil, err)) - - cases := map[string]struct { - ID string - err error - }{ - "retrieve membership for existing client": {clientA.ID, nil}, - "retrieve membership for non-existing client": {wrongID, nil}, - } - - for desc, tc := range cases { - mp, err := grepo.Memberships(context.Background(), tc.ID, groups.GroupsPage{Page: groups.Page{Total: 10, Offset: 0, Limit: 10, Status: groups.AllStatus, Subject: clientB.ID, Action: "g_list"}}) - assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", desc, tc.err, err)) - if tc.ID == clientA.ID { - assert.ElementsMatch(t, mp.Memberships, []groups.Group{group}, fmt.Sprintf("%s: expected %v got %v\n", desc, []groups.Group{group}, mp.Memberships)) - } - } -} - -func TestGroupChangeStatus(t *testing.T) { - t.Cleanup(func() { testsutil.CleanUpDB(t, db) }) - dbMiddleware := postgres.NewDatabase(db, tracer) - repo := gpostgres.NewGroupRepo(dbMiddleware) - - group1 := groups.Group{ - ID: testsutil.GenerateUUID(t, idProvider), - Name: "active-group", - Status: groups.EnabledStatus, - } - group2 := groups.Group{ - ID: testsutil.GenerateUUID(t, idProvider), - Name: "inactive-group", - Status: groups.DisabledStatus, - } - - group1, err := repo.Save(context.Background(), group1) - assert.True(t, errors.Contains(err, nil), fmt.Sprintf("add new group: expected %v got %s\n", nil, err)) - group2, err = repo.Save(context.Background(), group2) - assert.True(t, errors.Contains(err, nil), fmt.Sprintf("add new disabled group: expected %v got %s\n", nil, err)) - - cases := []struct { - desc string - group groups.Group - err error - }{ - { - desc: "change group status for an active group", - group: groups.Group{ - ID: group1.ID, - Status: groups.DisabledStatus, - }, - err: nil, - }, - { - desc: "change group status for a inactive group", - group: groups.Group{ - ID: group2.ID, - Status: groups.EnabledStatus, - }, - err: nil, - }, - { - desc: "change group status for an invalid group", - group: groups.Group{ - ID: "invalid", - Status: groups.DisabledStatus, - }, - err: errors.ErrNotFound, - }, - } - - for _, tc := range cases { - expected, err := repo.ChangeStatus(context.Background(), tc.group.ID, tc.group.Status) - assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) - if err == nil { - assert.Equal(t, tc.group.Status, expected.Status, fmt.Sprintf("%s: expected %d got %d\n", tc.desc, tc.group.Status, expected.Status)) - } - } -} diff --git a/clients/groups/postgres/setup_test.go b/clients/groups/postgres/setup_test.go deleted file mode 100644 index 9cb273dbc56..00000000000 --- a/clients/groups/postgres/setup_test.go +++ /dev/null @@ -1,90 +0,0 @@ -// Package postgres_test contains tests for PostgreSQL repository -// implementations. -package postgres_test - -import ( - "database/sql" - "fmt" - "log" - "os" - "testing" - "time" - - "github.com/jmoiron/sqlx" - "github.com/mainflux/mainflux/clients/postgres" - dockertest "github.com/ory/dockertest/v3" - "github.com/ory/dockertest/v3/docker" - "go.opentelemetry.io/otel" -) - -var ( - db *sqlx.DB - database postgres.Database - tracer = otel.Tracer("repo_tests") -) - -func TestMain(m *testing.M) { - pool, err := dockertest.NewPool("") - if err != nil { - log.Fatalf("Could not connect to docker: %s", err) - } - - container, err := pool.RunWithOptions(&dockertest.RunOptions{ - Repository: "postgres", - Tag: "15.1-alpine", - Env: []string{ - "POSTGRES_USER=test", - "POSTGRES_PASSWORD=test", - "POSTGRES_DB=test", - "listen_addresses = '*'", - }, - }, func(config *docker.HostConfig) { - config.AutoRemove = true - config.RestartPolicy = docker.RestartPolicy{Name: "no"} - }) - if err != nil { - log.Fatalf("Could not start container: %s", err) - } - - port := container.GetPort("5432/tcp") - - // exponential backoff-retry, because the application in the container might not be ready to accept connections yet - pool.MaxWait = 120 * time.Second - if err := pool.Retry(func() error { - url := fmt.Sprintf("host=localhost port=%s user=test dbname=test password=test sslmode=disable", port) - db, err := sql.Open("pgx", url) - if err != nil { - return err - } - return db.Ping() - }); err != nil { - log.Fatalf("Could not connect to docker: %s", err) - } - - dbConfig := postgres.Config{ - Host: "localhost", - Port: port, - User: "test", - Pass: "test", - Name: "test", - SSLMode: "disable", - SSLCert: "", - SSLKey: "", - SSLRootCert: "", - } - - if db, err = postgres.Connect(dbConfig); err != nil { - log.Fatalf("Could not setup test DB connection: %s", err) - } - database = postgres.NewDatabase(db, tracer) - - code := m.Run() - - // Defers will not be run when using os.Exit - db.Close() - if err := pool.Purge(container); err != nil { - log.Fatalf("Could not purge container: %s", err) - } - - os.Exit(code) -} diff --git a/clients/groups/service.go b/clients/groups/service.go deleted file mode 100644 index 518a41fb8dd..00000000000 --- a/clients/groups/service.go +++ /dev/null @@ -1,167 +0,0 @@ -package groups - -import ( - "context" - "time" - - "github.com/mainflux/mainflux" - "github.com/mainflux/mainflux/clients/jwt" - "github.com/mainflux/mainflux/clients/policies" - "github.com/mainflux/mainflux/internal/apiutil" - "github.com/mainflux/mainflux/pkg/errors" -) - -// Possible token types are access and refresh tokens. -const ( - RefreshToken = "refresh" - AccessToken = "access" - MyKey = "mine" -) - -var ( - // ErrInvalidStatus indicates invalid status. - ErrInvalidStatus = errors.New("invalid groups status") - - // ErrStatusAlreadyAssigned indicated that the client or group has already been assigned the status. - ErrStatusAlreadyAssigned = errors.New("status already assigned") -) - -// Service unites Clients and Group services. -type Service interface { - GroupService -} - -type service struct { - groups GroupRepository - policies policies.PolicyRepository - tokens jwt.TokenRepository - idProvider mainflux.IDProvider -} - -// NewService returns a new Clients service implementation. -func NewService(g GroupRepository, p policies.PolicyRepository, t jwt.TokenRepository, idp mainflux.IDProvider) Service { - return service{ - groups: g, - policies: p, - tokens: t, - idProvider: idp, - } -} - -func (svc service) CreateGroup(ctx context.Context, token string, g Group) (Group, error) { - ownerID, err := svc.identify(ctx, token) - if err != nil { - return Group{}, err - } - groupID, err := svc.idProvider.ID() - if err != nil { - return Group{}, err - } - if g.Status != EnabledStatus && g.Status != DisabledStatus { - return Group{}, apiutil.ErrInvalidStatus - } - if g.OwnerID == "" { - g.OwnerID = ownerID - } - - g.ID = groupID - g.CreatedAt = time.Now() - g.UpdatedAt = g.CreatedAt - return svc.groups.Save(ctx, g) -} - -func (svc service) ViewGroup(ctx context.Context, token string, id string) (Group, error) { - if err := svc.authorize(ctx, "group", policies.Policy{Subject: token, Object: id, Actions: []string{"g_list"}}); err != nil { - return Group{}, err - } - - return svc.groups.RetrieveByID(ctx, id) -} - -func (svc service) ListGroups(ctx context.Context, token string, gm GroupsPage) (GroupsPage, error) { - id, err := svc.identify(ctx, token) - if err != nil { - return GroupsPage{}, err - } - gm.Subject = id - gm.OwnerID = id - gm.Action = "g_list" - return svc.groups.RetrieveAll(ctx, gm) -} - -func (svc service) ListMemberships(ctx context.Context, token, clientID string, gm GroupsPage) (MembershipsPage, error) { - id, err := svc.identify(ctx, token) - if err != nil { - return MembershipsPage{}, err - } - gm.Subject = id - gm.Action = "g_list" - return svc.groups.Memberships(ctx, clientID, gm) -} - -func (svc service) UpdateGroup(ctx context.Context, token string, g Group) (Group, error) { - if err := svc.authorize(ctx, "group", policies.Policy{Subject: token, Object: g.ID, Actions: []string{"g_update"}}); err != nil { - return Group{}, err - } - g.UpdatedAt = time.Now() - - return svc.groups.Update(ctx, g) -} - -func (svc service) EnableGroup(ctx context.Context, token, id string) (Group, error) { - if err := svc.authorize(ctx, "group", policies.Policy{Subject: token, Object: id, Actions: []string{"g_delete"}}); err != nil { - return Group{}, err - } - group, err := svc.changeGroupStatus(ctx, id, EnabledStatus) - if err != nil { - return Group{}, err - } - return group, nil -} - -func (svc service) DisableGroup(ctx context.Context, token, id string) (Group, error) { - if err := svc.authorize(ctx, "group", policies.Policy{Subject: token, Object: id, Actions: []string{"g_delete"}}); err != nil { - return Group{}, err - } - group, err := svc.changeGroupStatus(ctx, id, DisabledStatus) - if err != nil { - return Group{}, err - } - return group, nil -} - -func (svc service) authorize(ctx context.Context, entityType string, p policies.Policy) error { - if err := p.Validate(); err != nil { - return err - } - id, err := svc.identify(ctx, p.Subject) - if err != nil { - return err - } - p.Subject = id - return svc.policies.Evaluate(ctx, entityType, p) -} - -func (svc service) changeGroupStatus(ctx context.Context, id string, status Status) (Group, error) { - dbGroup, err := svc.groups.RetrieveByID(ctx, id) - if err != nil { - return Group{}, err - } - if dbGroup.Status == status { - return Group{}, ErrStatusAlreadyAssigned - } - - return svc.groups.ChangeStatus(ctx, id, status) -} - -func (svc service) identify(ctx context.Context, tkn string) (string, error) { - claims, err := svc.tokens.Parse(ctx, tkn) - if err != nil { - return "", errors.Wrap(errors.ErrAuthentication, err) - } - if claims.Type != AccessToken { - return "", errors.ErrAuthentication - } - - return claims.ClientID, nil -} diff --git a/clients/groups/service_test.go b/clients/groups/service_test.go deleted file mode 100644 index 73dbb96a97c..00000000000 --- a/clients/groups/service_test.go +++ /dev/null @@ -1,786 +0,0 @@ -package groups_test - -import ( - context "context" - fmt "fmt" - "testing" - "time" - - "github.com/mainflux/mainflux/clients/clients" - cmocks "github.com/mainflux/mainflux/clients/clients/mocks" - "github.com/mainflux/mainflux/clients/groups" - "github.com/mainflux/mainflux/clients/groups/mocks" - gmocks "github.com/mainflux/mainflux/clients/groups/mocks" - "github.com/mainflux/mainflux/clients/hasher" - "github.com/mainflux/mainflux/clients/jwt" - pmocks "github.com/mainflux/mainflux/clients/policies/mocks" - "github.com/mainflux/mainflux/internal/testsutil" - "github.com/mainflux/mainflux/pkg/errors" - "github.com/mainflux/mainflux/pkg/uuid" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" - "github.com/stretchr/testify/require" -) - -var ( - idProvider = uuid.New() - phasher = hasher.New() - secret = "strongsecret" - validGMetadata = groups.Metadata{"role": "client"} - inValidToken = "invalidToken" - description = "shortdescription" - gName = "groupname" - group = groups.Group{ - Name: gName, - Description: description, - Metadata: validGMetadata, - Status: groups.EnabledStatus, - } - withinDuration = 5 * time.Second -) - -func generateValidToken(t *testing.T, clientID string, svc clients.Service, cRepo *cmocks.ClientRepository) string { - client := clients.Client{ - ID: clientID, - Name: "validtoken", - Credentials: clients.Credentials{ - Identity: "validtoken", - Secret: secret, - }, - Status: clients.EnabledStatus, - } - rClient := client - rClient.Credentials.Secret, _ = phasher.Hash(client.Credentials.Secret) - - repoCall := cRepo.On("RetrieveByIdentity", context.Background(), mock.Anything).Return(rClient, nil) - token, err := svc.IssueToken(context.Background(), client.Credentials.Identity, client.Credentials.Secret) - assert.True(t, errors.Contains(err, nil), fmt.Sprintf("Create token expected nil got %s\n", err)) - repoCall.Unset() - return token.AccessToken -} - -func TestCreateGroup(t *testing.T) { - cRepo := new(cmocks.ClientRepository) - gRepo := new(gmocks.GroupRepository) - pRepo := new(pmocks.PolicyRepository) - tokenizer := jwt.NewTokenRepo([]byte(secret)) - - csvc := clients.NewService(cRepo, pRepo, tokenizer, phasher, idProvider) - svc := groups.NewService(gRepo, pRepo, tokenizer, idProvider) - - cases := []struct { - desc string - group groups.Group - err error - }{ - { - desc: "create new group", - group: group, - err: nil, - }, - { - desc: "create group with existing name", - group: group, - err: nil, - }, - { - desc: "create group with parent", - group: groups.Group{ - Name: gName, - ParentID: testsutil.GenerateUUID(t, idProvider), - Status: groups.EnabledStatus, - }, - err: nil, - }, - { - desc: "create group with invalid parent", - group: groups.Group{ - Name: gName, - ParentID: mocks.WrongID, - }, - err: errors.ErrCreateEntity, - }, - { - desc: "create group with invalid owner", - group: groups.Group{ - Name: gName, - OwnerID: mocks.WrongID, - }, - err: errors.ErrCreateEntity, - }, - { - desc: "create group with missing name", - group: groups.Group{}, - err: errors.ErrMalformedEntity, - }, - } - - for _, tc := range cases { - repoCall := pRepo.On("Evaluate", context.Background(), "group", mock.Anything).Return(nil) - repoCall1 := gRepo.On("Save", context.Background(), mock.Anything).Return(tc.group, tc.err) - createdAt := time.Now() - expected, err := svc.CreateGroup(context.Background(), generateValidToken(t, testsutil.GenerateUUID(t, idProvider), csvc, cRepo), tc.group) - assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) - if err == nil { - assert.NotEmpty(t, expected.ID, fmt.Sprintf("%s: expected %s not to be empty\n", tc.desc, expected.ID)) - assert.WithinDuration(t, expected.CreatedAt, createdAt, withinDuration, fmt.Sprintf("%s: expected %v got %v\n", tc.desc, expected.CreatedAt, createdAt)) - tc.group.ID = expected.ID - tc.group.CreatedAt = expected.CreatedAt - tc.group.UpdatedAt = expected.UpdatedAt - tc.group.OwnerID = expected.OwnerID - assert.Equal(t, tc.group, expected, fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.group, expected)) - } - repoCall.Unset() - repoCall1.Unset() - } -} - -func TestUpdateGroup(t *testing.T) { - cRepo := new(cmocks.ClientRepository) - gRepo := new(gmocks.GroupRepository) - pRepo := new(pmocks.PolicyRepository) - tokenizer := jwt.NewTokenRepo([]byte(secret)) - - csvc := clients.NewService(cRepo, pRepo, tokenizer, phasher, idProvider) - svc := groups.NewService(gRepo, pRepo, tokenizer, idProvider) - - group.ID = testsutil.GenerateUUID(t, idProvider) - - cases := []struct { - desc string - token string - group groups.Group - response groups.Group - err error - }{ - { - desc: "update group name", - group: groups.Group{ - ID: group.ID, - Name: "NewName", - }, - response: groups.Group{ - ID: group.ID, - Name: "NewName", - }, - token: generateValidToken(t, testsutil.GenerateUUID(t, idProvider), csvc, cRepo), - err: nil, - }, - { - desc: "update group description", - group: groups.Group{ - ID: group.ID, - Description: "NewDescription", - }, - response: groups.Group{ - ID: group.ID, - Description: "NewDescription", - }, - token: generateValidToken(t, testsutil.GenerateUUID(t, idProvider), csvc, cRepo), - err: nil, - }, - { - desc: "update group metadata", - group: groups.Group{ - ID: group.ID, - Metadata: groups.Metadata{ - "field": "value2", - }, - }, - response: groups.Group{ - ID: group.ID, - Metadata: groups.Metadata{ - "field": "value2", - }, - }, - token: generateValidToken(t, testsutil.GenerateUUID(t, idProvider), csvc, cRepo), - err: nil, - }, - { - desc: "update group name with invalid group id", - group: groups.Group{ - ID: mocks.WrongID, - Name: "NewName", - }, - response: groups.Group{}, - token: generateValidToken(t, testsutil.GenerateUUID(t, idProvider), csvc, cRepo), - err: errors.ErrNotFound, - }, - { - desc: "update group description with invalid group id", - group: groups.Group{ - ID: mocks.WrongID, - Description: "NewDescription", - }, - response: groups.Group{}, - token: generateValidToken(t, testsutil.GenerateUUID(t, idProvider), csvc, cRepo), - err: errors.ErrNotFound, - }, - { - desc: "update group metadata with invalid group id", - group: groups.Group{ - ID: mocks.WrongID, - Metadata: groups.Metadata{ - "field": "value2", - }, - }, - response: groups.Group{}, - token: generateValidToken(t, testsutil.GenerateUUID(t, idProvider), csvc, cRepo), - err: errors.ErrNotFound, - }, - { - desc: "update group name with invalid token", - group: groups.Group{ - ID: group.ID, - Name: "NewName", - }, - response: groups.Group{}, - token: inValidToken, - err: errors.ErrAuthentication, - }, - { - desc: "update group description with invalid token", - group: groups.Group{ - ID: group.ID, - Description: "NewDescription", - }, - response: groups.Group{}, - token: inValidToken, - err: errors.ErrAuthentication, - }, - { - desc: "update group metadata with invalid token", - group: groups.Group{ - ID: group.ID, - Metadata: groups.Metadata{ - "field": "value2", - }, - }, - response: groups.Group{}, - token: inValidToken, - err: errors.ErrAuthentication, - }, - } - - for _, tc := range cases { - repoCall := pRepo.On("Evaluate", context.Background(), "group", mock.Anything).Return(nil) - repoCall1 := gRepo.On("Update", context.Background(), mock.Anything).Return(tc.response, tc.err) - expectedGroup, err := svc.UpdateGroup(context.Background(), tc.token, tc.group) - assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) - assert.Equal(t, tc.response, expectedGroup, fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.response, expectedGroup)) - repoCall.Unset() - repoCall1.Unset() - } - -} - -func TestViewGroup(t *testing.T) { - cRepo := new(cmocks.ClientRepository) - gRepo := new(gmocks.GroupRepository) - pRepo := new(pmocks.PolicyRepository) - tokenizer := jwt.NewTokenRepo([]byte(secret)) - - csvc := clients.NewService(cRepo, pRepo, tokenizer, phasher, idProvider) - svc := groups.NewService(gRepo, pRepo, tokenizer, idProvider) - - group.ID = testsutil.GenerateUUID(t, idProvider) - - cases := []struct { - desc string - token string - groupID string - response groups.Group - err error - }{ - { - - desc: "view group", - token: generateValidToken(t, testsutil.GenerateUUID(t, idProvider), csvc, cRepo), - groupID: group.ID, - response: group, - err: nil, - }, - { - desc: "view group with invalid token", - token: "wrongtoken", - groupID: group.ID, - response: groups.Group{}, - err: errors.ErrAuthentication, - }, - { - desc: "view group for wrong id", - token: generateValidToken(t, testsutil.GenerateUUID(t, idProvider), csvc, cRepo), - groupID: mocks.WrongID, - response: groups.Group{}, - err: errors.ErrNotFound, - }, - } - - for _, tc := range cases { - repoCall := pRepo.On("Evaluate", context.Background(), "group", mock.Anything).Return(nil) - repoCall1 := gRepo.On("RetrieveByID", context.Background(), mock.Anything).Return(tc.response, tc.err) - expected, err := svc.ViewGroup(context.Background(), tc.token, tc.groupID) - assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) - assert.Equal(t, expected, tc.response, fmt.Sprintf("%s: expected %v got %v\n", tc.desc, expected, tc.response)) - repoCall.Unset() - repoCall1.Unset() - } -} - -func TestListGroups(t *testing.T) { - cRepo := new(cmocks.ClientRepository) - gRepo := new(gmocks.GroupRepository) - pRepo := new(pmocks.PolicyRepository) - tokenizer := jwt.NewTokenRepo([]byte(secret)) - - csvc := clients.NewService(cRepo, pRepo, tokenizer, phasher, idProvider) - svc := groups.NewService(gRepo, pRepo, tokenizer, idProvider) - - nGroups := uint64(200) - parentID := "" - var aGroups = []groups.Group{} - for i := uint64(0); i < nGroups; i++ { - group := groups.Group{ - ID: testsutil.GenerateUUID(t, idProvider), - Name: fmt.Sprintf("Group_%d", i), - Description: description, - Metadata: groups.Metadata{ - "field": "value", - }, - ParentID: parentID, - } - parentID = group.ID - aGroups = append(aGroups, group) - } - - cases := []struct { - desc string - token string - size uint64 - response groups.GroupsPage - page groups.GroupsPage - err error - }{ - { - desc: "list all groups", - token: generateValidToken(t, testsutil.GenerateUUID(t, idProvider), csvc, cRepo), - size: nGroups, - err: nil, - page: groups.GroupsPage{ - Page: groups.Page{ - Offset: 0, - Total: nGroups, - Limit: nGroups, - }, - }, - response: groups.GroupsPage{ - Page: groups.Page{ - Offset: 0, - Total: nGroups, - Limit: nGroups, - }, - Groups: aGroups, - }, - }, - { - desc: "list groups with an offset", - token: generateValidToken(t, testsutil.GenerateUUID(t, idProvider), csvc, cRepo), - size: 150, - err: nil, - page: groups.GroupsPage{ - Page: groups.Page{ - Offset: 50, - Total: nGroups, - Limit: nGroups, - }, - }, - response: groups.GroupsPage{ - Page: groups.Page{ - Offset: 0, - Total: 150, - Limit: nGroups, - }, - Groups: aGroups[50:nGroups], - }, - }, - } - - for _, tc := range cases { - repoCall := pRepo.On("Evaluate", context.Background(), "group", mock.Anything).Return(nil) - repoCall1 := gRepo.On("RetrieveAll", context.Background(), mock.Anything).Return(tc.response, tc.err) - page, err := svc.ListGroups(context.Background(), tc.token, tc.page) - assert.Equal(t, tc.response, page, fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.response, page)) - assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) - repoCall.Unset() - repoCall1.Unset() - } - -} - -func TestEnableGroup(t *testing.T) { - cRepo := new(cmocks.ClientRepository) - gRepo := new(gmocks.GroupRepository) - pRepo := new(pmocks.PolicyRepository) - tokenizer := jwt.NewTokenRepo([]byte(secret)) - - csvc := clients.NewService(cRepo, pRepo, tokenizer, phasher, idProvider) - svc := groups.NewService(gRepo, pRepo, tokenizer, idProvider) - - enabledGroup1 := groups.Group{ID: testsutil.GenerateUUID(t, idProvider), Name: "group1", Status: groups.EnabledStatus} - disabledGroup := groups.Group{ID: testsutil.GenerateUUID(t, idProvider), Name: "group2", Status: groups.DisabledStatus} - disabledGroup1 := disabledGroup - disabledGroup1.Status = groups.EnabledStatus - - casesEnabled := []struct { - desc string - id string - token string - group groups.Group - response groups.Group - err error - }{ - { - desc: "enable disabled group", - id: disabledGroup.ID, - token: generateValidToken(t, testsutil.GenerateUUID(t, idProvider), csvc, cRepo), - group: disabledGroup, - response: disabledGroup1, - err: nil, - }, - { - desc: "enable enabled group", - id: enabledGroup1.ID, - token: generateValidToken(t, testsutil.GenerateUUID(t, idProvider), csvc, cRepo), - group: enabledGroup1, - response: enabledGroup1, - err: clients.ErrStatusAlreadyAssigned, - }, - { - desc: "enable non-existing group", - id: mocks.WrongID, - token: generateValidToken(t, testsutil.GenerateUUID(t, idProvider), csvc, cRepo), - group: groups.Group{}, - response: groups.Group{}, - err: errors.ErrNotFound, - }, - } - - for _, tc := range casesEnabled { - repoCall := pRepo.On("Evaluate", context.Background(), "group", mock.Anything).Return(nil) - repoCall1 := gRepo.On("RetrieveByID", context.Background(), mock.Anything).Return(tc.group, tc.err) - repoCall2 := gRepo.On("ChangeStatus", context.Background(), mock.Anything, mock.Anything).Return(tc.response, tc.err) - _, err := svc.EnableGroup(context.Background(), tc.token, tc.id) - assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) - repoCall1.Unset() - repoCall2.Unset() - repoCall.Unset() - } - - casesDisabled := []struct { - desc string - status groups.Status - size uint64 - response groups.GroupsPage - }{ - { - desc: "list activated groups", - status: groups.EnabledStatus, - size: 2, - response: groups.GroupsPage{ - Page: groups.Page{ - Total: 2, - Offset: 0, - Limit: 100, - }, - Groups: []groups.Group{enabledGroup1, disabledGroup1}, - }, - }, - { - desc: "list deactivated groups", - status: groups.DisabledStatus, - size: 1, - response: groups.GroupsPage{ - Page: groups.Page{ - Total: 1, - Offset: 0, - Limit: 100, - }, - Groups: []groups.Group{disabledGroup}, - }, - }, - { - desc: "list activated and deactivated groups", - status: groups.AllStatus, - size: 3, - response: groups.GroupsPage{ - Page: groups.Page{ - Total: 3, - Offset: 0, - Limit: 100, - }, - Groups: []groups.Group{enabledGroup1, disabledGroup, disabledGroup1}, - }, - }, - } - - for _, tc := range casesDisabled { - pm := groups.GroupsPage{ - Page: groups.Page{ - Offset: 0, - Limit: 100, - Status: tc.status, - }, - } - repoCall := pRepo.On("Evaluate", context.Background(), "group", mock.Anything).Return(nil) - repoCall1 := gRepo.On("RetrieveAll", context.Background(), mock.Anything).Return(tc.response, nil) - page, err := svc.ListGroups(context.Background(), generateValidToken(t, testsutil.GenerateUUID(t, idProvider), csvc, cRepo), pm) - require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err)) - size := uint64(len(page.Groups)) - assert.Equal(t, tc.size, size, fmt.Sprintf("%s: expected size %d got %d\n", tc.desc, tc.size, size)) - repoCall.Unset() - repoCall1.Unset() - } -} - -func TestDisableGroup(t *testing.T) { - cRepo := new(cmocks.ClientRepository) - gRepo := new(gmocks.GroupRepository) - pRepo := new(pmocks.PolicyRepository) - tokenizer := jwt.NewTokenRepo([]byte(secret)) - - csvc := clients.NewService(cRepo, pRepo, tokenizer, phasher, idProvider) - svc := groups.NewService(gRepo, pRepo, tokenizer, idProvider) - - enabledGroup1 := groups.Group{ID: testsutil.GenerateUUID(t, idProvider), Name: "group1", Status: groups.EnabledStatus} - disabledGroup := groups.Group{ID: testsutil.GenerateUUID(t, idProvider), Name: "group2", Status: groups.DisabledStatus} - disabledGroup1 := enabledGroup1 - disabledGroup1.Status = groups.DisabledStatus - - casesDisabled := []struct { - desc string - id string - token string - group groups.Group - response groups.Group - err error - }{ - { - desc: "disable enabled group", - id: enabledGroup1.ID, - token: generateValidToken(t, testsutil.GenerateUUID(t, idProvider), csvc, cRepo), - group: enabledGroup1, - response: disabledGroup1, - err: nil, - }, - { - desc: "disable disabled group", - id: disabledGroup.ID, - token: generateValidToken(t, testsutil.GenerateUUID(t, idProvider), csvc, cRepo), - group: disabledGroup, - response: groups.Group{}, - err: clients.ErrStatusAlreadyAssigned, - }, - { - desc: "disable non-existing group", - id: mocks.WrongID, - group: groups.Group{}, - token: generateValidToken(t, testsutil.GenerateUUID(t, idProvider), csvc, cRepo), - response: groups.Group{}, - err: errors.ErrNotFound, - }, - } - - for _, tc := range casesDisabled { - repoCall := pRepo.On("Evaluate", context.Background(), "group", mock.Anything).Return(nil) - repoCall1 := gRepo.On("RetrieveByID", context.Background(), mock.Anything).Return(tc.group, tc.err) - repoCall2 := gRepo.On("ChangeStatus", context.Background(), mock.Anything, mock.Anything).Return(tc.response, tc.err) - _, err := svc.DisableGroup(context.Background(), tc.token, tc.id) - assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) - repoCall.Unset() - repoCall1.Unset() - repoCall2.Unset() - } - - casesEnabled := []struct { - desc string - status groups.Status - size uint64 - response groups.GroupsPage - }{ - { - desc: "list activated groups", - status: groups.EnabledStatus, - size: 1, - response: groups.GroupsPage{ - Page: groups.Page{ - Total: 1, - Offset: 0, - Limit: 100, - }, - Groups: []groups.Group{enabledGroup1}, - }, - }, - { - desc: "list deactivated groups", - status: groups.DisabledStatus, - size: 2, - response: groups.GroupsPage{ - Page: groups.Page{ - Total: 2, - Offset: 0, - Limit: 100, - }, - Groups: []groups.Group{disabledGroup1, disabledGroup}, - }, - }, - { - desc: "list activated and deactivated groups", - status: groups.AllStatus, - size: 3, - response: groups.GroupsPage{ - Page: groups.Page{ - Total: 3, - Offset: 0, - Limit: 100, - }, - Groups: []groups.Group{enabledGroup1, disabledGroup, disabledGroup1}, - }, - }, - } - - for _, tc := range casesEnabled { - pm := groups.GroupsPage{ - Page: groups.Page{ - Offset: 0, - Limit: 100, - Status: tc.status, - }, - } - repoCall := pRepo.On("Evaluate", context.Background(), "group", mock.Anything).Return(nil) - repoCall1 := gRepo.On("RetrieveAll", context.Background(), mock.Anything).Return(tc.response, nil) - page, err := svc.ListGroups(context.Background(), generateValidToken(t, testsutil.GenerateUUID(t, idProvider), csvc, cRepo), pm) - require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err)) - size := uint64(len(page.Groups)) - assert.Equal(t, tc.size, size, fmt.Sprintf("%s: expected size %d got %d\n", tc.desc, tc.size, size)) - repoCall.Unset() - repoCall1.Unset() - } -} - -func TestListMemberships(t *testing.T) { - cRepo := new(cmocks.ClientRepository) - gRepo := new(gmocks.GroupRepository) - pRepo := new(pmocks.PolicyRepository) - tokenizer := jwt.NewTokenRepo([]byte(secret)) - csvc := clients.NewService(cRepo, pRepo, tokenizer, phasher, idProvider) - svc := groups.NewService(gRepo, pRepo, tokenizer, idProvider) - - var nGroups = uint64(100) - var aGroups = []groups.Group{} - for i := uint64(1); i < nGroups; i++ { - group := groups.Group{ - Name: fmt.Sprintf("membership_%d@example.com", i), - Metadata: groups.Metadata{"role": "group"}, - } - aGroups = append(aGroups, group) - } - validID := testsutil.GenerateUUID(t, idProvider) - validToken := generateValidToken(t, validID, csvc, cRepo) - - cases := []struct { - desc string - token string - clientID string - page groups.GroupsPage - response groups.MembershipsPage - err error - }{ - { - desc: "list clients with authorized token", - token: validToken, - clientID: testsutil.GenerateUUID(t, idProvider), - page: groups.GroupsPage{ - Page: groups.Page{ - Action: "g_list", - Subject: validID, - }, - }, - response: groups.MembershipsPage{ - Page: groups.Page{ - Total: nGroups, - Offset: 0, - Limit: 0, - }, - Memberships: aGroups, - }, - err: nil, - }, - { - desc: "list clients with offset and limit", - token: validToken, - clientID: testsutil.GenerateUUID(t, idProvider), - page: groups.GroupsPage{ - Page: groups.Page{ - Offset: 6, - Total: nGroups, - Limit: nGroups, - Status: groups.AllStatus, - Subject: validID, - Action: "g_list", - }, - }, - response: groups.MembershipsPage{ - Page: groups.Page{ - Total: nGroups - 6, - }, - Memberships: aGroups[6:nGroups], - }, - }, - { - desc: "list clients with an invalid token", - token: inValidToken, - clientID: testsutil.GenerateUUID(t, idProvider), - page: groups.GroupsPage{ - Page: groups.Page{ - Action: "g_list", - Subject: validID, - }, - }, - response: groups.MembershipsPage{ - Page: groups.Page{ - Total: 0, - Offset: 0, - Limit: 0, - }, - }, - err: errors.ErrAuthentication, - }, - { - desc: "list clients with an invalid id", - token: validToken, - clientID: mocks.WrongID, - page: groups.GroupsPage{ - Page: groups.Page{ - Action: "g_list", - Subject: validID, - }, - }, - response: groups.MembershipsPage{ - Page: groups.Page{ - Total: 0, - Offset: 0, - Limit: 0, - }, - }, - err: errors.ErrNotFound, - }, - } - - for _, tc := range cases { - repoCall := gRepo.On("Memberships", context.Background(), tc.clientID, tc.page).Return(tc.response, tc.err) - page, err := svc.ListMemberships(context.Background(), tc.token, tc.clientID, tc.page) - assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) - assert.Equal(t, tc.response, page, fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.response, page)) - repoCall.Unset() - } -} diff --git a/clients/groups/status.go b/clients/groups/status.go deleted file mode 100644 index 39fffd21508..00000000000 --- a/clients/groups/status.go +++ /dev/null @@ -1,53 +0,0 @@ -package groups - -import "github.com/mainflux/mainflux/internal/apiutil" - -// Status represents Group status. -type Status uint8 - -// Possible Group status values -const ( - DisabledStatus Status = iota - EnabledStatus - - // AllStatus is used for querying purposes to list groups irrespective - // of their status - both active and inactive. It is never stored in the - // database as the actual Group status and should always be the largest - // value in this enumeration. - AllStatus -) - -// String representation of the possible status values. -const ( - Disabled = "disabled" - Enabled = "enabled" - All = "all" - Unknown = "unknown" -) - -// String converts group status to string literal. -func (s Status) String() string { - switch s { - case DisabledStatus: - return Disabled - case EnabledStatus: - return Enabled - case AllStatus: - return All - default: - return Unknown - } -} - -// ToStatus converts string value to a valid Group status. -func ToStatus(status string) (Status, error) { - switch status { - case Disabled: - return DisabledStatus, nil - case Enabled: - return EnabledStatus, nil - case All: - return AllStatus, nil - } - return Status(0), apiutil.ErrInvalidStatus -} diff --git a/clients/groups/tracing/tracing.go b/clients/groups/tracing/tracing.go deleted file mode 100644 index b653b8a1e5f..00000000000 --- a/clients/groups/tracing/tracing.go +++ /dev/null @@ -1,72 +0,0 @@ -package tracing - -import ( - "context" - - "github.com/mainflux/mainflux/clients/groups" - "go.opentelemetry.io/otel/attribute" - "go.opentelemetry.io/otel/trace" -) - -var _ groups.Service = (*tracingMiddleware)(nil) - -type tracingMiddleware struct { - tracer trace.Tracer - gsvc groups.Service -} - -func TracingMiddleware(gsvc groups.Service, tracer trace.Tracer) groups.Service { - return &tracingMiddleware{tracer, gsvc} -} - -func (tm *tracingMiddleware) ListMemberships(ctx context.Context, token, clientID string, gm groups.GroupsPage) (groups.MembershipsPage, error) { - ctx, span := tm.tracer.Start(ctx, "svc_list_memberships") - defer span.End() - return tm.gsvc.ListMemberships(ctx, token, clientID, gm) -} - -func (tm *tracingMiddleware) CreateGroup(ctx context.Context, token string, g groups.Group) (groups.Group, error) { - ctx, span := tm.tracer.Start(ctx, "svc_create_group", trace.WithAttributes(attribute.String("Name", g.Name))) - defer span.End() - - return tm.gsvc.CreateGroup(ctx, token, g) - -} - -func (tm *tracingMiddleware) ViewGroup(ctx context.Context, token string, id string) (groups.Group, error) { - ctx, span := tm.tracer.Start(ctx, "svc_view_group", trace.WithAttributes(attribute.String("ID", id))) - defer span.End() - - return tm.gsvc.ViewGroup(ctx, token, id) - -} - -func (tm *tracingMiddleware) ListGroups(ctx context.Context, token string, gm groups.GroupsPage) (groups.GroupsPage, error) { - ctx, span := tm.tracer.Start(ctx, "svc_list_groups") - defer span.End() - - return tm.gsvc.ListGroups(ctx, token, gm) - -} - -func (tm *tracingMiddleware) UpdateGroup(ctx context.Context, token string, g groups.Group) (groups.Group, error) { - ctx, span := tm.tracer.Start(ctx, "svc_update_group", trace.WithAttributes(attribute.String("Name", g.Name))) - defer span.End() - - return tm.gsvc.UpdateGroup(ctx, token, g) - -} - -func (tm *tracingMiddleware) EnableGroup(ctx context.Context, token, id string) (groups.Group, error) { - ctx, span := tm.tracer.Start(ctx, "svc_enable_group", trace.WithAttributes(attribute.String("ID", id))) - defer span.End() - - return tm.gsvc.EnableGroup(ctx, token, id) -} - -func (tm *tracingMiddleware) DisableGroup(ctx context.Context, token, id string) (groups.Group, error) { - ctx, span := tm.tracer.Start(ctx, "svc_disable_group", trace.WithAttributes(attribute.String("ID", id))) - defer span.End() - - return tm.gsvc.DisableGroup(ctx, token, id) -} diff --git a/clients/hasher/hasher.go b/clients/hasher/hasher.go deleted file mode 100644 index ce46e8407eb..00000000000 --- a/clients/hasher/hasher.go +++ /dev/null @@ -1,40 +0,0 @@ -// Package hasher provides a hasher implementation utilizing bcrypt. -package hasher - -import ( - "github.com/mainflux/mainflux/clients/clients" - "github.com/mainflux/mainflux/pkg/errors" - "golang.org/x/crypto/bcrypt" -) - -const cost int = 10 - -var ( - errHashPassword = errors.New("Generate hash from password failed") - errComparePassword = errors.New("Compare hash and password failed") -) - -var _ clients.Hasher = (*bcryptHasher)(nil) - -type bcryptHasher struct{} - -// New instantiates a bcrypt-based hasher implementation. -func New() clients.Hasher { - return &bcryptHasher{} -} - -func (bh *bcryptHasher) Hash(pwd string) (string, error) { - hash, err := bcrypt.GenerateFromPassword([]byte(pwd), cost) - if err != nil { - return "", errors.Wrap(errHashPassword, err) - } - - return string(hash), nil -} - -func (bh *bcryptHasher) Compare(plain, hashed string) error { - if err := bcrypt.CompareHashAndPassword([]byte(hashed), []byte(plain)); err != nil { - return errors.Wrap(errComparePassword, err) - } - return nil -} diff --git a/clients/jwt/jwt.go b/clients/jwt/jwt.go deleted file mode 100644 index fe2125c3d87..00000000000 --- a/clients/jwt/jwt.go +++ /dev/null @@ -1,48 +0,0 @@ -package jwt - -import ( - "context" -) - -// Possible token types are access and refresh tokens. -const ( - RefreshToken = "refresh" - AccessToken = "access" -) - -// Token is used for authentication purposes. -// It contains AccessToken, RefreshToken, Type and AccessExpiry. -type Token struct { - AccessToken string // AccessToken contains the security credentials for a login session and identifies the client. - RefreshToken string // RefreshToken is a credential artifact that OAuth can use to get a new access token without client interaction. - AccessType string // AccessType is the specific type of access token issued. It can be Bearer, Client or Basic. -} - -// Claims are the Client's internal JWT Claims. -type Claims struct { - ClientID string // ClientID is the client unique identifier. - Role string // Role is the role of the specific client. - Tag string // Tag related to the client. - Type string // Type denotes the type of claim. Either AccessToken or RefreshToken. -} - -// TokenService specifies an API that must be fulfilled by the domain service -// implementation, and all of its decorators (e.g. logging & metrics). -type TokenService interface { - // IssueToken issues a new access and refresh token. - IssueToken(ctx context.Context, identity, secret string) (Token, error) - - // RefreshToken refreshes expired access tokens. - // After an access token expires, the refresh token is used to get - // a new pair of access and refresh tokens. - RefreshToken(ctx context.Context, accessToken string) (Token, error) -} - -// TokenRepository specifies an account persistence API. -type TokenRepository interface { - // Issue issues a new access and refresh token. - Issue(ctx context.Context, claim Claims) (Token, error) - - // Parse checks the validity of a token. - Parse(ctx context.Context, token string) (Claims, error) -} diff --git a/clients/jwt/tokens.go b/clients/jwt/tokens.go deleted file mode 100644 index 2aa9134c3c0..00000000000 --- a/clients/jwt/tokens.go +++ /dev/null @@ -1,102 +0,0 @@ -package jwt - -import ( - "context" - "time" - - "github.com/lestrrat-go/jwx/v2/jwa" - "github.com/lestrrat-go/jwx/v2/jwt" - "github.com/mainflux/mainflux/pkg/errors" -) - -const issuerName = "clients.auth" - -var _ TokenRepository = (*tokenRepo)(nil) - -var ( - accessDuration time.Duration = time.Hour * 15 - refreshDuration time.Duration = time.Hour * 24 -) - -type tokenRepo struct { - secret []byte -} - -// NewTokenRepo instantiates an implementation of Token repository. -func NewTokenRepo(secret []byte) TokenRepository { - return &tokenRepo{ - secret: secret, - } -} - -func (repo tokenRepo) Issue(ctx context.Context, claim Claims) (Token, error) { - aexpiry := time.Now().Add(accessDuration) - accessToken, err := jwt.NewBuilder(). - Issuer(issuerName). - IssuedAt(time.Now()). - Subject(claim.ClientID). - Claim("type", AccessToken). - Claim("role", claim.Role). - Claim("tag", claim.Tag). - Expiration(aexpiry). - Build() - if err != nil { - return Token{}, errors.Wrap(errors.ErrAuthentication, err) - } - signedAccessToken, err := jwt.Sign(accessToken, jwt.WithKey(jwa.HS512, repo.secret)) - if err != nil { - return Token{}, errors.Wrap(errors.ErrAuthentication, err) - } - refreshToken, err := jwt.NewBuilder(). - Issuer(issuerName). - IssuedAt(time.Now()). - Subject(claim.ClientID). - Claim("type", RefreshToken). - Claim("role", claim.Role). - Claim("tag", claim.Tag). - Expiration(time.Now().Add(refreshDuration)). - Build() - if err != nil { - return Token{}, errors.Wrap(errors.ErrAuthentication, err) - } - signedRefreshToken, err := jwt.Sign(refreshToken, jwt.WithKey(jwa.HS512, repo.secret)) - if err != nil { - return Token{}, errors.Wrap(errors.ErrAuthentication, err) - } - - return Token{ - AccessToken: string(signedAccessToken[:]), - RefreshToken: string(signedRefreshToken[:]), - AccessType: "Bearer", - }, nil -} - -func (repo tokenRepo) Parse(ctx context.Context, accessToken string) (Claims, error) { - token, err := jwt.Parse( - []byte(accessToken), - jwt.WithValidate(true), - jwt.WithKey(jwa.HS512, repo.secret), - ) - if err != nil { - return Claims{}, errors.Wrap(errors.ErrAuthentication, err) - } - tType, ok := token.Get("type") - if !ok { - return Claims{}, errors.Wrap(errors.ErrAuthentication, err) - } - role, ok := token.Get("role") - if !ok { - return Claims{}, errors.Wrap(errors.ErrAuthentication, err) - } - tag, ok := token.Get("tag") - if !ok { - return Claims{}, errors.Wrap(errors.ErrAuthentication, err) - } - claim := Claims{ - ClientID: token.Subject(), - Role: role.(string), - Tag: tag.(string), - Type: tType.(string), - } - return claim, nil -} diff --git a/clients/jwt/tracing.go b/clients/jwt/tracing.go deleted file mode 100644 index c075f37b4d6..00000000000 --- a/clients/jwt/tracing.go +++ /dev/null @@ -1,37 +0,0 @@ -package jwt - -import ( - "context" - - "go.opentelemetry.io/otel/attribute" - "go.opentelemetry.io/otel/trace" -) - -var _ TokenRepository = (*tokenRepoMiddlware)(nil) - -type tokenRepoMiddlware struct { - repo TokenRepository - tracer trace.Tracer -} - -// NewTokenRepoMiddleware instantiates an implementation of tracing Token repository. -func NewTokenRepoMiddleware(repo TokenRepository, tracer trace.Tracer) TokenRepository { - return &tokenRepoMiddlware{ - repo: repo, - tracer: tracer, - } -} - -func (trm tokenRepoMiddlware) Issue(ctx context.Context, claim Claims) (Token, error) { - ctx, span := trm.tracer.Start(ctx, "issue_token", trace.WithAttributes(attribute.String("clientid", claim.ClientID))) - defer span.End() - - return trm.repo.Issue(ctx, claim) -} - -func (trm tokenRepoMiddlware) Parse(ctx context.Context, accessToken string) (Claims, error) { - ctx, span := trm.tracer.Start(ctx, "parse_token", trace.WithAttributes(attribute.String("accesstoken", accessToken))) - defer span.End() - - return trm.repo.Parse(ctx, accessToken) -} diff --git a/clients/policies/api/grpc/client.go b/clients/policies/api/grpc/client.go deleted file mode 100644 index db50eb2756b..00000000000 --- a/clients/policies/api/grpc/client.go +++ /dev/null @@ -1,66 +0,0 @@ -package grpc - -import ( - "context" - "time" - - "github.com/go-kit/kit/endpoint" - kitgrpc "github.com/go-kit/kit/transport/grpc" - "github.com/mainflux/mainflux/clients/policies" - opentracing "github.com/opentracing/opentracing-go" - "go.opentelemetry.io/contrib/instrumentation/github.com/go-kit/kit/otelkit" - "google.golang.org/grpc" -) - -const svcName = "policies.AuthService" - -var _ policies.AuthServiceClient = (*grpcClient)(nil) - -type grpcClient struct { - authorize endpoint.Endpoint - timeout time.Duration -} - -// NewClient returns new gRPC client instance. -func NewClient(tracer opentracing.Tracer, conn *grpc.ClientConn, timeout time.Duration) policies.AuthServiceClient { - return &grpcClient{ - authorize: otelkit.EndpointMiddleware(otelkit.WithOperation("authorize"))(kitgrpc.NewClient( - conn, - svcName, - "Authorize", - encodeAuthorizeRequest, - decodeAuthorizeResponse, - policies.AuthorizeRes{}, - ).Endpoint()), - - timeout: timeout, - } -} - -func (client grpcClient) Authorize(ctx context.Context, req *policies.AuthorizeReq, _ ...grpc.CallOption) (r *policies.AuthorizeRes, err error) { - ctx, close := context.WithTimeout(ctx, client.timeout) - defer close() - - res, err := client.authorize(ctx, authReq{Act: req.GetAct(), Obj: req.GetObj(), Sub: req.GetSub(), EntityType: req.GetEntityType()}) - if err != nil { - return &policies.AuthorizeRes{}, err - } - - ar := res.(authorizeRes) - return &policies.AuthorizeRes{Authorized: ar.authorized}, err -} - -func decodeAuthorizeResponse(_ context.Context, grpcRes interface{}) (interface{}, error) { - res := grpcRes.(*policies.AuthorizeRes) - return authorizeRes{authorized: res.Authorized}, nil -} - -func encodeAuthorizeRequest(_ context.Context, grpcReq interface{}) (interface{}, error) { - req := grpcReq.(authReq) - return &policies.AuthorizeReq{ - Sub: req.Sub, - Obj: req.Obj, - Act: req.Act, - EntityType: req.EntityType, - }, nil -} diff --git a/clients/policies/api/grpc/endpoint.go b/clients/policies/api/grpc/endpoint.go deleted file mode 100644 index 7da15d1d0d1..00000000000 --- a/clients/policies/api/grpc/endpoint.go +++ /dev/null @@ -1,24 +0,0 @@ -package grpc - -import ( - "context" - - "github.com/go-kit/kit/endpoint" - "github.com/mainflux/mainflux/clients/policies" -) - -func authorizeEndpoint(svc policies.Service) endpoint.Endpoint { - return func(ctx context.Context, request interface{}) (interface{}, error) { - req := request.(authReq) - - if err := req.validate(); err != nil { - return authorizeRes{}, err - } - - err := svc.Authorize(ctx, req.EntityType, policies.Policy{Subject: req.Sub, Object: req.Obj, Actions: []string{req.Act}}) - if err != nil { - return authorizeRes{}, err - } - return authorizeRes{authorized: true}, err - } -} diff --git a/clients/policies/api/grpc/requests.go b/clients/policies/api/grpc/requests.go deleted file mode 100644 index 8a4cd4d3059..00000000000 --- a/clients/policies/api/grpc/requests.go +++ /dev/null @@ -1,33 +0,0 @@ -package grpc - -import ( - "github.com/mainflux/mainflux/internal/apiutil" -) - -// authReq represents authorization request. It contains: -// 1. subject - an action invoker (client) -// 2. object - an entity over which action will be executed (client, group, computation, dataset) -// 3. action - type of action that will be executed (read/write) -type authReq struct { - Sub string - Obj string - Act string - EntityType string -} - -func (req authReq) validate() error { - if req.Sub == "" { - return apiutil.ErrMissingPolicySub - } - if req.Obj == "" { - return apiutil.ErrMissingPolicyObj - } - if req.Act == "" { - return apiutil.ErrMissingPolicyAct - } - if req.EntityType == "" { - return apiutil.ErrMissingPolicyEntityType - } - - return nil -} diff --git a/clients/policies/api/grpc/responses.go b/clients/policies/api/grpc/responses.go deleted file mode 100644 index fd0fba28a0c..00000000000 --- a/clients/policies/api/grpc/responses.go +++ /dev/null @@ -1,5 +0,0 @@ -package grpc - -type authorizeRes struct { - authorized bool -} diff --git a/clients/policies/api/grpc/transport.go b/clients/policies/api/grpc/transport.go deleted file mode 100644 index b30af2754d6..00000000000 --- a/clients/policies/api/grpc/transport.go +++ /dev/null @@ -1,73 +0,0 @@ -package grpc - -import ( - "context" - - kitgrpc "github.com/go-kit/kit/transport/grpc" - "github.com/mainflux/mainflux/clients/policies" - "github.com/mainflux/mainflux/internal/apiutil" - "github.com/mainflux/mainflux/pkg/errors" - "go.opentelemetry.io/contrib/instrumentation/github.com/go-kit/kit/otelkit" - "google.golang.org/grpc/codes" - "google.golang.org/grpc/status" -) - -var _ policies.AuthServiceServer = (*grpcServer)(nil) - -type grpcServer struct { - authorize kitgrpc.Handler - policies.UnimplementedAuthServiceServer -} - -// NewServer returns new AuthServiceServer instance. -func NewServer(svc policies.Service) policies.AuthServiceServer { - return &grpcServer{ - authorize: kitgrpc.NewServer( - otelkit.EndpointMiddleware(otelkit.WithOperation("authorize"))(authorizeEndpoint(svc)), - decodeAuthorizeRequest, - encodeAuthorizeResponse, - ), - } -} - -func (s *grpcServer) Authorize(ctx context.Context, req *policies.AuthorizeReq) (*policies.AuthorizeRes, error) { - _, res, err := s.authorize.ServeGRPC(ctx, req) - if err != nil { - return nil, encodeError(err) - } - return res.(*policies.AuthorizeRes), nil -} - -func decodeAuthorizeRequest(_ context.Context, grpcReq interface{}) (interface{}, error) { - req := grpcReq.(*policies.AuthorizeReq) - return authReq{Act: req.GetAct(), Obj: req.GetObj(), Sub: req.GetSub(), EntityType: req.GetEntityType()}, nil -} - -func encodeAuthorizeResponse(_ context.Context, grpcRes interface{}) (interface{}, error) { - res := grpcRes.(authorizeRes) - return &policies.AuthorizeRes{Authorized: res.authorized}, nil -} - -func encodeError(err error) error { - switch { - case errors.Contains(err, nil): - return nil - case errors.Contains(err, errors.ErrMalformedEntity), - err == apiutil.ErrInvalidAuthKey, - err == apiutil.ErrMissingID, - err == apiutil.ErrMissingPolicySub, - err == apiutil.ErrMissingPolicyObj, - err == apiutil.ErrMissingPolicyAct, - err == apiutil.ErrMalformedPolicy, - err == apiutil.ErrMissingPolicyOwner, - err == apiutil.ErrHigherPolicyRank: - return status.Error(codes.InvalidArgument, err.Error()) - case errors.Contains(err, errors.ErrAuthentication), - err == apiutil.ErrBearerToken: - return status.Error(codes.Unauthenticated, err.Error()) - case errors.Contains(err, errors.ErrAuthorization): - return status.Error(codes.PermissionDenied, err.Error()) - default: - return status.Error(codes.Internal, "internal server error") - } -} diff --git a/clients/policies/api/http/endpoints.go b/clients/policies/api/http/endpoints.go deleted file mode 100644 index fbe120143f7..00000000000 --- a/clients/policies/api/http/endpoints.go +++ /dev/null @@ -1,141 +0,0 @@ -package api - -import ( - "context" - - "github.com/go-kit/kit/endpoint" - "github.com/mainflux/mainflux/clients/policies" -) - -func authorizeEndpoint(svc policies.Service) endpoint.Endpoint { - return func(ctx context.Context, request interface{}) (interface{}, error) { - req := request.(authorizeReq) - if err := req.validate(); err != nil { - return authorizeRes{authorized: false}, err - } - policy := policies.Policy{ - Subject: req.Subject, - Object: req.Object, - Actions: req.Actions, - } - err := svc.Authorize(ctx, req.EntityType, policy) - if err != nil { - return authorizeRes{authorized: false}, err - } - - return authorizeRes{authorized: true}, nil - } -} - -func createPolicyEndpoint(svc policies.Service) endpoint.Endpoint { - return func(ctx context.Context, request interface{}) (interface{}, error) { - req := request.(createPolicyReq) - if err := req.validate(); err != nil { - return addPolicyRes{}, err - } - - policy := policies.Policy{ - Subject: req.Subject, - Object: req.Object, - Actions: req.Actions, - } - err := svc.AddPolicy(ctx, req.token, policy) - if err != nil { - return addPolicyRes{}, err - } - - return addPolicyRes{created: true}, nil - } -} - -func updatePolicyEndpoint(svc policies.Service) endpoint.Endpoint { - return func(ctx context.Context, request interface{}) (interface{}, error) { - req := request.(updatePolicyReq) - if err := req.validate(); err != nil { - return updatePolicyRes{}, err - } - - policy := policies.Policy{ - Subject: req.Subject, - Object: req.Object, - Actions: req.Actions, - } - - err := svc.UpdatePolicy(ctx, req.token, policy) - if err != nil { - return updatePolicyRes{}, err - } - - res := updatePolicyRes{updated: false} - return res, nil - } -} - -func listPolicyEndpoint(svc policies.Service) endpoint.Endpoint { - return func(ctx context.Context, request interface{}) (interface{}, error) { - req := request.(listPolicyReq) - if err := req.validate(); err != nil { - return listPolicyRes{}, err - } - pm := policies.Page{ - Total: req.Total, - Offset: req.Offset, - Limit: req.Limit, - OwnerID: req.OwnerID, - Subject: req.Subject, - Object: req.Object, - Action: req.Actions, - } - page, err := svc.ListPolicy(ctx, req.token, pm) - if err != nil { - return listPolicyRes{}, err - } - return buildGroupsResponse(page), nil - } -} - -func deletePolicyEndpoint(svc policies.Service) endpoint.Endpoint { - return func(ctx context.Context, request interface{}) (interface{}, error) { - req := request.(deletePolicyReq) - if err := req.validate(); err != nil { - return deletePolicyRes{}, err - } - policy := policies.Policy{ - Subject: req.Subject, - Object: req.Object, - } - if err := svc.DeletePolicy(ctx, req.token, policy); err != nil { - return deletePolicyRes{}, err - } - - return deletePolicyRes{}, nil - } -} - -func toViewPolicyRes(group policies.Policy) viewPolicyRes { - return viewPolicyRes{ - OwnerID: group.OwnerID, - Subject: group.Subject, - Object: group.Object, - Actions: group.Actions, - CreatedAt: group.CreatedAt, - UpdatedAt: group.UpdatedAt, - } -} - -func buildGroupsResponse(page policies.PolicyPage) listPolicyRes { - res := listPolicyRes{ - pageRes: pageRes{ - Limit: page.Limit, - Offset: page.Offset, - Total: page.Total, - }, - Policies: []viewPolicyRes{}, - } - - for _, group := range page.Policies { - res.Policies = append(res.Policies, toViewPolicyRes(group)) - } - - return res -} diff --git a/clients/policies/api/http/logging.go b/clients/policies/api/http/logging.go deleted file mode 100644 index 83bdc7ba568..00000000000 --- a/clients/policies/api/http/logging.go +++ /dev/null @@ -1,81 +0,0 @@ -package api - -import ( - "context" - "fmt" - "time" - - "github.com/mainflux/mainflux/clients/policies" - log "github.com/mainflux/mainflux/logger" -) - -var _ policies.Service = (*loggingMiddleware)(nil) - -type loggingMiddleware struct { - logger log.Logger - svc policies.Service -} - -func LoggingMiddleware(svc policies.Service, logger log.Logger) policies.Service { - return &loggingMiddleware{logger, svc} -} - -func (lm *loggingMiddleware) Authorize(ctx context.Context, domain string, p policies.Policy) (err error) { - defer func(begin time.Time) { - message := fmt.Sprintf("Method authorize for client %s took %s to complete", p.Subject, time.Since(begin)) - if err != nil { - lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err)) - return - } - lm.logger.Info(fmt.Sprintf("%s without errors.", message)) - }(time.Now()) - return lm.svc.Authorize(ctx, domain, p) -} - -func (lm *loggingMiddleware) AddPolicy(ctx context.Context, token string, p policies.Policy) (err error) { - defer func(begin time.Time) { - message := fmt.Sprintf("Method add_policy for client %s and token %s took %s to complete", p.Subject, token, time.Since(begin)) - if err != nil { - lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err)) - return - } - lm.logger.Info(fmt.Sprintf("%s without errors.", message)) - }(time.Now()) - return lm.svc.AddPolicy(ctx, token, p) -} - -func (lm *loggingMiddleware) UpdatePolicy(ctx context.Context, token string, p policies.Policy) (err error) { - defer func(begin time.Time) { - message := fmt.Sprintf("Method update_policy for client %s and token %s took %s to complete", p.Subject, token, time.Since(begin)) - if err != nil { - lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err)) - return - } - lm.logger.Info(fmt.Sprintf("%s without errors.", message)) - }(time.Now()) - return lm.svc.UpdatePolicy(ctx, token, p) -} - -func (lm *loggingMiddleware) ListPolicy(ctx context.Context, token string, cp policies.Page) (cg policies.PolicyPage, err error) { - defer func(begin time.Time) { - message := fmt.Sprintf("Method list_policy for client %s and token %s took %s to complete", cp.Subject, token, time.Since(begin)) - if err != nil { - lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err)) - return - } - lm.logger.Info(fmt.Sprintf("%s without errors.", message)) - }(time.Now()) - return lm.svc.ListPolicy(ctx, token, cp) -} - -func (lm *loggingMiddleware) DeletePolicy(ctx context.Context, token string, p policies.Policy) (err error) { - defer func(begin time.Time) { - message := fmt.Sprintf("Method delete_policy for client %s in object %s and token %s took %s to complete", p.Subject, p.Object, token, time.Since(begin)) - if err != nil { - lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err)) - return - } - lm.logger.Info(fmt.Sprintf("%s without errors.", message)) - }(time.Now()) - return lm.svc.DeletePolicy(ctx, token, p) -} diff --git a/clients/policies/api/http/metrics.go b/clients/policies/api/http/metrics.go deleted file mode 100644 index 1773479c03d..00000000000 --- a/clients/policies/api/http/metrics.go +++ /dev/null @@ -1,66 +0,0 @@ -package api - -import ( - "context" - "time" - - "github.com/go-kit/kit/metrics" - "github.com/mainflux/mainflux/clients/policies" -) - -var _ policies.Service = (*metricsMiddleware)(nil) - -type metricsMiddleware struct { - counter metrics.Counter - latency metrics.Histogram - svc policies.Service -} - -// MetricsMiddleware returns a new metrics middleware wrapper. -func MetricsMiddleware(svc policies.Service, counter metrics.Counter, latency metrics.Histogram) policies.Service { - return &metricsMiddleware{ - counter: counter, - latency: latency, - svc: svc, - } -} - -func (ms *metricsMiddleware) Authorize(ctx context.Context, entityType string, p policies.Policy) (err error) { - defer func(begin time.Time) { - ms.counter.With("method", "authorize").Add(1) - ms.latency.With("method", "authorize").Observe(time.Since(begin).Seconds()) - }(time.Now()) - return ms.svc.Authorize(ctx, entityType, p) -} - -func (ms *metricsMiddleware) AddPolicy(ctx context.Context, token string, p policies.Policy) (err error) { - defer func(begin time.Time) { - ms.counter.With("method", "add_policy").Add(1) - ms.latency.With("method", "add_policy").Observe(time.Since(begin).Seconds()) - }(time.Now()) - return ms.svc.AddPolicy(ctx, token, p) -} - -func (ms *metricsMiddleware) UpdatePolicy(ctx context.Context, token string, p policies.Policy) (err error) { - defer func(begin time.Time) { - ms.counter.With("method", "update_policy").Add(1) - ms.latency.With("method", "update_policy").Observe(time.Since(begin).Seconds()) - }(time.Now()) - return ms.svc.UpdatePolicy(ctx, token, p) -} - -func (ms *metricsMiddleware) ListPolicy(ctx context.Context, token string, cp policies.Page) (cg policies.PolicyPage, err error) { - defer func(begin time.Time) { - ms.counter.With("method", "list_policies").Add(1) - ms.latency.With("method", "list_policies").Observe(time.Since(begin).Seconds()) - }(time.Now()) - return ms.svc.ListPolicy(ctx, token, cp) -} - -func (ms *metricsMiddleware) DeletePolicy(ctx context.Context, token string, p policies.Policy) (err error) { - defer func(begin time.Time) { - ms.counter.With("method", "delete_policy").Add(1) - ms.latency.With("method", "delete_policy").Observe(time.Since(begin).Seconds()) - }(time.Now()) - return ms.svc.DeletePolicy(ctx, token, p) -} diff --git a/clients/policies/api/http/requests.go b/clients/policies/api/http/requests.go deleted file mode 100644 index d767d23f2d8..00000000000 --- a/clients/policies/api/http/requests.go +++ /dev/null @@ -1,110 +0,0 @@ -package api - -import ( - "github.com/mainflux/mainflux/clients/policies" - "github.com/mainflux/mainflux/internal/apiutil" -) - -type authorizeReq struct { - Subject string `json:"subject,omitempty"` - Object string `json:"object,omitempty"` - Actions []string `json:"actions,omitempty"` - EntityType string `json:"entity_type,omitempty"` -} - -func (req authorizeReq) validate() error { - for _, a := range req.Actions { - if ok := policies.ValidateAction(a); !ok { - return apiutil.ErrMissingPolicyAct - } - } - if req.Subject == "" { - return apiutil.ErrMissingPolicySub - } - if req.Object == "" { - return apiutil.ErrMissingPolicyObj - } - return nil -} - -type createPolicyReq struct { - token string - Owner string `json:"owner,omitempty"` - Subject string `json:"subject,omitempty"` - Object string `json:"object,omitempty"` - Actions []string `json:"actions,omitempty"` -} - -func (req createPolicyReq) validate() error { - for _, a := range req.Actions { - if ok := policies.ValidateAction(a); !ok { - return apiutil.ErrMissingPolicyAct - } - } - if req.Subject == "" { - return apiutil.ErrMissingPolicySub - } - if req.Object == "" { - return apiutil.ErrMissingPolicyObj - } - return nil -} - -type updatePolicyReq struct { - token string - Subject string `json:"subject,omitempty"` - Object string `json:"object,omitempty"` - Actions []string `json:"actions,omitempty"` -} - -func (req updatePolicyReq) validate() error { - for _, a := range req.Actions { - if ok := policies.ValidateAction(a); !ok { - return apiutil.ErrMissingPolicyAct - } - } - if req.Subject == "" { - return apiutil.ErrMissingPolicySub - } - if req.Object == "" { - return apiutil.ErrMissingPolicyObj - } - return nil -} - -type listPolicyReq struct { - token string - Total uint64 - Offset uint64 - Limit uint64 - OwnerID string - Subject string - Object string - Actions string -} - -func (req listPolicyReq) validate() error { - if req.Actions != "" { - if ok := policies.ValidateAction(req.Actions); !ok { - return apiutil.ErrMissingPolicyAct - } - } - return nil -} - -type deletePolicyReq struct { - token string - Subject string `json:"subject,omitempty"` - Object string `json:"object,omitempty"` -} - -func (req deletePolicyReq) validate() error { - if req.Subject == "" { - return apiutil.ErrMissingPolicySub - } - if req.Object == "" { - return apiutil.ErrMissingPolicyObj - } - - return nil -} diff --git a/clients/policies/api/http/responses.go b/clients/policies/api/http/responses.go deleted file mode 100644 index bd57fd3ba65..00000000000 --- a/clients/policies/api/http/responses.go +++ /dev/null @@ -1,136 +0,0 @@ -package api - -import ( - "fmt" - "net/http" - "time" - - "github.com/mainflux/mainflux" -) - -var ( - _ mainflux.Response = (*authorizeRes)(nil) - _ mainflux.Response = (*addPolicyRes)(nil) - _ mainflux.Response = (*viewPolicyRes)(nil) - _ mainflux.Response = (*listPolicyRes)(nil) - _ mainflux.Response = (*updatePolicyRes)(nil) - _ mainflux.Response = (*deletePolicyRes)(nil) -) - -type pageRes struct { - Limit uint64 `json:"limit,omitempty"` - Offset uint64 `json:"offset,omitempty"` - Total uint64 `json:"total"` - Level uint64 `json:"level"` -} - -type authorizeRes struct { - authorized bool -} - -func (res authorizeRes) Code() int { - return http.StatusOK -} - -func (res authorizeRes) Headers() map[string]string { - return map[string]string{} -} - -func (res authorizeRes) Empty() bool { - return false -} - -type addPolicyRes struct { - id string - created bool -} - -func (res addPolicyRes) Code() int { - if res.created { - return http.StatusCreated - } - - return http.StatusOK -} - -func (res addPolicyRes) Headers() map[string]string { - if res.created { - return map[string]string{ - "Location": fmt.Sprintf("/groups/%s", res.id), - } - } - - return map[string]string{} -} - -func (res addPolicyRes) Empty() bool { - return true -} - -type viewPolicyRes struct { - OwnerID string `json:"owner_id"` - Subject string `json:"subject"` - Object string `json:"object"` - Actions []string `json:"actions"` - CreatedAt time.Time `json:"created_at"` - UpdatedAt time.Time `json:"updated_at"` -} - -func (res viewPolicyRes) Code() int { - return http.StatusOK -} - -func (res viewPolicyRes) Headers() map[string]string { - return map[string]string{} -} - -func (res viewPolicyRes) Empty() bool { - return false -} - -type updatePolicyRes struct { - updated bool -} - -func (res updatePolicyRes) Code() int { - return http.StatusNoContent -} - -func (res updatePolicyRes) Headers() map[string]string { - return map[string]string{} -} - -func (res updatePolicyRes) Empty() bool { - return true -} - -type listPolicyRes struct { - pageRes - Policies []viewPolicyRes `json:"policies"` -} - -func (res listPolicyRes) Code() int { - return http.StatusOK -} - -func (res listPolicyRes) Headers() map[string]string { - return map[string]string{} -} - -func (res listPolicyRes) Empty() bool { - return false -} - -type deletePolicyRes struct{} - -func (res deletePolicyRes) Code() int { - return http.StatusNoContent -} - -func (res deletePolicyRes) Headers() map[string]string { - return map[string]string{} -} - -func (res deletePolicyRes) Empty() bool { - return true -} diff --git a/clients/policies/api/http/transport.go b/clients/policies/api/http/transport.go deleted file mode 100644 index 7f199443608..00000000000 --- a/clients/policies/api/http/transport.go +++ /dev/null @@ -1,163 +0,0 @@ -package api - -import ( - "context" - "encoding/json" - "net/http" - "strings" - - kithttp "github.com/go-kit/kit/transport/http" - "github.com/go-zoo/bone" - "github.com/mainflux/mainflux/clients/policies" - "github.com/mainflux/mainflux/internal/api" - "github.com/mainflux/mainflux/internal/apiutil" - "github.com/mainflux/mainflux/logger" - "github.com/mainflux/mainflux/pkg/errors" - "go.opentelemetry.io/contrib/instrumentation/github.com/go-kit/kit/otelkit" -) - -// MakeHandler returns a HTTP handler for API endpoints. -func MakePolicyHandler(svc policies.Service, mux *bone.Mux, logger logger.Logger) { - opts := []kithttp.ServerOption{ - kithttp.ServerErrorEncoder(apiutil.LoggingErrorEncoder(logger, api.EncodeError)), - } - mux.Post("/authorize", kithttp.NewServer( - otelkit.EndpointMiddleware(otelkit.WithOperation("authorize"))(authorizeEndpoint(svc)), - decodeAuthorize, - api.EncodeResponse, - opts..., - )) - - mux.Post("/policies", kithttp.NewServer( - otelkit.EndpointMiddleware(otelkit.WithOperation("add_policy"))(createPolicyEndpoint(svc)), - decodePolicyCreate, - api.EncodeResponse, - opts..., - )) - - mux.Put("/policies", kithttp.NewServer( - otelkit.EndpointMiddleware(otelkit.WithOperation("update_policy"))(updatePolicyEndpoint(svc)), - decodePolicyUpdate, - api.EncodeResponse, - opts..., - )) - - mux.Get("/policies", kithttp.NewServer( - otelkit.EndpointMiddleware(otelkit.WithOperation("list_policies"))(listPolicyEndpoint(svc)), - decodeListPoliciesRequest, - api.EncodeResponse, - opts..., - )) - - mux.Delete("/policies/:subject/:object", kithttp.NewServer( - otelkit.EndpointMiddleware(otelkit.WithOperation("delete_policy"))(deletePolicyEndpoint(svc)), - deletePolicyRequest, - api.EncodeResponse, - opts..., - )) -} - -func decodeAuthorize(_ context.Context, r *http.Request) (interface{}, error) { - if !strings.Contains(r.Header.Get("Content-Type"), api.ContentType) { - return nil, errors.ErrUnsupportedContentType - } - - var authReq authorizeReq - if err := json.NewDecoder(r.Body).Decode(&authReq); err != nil { - return nil, errors.Wrap(errors.ErrMalformedEntity, err) - } - - return authReq, nil -} - -func decodePolicyCreate(_ context.Context, r *http.Request) (interface{}, error) { - if !strings.Contains(r.Header.Get("Content-Type"), api.ContentType) { - return nil, errors.ErrUnsupportedContentType - } - - var m policies.Policy - if err := json.NewDecoder(r.Body).Decode(&m); err != nil { - return nil, errors.Wrap(errors.ErrMalformedEntity, err) - } - - req := createPolicyReq{ - token: apiutil.ExtractBearerToken(r), - Subject: m.Subject, - Object: m.Object, - Actions: m.Actions, - } - - return req, nil -} - -func decodePolicyUpdate(_ context.Context, r *http.Request) (interface{}, error) { - if !strings.Contains(r.Header.Get("Content-Type"), api.ContentType) { - return nil, errors.ErrUnsupportedContentType - } - var m policies.Policy - if err := json.NewDecoder(r.Body).Decode(&m); err != nil { - return nil, errors.Wrap(errors.ErrMalformedEntity, err) - } - - req := updatePolicyReq{ - token: apiutil.ExtractBearerToken(r), - Subject: m.Subject, - Object: m.Object, - Actions: m.Actions, - } - - return req, nil -} - -func decodeListPoliciesRequest(_ context.Context, r *http.Request) (interface{}, error) { - total, err := apiutil.ReadNumQuery[uint64](r, api.OffsetKey, api.DefOffset) - if err != nil { - return nil, err - } - offset, err := apiutil.ReadNumQuery[uint64](r, api.OffsetKey, api.DefOffset) - if err != nil { - return nil, err - } - limit, err := apiutil.ReadNumQuery[uint64](r, api.LimitKey, api.DefLimit) - if err != nil { - return nil, err - } - ownerID, err := apiutil.ReadStringQuery(r, api.OwnerKey, "") - if err != nil { - return nil, err - } - subject, err := apiutil.ReadStringQuery(r, api.SubjectKey, "") - if err != nil { - return nil, err - } - object, err := apiutil.ReadStringQuery(r, api.ObjectKey, "") - if err != nil { - return nil, err - } - policy, err := apiutil.ReadStringQuery(r, api.PolicyKey, "") - if err != nil { - return nil, err - } - - req := listPolicyReq{ - token: apiutil.ExtractBearerToken(r), - Total: total, - Offset: offset, - Limit: limit, - OwnerID: ownerID, - Subject: subject, - Object: object, - Actions: policy, - } - return req, nil -} - -func deletePolicyRequest(_ context.Context, r *http.Request) (interface{}, error) { - req := deletePolicyReq{ - token: apiutil.ExtractBearerToken(r), - Subject: bone.GetValue(r, "subject"), - Object: bone.GetValue(r, "object"), - } - - return req, nil -} diff --git a/clients/policies/clientauth.pb.go b/clients/policies/clientauth.pb.go deleted file mode 100644 index 9e0f46403f0..00000000000 --- a/clients/policies/clientauth.pb.go +++ /dev/null @@ -1,241 +0,0 @@ -// Code generated by protoc-gen-go. DO NOT EDIT. -// versions: -// protoc-gen-go v1.28.1 -// protoc v3.21.12 -// source: clients/policies/clientauth.proto - -package policies - -import ( - protoreflect "google.golang.org/protobuf/reflect/protoreflect" - protoimpl "google.golang.org/protobuf/runtime/protoimpl" - reflect "reflect" - sync "sync" -) - -const ( - // Verify that this generated code is sufficiently up-to-date. - _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) - // Verify that runtime/protoimpl is sufficiently up-to-date. - _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) -) - -type AuthorizeReq struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Sub string `protobuf:"bytes,1,opt,name=sub,proto3" json:"sub,omitempty"` - Obj string `protobuf:"bytes,2,opt,name=obj,proto3" json:"obj,omitempty"` - Act string `protobuf:"bytes,3,opt,name=act,proto3" json:"act,omitempty"` - EntityType string `protobuf:"bytes,4,opt,name=entityType,proto3" json:"entityType,omitempty"` -} - -func (x *AuthorizeReq) Reset() { - *x = AuthorizeReq{} - if protoimpl.UnsafeEnabled { - mi := &file_clients_policies_clientauth_proto_msgTypes[0] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *AuthorizeReq) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*AuthorizeReq) ProtoMessage() {} - -func (x *AuthorizeReq) ProtoReflect() protoreflect.Message { - mi := &file_clients_policies_clientauth_proto_msgTypes[0] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use AuthorizeReq.ProtoReflect.Descriptor instead. -func (*AuthorizeReq) Descriptor() ([]byte, []int) { - return file_clients_policies_clientauth_proto_rawDescGZIP(), []int{0} -} - -func (x *AuthorizeReq) GetSub() string { - if x != nil { - return x.Sub - } - return "" -} - -func (x *AuthorizeReq) GetObj() string { - if x != nil { - return x.Obj - } - return "" -} - -func (x *AuthorizeReq) GetAct() string { - if x != nil { - return x.Act - } - return "" -} - -func (x *AuthorizeReq) GetEntityType() string { - if x != nil { - return x.EntityType - } - return "" -} - -type AuthorizeRes struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Authorized bool `protobuf:"varint,1,opt,name=authorized,proto3" json:"authorized,omitempty"` -} - -func (x *AuthorizeRes) Reset() { - *x = AuthorizeRes{} - if protoimpl.UnsafeEnabled { - mi := &file_clients_policies_clientauth_proto_msgTypes[1] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *AuthorizeRes) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*AuthorizeRes) ProtoMessage() {} - -func (x *AuthorizeRes) ProtoReflect() protoreflect.Message { - mi := &file_clients_policies_clientauth_proto_msgTypes[1] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use AuthorizeRes.ProtoReflect.Descriptor instead. -func (*AuthorizeRes) Descriptor() ([]byte, []int) { - return file_clients_policies_clientauth_proto_rawDescGZIP(), []int{1} -} - -func (x *AuthorizeRes) GetAuthorized() bool { - if x != nil { - return x.Authorized - } - return false -} - -var File_clients_policies_clientauth_proto protoreflect.FileDescriptor - -var file_clients_policies_clientauth_proto_rawDesc = []byte{ - 0x0a, 0x21, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x73, 0x2f, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x69, - 0x65, 0x73, 0x2f, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x61, 0x75, 0x74, 0x68, 0x2e, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x12, 0x08, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, 0x22, 0x64, 0x0a, - 0x0c, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x52, 0x65, 0x71, 0x12, 0x10, 0x0a, - 0x03, 0x73, 0x75, 0x62, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x73, 0x75, 0x62, 0x12, - 0x10, 0x0a, 0x03, 0x6f, 0x62, 0x6a, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6f, 0x62, - 0x6a, 0x12, 0x10, 0x0a, 0x03, 0x61, 0x63, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, - 0x61, 0x63, 0x74, 0x12, 0x1e, 0x0a, 0x0a, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x54, 0x79, 0x70, - 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x54, - 0x79, 0x70, 0x65, 0x22, 0x2e, 0x0a, 0x0c, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, - 0x52, 0x65, 0x73, 0x12, 0x1e, 0x0a, 0x0a, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, - 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, - 0x7a, 0x65, 0x64, 0x32, 0x4c, 0x0a, 0x0b, 0x41, 0x75, 0x74, 0x68, 0x53, 0x65, 0x72, 0x76, 0x69, - 0x63, 0x65, 0x12, 0x3d, 0x0a, 0x09, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x12, - 0x16, 0x2e, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, 0x2e, 0x41, 0x75, 0x74, 0x68, 0x6f, - 0x72, 0x69, 0x7a, 0x65, 0x52, 0x65, 0x71, 0x1a, 0x16, 0x2e, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x69, - 0x65, 0x73, 0x2e, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x52, 0x65, 0x73, 0x22, - 0x00, 0x42, 0x0c, 0x5a, 0x0a, 0x2e, 0x2f, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, 0x62, - 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, -} - -var ( - file_clients_policies_clientauth_proto_rawDescOnce sync.Once - file_clients_policies_clientauth_proto_rawDescData = file_clients_policies_clientauth_proto_rawDesc -) - -func file_clients_policies_clientauth_proto_rawDescGZIP() []byte { - file_clients_policies_clientauth_proto_rawDescOnce.Do(func() { - file_clients_policies_clientauth_proto_rawDescData = protoimpl.X.CompressGZIP(file_clients_policies_clientauth_proto_rawDescData) - }) - return file_clients_policies_clientauth_proto_rawDescData -} - -var file_clients_policies_clientauth_proto_msgTypes = make([]protoimpl.MessageInfo, 2) -var file_clients_policies_clientauth_proto_goTypes = []interface{}{ - (*AuthorizeReq)(nil), // 0: policies.AuthorizeReq - (*AuthorizeRes)(nil), // 1: policies.AuthorizeRes -} -var file_clients_policies_clientauth_proto_depIdxs = []int32{ - 0, // 0: policies.AuthService.Authorize:input_type -> policies.AuthorizeReq - 1, // 1: policies.AuthService.Authorize:output_type -> policies.AuthorizeRes - 1, // [1:2] is the sub-list for method output_type - 0, // [0:1] is the sub-list for method input_type - 0, // [0:0] is the sub-list for extension type_name - 0, // [0:0] is the sub-list for extension extendee - 0, // [0:0] is the sub-list for field type_name -} - -func init() { file_clients_policies_clientauth_proto_init() } -func file_clients_policies_clientauth_proto_init() { - if File_clients_policies_clientauth_proto != nil { - return - } - if !protoimpl.UnsafeEnabled { - file_clients_policies_clientauth_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*AuthorizeReq); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_clients_policies_clientauth_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*AuthorizeRes); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - } - type x struct{} - out := protoimpl.TypeBuilder{ - File: protoimpl.DescBuilder{ - GoPackagePath: reflect.TypeOf(x{}).PkgPath(), - RawDescriptor: file_clients_policies_clientauth_proto_rawDesc, - NumEnums: 0, - NumMessages: 2, - NumExtensions: 0, - NumServices: 1, - }, - GoTypes: file_clients_policies_clientauth_proto_goTypes, - DependencyIndexes: file_clients_policies_clientauth_proto_depIdxs, - MessageInfos: file_clients_policies_clientauth_proto_msgTypes, - }.Build() - File_clients_policies_clientauth_proto = out.File - file_clients_policies_clientauth_proto_rawDesc = nil - file_clients_policies_clientauth_proto_goTypes = nil - file_clients_policies_clientauth_proto_depIdxs = nil -} diff --git a/clients/policies/clientauth.proto b/clients/policies/clientauth.proto deleted file mode 100644 index 625b832af7d..00000000000 --- a/clients/policies/clientauth.proto +++ /dev/null @@ -1,19 +0,0 @@ -syntax = "proto3"; - -package policies; - -option go_package = "./policies"; - -service AuthService { - rpc Authorize(AuthorizeReq) returns (AuthorizeRes) {} -} - -message AuthorizeReq { - string sub = 1; - string obj = 2; - string act = 3; - string entityType = 4; -} -message AuthorizeRes { - bool authorized = 1; -} diff --git a/clients/policies/clientauth_grpc.pb.go b/clients/policies/clientauth_grpc.pb.go deleted file mode 100644 index e2006745a09..00000000000 --- a/clients/policies/clientauth_grpc.pb.go +++ /dev/null @@ -1,105 +0,0 @@ -// Code generated by protoc-gen-go-grpc. DO NOT EDIT. -// versions: -// - protoc-gen-go-grpc v1.2.0 -// - protoc v3.21.12 -// source: clients/policies/clientauth.proto - -package policies - -import ( - context "context" - grpc "google.golang.org/grpc" - codes "google.golang.org/grpc/codes" - status "google.golang.org/grpc/status" -) - -// This is a compile-time assertion to ensure that this generated file -// is compatible with the grpc package it is being compiled against. -// Requires gRPC-Go v1.32.0 or later. -const _ = grpc.SupportPackageIsVersion7 - -// AuthServiceClient is the client API for AuthService service. -// -// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. -type AuthServiceClient interface { - Authorize(ctx context.Context, in *AuthorizeReq, opts ...grpc.CallOption) (*AuthorizeRes, error) -} - -type authServiceClient struct { - cc grpc.ClientConnInterface -} - -func NewAuthServiceClient(cc grpc.ClientConnInterface) AuthServiceClient { - return &authServiceClient{cc} -} - -func (c *authServiceClient) Authorize(ctx context.Context, in *AuthorizeReq, opts ...grpc.CallOption) (*AuthorizeRes, error) { - out := new(AuthorizeRes) - err := c.cc.Invoke(ctx, "/policies.AuthService/Authorize", in, out, opts...) - if err != nil { - return nil, err - } - return out, nil -} - -// AuthServiceServer is the server API for AuthService service. -// All implementations must embed UnimplementedAuthServiceServer -// for forward compatibility -type AuthServiceServer interface { - Authorize(context.Context, *AuthorizeReq) (*AuthorizeRes, error) - mustEmbedUnimplementedAuthServiceServer() -} - -// UnimplementedAuthServiceServer must be embedded to have forward compatible implementations. -type UnimplementedAuthServiceServer struct { -} - -func (UnimplementedAuthServiceServer) Authorize(context.Context, *AuthorizeReq) (*AuthorizeRes, error) { - return nil, status.Errorf(codes.Unimplemented, "method Authorize not implemented") -} -func (UnimplementedAuthServiceServer) mustEmbedUnimplementedAuthServiceServer() {} - -// UnsafeAuthServiceServer may be embedded to opt out of forward compatibility for this service. -// Use of this interface is not recommended, as added methods to AuthServiceServer will -// result in compilation errors. -type UnsafeAuthServiceServer interface { - mustEmbedUnimplementedAuthServiceServer() -} - -func RegisterAuthServiceServer(s grpc.ServiceRegistrar, srv AuthServiceServer) { - s.RegisterService(&AuthService_ServiceDesc, srv) -} - -func _AuthService_Authorize_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(AuthorizeReq) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(AuthServiceServer).Authorize(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: "/policies.AuthService/Authorize", - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(AuthServiceServer).Authorize(ctx, req.(*AuthorizeReq)) - } - return interceptor(ctx, in, info, handler) -} - -// AuthService_ServiceDesc is the grpc.ServiceDesc for AuthService service. -// It's only intended for direct use with grpc.RegisterService, -// and not to be introspected or modified (even as a copy) -var AuthService_ServiceDesc = grpc.ServiceDesc{ - ServiceName: "policies.AuthService", - HandlerType: (*AuthServiceServer)(nil), - Methods: []grpc.MethodDesc{ - { - MethodName: "Authorize", - Handler: _AuthService_Authorize_Handler, - }, - }, - Streams: []grpc.StreamDesc{}, - Metadata: "clients/policies/clientauth.proto", -} diff --git a/clients/policies/mocks/policies.go b/clients/policies/mocks/policies.go deleted file mode 100644 index da001f4c4a9..00000000000 --- a/clients/policies/mocks/policies.go +++ /dev/null @@ -1,42 +0,0 @@ -package mocks - -import ( - "context" - - "github.com/mainflux/mainflux/clients/policies" - "github.com/stretchr/testify/mock" -) - -type PolicyRepository struct { - mock.Mock -} - -func (m *PolicyRepository) Delete(ctx context.Context, p policies.Policy) error { - ret := m.Called(ctx, p) - - return ret.Error(0) -} - -func (m *PolicyRepository) Retrieve(ctx context.Context, pm policies.Page) (policies.PolicyPage, error) { - ret := m.Called(ctx, pm) - - return ret.Get(0).(policies.PolicyPage), ret.Error(1) -} - -func (m *PolicyRepository) Save(ctx context.Context, p policies.Policy) error { - ret := m.Called(ctx, p) - - return ret.Error(0) -} - -func (m *PolicyRepository) Update(ctx context.Context, p policies.Policy) error { - ret := m.Called(ctx, p) - - return ret.Error(0) -} - -func (m *PolicyRepository) Evaluate(ctx context.Context, entityType string, p policies.Policy) error { - ret := m.Called(ctx, entityType, p) - - return ret.Error(0) -} diff --git a/clients/policies/page.go b/clients/policies/page.go deleted file mode 100644 index fb42d46c18f..00000000000 --- a/clients/policies/page.go +++ /dev/null @@ -1,33 +0,0 @@ -package policies - -import "github.com/mainflux/mainflux/internal/apiutil" - -// Metadata represents arbitrary JSON. -type Metadata map[string]interface{} - -// Page contains page metadata that helps navigation. -type Page struct { - Total uint64 - Offset uint64 - Limit uint64 - Level uint64 - Name string - Identifier string - OwnerID string - Subject string - Object string - Action string - Tag string - Metadata Metadata - SharedBy string -} - -// Validate check page actions. -func (p Page) Validate() error { - if p.Action != "" { - if ok := ValidateAction(p.Action); !ok { - return apiutil.ErrMissingPolicyAct - } - } - return nil -} diff --git a/clients/policies/policies.go b/clients/policies/policies.go deleted file mode 100644 index cb6945fa82b..00000000000 --- a/clients/policies/policies.go +++ /dev/null @@ -1,104 +0,0 @@ -package policies - -import ( - "context" - "time" - - "github.com/mainflux/mainflux/internal/apiutil" -) - -// PolicyTypes contains a list of the available policy types currently supported -var PolicyTypes = []string{"c_delete", "c_update", "c_add", "c_list", "g_delete", "g_update", "g_add", "g_list", "m_write", "m_read"} - -// Policy represents an argument struct for making a policy related function calls. -type Policy struct { - OwnerID string `json:"owner_id"` - Subject string `json:"subject"` - Object string `json:"object"` - Actions []string `json:"actions"` - CreatedAt time.Time `json:"created_at"` - UpdatedAt time.Time `json:"updated_at"` -} - -// PolicyPage contains a page of policies. -type PolicyPage struct { - Page - Policies []Policy -} - -// PolicyRepository specifies an account persistence API. -type PolicyRepository interface { - // Save creates a policy for the given Subject, so that, after - // Save, `Subject` has a `relation` on `group_id`. Returns a non-nil - // error in case of failures. - Save(ctx context.Context, p Policy) error - - // Evaluate is used to evaluate if you have the correct permissions. - // We evaluate if we are in the same group first then evaluate if the - // object has that action over the subject - Evaluate(ctx context.Context, entityType string, p Policy) error - - // Update updates the policy type. - Update(ctx context.Context, p Policy) error - - // Retrieve retrieves policy for a given input. - Retrieve(ctx context.Context, pm Page) (PolicyPage, error) - - // Delete deletes the policy - Delete(ctx context.Context, p Policy) error -} - -// PolicyService represents a authorization service. It exposes -// functionalities through `auth` to perform authorization. -type PolicyService interface { - // Authorize checks authorization of the given `subject`. Basically, - // Authorize verifies that Is `subject` allowed to `relation` on - // `object`. Authorize returns a non-nil error if the subject has - // no relation on the object (which simply means the operation is - // denied). - Authorize(ctx context.Context, entityType string, p Policy) error - - // AddPolicy creates a policy for the given subject, so that, after - // AddPolicy, `subject` has a `relation` on `object`. Returns a non-nil - // error in case of failures. - AddPolicy(ctx context.Context, token string, p Policy) error - - // UpdatePolicy updates policies based on the given policy structure. - UpdatePolicy(ctx context.Context, token string, p Policy) error - - // ListPolicy lists policies based on the given policy structure. - ListPolicy(ctx context.Context, token string, pm Page) (PolicyPage, error) - - // DeletePolicy removes a policy. - DeletePolicy(ctx context.Context, token string, p Policy) error -} - -// Validate returns an error if policy representation is invalid. -func (p Policy) Validate() error { - if p.Subject == "" { - return apiutil.ErrMissingPolicySub - } - if p.Object == "" { - return apiutil.ErrMissingPolicyObj - } - if len(p.Actions) == 0 { - return apiutil.ErrMissingPolicyAct - } - for _, p := range p.Actions { - if ok := ValidateAction(p); !ok { - return apiutil.ErrMissingPolicyAct - } - } - return nil -} - -// ValidateAction check if the action is in policies -func ValidateAction(act string) bool { - for _, v := range PolicyTypes { - if v == act { - return true - } - } - return false - -} diff --git a/clients/policies/postgres/doc.go b/clients/policies/postgres/doc.go deleted file mode 100644 index bf560bea285..00000000000 --- a/clients/policies/postgres/doc.go +++ /dev/null @@ -1 +0,0 @@ -package postgres diff --git a/clients/policies/postgres/policies.go b/clients/policies/postgres/policies.go deleted file mode 100644 index fe37c1ff1ba..00000000000 --- a/clients/policies/postgres/policies.go +++ /dev/null @@ -1,227 +0,0 @@ -package postgres - -import ( - "context" - "fmt" - "strings" - "time" - - "github.com/jackc/pgtype" - "github.com/mainflux/mainflux/clients/policies" - "github.com/mainflux/mainflux/clients/postgres" - "github.com/mainflux/mainflux/pkg/errors" -) - -var _ policies.PolicyRepository = (*policyRepository)(nil) - -var ( - // ErrInvalidEntityType indicates that the entity type is invalid. - ErrInvalidEntityType = errors.New("invalid entity type") -) - -type policyRepository struct { - db postgres.Database -} - -// NewPolicyRepo instantiates a PostgreSQL implementation of policy repository. -func NewPolicyRepo(db postgres.Database) policies.PolicyRepository { - return &policyRepository{ - db: db, - } -} - -func (pr policyRepository) Save(ctx context.Context, policy policies.Policy) error { - q := `INSERT INTO policies (owner_id, subject, object, actions, created_at, updated_at) - VALUES (:owner_id, :subject, :object, :actions, :created_at, :updated_at)` - - dbp, err := toDBPolicy(policy) - if err != nil { - return errors.Wrap(errors.ErrCreateEntity, err) - } - - row, err := pr.db.NamedQueryContext(ctx, q, dbp) - if err != nil { - return postgres.HandleError(err, errors.ErrCreateEntity) - } - - defer row.Close() - - return nil -} - -func (pr policyRepository) Evaluate(ctx context.Context, entityType string, policy policies.Policy) error { - q := "" - switch entityType { - case "client": - // Evaluates if two clients are connected to the same group and the subject has the specified action - // or subject is the owner of the object - q = fmt.Sprintf(`(SELECT subject FROM policies WHERE (subject = :subject AND object IN ( - SELECT object FROM policies WHERE subject = '%s') AND '%s'=ANY(actions))) UNION - (SELECT id as subject FROM clients WHERE owner = :subject AND id = '%s') LIMIT 1`, policy.Object, policy.Actions[0], policy.Object) - case "group": - // Evaluates if client is connected to the specified group and has the required action - q = fmt.Sprintf(`(SELECT subject FROM policies WHERE subject = :subject AND object = :object AND '%s'=ANY(actions)) - UNION (SELECT id as subject FROM groups WHERE owner_id = :subject AND id = :object) LIMIT 1`, policy.Actions[0]) - default: - return ErrInvalidEntityType - } - - dbu, err := toDBPolicy(policy) - if err != nil { - return errors.Wrap(errors.ErrAuthorization, err) - } - row, err := pr.db.NamedQueryContext(ctx, q, dbu) - if err != nil { - return postgres.HandleError(err, errors.ErrAuthorization) - } - - defer row.Close() - - if ok := row.Next(); !ok { - return errors.Wrap(errors.ErrAuthorization, row.Err()) - } - var rPolicy dbPolicy - if err := row.StructScan(&rPolicy); err != nil { - return err - } - return nil -} - -func (pr policyRepository) Update(ctx context.Context, policy policies.Policy) error { - if err := policy.Validate(); err != nil { - return errors.Wrap(errors.ErrCreateEntity, err) - } - q := `UPDATE policies SET actions = :actions, updated_at = :updated_at - WHERE subject = :subject AND object = :object` - - dbu, err := toDBPolicy(policy) - if err != nil { - return errors.Wrap(errors.ErrUpdateEntity, err) - } - - if _, err := pr.db.NamedExecContext(ctx, q, dbu); err != nil { - return errors.Wrap(errors.ErrUpdateEntity, err) - } - - return nil -} - -func (pr policyRepository) Retrieve(ctx context.Context, pm policies.Page) (policies.PolicyPage, error) { - var query []string - var emq string - - if pm.OwnerID != "" { - query = append(query, fmt.Sprintf("owner_id = '%s'", pm.OwnerID)) - } - if pm.Subject != "" { - query = append(query, fmt.Sprintf("subject = '%s'", pm.Subject)) - } - if pm.Object != "" { - query = append(query, fmt.Sprintf("object = '%s'", pm.Object)) - } - if pm.Action != "" { - query = append(query, fmt.Sprintf("'%s' = ANY (actions)", pm.Action)) - } - - if len(query) > 0 { - emq = fmt.Sprintf(" WHERE %s", strings.Join(query, " AND ")) - } - - q := fmt.Sprintf(`SELECT owner_id, subject, object, actions - FROM policies %s ORDER BY updated_at LIMIT :limit OFFSET :offset;`, emq) - params := map[string]interface{}{ - "limit": pm.Limit, - "offset": pm.Offset, - } - rows, err := pr.db.NamedQueryContext(ctx, q, params) - if err != nil { - return policies.PolicyPage{}, errors.Wrap(errors.ErrViewEntity, err) - } - defer rows.Close() - - var items []policies.Policy - for rows.Next() { - dbp := dbPolicy{} - if err := rows.StructScan(&dbp); err != nil { - return policies.PolicyPage{}, errors.Wrap(errors.ErrViewEntity, err) - } - - policy, err := toPolicy(dbp) - if err != nil { - return policies.PolicyPage{}, err - } - - items = append(items, policy) - } - - cq := fmt.Sprintf(`SELECT COUNT(*) FROM policies %s;`, emq) - - total, err := postgres.Total(ctx, pr.db, cq, params) - if err != nil { - return policies.PolicyPage{}, errors.Wrap(errors.ErrViewEntity, err) - } - - page := policies.PolicyPage{ - Policies: items, - Page: policies.Page{ - Total: total, - Offset: pm.Offset, - Limit: pm.Limit, - }, - } - - return page, nil -} - -func (pr policyRepository) Delete(ctx context.Context, p policies.Policy) error { - dbp := dbPolicy{ - Subject: p.Subject, - Object: p.Object, - } - q := `DELETE FROM policies WHERE subject = :subject AND object = :object` - if _, err := pr.db.NamedExecContext(ctx, q, dbp); err != nil { - return errors.Wrap(errors.ErrRemoveEntity, err) - } - return nil -} - -type dbPolicy struct { - OwnerID string `db:"owner_id"` - Subject string `db:"subject"` - Object string `db:"object"` - Actions pgtype.TextArray `db:"actions"` - CreatedAt time.Time `db:"created_at"` - UpdatedAt time.Time `db:"updated_at"` -} - -func toDBPolicy(p policies.Policy) (dbPolicy, error) { - var ps pgtype.TextArray - if err := ps.Set(p.Actions); err != nil { - return dbPolicy{}, err - } - - return dbPolicy{ - OwnerID: p.OwnerID, - Subject: p.Subject, - Object: p.Object, - Actions: ps, - CreatedAt: p.CreatedAt, - UpdatedAt: p.UpdatedAt, - }, nil -} - -func toPolicy(dbp dbPolicy) (policies.Policy, error) { - var ps []string - for _, e := range dbp.Actions.Elements { - ps = append(ps, e.String) - } - - return policies.Policy{ - OwnerID: dbp.OwnerID, - Subject: dbp.Subject, - Object: dbp.Object, - Actions: ps, - CreatedAt: dbp.CreatedAt, - UpdatedAt: dbp.UpdatedAt, - }, nil -} diff --git a/clients/policies/postgres/policies_test.go b/clients/policies/postgres/policies_test.go deleted file mode 100644 index de3f25930bd..00000000000 --- a/clients/policies/postgres/policies_test.go +++ /dev/null @@ -1,671 +0,0 @@ -package postgres_test - -import ( - "context" - "fmt" - "testing" - - "github.com/mainflux/mainflux/clients/clients" - cpostgres "github.com/mainflux/mainflux/clients/clients/postgres" - "github.com/mainflux/mainflux/clients/groups" - gpostgres "github.com/mainflux/mainflux/clients/groups/postgres" - "github.com/mainflux/mainflux/clients/policies" - ppostgres "github.com/mainflux/mainflux/clients/policies/postgres" - "github.com/mainflux/mainflux/clients/postgres" - "github.com/mainflux/mainflux/internal/apiutil" - "github.com/mainflux/mainflux/internal/testsutil" - "github.com/mainflux/mainflux/pkg/errors" - "github.com/mainflux/mainflux/pkg/uuid" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -var ( - idProvider = uuid.New() -) - -func TestPoliciesSave(t *testing.T) { - t.Cleanup(func() { testsutil.CleanUpDB(t, db) }) - repo := ppostgres.NewPolicyRepo(database) - crepo := cpostgres.NewClientRepo(database) - - uid := testsutil.GenerateUUID(t, idProvider) - - client := clients.Client{ - ID: uid, - Name: "policy-save@example.com", - Credentials: clients.Credentials{ - Identity: "policy-save@example.com", - Secret: "pass", - }, - Status: clients.EnabledStatus, - } - - client, err := crepo.Save(context.Background(), client) - require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err)) - - uid = testsutil.GenerateUUID(t, idProvider) - - cases := []struct { - desc string - policy policies.Policy - err error - }{ - { - desc: "add new policy successfully", - policy: policies.Policy{ - OwnerID: client.ID, - Subject: client.ID, - Object: uid, - Actions: []string{"c_delete"}, - }, - err: nil, - }, - { - desc: "add policy with duplicate subject, object and action", - policy: policies.Policy{ - OwnerID: client.ID, - Subject: client.ID, - Object: uid, - Actions: []string{"c_delete"}, - }, - err: errors.ErrConflict, - }, - } - - for _, tc := range cases { - err := repo.Save(context.Background(), tc.policy) - assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) - } -} - -func TestPoliciesEvaluate(t *testing.T) { - t.Cleanup(func() { testsutil.CleanUpDB(t, db) }) - repo := ppostgres.NewPolicyRepo(database) - crepo := cpostgres.NewClientRepo(database) - grepo := gpostgres.NewGroupRepo(database) - - client1 := clients.Client{ - ID: testsutil.GenerateUUID(t, idProvider), - Name: "connectedclients-clientA@example.com", - Credentials: clients.Credentials{ - Identity: "connectedclients-clientA@example.com", - Secret: "pass", - }, - Status: clients.EnabledStatus, - } - client2 := clients.Client{ - ID: testsutil.GenerateUUID(t, idProvider), - Name: "connectedclients-clientB@example.com", - Credentials: clients.Credentials{ - Identity: "connectedclients-clientB@example.com", - Secret: "pass", - }, - Status: clients.EnabledStatus, - } - group := groups.Group{ - ID: testsutil.GenerateUUID(t, idProvider), - Name: "connecting-group@example.com", - } - - client1, err := crepo.Save(context.Background(), client1) - require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err)) - client2, err = crepo.Save(context.Background(), client2) - require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err)) - group, err = grepo.Save(context.Background(), group) - require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err)) - - policy1 := policies.Policy{ - OwnerID: client1.ID, - Subject: client1.ID, - Object: group.ID, - Actions: []string{"c_update", "g_update"}, - } - policy2 := policies.Policy{ - OwnerID: client2.ID, - Subject: client2.ID, - Object: group.ID, - Actions: []string{"c_update", "g_update"}, - } - err = repo.Save(context.Background(), policy1) - require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err)) - err = repo.Save(context.Background(), policy2) - require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err)) - - cases := map[string]struct { - Subject string - Object string - Action string - Domain string - err error - }{ - "evaluate valid client update": {client1.ID, client2.ID, "c_update", "client", nil}, - "evaluate valid group update": {client1.ID, group.ID, "g_update", "group", nil}, - "evaluate valid client list": {client1.ID, client2.ID, "c_list", "client", errors.ErrAuthorization}, - "evaluate valid group list": {client1.ID, group.ID, "g_list", "group", errors.ErrAuthorization}, - "evaluate invalid client delete": {client1.ID, client2.ID, "c_delete", "client", errors.ErrAuthorization}, - "evaluate invalid group delete": {client1.ID, group.ID, "g_delete", "group", errors.ErrAuthorization}, - "evaluate invalid client update": {"unknown", "unknown", "c_update", "client", errors.ErrAuthorization}, - "evaluate invalid group update": {"unknown", "unknown", "c_update", "group", errors.ErrAuthorization}, - } - - for desc, tc := range cases { - p := policies.Policy{ - Subject: tc.Subject, - Object: tc.Object, - Actions: []string{tc.Action}, - } - err := repo.Evaluate(context.Background(), tc.Domain, p) - assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", desc, tc.err, err)) - } -} - -func TestPoliciesRetrieve(t *testing.T) { - t.Cleanup(func() { testsutil.CleanUpDB(t, db) }) - repo := ppostgres.NewPolicyRepo(database) - crepo := cpostgres.NewClientRepo(database) - - uid := testsutil.GenerateUUID(t, idProvider) - - client := clients.Client{ - ID: uid, - Name: "single-policy-retrieval@example.com", - Credentials: clients.Credentials{ - Identity: "single-policy-retrieval@example.com", - Secret: "pass", - }, - Status: clients.EnabledStatus, - } - - client, err := crepo.Save(context.Background(), client) - require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err)) - - uid, err = idProvider.ID() - require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err)) - - policy := policies.Policy{ - OwnerID: client.ID, - Subject: client.ID, - Object: uid, - Actions: []string{"c_delete"}, - } - - err = repo.Save(context.Background(), policy) - require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err)) - - cases := map[string]struct { - Subject string - Object string - err error - }{ - "retrieve existing policy": {uid, uid, nil}, - "retrieve non-existing policy": {"unknown", "unknown", nil}, - } - - for desc, tc := range cases { - pm := policies.Page{ - Subject: tc.Subject, - Object: tc.Object, - } - _, err := repo.Retrieve(context.Background(), pm) - assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", desc, tc.err, err)) - } -} - -func TestPoliciesUpdate(t *testing.T) { - t.Cleanup(func() { testsutil.CleanUpDB(t, db) }) - repo := ppostgres.NewPolicyRepo(database) - crepo := cpostgres.NewClientRepo(database) - - cid := testsutil.GenerateUUID(t, idProvider) - pid := testsutil.GenerateUUID(t, idProvider) - - client := clients.Client{ - ID: cid, - Name: "policy-update@example.com", - Credentials: clients.Credentials{ - Identity: "policy-update@example.com", - Secret: "pass", - }, - Status: clients.EnabledStatus, - } - - _, err := crepo.Save(context.Background(), client) - require.Nil(t, err, fmt.Sprintf("unexpected error during saving client: %s", err)) - - policy := policies.Policy{ - OwnerID: cid, - Subject: cid, - Object: pid, - Actions: []string{"c_delete"}, - } - err = repo.Save(context.Background(), policy) - require.Nil(t, err, fmt.Sprintf("unexpected error during saving policy: %s", err)) - - cases := []struct { - desc string - policy policies.Policy - resp policies.Policy - err error - }{ - { - desc: "update policy successfully", - policy: policies.Policy{ - OwnerID: cid, - Subject: cid, - Object: pid, - Actions: []string{"c_update"}, - }, - resp: policies.Policy{ - OwnerID: cid, - Subject: cid, - Object: pid, - Actions: []string{"c_update"}, - }, - err: nil, - }, - { - desc: "update policy with missing owner id", - policy: policies.Policy{ - OwnerID: "", - Subject: cid, - Object: pid, - Actions: []string{"c_delete"}, - }, - resp: policies.Policy{ - OwnerID: cid, - Subject: cid, - Object: pid, - Actions: []string{"c_delete"}, - }, - err: nil, - }, - { - desc: "update policy with missing subject", - policy: policies.Policy{ - OwnerID: cid, - Subject: "", - Object: pid, - Actions: []string{"c_add"}, - }, - resp: policies.Policy{ - OwnerID: cid, - Subject: cid, - Object: pid, - Actions: []string{"c_delete"}, - }, - err: apiutil.ErrMissingPolicySub, - }, - { - desc: "update policy with missing object", - policy: policies.Policy{ - OwnerID: cid, - Subject: cid, - Object: "", - Actions: []string{"c_add"}, - }, - resp: policies.Policy{ - OwnerID: cid, - Subject: cid, - Object: pid, - Actions: []string{"c_delete"}, - }, - - err: apiutil.ErrMissingPolicyObj, - }, - { - desc: "update policy with missing action", - policy: policies.Policy{ - OwnerID: cid, - Subject: cid, - Object: pid, - Actions: []string{""}, - }, - resp: policies.Policy{ - OwnerID: cid, - Subject: cid, - Object: pid, - Actions: []string{"c_delete"}, - }, - err: apiutil.ErrMissingPolicyAct, - }, - } - - for _, tc := range cases { - err := repo.Update(context.Background(), tc.policy) - assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) - policPage, err := repo.Retrieve(context.Background(), policies.Page{ - Offset: uint64(0), - Limit: uint64(10), - Subject: tc.policy.Subject, - }) - if err == nil { - assert.Equal(t, tc.resp, policPage.Policies[0], fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) - } - } -} - -func TestPoliciesRetrievalAll(t *testing.T) { - t.Cleanup(func() { testsutil.CleanUpDB(t, db) }) - postgres.NewDatabase(db, tracer) - repo := ppostgres.NewPolicyRepo(database) - crepo := cpostgres.NewClientRepo(database) - - var nPolicies = uint64(10) - - clientA := clients.Client{ - ID: testsutil.GenerateUUID(t, idProvider), - Name: "policyA-retrievalall@example.com", - Credentials: clients.Credentials{ - Identity: "policyA-retrievalall@example.com", - Secret: "pass", - }, - Status: clients.EnabledStatus, - } - clientB := clients.Client{ - ID: testsutil.GenerateUUID(t, idProvider), - Name: "policyB-retrievalall@example.com", - Credentials: clients.Credentials{ - Identity: "policyB-retrievalall@example.com", - Secret: "pass", - }, - Status: clients.EnabledStatus, - } - - clientA, err := crepo.Save(context.Background(), clientA) - require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err)) - clientB, err = crepo.Save(context.Background(), clientB) - require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err)) - - for i := uint64(0); i < nPolicies; i++ { - obj := fmt.Sprintf("TestRetrieveAll%d@example.com", i) - if i%2 == 0 { - policy := policies.Policy{ - OwnerID: clientA.ID, - Subject: clientA.ID, - Object: obj, - Actions: []string{"c_delete"}, - } - err = repo.Save(context.Background(), policy) - require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err)) - } - policy := policies.Policy{ - Subject: clientB.ID, - Object: obj, - Actions: []string{"c_add", "c_update"}, - } - err = repo.Save(context.Background(), policy) - require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err)) - } - - cases := map[string]struct { - size uint64 - pm policies.Page - }{ - "retrieve all policies with limit and offset": { - pm: policies.Page{ - Offset: 5, - Limit: nPolicies, - }, - size: 10, - }, - "retrieve all policies by owner id": { - pm: policies.Page{ - Offset: 0, - Limit: nPolicies, - Total: nPolicies, - OwnerID: clientA.ID, - }, - size: 5, - }, - "retrieve policies by wrong owner id": { - pm: policies.Page{ - Offset: 0, - Limit: nPolicies, - Total: nPolicies, - OwnerID: clientB.ID, - }, - size: 0, - }, - "retrieve all policies by Subject": { - pm: policies.Page{ - Offset: 0, - Limit: nPolicies, - Total: nPolicies, - Subject: clientA.ID, - }, - size: 5, - }, - "retrieve policies by wrong Subject": { - pm: policies.Page{ - Offset: 0, - Limit: nPolicies, - Total: nPolicies, - Subject: "wrongSubject", - }, - size: 0, - }, - - "retrieve all policies by Object": { - pm: policies.Page{ - Offset: 0, - Limit: nPolicies, - Total: nPolicies, - Object: "TestRetrieveAll1@example.com", - }, - size: 1, - }, - "retrieve policies by wrong Object": { - pm: policies.Page{ - Offset: 0, - Limit: nPolicies, - Total: nPolicies, - Object: "TestRetrieveAll45@example.com", - }, - size: 0, - }, - "retrieve all policies by Action": { - pm: policies.Page{ - Offset: 0, - Limit: nPolicies, - Total: nPolicies, - Action: "c_delete", - }, - size: 5, - }, - "retrieve policies by wrong Action": { - pm: policies.Page{ - Offset: 0, - Limit: nPolicies, - Total: nPolicies, - Action: "wrongAction", - }, - size: 0, - }, - "retrieve all policies by owner id and subject": { - pm: policies.Page{ - Offset: 0, - Limit: nPolicies, - Total: nPolicies, - OwnerID: clientA.ID, - Subject: clientA.ID, - }, - size: 5, - }, - "retrieve policies by wrong owner id and correct subject": { - pm: policies.Page{ - Offset: 0, - Limit: nPolicies, - Total: nPolicies, - OwnerID: clientB.ID, - Subject: clientA.ID, - }, - size: 0, - }, - "retrieve policies by correct owner id and wrong subject": { - pm: policies.Page{ - Offset: 0, - Limit: nPolicies, - Total: nPolicies, - OwnerID: clientA.ID, - Subject: "wrongSubject", - }, - size: 0, - }, - "retrieve policies by wrong owner id and wrong subject": { - pm: policies.Page{ - Offset: 0, - Limit: nPolicies, - Total: nPolicies, - OwnerID: clientB.ID, - }, - size: 0, - }, - "retrieve all policies by owner id and object": { - pm: policies.Page{ - Offset: 0, - Limit: nPolicies, - Total: nPolicies, - OwnerID: clientA.ID, - Object: "TestRetrieveAll2@example.com", - }, - size: 1, - }, - "retrieve policies by wrong owner id and correct object": { - pm: policies.Page{ - Offset: 0, - Limit: nPolicies, - Total: nPolicies, - OwnerID: clientB.ID, - Object: "TestRetrieveAll1@example.com", - }, - size: 0, - }, - "retrieve policies by correct owner id and wrong object": { - pm: policies.Page{ - Offset: 0, - Limit: nPolicies, - Total: nPolicies, - OwnerID: clientA.ID, - Object: "TestRetrieveAll45@example.com", - }, - size: 0, - }, - "retrieve policies by wrong owner id and wrong object": { - pm: policies.Page{ - Offset: 0, - Limit: nPolicies, - Total: nPolicies, - OwnerID: clientB.ID, - Object: "TestRetrieveAll45@example.com", - }, - size: 0, - }, - "retrieve all policies by owner id and action": { - pm: policies.Page{ - Offset: 0, - Limit: nPolicies, - Total: nPolicies, - OwnerID: clientA.ID, - Action: "c_delete", - }, - size: 5, - }, - "retrieve policies by wrong owner id and correct action": { - pm: policies.Page{ - Offset: 0, - Limit: nPolicies, - Total: nPolicies, - OwnerID: clientB.ID, - Action: "c_delete", - }, - size: 0, - }, - "retrieve policies by correct owner id and wrong action": { - pm: policies.Page{ - Offset: 0, - Limit: nPolicies, - Total: nPolicies, - OwnerID: clientA.ID, - Action: "wrongAction", - }, - size: 0, - }, - "retrieve policies by wrong owner id and wrong action": { - pm: policies.Page{ - Offset: 0, - Limit: nPolicies, - Total: nPolicies, - OwnerID: clientB.ID, - Action: "wrongAction", - }, - size: 0, - }, - } - for desc, tc := range cases { - page, err := repo.Retrieve(context.Background(), tc.pm) - size := uint64(len(page.Policies)) - assert.Equal(t, tc.size, size, fmt.Sprintf("%s: expected size %d got %d\n", desc, tc.size, size)) - assert.Nil(t, err, fmt.Sprintf("%s: expected no error got %d\n", desc, err)) - } -} - -func TestPoliciesDelete(t *testing.T) { - t.Cleanup(func() { testsutil.CleanUpDB(t, db) }) - repo := ppostgres.NewPolicyRepo(database) - crepo := cpostgres.NewClientRepo(database) - - client := clients.Client{ - ID: testsutil.GenerateUUID(t, idProvider), - Name: "policy-delete@example.com", - Credentials: clients.Credentials{ - Identity: "policy-delete@example.com", - Secret: "pass", - }, - Status: clients.EnabledStatus, - } - - subject, err := crepo.Save(context.Background(), client) - require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err)) - - objectID := testsutil.GenerateUUID(t, idProvider) - - policy := policies.Policy{ - OwnerID: subject.ID, - Subject: subject.ID, - Object: objectID, - Actions: []string{"c_delete"}, - } - - err = repo.Save(context.Background(), policy) - require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err)) - - cases := map[string]struct { - Subject string - Object string - err error - }{ - "delete non-existing policy": {"unknown", "unknown", nil}, - "delete non-existing policy with correct subject": {subject.ID, "unknown", nil}, - "delete non-existing policy with correct object": {"unknown", objectID, nil}, - "delete existing policy": {subject.ID, objectID, nil}, - } - - for desc, tc := range cases { - policy := policies.Policy{ - Subject: tc.Subject, - Object: tc.Object, - } - err := repo.Delete(context.Background(), policy) - assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", desc, tc.err, err)) - } - pm := policies.Page{ - OwnerID: subject.ID, - Subject: subject.ID, - Object: objectID, - Action: "c_delete", - } - policyPage, err := repo.Retrieve(context.Background(), pm) - assert.Equal(t, uint64(0), policyPage.Total, fmt.Sprintf("retrieve policies unexpected total %d\n", policyPage.Total)) - require.Nil(t, err, fmt.Sprintf("retrieve policies unexpected error: %s", err)) -} diff --git a/clients/policies/postgres/setup_test.go b/clients/policies/postgres/setup_test.go deleted file mode 100644 index 9cb273dbc56..00000000000 --- a/clients/policies/postgres/setup_test.go +++ /dev/null @@ -1,90 +0,0 @@ -// Package postgres_test contains tests for PostgreSQL repository -// implementations. -package postgres_test - -import ( - "database/sql" - "fmt" - "log" - "os" - "testing" - "time" - - "github.com/jmoiron/sqlx" - "github.com/mainflux/mainflux/clients/postgres" - dockertest "github.com/ory/dockertest/v3" - "github.com/ory/dockertest/v3/docker" - "go.opentelemetry.io/otel" -) - -var ( - db *sqlx.DB - database postgres.Database - tracer = otel.Tracer("repo_tests") -) - -func TestMain(m *testing.M) { - pool, err := dockertest.NewPool("") - if err != nil { - log.Fatalf("Could not connect to docker: %s", err) - } - - container, err := pool.RunWithOptions(&dockertest.RunOptions{ - Repository: "postgres", - Tag: "15.1-alpine", - Env: []string{ - "POSTGRES_USER=test", - "POSTGRES_PASSWORD=test", - "POSTGRES_DB=test", - "listen_addresses = '*'", - }, - }, func(config *docker.HostConfig) { - config.AutoRemove = true - config.RestartPolicy = docker.RestartPolicy{Name: "no"} - }) - if err != nil { - log.Fatalf("Could not start container: %s", err) - } - - port := container.GetPort("5432/tcp") - - // exponential backoff-retry, because the application in the container might not be ready to accept connections yet - pool.MaxWait = 120 * time.Second - if err := pool.Retry(func() error { - url := fmt.Sprintf("host=localhost port=%s user=test dbname=test password=test sslmode=disable", port) - db, err := sql.Open("pgx", url) - if err != nil { - return err - } - return db.Ping() - }); err != nil { - log.Fatalf("Could not connect to docker: %s", err) - } - - dbConfig := postgres.Config{ - Host: "localhost", - Port: port, - User: "test", - Pass: "test", - Name: "test", - SSLMode: "disable", - SSLCert: "", - SSLKey: "", - SSLRootCert: "", - } - - if db, err = postgres.Connect(dbConfig); err != nil { - log.Fatalf("Could not setup test DB connection: %s", err) - } - database = postgres.NewDatabase(db, tracer) - - code := m.Run() - - // Defers will not be run when using os.Exit - db.Close() - if err := pool.Purge(container); err != nil { - log.Fatalf("Could not purge container: %s", err) - } - - os.Exit(code) -} diff --git a/clients/policies/service.go b/clients/policies/service.go deleted file mode 100644 index 2380b07edf7..00000000000 --- a/clients/policies/service.go +++ /dev/null @@ -1,154 +0,0 @@ -package policies - -import ( - "context" - "time" - - "github.com/mainflux/mainflux" - "github.com/mainflux/mainflux/clients/jwt" - "github.com/mainflux/mainflux/internal/apiutil" - "github.com/mainflux/mainflux/pkg/errors" -) - -// Possible token types are access and refresh tokens. -const ( - AccessToken = "access" -) - -// Service unites Clients and Group services. -type Service interface { - PolicyService -} - -type service struct { - policies PolicyRepository - idProvider mainflux.IDProvider - tokens jwt.TokenRepository -} - -// NewService returns a new Clients service implementation. -func NewService(p PolicyRepository, t jwt.TokenRepository, idp mainflux.IDProvider) Service { - return service{ - policies: p, - tokens: t, - idProvider: idp, - } -} - -func (svc service) Authorize(ctx context.Context, entityType string, p Policy) error { - if err := p.Validate(); err != nil { - return err - } - id, err := svc.identify(ctx, p.Subject) - if err != nil { - return err - } - p.Subject = id - return svc.policies.Evaluate(ctx, entityType, p) -} -func (svc service) UpdatePolicy(ctx context.Context, token string, p Policy) error { - id, err := svc.identify(ctx, token) - if err != nil { - return err - } - if err := p.Validate(); err != nil { - return err - } - if err := svc.checkActionRank(ctx, id, p); err != nil { - return err - } - p.UpdatedAt = time.Now() - - return svc.policies.Update(ctx, p) -} - -func (svc service) AddPolicy(ctx context.Context, token string, p Policy) error { - id, err := svc.identify(ctx, token) - if err != nil { - return err - } - if err := p.Validate(); err != nil { - return err - } - - page, err := svc.policies.Retrieve(ctx, Page{Subject: p.Subject, Object: p.Object}) - if err != nil { - return err - } - if len(page.Policies) != 0 { - return svc.policies.Update(ctx, p) - } - if err := svc.checkActionRank(ctx, id, p); err != nil { - return err - } - p.OwnerID = id - p.CreatedAt = time.Now() - p.UpdatedAt = p.CreatedAt - - return svc.policies.Save(ctx, p) -} - -func (svc service) DeletePolicy(ctx context.Context, token string, p Policy) error { - id, err := svc.identify(ctx, token) - if err != nil { - return err - } - if err := svc.checkActionRank(ctx, id, p); err != nil { - return err - } - - return svc.policies.Delete(ctx, p) -} - -func (svc service) ListPolicy(ctx context.Context, token string, pm Page) (PolicyPage, error) { - if _, err := svc.identify(ctx, token); err != nil { - return PolicyPage{}, err - } - if err := pm.Validate(); err != nil { - return PolicyPage{}, err - } - - page, err := svc.policies.Retrieve(ctx, pm) - if err != nil { - return PolicyPage{}, err - } - - return page, err -} - -// checkActionRank check if an action is in the provide list of actions -func (svc service) checkActionRank(ctx context.Context, clientID string, p Policy) error { - page, err := svc.policies.Retrieve(ctx, Page{Subject: clientID, Object: p.Object}) - if err != nil { - return err - } - if len(page.Policies) != 0 { - for _, a := range p.Actions { - var found = false - for _, v := range page.Policies[0].Actions { - if v == a { - found = true - break - } - } - if !found { - return apiutil.ErrHigherPolicyRank - } - } - } - - return nil - -} - -func (svc service) identify(ctx context.Context, tkn string) (string, error) { - claims, err := svc.tokens.Parse(ctx, tkn) - if err != nil { - return "", errors.Wrap(errors.ErrAuthentication, err) - } - if claims.Type != AccessToken { - return "", errors.ErrAuthentication - } - - return claims.ClientID, nil -} diff --git a/clients/policies/service_test.go b/clients/policies/service_test.go deleted file mode 100644 index 098c1c8bdcc..00000000000 --- a/clients/policies/service_test.go +++ /dev/null @@ -1,371 +0,0 @@ -package policies_test - -import ( - context "context" - fmt "fmt" - "testing" - - "github.com/mainflux/mainflux/clients/clients" - cmocks "github.com/mainflux/mainflux/clients/clients/mocks" - "github.com/mainflux/mainflux/clients/hasher" - "github.com/mainflux/mainflux/clients/jwt" - "github.com/mainflux/mainflux/clients/policies" - pmocks "github.com/mainflux/mainflux/clients/policies/mocks" - "github.com/mainflux/mainflux/internal/apiutil" - "github.com/mainflux/mainflux/internal/testsutil" - "github.com/mainflux/mainflux/pkg/errors" - "github.com/mainflux/mainflux/pkg/uuid" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" - "github.com/stretchr/testify/require" -) - -var ( - idProvider = uuid.New() - phasher = hasher.New() - secret = "strongsecret" - inValidToken = "invalidToken" - memberActions = []string{"g_list"} - authoritiesObj = "authorities" -) - -func generateValidToken(t *testing.T, clientID string, svc clients.Service, cRepo *cmocks.ClientRepository) string { - client := clients.Client{ - ID: clientID, - Name: "validtoken", - Credentials: clients.Credentials{ - Identity: "validtoken", - Secret: secret, - }, - Status: clients.EnabledStatus, - } - rClient := client - rClient.Credentials.Secret, _ = phasher.Hash(client.Credentials.Secret) - - repoCall := cRepo.On("RetrieveByIdentity", context.Background(), mock.Anything).Return(rClient, nil) - token, err := svc.IssueToken(context.Background(), client.Credentials.Identity, client.Credentials.Secret) - assert.True(t, errors.Contains(err, nil), fmt.Sprintf("Create token expected nil got %s\n", err)) - repoCall.Unset() - return token.AccessToken -} - -func TestAddPolicy(t *testing.T) { - cRepo := new(cmocks.ClientRepository) - pRepo := new(pmocks.PolicyRepository) - tokenizer := jwt.NewTokenRepo([]byte(secret)) - csvc := clients.NewService(cRepo, pRepo, tokenizer, phasher, idProvider) - svc := policies.NewService(pRepo, tokenizer, idProvider) - - policy := policies.Policy{Object: "obj1", Actions: []string{"m_read"}, Subject: "sub1"} - - cases := []struct { - desc string - policy policies.Policy - page policies.PolicyPage - token string - err error - }{ - { - desc: "add new policy", - policy: policy, - page: policies.PolicyPage{}, - token: generateValidToken(t, testsutil.GenerateUUID(t, idProvider), csvc, cRepo), - err: nil, - }, - { - desc: "add existing policy", - policy: policy, - page: policies.PolicyPage{Policies: []policies.Policy{policy}}, - token: generateValidToken(t, testsutil.GenerateUUID(t, idProvider), csvc, cRepo), - err: errors.ErrConflict, - }, - { - desc: "add a new policy with owner", - page: policies.PolicyPage{}, - policy: policies.Policy{ - OwnerID: testsutil.GenerateUUID(t, idProvider), - Object: "objwithowner", - Actions: []string{"m_read"}, - Subject: "subwithowner", - }, - err: nil, - token: generateValidToken(t, testsutil.GenerateUUID(t, idProvider), csvc, cRepo), - }, - { - desc: "add a new policy with more actions", - page: policies.PolicyPage{}, - policy: policies.Policy{ - Object: "obj2", - Actions: []string{"c_delete", "c_update", "c_add", "c_list"}, - Subject: "sub2", - }, - err: nil, - token: generateValidToken(t, testsutil.GenerateUUID(t, idProvider), csvc, cRepo), - }, - { - desc: "add a new policy with wrong action", - page: policies.PolicyPage{}, - policy: policies.Policy{ - Object: "obj3", - Actions: []string{"wrong"}, - Subject: "sub3", - }, - err: apiutil.ErrMissingPolicyAct, - token: generateValidToken(t, testsutil.GenerateUUID(t, idProvider), csvc, cRepo), - }, - { - desc: "add a new policy with empty object", - page: policies.PolicyPage{}, - policy: policies.Policy{ - Actions: []string{"c_delete"}, - Subject: "sub4", - }, - err: apiutil.ErrMissingPolicyObj, - token: generateValidToken(t, testsutil.GenerateUUID(t, idProvider), csvc, cRepo), - }, - { - desc: "add a new policy with empty subject", - page: policies.PolicyPage{}, - policy: policies.Policy{ - Actions: []string{"c_delete"}, - Object: "obj4", - }, - err: apiutil.ErrMissingPolicySub, - token: generateValidToken(t, testsutil.GenerateUUID(t, idProvider), csvc, cRepo), - }, - { - desc: "add a new policy with empty action", - page: policies.PolicyPage{}, - policy: policies.Policy{ - Subject: "sub5", - Object: "obj5", - }, - err: apiutil.ErrMissingPolicyAct, - token: generateValidToken(t, testsutil.GenerateUUID(t, idProvider), csvc, cRepo), - }, - } - - for _, tc := range cases { - repo1Call := pRepo.On("Evaluate", context.Background(), "client", mock.Anything).Return(nil) - repoCall := pRepo.On("Update", context.Background(), tc.policy).Return(tc.err) - repoCall1 := pRepo.On("Save", context.Background(), mock.Anything).Return(tc.err) - repoCall2 := pRepo.On("Retrieve", context.Background(), mock.Anything).Return(tc.page, nil) - err := svc.AddPolicy(context.Background(), tc.token, tc.policy) - assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) - if err == nil { - tc.policy.Subject = tc.token - err = svc.Authorize(context.Background(), "client", tc.policy) - require.Nil(t, err, fmt.Sprintf("checking shared %v policy expected to be succeed: %#v", tc.policy, err)) - } - repoCall1.Parent.AssertCalled(t, "Save", context.Background(), mock.Anything) - repoCall.Unset() - repoCall1.Unset() - repoCall2.Unset() - repo1Call.Unset() - } - -} - -func TestAuthorize(t *testing.T) { - cRepo := new(cmocks.ClientRepository) - pRepo := new(pmocks.PolicyRepository) - tokenizer := jwt.NewTokenRepo([]byte(secret)) - csvc := clients.NewService(cRepo, pRepo, tokenizer, phasher, idProvider) - svc := policies.NewService(pRepo, tokenizer, idProvider) - - cases := []struct { - desc string - policy policies.Policy - domain string - err error - }{ - { - desc: "check valid policy in client domain", - policy: policies.Policy{Object: "client1", Actions: []string{"c_update"}, Subject: generateValidToken(t, testsutil.GenerateUUID(t, idProvider), csvc, cRepo)}, - domain: "client", - err: nil, - }, - { - desc: "check valid policy in group domain", - policy: policies.Policy{Object: "client1", Actions: []string{"g_update"}, Subject: generateValidToken(t, testsutil.GenerateUUID(t, idProvider), csvc, cRepo)}, - domain: "group", - err: errors.ErrConflict, - }, - { - desc: "check invalid policy in client domain", - policy: policies.Policy{Object: "client3", Actions: []string{"c_update"}, Subject: generateValidToken(t, testsutil.GenerateUUID(t, idProvider), csvc, cRepo)}, - domain: "client", - err: nil, - }, - { - desc: "check invalid policy in group domain", - policy: policies.Policy{Object: "client3", Actions: []string{"g_update"}, Subject: generateValidToken(t, testsutil.GenerateUUID(t, idProvider), csvc, cRepo)}, - domain: "group", - err: nil, - }, - } - - for _, tc := range cases { - repoCall := pRepo.On("Evaluate", context.Background(), tc.domain, mock.Anything).Return(tc.err) - err := svc.Authorize(context.Background(), tc.domain, tc.policy) - assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) - repoCall.Unset() - } - -} - -func TestDeletePolicy(t *testing.T) { - cRepo := new(cmocks.ClientRepository) - pRepo := new(pmocks.PolicyRepository) - tokenizer := jwt.NewTokenRepo([]byte(secret)) - csvc := clients.NewService(cRepo, pRepo, tokenizer, phasher, idProvider) - svc := policies.NewService(pRepo, tokenizer, idProvider) - - pr := policies.Policy{Object: authoritiesObj, Actions: memberActions, Subject: testsutil.GenerateUUID(t, idProvider)} - - repoCall := pRepo.On("Delete", context.Background(), mock.Anything).Return(nil) - repoCall1 := pRepo.On("Retrieve", context.Background(), mock.Anything).Return(policies.PolicyPage{Policies: []policies.Policy{pr}}, nil) - err := svc.DeletePolicy(context.Background(), generateValidToken(t, testsutil.GenerateUUID(t, idProvider), csvc, cRepo), pr) - require.Nil(t, err, fmt.Sprintf("deleting %v policy expected to succeed: %s", pr, err)) - repoCall.Unset() - repoCall1.Unset() -} - -func TestListPolicies(t *testing.T) { - cRepo := new(cmocks.ClientRepository) - pRepo := new(pmocks.PolicyRepository) - tokenizer := jwt.NewTokenRepo([]byte(secret)) - csvc := clients.NewService(cRepo, pRepo, tokenizer, phasher, idProvider) - svc := policies.NewService(pRepo, tokenizer, idProvider) - - id := testsutil.GenerateUUID(t, idProvider) - - readPolicy := "m_read" - writePolicy := "m_write" - - var nPolicy = uint64(10) - var aPolicies = []policies.Policy{} - for i := uint64(0); i < nPolicy; i++ { - pr := policies.Policy{ - OwnerID: id, - Actions: []string{readPolicy}, - Subject: fmt.Sprintf("thing_%d", i), - Object: fmt.Sprintf("client_%d", i), - } - if i%3 == 0 { - pr.Actions = []string{writePolicy} - } - aPolicies = append(aPolicies, pr) - } - - cases := []struct { - desc string - token string - page policies.Page - response policies.PolicyPage - err error - }{ - { - desc: "list policies with authorized token", - token: generateValidToken(t, testsutil.GenerateUUID(t, idProvider), csvc, cRepo), - err: nil, - response: policies.PolicyPage{ - Page: policies.Page{ - Offset: 0, - Total: nPolicy, - }, - Policies: aPolicies, - }, - }, - { - desc: "list policies with invalid token", - token: inValidToken, - err: errors.ErrAuthentication, - response: policies.PolicyPage{ - Page: policies.Page{ - Offset: 0, - }, - }, - }, - { - desc: "list policies with offset and limit", - token: generateValidToken(t, testsutil.GenerateUUID(t, idProvider), csvc, cRepo), - page: policies.Page{ - Offset: 6, - Limit: nPolicy, - }, - response: policies.PolicyPage{ - Page: policies.Page{ - Offset: 6, - Total: nPolicy, - }, - Policies: aPolicies[6:10], - }, - }, - { - desc: "list policies with wrong action", - token: generateValidToken(t, testsutil.GenerateUUID(t, idProvider), csvc, cRepo), - page: policies.Page{ - Action: "wrong", - }, - response: policies.PolicyPage{}, - err: apiutil.ErrMissingPolicyAct, - }, - } - - for _, tc := range cases { - repoCall := pRepo.On("Retrieve", context.Background(), tc.page).Return(tc.response, tc.err) - page, err := svc.ListPolicy(context.Background(), tc.token, tc.page) - assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) - assert.Equal(t, tc.response, page, fmt.Sprintf("%s: expected size %v got %v\n", tc.desc, tc.response, page)) - repoCall.Unset() - } - -} - -func TestUpdatePolicies(t *testing.T) { - cRepo := new(cmocks.ClientRepository) - pRepo := new(pmocks.PolicyRepository) - tokenizer := jwt.NewTokenRepo([]byte(secret)) - csvc := clients.NewService(cRepo, pRepo, tokenizer, phasher, idProvider) - svc := policies.NewService(pRepo, tokenizer, idProvider) - - policy := policies.Policy{Object: "obj1", Actions: []string{"m_read"}, Subject: "sub1"} - - cases := []struct { - desc string - action []string - token string - err error - }{ - { - desc: "update policy actions with valid token", - action: []string{"m_write"}, - token: generateValidToken(t, testsutil.GenerateUUID(t, idProvider), csvc, cRepo), - err: nil, - }, - { - desc: "update policy action with invalid token", - action: []string{"m_write"}, - token: "non-existent", - err: errors.ErrAuthentication, - }, - { - desc: "update policy action with wrong policy action", - action: []string{"wrong"}, - token: generateValidToken(t, testsutil.GenerateUUID(t, idProvider), csvc, cRepo), - err: apiutil.ErrMissingPolicyAct, - }, - } - - for _, tc := range cases { - policy.Actions = tc.action - repoCall := pRepo.On("Retrieve", context.Background(), mock.Anything).Return(policies.PolicyPage{Policies: []policies.Policy{policy}}, nil) - repoCall1 := pRepo.On("Update", context.Background(), mock.Anything).Return(tc.err) - err := svc.UpdatePolicy(context.Background(), tc.token, policy) - assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) - repoCall1.Parent.AssertCalled(t, "Update", context.Background(), mock.Anything) - repoCall.Unset() - repoCall1.Unset() - } -} diff --git a/clients/policies/status.go b/clients/policies/status.go deleted file mode 100644 index 90870332fe6..00000000000 --- a/clients/policies/status.go +++ /dev/null @@ -1,53 +0,0 @@ -package policies - -import "github.com/mainflux/mainflux/internal/apiutil" - -// Status represents Group status. -type Status uint8 - -// Possible Group status values -const ( - DisabledStatus Status = iota - EnabledStatus - - // AllStatus is used for querying purposes to list groups irrespective - // of their status - both active and inactive. It is never stored in the - // database as the actual Group status and should always be the largest - // value in this enumeration. - AllStatus -) - -// String representation of the possible status values. -const ( - Disabled = "disabled" - Enabled = "enabled" - All = "all" - Unknown = "unknown" -) - -// String converts group status to string literal. -func (s Status) String() string { - switch s { - case DisabledStatus: - return Disabled - case EnabledStatus: - return Enabled - case AllStatus: - return All - default: - return Unknown - } -} - -// ToStatus converts string value to a valid Group status. -func ToStatus(status string) (Status, error) { - switch status { - case Disabled: - return DisabledStatus, nil - case Enabled: - return EnabledStatus, nil - case All: - return AllStatus, nil - } - return Status(0), apiutil.ErrInvalidStatus -} diff --git a/clients/policies/tracing/tracing.go b/clients/policies/tracing/tracing.go deleted file mode 100644 index 78f4614602e..00000000000 --- a/clients/policies/tracing/tracing.go +++ /dev/null @@ -1,58 +0,0 @@ -package tracing - -import ( - "context" - - "github.com/mainflux/mainflux/clients/policies" - "go.opentelemetry.io/otel/attribute" - "go.opentelemetry.io/otel/trace" -) - -var _ policies.Service = (*tracingMiddleware)(nil) - -type tracingMiddleware struct { - tracer trace.Tracer - psvc policies.Service -} - -func TracingMiddleware(psvc policies.Service, tracer trace.Tracer) policies.Service { - return &tracingMiddleware{tracer, psvc} -} - -func (tm *tracingMiddleware) Authorize(ctx context.Context, domain string, p policies.Policy) error { - ctx, span := tm.tracer.Start(ctx, "svc_authorize", trace.WithAttributes(attribute.StringSlice("Action", p.Actions))) - defer span.End() - - return tm.psvc.Authorize(ctx, domain, p) -} -func (tm *tracingMiddleware) UpdatePolicy(ctx context.Context, token string, p policies.Policy) error { - ctx, span := tm.tracer.Start(ctx, "svc_update_policy", trace.WithAttributes(attribute.StringSlice("Actions", p.Actions))) - defer span.End() - - return tm.psvc.UpdatePolicy(ctx, token, p) - -} - -func (tm *tracingMiddleware) AddPolicy(ctx context.Context, token string, p policies.Policy) error { - ctx, span := tm.tracer.Start(ctx, "svc_add_policy", trace.WithAttributes(attribute.StringSlice("Actions", p.Actions))) - defer span.End() - - return tm.psvc.AddPolicy(ctx, token, p) - -} - -func (tm *tracingMiddleware) DeletePolicy(ctx context.Context, token string, p policies.Policy) error { - ctx, span := tm.tracer.Start(ctx, "svc_delete_policy", trace.WithAttributes(attribute.String("Subject", p.Subject), attribute.String("Object", p.Object))) - defer span.End() - - return tm.psvc.DeletePolicy(ctx, token, p) - -} - -func (tm *tracingMiddleware) ListPolicy(ctx context.Context, token string, pm policies.Page) (policies.PolicyPage, error) { - ctx, span := tm.tracer.Start(ctx, "svc_list_policy") - defer span.End() - - return tm.psvc.ListPolicy(ctx, token, pm) - -} diff --git a/clients/postgres/init.go b/clients/postgres/init.go deleted file mode 100644 index a333c281ca6..00000000000 --- a/clients/postgres/init.go +++ /dev/null @@ -1,93 +0,0 @@ -package postgres - -import ( - "fmt" - - _ "github.com/jackc/pgx/v5/stdlib" // required for SQL access - "github.com/jmoiron/sqlx" - migrate "github.com/rubenv/sql-migrate" -) - -// Config defines the options that are used when connecting to a PostgreSQL instance -type Config struct { - Host string - Port string - User string - Pass string - Name string - SSLMode string - SSLCert string - SSLKey string - SSLRootCert string -} - -// Connect creates a connection to the PostgreSQL instance and applies any -// unapplied database migrations. A non-nil error is returned to indicate -// failure. -func Connect(cfg Config) (*sqlx.DB, error) { - url := fmt.Sprintf("host=%s port=%s user=%s dbname=%s password=%s sslmode=%s sslcert=%s sslkey=%s sslrootcert=%s", cfg.Host, cfg.Port, cfg.User, cfg.Name, cfg.Pass, cfg.SSLMode, cfg.SSLCert, cfg.SSLKey, cfg.SSLRootCert) - db, err := sqlx.Open("pgx", url) - if err != nil { - return nil, err - } - - if err := migrateDB(db); err != nil { - return nil, err - } - return db, nil -} - -func migrateDB(db *sqlx.DB) error { - migrations := &migrate.MemoryMigrationSource{ - Migrations: []*migrate.Migration{ - { - Id: "clients_01", - Up: []string{ - `CREATE TABLE IF NOT EXISTS clients ( - id VARCHAR(254) PRIMARY KEY, - name VARCHAR(254), - owner VARCHAR(254), - identity VARCHAR(254) UNIQUE NOT NULL, - secret TEXT NOT NULL, - tags TEXT[], - metadata JSONB, - created_at TIMESTAMP, - updated_at TIMESTAMP, - status SMALLINT NOT NULL CHECK (status >= 0) DEFAULT 1 - )`, - `CREATE TABLE IF NOT EXISTS groups ( - id VARCHAR(254) PRIMARY KEY, - parent_id VARCHAR(254), - owner_id VARCHAR(254) NOT NULL, - name VARCHAR(254) NOT NULL, - description VARCHAR(1024), - metadata JSONB, - created_at TIMESTAMP, - updated_at TIMESTAMP, - status SMALLINT NOT NULL CHECK (status >= 0) DEFAULT 1, - UNIQUE (owner_id, name), - FOREIGN KEY (parent_id) REFERENCES groups (id) ON DELETE CASCADE - )`, - `CREATE TABLE IF NOT EXISTS policies ( - owner_id VARCHAR(254) NOT NULL, - subject VARCHAR(254) NOT NULL, - object VARCHAR(254) NOT NULL, - actions TEXT[] NOT NULL, - created_at TIMESTAMP, - updated_at TIMESTAMP, - FOREIGN KEY (subject) REFERENCES clients (id), - PRIMARY KEY (subject, object, actions) - )`, - }, - Down: []string{ - `DROP TABLE IF EXISTS clients`, - `DROP TABLE IF EXISTS groups`, - `DROP TABLE IF EXISTS memberships`, - }, - }, - }, - } - - _, err := migrate.Exec(db.DB, "postgres", migrations, migrate.Up) - return err -} diff --git a/cmd/clients/main.go b/cmd/clients/main.go deleted file mode 100644 index 1c3d0dac3dd..00000000000 --- a/cmd/clients/main.go +++ /dev/null @@ -1,409 +0,0 @@ -package main - -import ( - "context" - "fmt" - "log" - "net" - "net/http" - "os" - "time" - - kitprometheus "github.com/go-kit/kit/metrics/prometheus" - "github.com/go-zoo/bone" - "github.com/jmoiron/sqlx" - "github.com/mainflux/mainflux" - "github.com/mainflux/mainflux/clients/clients" - capi "github.com/mainflux/mainflux/clients/clients/api" - cpostgres "github.com/mainflux/mainflux/clients/clients/postgres" - ctracing "github.com/mainflux/mainflux/clients/clients/tracing" - "github.com/mainflux/mainflux/clients/groups" - gapi "github.com/mainflux/mainflux/clients/groups/api" - gpostgres "github.com/mainflux/mainflux/clients/groups/postgres" - gtracing "github.com/mainflux/mainflux/clients/groups/tracing" - "github.com/mainflux/mainflux/clients/hasher" - "github.com/mainflux/mainflux/clients/jwt" - "github.com/mainflux/mainflux/clients/policies" - grpcapi "github.com/mainflux/mainflux/clients/policies/api/grpc" - papi "github.com/mainflux/mainflux/clients/policies/api/http" - ppostgres "github.com/mainflux/mainflux/clients/policies/postgres" - ppracing "github.com/mainflux/mainflux/clients/policies/tracing" - "github.com/mainflux/mainflux/clients/postgres" - "github.com/mainflux/mainflux/logger" - "github.com/mainflux/mainflux/pkg/errors" - "github.com/mainflux/mainflux/pkg/uuid" - stdprometheus "github.com/prometheus/client_golang/prometheus" - "go.opentelemetry.io/otel" - "go.opentelemetry.io/otel/exporters/jaeger" - "go.opentelemetry.io/otel/propagation" - "go.opentelemetry.io/otel/sdk/resource" - tracesdk "go.opentelemetry.io/otel/sdk/trace" - semconv "go.opentelemetry.io/otel/semconv/v1.12.0" - "go.opentelemetry.io/otel/trace" - "golang.org/x/sync/errgroup" - "google.golang.org/grpc" - "google.golang.org/grpc/credentials" - "google.golang.org/grpc/reflection" -) - -const ( - svcName = "clients" - stopWaitTime = 5 * time.Second - - defLogLevel = "debug" - defSecretKey = "clientsecret" - defAdminIdentity = "admin@example.com" - defAdminSecret = "12345678" - defDBHost = "localhost" - defDBPort = "5432" - defDBUser = "mainflux" - defDBPass = "mainflux" - defDB = "clients" - defDBSSLMode = "disable" - defDBSSLCert = "" - defDBSSLKey = "" - defDBSSLRootCert = "" - defHTTPPort = "9191" - defGRPCPort = "9192" - defServerCert = "" - defServerKey = "" - defJaegerURL = "http://localhost:6831" - - envLogLevel = "MF_CLIENTS_LOG_LEVEL" - envSecretKey = "MF_CLIENTS_SECRET_KEY" - envAdminIdentity = "MF_CLIENTS_ADMIN_EMAIL" - envAdminSecret = "MF_CLIENTS_ADMIN_PASSWORD" - envDBHost = "MF_CLIENTS_DB_HOST" - envDBPort = "MF_CLIENTS_DB_PORT" - envDBUser = "MF_CLIENTS_DB_USER" - envDBPass = "MF_CLIENTS_DB_PASS" - envDB = "MF_CLIENTS_DB" - envDBSSLMode = "MF_CLIENTS_DB_SSL_MODE" - envDBSSLCert = "MF_CLIENTS_DB_SSL_CERT" - envDBSSLKey = "MF_CLIENTS_DB_SSL_KEY" - envDBSSLRootCert = "MF_CLIENTS_DB_SSL_ROOT_CERT" - envHTTPPort = "MF_CLIENTS_HTTP_PORT" - envGRPCPort = "MF_CLIENTS_GRPC_PORT" - envServerCert = "MF_CLIENTS_SERVER_CERT" - envServerKey = "MF_CLIENTS_SERVER_KEY" - envJaegerURL = "MF_CLIENTS_JAEGER_URL" -) - -type config struct { - logLevel string - secretKey string - adminIdentity string - adminSecret string - dbConfig postgres.Config - httpPort string - grpcPort string - serverCert string - serverKey string - jaegerURL string -} - -func main() { - cfg := loadConfig() - ctx, cancel := context.WithCancel(context.Background()) - g, ctx := errgroup.WithContext(ctx) - - logger, err := logger.New(os.Stdout, cfg.logLevel) - if err != nil { - log.Fatalf(err.Error()) - } - db := connectToDB(cfg.dbConfig, logger) - defer db.Close() - - tp, err := initJaeger(svcName, cfg.jaegerURL) - if err != nil { - logger.Error(fmt.Sprintf("Failed to init Jaeger: %s", err)) - } - defer func() { - if err := tp.Shutdown(context.Background()); err != nil { - logger.Error(fmt.Sprintf("Error shutting down tracer provider: %v", err)) - } - }() - tracer := otel.Tracer(svcName) - - csvc, gsvc, psvc := newService(db, tracer, cfg, logger) - - g.Go(func() error { - return startHTTPServer(ctx, csvc, gsvc, psvc, cfg.httpPort, cfg.serverCert, cfg.serverKey, logger) - }) - g.Go(func() error { - return startGRPCServer(ctx, psvc, cfg.grpcPort, cfg.serverCert, cfg.serverKey, logger) - }) - - g.Go(func() error { - if sig := errors.SignalHandler(ctx); sig != nil { - cancel() - logger.Info(fmt.Sprintf("%s service shutdown by signal: %s", svcName, sig)) - } - return nil - }) - - if err := g.Wait(); err != nil { - logger.Error(fmt.Sprintf("%s service terminated: %s", svcName, err)) - } -} - -func loadConfig() config { - dbConfig := postgres.Config{ - Host: mainflux.Env(envDBHost, defDBHost), - Port: mainflux.Env(envDBPort, defDBPort), - User: mainflux.Env(envDBUser, defDBUser), - Pass: mainflux.Env(envDBPass, defDBPass), - Name: mainflux.Env(envDB, defDB), - SSLMode: mainflux.Env(envDBSSLMode, defDBSSLMode), - SSLCert: mainflux.Env(envDBSSLCert, defDBSSLCert), - SSLKey: mainflux.Env(envDBSSLKey, defDBSSLKey), - SSLRootCert: mainflux.Env(envDBSSLRootCert, defDBSSLRootCert), - } - - return config{ - logLevel: mainflux.Env(envLogLevel, defLogLevel), - secretKey: mainflux.Env(envSecretKey, defSecretKey), - adminIdentity: mainflux.Env(envAdminIdentity, defAdminIdentity), - adminSecret: mainflux.Env(envAdminSecret, defAdminSecret), - dbConfig: dbConfig, - httpPort: mainflux.Env(envHTTPPort, defHTTPPort), - grpcPort: mainflux.Env(envGRPCPort, defGRPCPort), - serverCert: mainflux.Env(envServerCert, defServerCert), - serverKey: mainflux.Env(envServerKey, defServerKey), - jaegerURL: mainflux.Env(envJaegerURL, defJaegerURL), - } - -} - -func initJaeger(svcName, url string) (*tracesdk.TracerProvider, error) { - exporter, err := jaeger.New(jaeger.WithCollectorEndpoint(jaeger.WithEndpoint(url))) - if err != nil { - return nil, err - } - tp := tracesdk.NewTracerProvider( - tracesdk.WithSampler(tracesdk.AlwaysSample()), - tracesdk.WithBatcher(exporter), - tracesdk.WithSpanProcessor(tracesdk.NewBatchSpanProcessor(exporter)), - tracesdk.WithResource(resource.NewWithAttributes( - semconv.SchemaURL, - semconv.ServiceNameKey.String(svcName), - )), - ) - otel.SetTracerProvider(tp) - otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator(propagation.TraceContext{}, propagation.Baggage{})) - - return tp, nil -} - -func connectToDB(dbConfig postgres.Config, logger logger.Logger) *sqlx.DB { - db, err := postgres.Connect(dbConfig) - if err != nil { - logger.Error(fmt.Sprintf("Failed to connect to postgres: %s", err)) - os.Exit(1) - } - return db -} - -func newService(db *sqlx.DB, tracer trace.Tracer, c config, logger logger.Logger) (clients.Service, groups.GroupService, policies.PolicyService) { - database := postgres.NewDatabase(db, tracer) - cRepo := cpostgres.NewClientRepo(database) - gRepo := gpostgres.NewGroupRepo(database) - pRepo := ppostgres.NewPolicyRepo(database) - - idp := uuid.New() - hsr := hasher.New() - - tokenizer := jwt.NewTokenRepo([]byte(c.secretKey)) - tokenizer = jwt.NewTokenRepoMiddleware(tokenizer, tracer) - - csvc := clients.NewService(cRepo, pRepo, tokenizer, hsr, idp) - gsvc := groups.NewService(gRepo, pRepo, tokenizer, idp) - psvc := policies.NewService(pRepo, tokenizer, idp) - - csvc = ctracing.TracingMiddleware(csvc, tracer) - csvc = capi.LoggingMiddleware(csvc, logger) - csvc = capi.MetricsMiddleware( - csvc, - kitprometheus.NewCounterFrom(stdprometheus.CounterOpts{ - Namespace: svcName, - Subsystem: "api", - Name: "request_count", - Help: "Number of requests received.", - }, []string{"method"}), - kitprometheus.NewSummaryFrom(stdprometheus.SummaryOpts{ - Namespace: svcName, - Subsystem: "api", - Name: "request_latency_microseconds", - Help: "Total duration of requests in microseconds.", - }, []string{"method"}), - ) - - gsvc = gtracing.TracingMiddleware(gsvc, tracer) - gsvc = gapi.LoggingMiddleware(gsvc, logger) - gsvc = gapi.MetricsMiddleware( - gsvc, - kitprometheus.NewCounterFrom(stdprometheus.CounterOpts{ - Namespace: "clients_groups", - Subsystem: "api", - Name: "request_count", - Help: "Number of requests received.", - }, []string{"method"}), - kitprometheus.NewSummaryFrom(stdprometheus.SummaryOpts{ - Namespace: "clients_groups", - Subsystem: "api", - Name: "request_latency_microseconds", - Help: "Total duration of requests in microseconds.", - }, []string{"method"}), - ) - - psvc = ppracing.TracingMiddleware(psvc, tracer) - psvc = papi.LoggingMiddleware(psvc, logger) - psvc = papi.MetricsMiddleware( - psvc, - kitprometheus.NewCounterFrom(stdprometheus.CounterOpts{ - Namespace: "client_policies", - Subsystem: "api", - Name: "request_count", - Help: "Number of requests received.", - }, []string{"method"}), - kitprometheus.NewSummaryFrom(stdprometheus.SummaryOpts{ - Namespace: "client_policies", - Subsystem: "api", - Name: "request_latency_microseconds", - Help: "Total duration of requests in microseconds.", - }, []string{"method"}), - ) - - if err := createAdmin(c, cRepo, hsr, csvc); err != nil { - logger.Error(fmt.Sprintf("Failed to create admin client: %s", err)) - os.Exit(1) - } - return csvc, gsvc, psvc -} - -func startHTTPServer(ctx context.Context, csvc clients.Service, gsvc groups.Service, psvc policies.Service, port string, certFile string, keyFile string, logger logger.Logger) error { - p := fmt.Sprintf(":%s", port) - errCh := make(chan error) - m := bone.New() - capi.MakeClientsHandler(csvc, m, logger) - gapi.MakeGroupsHandler(gsvc, m, logger) - papi.MakePolicyHandler(psvc, m, logger) - server := &http.Server{Addr: p, Handler: m} - - switch { - case certFile != "" || keyFile != "": - logger.Info(fmt.Sprintf("Clients service started using https, cert %s key %s, exposed port %s", certFile, keyFile, port)) - go func() { - errCh <- server.ListenAndServeTLS(certFile, keyFile) - }() - default: - logger.Info(fmt.Sprintf("Clients service started using http, exposed port %s", port)) - go func() { - errCh <- server.ListenAndServe() - }() - } - - select { - case <-ctx.Done(): - ctxShutdown, cancelShutdown := context.WithTimeout(context.Background(), stopWaitTime) - defer cancelShutdown() - if err := server.Shutdown(ctxShutdown); err != nil { - logger.Error(fmt.Sprintf("Clients service error occurred during shutdown at %s: %s", p, err)) - return fmt.Errorf("clients service occurred during shutdown at %s: %w", p, err) - } - logger.Info(fmt.Sprintf("Clients service shutdown of http at %s", p)) - return nil - case err := <-errCh: - return err - } - -} - -func startGRPCServer(ctx context.Context, svc policies.Service, port string, certFile string, keyFile string, logger logger.Logger) error { - p := fmt.Sprintf(":%s", port) - errCh := make(chan error) - - listener, err := net.Listen("tcp", p) - if err != nil { - return fmt.Errorf("failed to listen on port %s: %w", port, err) - } - - var server *grpc.Server - switch { - case certFile != "" || keyFile != "": - creds, err := credentials.NewServerTLSFromFile(certFile, keyFile) - if err != nil { - return fmt.Errorf("failed to load auth certificates: %w", err) - } - logger.Info(fmt.Sprintf("Clients gRPC service started using https on port %s with cert %s key %s", port, certFile, keyFile)) - server = grpc.NewServer(grpc.Creds(creds)) - default: - logger.Info(fmt.Sprintf("Clients gRPC service started using http on port %s", port)) - server = grpc.NewServer() - } - - reflection.Register(server) - policies.RegisterAuthServiceServer(server, grpcapi.NewServer(svc)) - logger.Info(fmt.Sprintf("Clients gRPC service started, exposed port %s", port)) - go func() { - errCh <- server.Serve(listener) - }() - - select { - case <-ctx.Done(): - c := make(chan bool) - go func() { - defer close(c) - server.GracefulStop() - }() - select { - case <-c: - case <-time.After(stopWaitTime): - } - logger.Info(fmt.Sprintf("Authentication gRPC service shutdown at %s", p)) - return nil - case err := <-errCh: - return err - } -} - -func createAdmin(c config, crepo clients.ClientRepository, hsr clients.Hasher, svc clients.Service) error { - id, err := uuid.New().ID() - if err != nil { - return err - } - hash, err := hsr.Hash(c.adminSecret) - if err != nil { - return err - } - - client := clients.Client{ - ID: id, - Name: "admin", - Credentials: clients.Credentials{ - Identity: c.adminIdentity, - Secret: hash, - }, - Metadata: clients.Metadata{ - "role": "admin", - }, - CreatedAt: time.Now(), - UpdatedAt: time.Now(), - Status: clients.EnabledStatus, - } - - if _, err := crepo.RetrieveByIdentity(context.Background(), client.Credentials.Identity); err == nil { - return nil - } - - // Create an admin - if _, err = crepo.Save(context.Background(), client); err != nil { - return err - } - _, err = svc.IssueToken(context.Background(), c.adminIdentity, c.adminSecret) - if err != nil { - return err - } - - return nil -} diff --git a/docker/.env b/docker/.env index 9498275c4cc..3cff0618adf 100644 --- a/docker/.env +++ b/docker/.env @@ -60,6 +60,7 @@ MF_USERS_ACCESS_TOKEN_DURATION=15m MF_USERS_REFRESH_TOKEN_DURATION=24h MF_USERS_ADMIN_EMAIL=admin@example.com MF_USERS_ADMIN_PASSWORD=12345678 +MF_USERS_JAEGER_URL=http://jaeger:14268/api/traces MF_USERS_RESET_PWD_TEMPLATE=users.tmpl MF_USERS_PASS_REGEX=^.{8,}$$ diff --git a/pkg/sdk/go/requests.go b/pkg/sdk/go/requests.go index 8a26fe6eadd..cca3e44dc81 100644 --- a/pkg/sdk/go/requests.go +++ b/pkg/sdk/go/requests.go @@ -41,3 +41,8 @@ type tokenReq struct { type identifyThingReq struct { Token string `json:"token,omitempty"` } + +type tokenReq struct { + Identity string `json:"identity"` + Secret string `json:"secret"` +} diff --git a/pkg/sdk/go/sdk.go b/pkg/sdk/go/sdk.go index 3fac7ddb02b..4b47caab861 100644 --- a/pkg/sdk/go/sdk.go +++ b/pkg/sdk/go/sdk.go @@ -123,6 +123,15 @@ type SDK interface { // UpdateUserOwner updates the user's owner. UpdateUserOwner(user User, token string) (User, errors.SDKError) + // UpdateUserTags updates the user's tags. + UpdateUserTags(user User, token string) errors.SDKError + + // UpdateUserIdentity updates the user's identity + UpdateUserIdentity(user User, token string) errors.SDKError + + // UpdateUserOwner updates the user's owner. + UpdateUserOwner(user User, token string) errors.SDKError + // UpdatePassword updates user password. UpdatePassword(id, oldPass, newPass, token string) (User, errors.SDKError) diff --git a/users/clients/api/requests.go b/users/clients/api/requests.go index 6f45f6c12e9..faff0c3fcfe 100644 --- a/users/clients/api/requests.go +++ b/users/clients/api/requests.go @@ -130,7 +130,6 @@ func (req updateClientOwnerReq) validate() error { if req.id == "" { return apiutil.ErrMissingID } - return nil } diff --git a/users/policies/clientauth.pb.go b/users/policies/clientauth.pb.go new file mode 100644 index 00000000000..f482b2753a0 --- /dev/null +++ b/users/policies/clientauth.pb.go @@ -0,0 +1,923 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.28.1 +// protoc v3.21.12 +// source: users/policies/clientauth.proto + +package policies + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type AuthorizeReq struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Sub string `protobuf:"bytes,1,opt,name=sub,proto3" json:"sub,omitempty"` + Obj string `protobuf:"bytes,2,opt,name=obj,proto3" json:"obj,omitempty"` + Act string `protobuf:"bytes,3,opt,name=act,proto3" json:"act,omitempty"` + EntityType string `protobuf:"bytes,4,opt,name=entityType,proto3" json:"entityType,omitempty"` +} + +func (x *AuthorizeReq) Reset() { + *x = AuthorizeReq{} + if protoimpl.UnsafeEnabled { + mi := &file_users_policies_clientauth_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *AuthorizeReq) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AuthorizeReq) ProtoMessage() {} + +func (x *AuthorizeReq) ProtoReflect() protoreflect.Message { + mi := &file_users_policies_clientauth_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use AuthorizeReq.ProtoReflect.Descriptor instead. +func (*AuthorizeReq) Descriptor() ([]byte, []int) { + return file_users_policies_clientauth_proto_rawDescGZIP(), []int{0} +} + +func (x *AuthorizeReq) GetSub() string { + if x != nil { + return x.Sub + } + return "" +} + +func (x *AuthorizeReq) GetObj() string { + if x != nil { + return x.Obj + } + return "" +} + +func (x *AuthorizeReq) GetAct() string { + if x != nil { + return x.Act + } + return "" +} + +func (x *AuthorizeReq) GetEntityType() string { + if x != nil { + return x.EntityType + } + return "" +} + +type AuthorizeRes struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Authorized bool `protobuf:"varint,1,opt,name=authorized,proto3" json:"authorized,omitempty"` +} + +func (x *AuthorizeRes) Reset() { + *x = AuthorizeRes{} + if protoimpl.UnsafeEnabled { + mi := &file_users_policies_clientauth_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *AuthorizeRes) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AuthorizeRes) ProtoMessage() {} + +func (x *AuthorizeRes) ProtoReflect() protoreflect.Message { + mi := &file_users_policies_clientauth_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use AuthorizeRes.ProtoReflect.Descriptor instead. +func (*AuthorizeRes) Descriptor() ([]byte, []int) { + return file_users_policies_clientauth_proto_rawDescGZIP(), []int{1} +} + +func (x *AuthorizeRes) GetAuthorized() bool { + if x != nil { + return x.Authorized + } + return false +} + +type IssueReq struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + Email string `protobuf:"bytes,2,opt,name=email,proto3" json:"email,omitempty"` + Type uint32 `protobuf:"varint,3,opt,name=type,proto3" json:"type,omitempty"` +} + +func (x *IssueReq) Reset() { + *x = IssueReq{} + if protoimpl.UnsafeEnabled { + mi := &file_users_policies_clientauth_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *IssueReq) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*IssueReq) ProtoMessage() {} + +func (x *IssueReq) ProtoReflect() protoreflect.Message { + mi := &file_users_policies_clientauth_proto_msgTypes[2] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use IssueReq.ProtoReflect.Descriptor instead. +func (*IssueReq) Descriptor() ([]byte, []int) { + return file_users_policies_clientauth_proto_rawDescGZIP(), []int{2} +} + +func (x *IssueReq) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +func (x *IssueReq) GetEmail() string { + if x != nil { + return x.Email + } + return "" +} + +func (x *IssueReq) GetType() uint32 { + if x != nil { + return x.Type + } + return 0 +} + +type Token struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Value string `protobuf:"bytes,1,opt,name=value,proto3" json:"value,omitempty"` +} + +func (x *Token) Reset() { + *x = Token{} + if protoimpl.UnsafeEnabled { + mi := &file_users_policies_clientauth_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Token) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Token) ProtoMessage() {} + +func (x *Token) ProtoReflect() protoreflect.Message { + mi := &file_users_policies_clientauth_proto_msgTypes[3] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Token.ProtoReflect.Descriptor instead. +func (*Token) Descriptor() ([]byte, []int) { + return file_users_policies_clientauth_proto_rawDescGZIP(), []int{3} +} + +func (x *Token) GetValue() string { + if x != nil { + return x.Value + } + return "" +} + +type UserIdentity struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + Email string `protobuf:"bytes,2,opt,name=email,proto3" json:"email,omitempty"` +} + +func (x *UserIdentity) Reset() { + *x = UserIdentity{} + if protoimpl.UnsafeEnabled { + mi := &file_users_policies_clientauth_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *UserIdentity) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UserIdentity) ProtoMessage() {} + +func (x *UserIdentity) ProtoReflect() protoreflect.Message { + mi := &file_users_policies_clientauth_proto_msgTypes[4] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use UserIdentity.ProtoReflect.Descriptor instead. +func (*UserIdentity) Descriptor() ([]byte, []int) { + return file_users_policies_clientauth_proto_rawDescGZIP(), []int{4} +} + +func (x *UserIdentity) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +func (x *UserIdentity) GetEmail() string { + if x != nil { + return x.Email + } + return "" +} + +type AddPolicyReq struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Token string `protobuf:"bytes,1,opt,name=token,proto3" json:"token,omitempty"` + Sub string `protobuf:"bytes,2,opt,name=sub,proto3" json:"sub,omitempty"` + Obj string `protobuf:"bytes,3,opt,name=obj,proto3" json:"obj,omitempty"` + Act []string `protobuf:"bytes,4,rep,name=act,proto3" json:"act,omitempty"` +} + +func (x *AddPolicyReq) Reset() { + *x = AddPolicyReq{} + if protoimpl.UnsafeEnabled { + mi := &file_users_policies_clientauth_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *AddPolicyReq) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AddPolicyReq) ProtoMessage() {} + +func (x *AddPolicyReq) ProtoReflect() protoreflect.Message { + mi := &file_users_policies_clientauth_proto_msgTypes[5] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use AddPolicyReq.ProtoReflect.Descriptor instead. +func (*AddPolicyReq) Descriptor() ([]byte, []int) { + return file_users_policies_clientauth_proto_rawDescGZIP(), []int{5} +} + +func (x *AddPolicyReq) GetToken() string { + if x != nil { + return x.Token + } + return "" +} + +func (x *AddPolicyReq) GetSub() string { + if x != nil { + return x.Sub + } + return "" +} + +func (x *AddPolicyReq) GetObj() string { + if x != nil { + return x.Obj + } + return "" +} + +func (x *AddPolicyReq) GetAct() []string { + if x != nil { + return x.Act + } + return nil +} + +type AddPolicyRes struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Authorized bool `protobuf:"varint,1,opt,name=authorized,proto3" json:"authorized,omitempty"` +} + +func (x *AddPolicyRes) Reset() { + *x = AddPolicyRes{} + if protoimpl.UnsafeEnabled { + mi := &file_users_policies_clientauth_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *AddPolicyRes) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AddPolicyRes) ProtoMessage() {} + +func (x *AddPolicyRes) ProtoReflect() protoreflect.Message { + mi := &file_users_policies_clientauth_proto_msgTypes[6] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use AddPolicyRes.ProtoReflect.Descriptor instead. +func (*AddPolicyRes) Descriptor() ([]byte, []int) { + return file_users_policies_clientauth_proto_rawDescGZIP(), []int{6} +} + +func (x *AddPolicyRes) GetAuthorized() bool { + if x != nil { + return x.Authorized + } + return false +} + +type DeletePolicyReq struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Sub string `protobuf:"bytes,1,opt,name=sub,proto3" json:"sub,omitempty"` + Obj string `protobuf:"bytes,2,opt,name=obj,proto3" json:"obj,omitempty"` + Act string `protobuf:"bytes,3,opt,name=act,proto3" json:"act,omitempty"` +} + +func (x *DeletePolicyReq) Reset() { + *x = DeletePolicyReq{} + if protoimpl.UnsafeEnabled { + mi := &file_users_policies_clientauth_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *DeletePolicyReq) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DeletePolicyReq) ProtoMessage() {} + +func (x *DeletePolicyReq) ProtoReflect() protoreflect.Message { + mi := &file_users_policies_clientauth_proto_msgTypes[7] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DeletePolicyReq.ProtoReflect.Descriptor instead. +func (*DeletePolicyReq) Descriptor() ([]byte, []int) { + return file_users_policies_clientauth_proto_rawDescGZIP(), []int{7} +} + +func (x *DeletePolicyReq) GetSub() string { + if x != nil { + return x.Sub + } + return "" +} + +func (x *DeletePolicyReq) GetObj() string { + if x != nil { + return x.Obj + } + return "" +} + +func (x *DeletePolicyReq) GetAct() string { + if x != nil { + return x.Act + } + return "" +} + +type DeletePolicyRes struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Deleted bool `protobuf:"varint,1,opt,name=deleted,proto3" json:"deleted,omitempty"` +} + +func (x *DeletePolicyRes) Reset() { + *x = DeletePolicyRes{} + if protoimpl.UnsafeEnabled { + mi := &file_users_policies_clientauth_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *DeletePolicyRes) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DeletePolicyRes) ProtoMessage() {} + +func (x *DeletePolicyRes) ProtoReflect() protoreflect.Message { + mi := &file_users_policies_clientauth_proto_msgTypes[8] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DeletePolicyRes.ProtoReflect.Descriptor instead. +func (*DeletePolicyRes) Descriptor() ([]byte, []int) { + return file_users_policies_clientauth_proto_rawDescGZIP(), []int{8} +} + +func (x *DeletePolicyRes) GetDeleted() bool { + if x != nil { + return x.Deleted + } + return false +} + +type ListPoliciesReq struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Sub string `protobuf:"bytes,1,opt,name=sub,proto3" json:"sub,omitempty"` + Obj string `protobuf:"bytes,2,opt,name=obj,proto3" json:"obj,omitempty"` + Act string `protobuf:"bytes,3,opt,name=act,proto3" json:"act,omitempty"` +} + +func (x *ListPoliciesReq) Reset() { + *x = ListPoliciesReq{} + if protoimpl.UnsafeEnabled { + mi := &file_users_policies_clientauth_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ListPoliciesReq) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListPoliciesReq) ProtoMessage() {} + +func (x *ListPoliciesReq) ProtoReflect() protoreflect.Message { + mi := &file_users_policies_clientauth_proto_msgTypes[9] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListPoliciesReq.ProtoReflect.Descriptor instead. +func (*ListPoliciesReq) Descriptor() ([]byte, []int) { + return file_users_policies_clientauth_proto_rawDescGZIP(), []int{9} +} + +func (x *ListPoliciesReq) GetSub() string { + if x != nil { + return x.Sub + } + return "" +} + +func (x *ListPoliciesReq) GetObj() string { + if x != nil { + return x.Obj + } + return "" +} + +func (x *ListPoliciesReq) GetAct() string { + if x != nil { + return x.Act + } + return "" +} + +type ListPoliciesRes struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Objects []string `protobuf:"bytes,1,rep,name=objects,proto3" json:"objects,omitempty"` +} + +func (x *ListPoliciesRes) Reset() { + *x = ListPoliciesRes{} + if protoimpl.UnsafeEnabled { + mi := &file_users_policies_clientauth_proto_msgTypes[10] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ListPoliciesRes) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListPoliciesRes) ProtoMessage() {} + +func (x *ListPoliciesRes) ProtoReflect() protoreflect.Message { + mi := &file_users_policies_clientauth_proto_msgTypes[10] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListPoliciesRes.ProtoReflect.Descriptor instead. +func (*ListPoliciesRes) Descriptor() ([]byte, []int) { + return file_users_policies_clientauth_proto_rawDescGZIP(), []int{10} +} + +func (x *ListPoliciesRes) GetObjects() []string { + if x != nil { + return x.Objects + } + return nil +} + +var File_users_policies_clientauth_proto protoreflect.FileDescriptor + +var file_users_policies_clientauth_proto_rawDesc = []byte{ + 0x0a, 0x1f, 0x75, 0x73, 0x65, 0x72, 0x73, 0x2f, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, + 0x2f, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x61, 0x75, 0x74, 0x68, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x12, 0x08, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, 0x22, 0x64, 0x0a, 0x0c, 0x41, + 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x52, 0x65, 0x71, 0x12, 0x10, 0x0a, 0x03, 0x73, + 0x75, 0x62, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x73, 0x75, 0x62, 0x12, 0x10, 0x0a, + 0x03, 0x6f, 0x62, 0x6a, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6f, 0x62, 0x6a, 0x12, + 0x10, 0x0a, 0x03, 0x61, 0x63, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x61, 0x63, + 0x74, 0x12, 0x1e, 0x0a, 0x0a, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x54, 0x79, 0x70, 0x65, 0x18, + 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x54, 0x79, 0x70, + 0x65, 0x22, 0x2e, 0x0a, 0x0c, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x52, 0x65, + 0x73, 0x12, 0x1e, 0x0a, 0x0a, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x64, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, + 0x64, 0x22, 0x44, 0x0a, 0x08, 0x49, 0x73, 0x73, 0x75, 0x65, 0x52, 0x65, 0x71, 0x12, 0x0e, 0x0a, + 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x14, 0x0a, + 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x6d, + 0x61, 0x69, 0x6c, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x0d, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x22, 0x1d, 0x0a, 0x05, 0x54, 0x6f, 0x6b, 0x65, 0x6e, + 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x34, 0x0a, 0x0c, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, + 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x22, 0x5a, 0x0a, 0x0c, + 0x41, 0x64, 0x64, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, 0x65, 0x71, 0x12, 0x14, 0x0a, 0x05, + 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x74, 0x6f, 0x6b, + 0x65, 0x6e, 0x12, 0x10, 0x0a, 0x03, 0x73, 0x75, 0x62, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x03, 0x73, 0x75, 0x62, 0x12, 0x10, 0x0a, 0x03, 0x6f, 0x62, 0x6a, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x03, 0x6f, 0x62, 0x6a, 0x12, 0x10, 0x0a, 0x03, 0x61, 0x63, 0x74, 0x18, 0x04, 0x20, + 0x03, 0x28, 0x09, 0x52, 0x03, 0x61, 0x63, 0x74, 0x22, 0x2e, 0x0a, 0x0c, 0x41, 0x64, 0x64, 0x50, + 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, 0x65, 0x73, 0x12, 0x1e, 0x0a, 0x0a, 0x61, 0x75, 0x74, 0x68, + 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x61, 0x75, + 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x64, 0x22, 0x47, 0x0a, 0x0f, 0x44, 0x65, 0x6c, 0x65, + 0x74, 0x65, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, 0x65, 0x71, 0x12, 0x10, 0x0a, 0x03, 0x73, + 0x75, 0x62, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x73, 0x75, 0x62, 0x12, 0x10, 0x0a, + 0x03, 0x6f, 0x62, 0x6a, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6f, 0x62, 0x6a, 0x12, + 0x10, 0x0a, 0x03, 0x61, 0x63, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x61, 0x63, + 0x74, 0x22, 0x2b, 0x0a, 0x0f, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x6f, 0x6c, 0x69, 0x63, + 0x79, 0x52, 0x65, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x22, 0x47, + 0x0a, 0x0f, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, 0x52, 0x65, + 0x71, 0x12, 0x10, 0x0a, 0x03, 0x73, 0x75, 0x62, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, + 0x73, 0x75, 0x62, 0x12, 0x10, 0x0a, 0x03, 0x6f, 0x62, 0x6a, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x03, 0x6f, 0x62, 0x6a, 0x12, 0x10, 0x0a, 0x03, 0x61, 0x63, 0x74, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x03, 0x61, 0x63, 0x74, 0x22, 0x2b, 0x0a, 0x0f, 0x4c, 0x69, 0x73, 0x74, 0x50, + 0x6f, 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, 0x52, 0x65, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x6f, 0x62, + 0x6a, 0x65, 0x63, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, 0x6f, 0x62, 0x6a, + 0x65, 0x63, 0x74, 0x73, 0x32, 0x82, 0x03, 0x0a, 0x0b, 0x41, 0x75, 0x74, 0x68, 0x53, 0x65, 0x72, + 0x76, 0x69, 0x63, 0x65, 0x12, 0x3d, 0x0a, 0x09, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, + 0x65, 0x12, 0x16, 0x2e, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, 0x2e, 0x41, 0x75, 0x74, + 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x52, 0x65, 0x71, 0x1a, 0x16, 0x2e, 0x70, 0x6f, 0x6c, 0x69, + 0x63, 0x69, 0x65, 0x73, 0x2e, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x52, 0x65, + 0x73, 0x22, 0x00, 0x12, 0x2e, 0x0a, 0x05, 0x49, 0x73, 0x73, 0x75, 0x65, 0x12, 0x12, 0x2e, 0x70, + 0x6f, 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, 0x2e, 0x49, 0x73, 0x73, 0x75, 0x65, 0x52, 0x65, 0x71, + 0x1a, 0x0f, 0x2e, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, 0x2e, 0x54, 0x6f, 0x6b, 0x65, + 0x6e, 0x22, 0x00, 0x12, 0x35, 0x0a, 0x08, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x79, 0x12, + 0x0f, 0x2e, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, 0x2e, 0x54, 0x6f, 0x6b, 0x65, 0x6e, + 0x1a, 0x16, 0x2e, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, 0x2e, 0x55, 0x73, 0x65, 0x72, + 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x22, 0x00, 0x12, 0x3d, 0x0a, 0x09, 0x41, 0x64, + 0x64, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x12, 0x16, 0x2e, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x69, + 0x65, 0x73, 0x2e, 0x41, 0x64, 0x64, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, 0x65, 0x71, 0x1a, + 0x16, 0x2e, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, 0x2e, 0x41, 0x64, 0x64, 0x50, 0x6f, + 0x6c, 0x69, 0x63, 0x79, 0x52, 0x65, 0x73, 0x22, 0x00, 0x12, 0x46, 0x0a, 0x0c, 0x44, 0x65, 0x6c, + 0x65, 0x74, 0x65, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x12, 0x19, 0x2e, 0x70, 0x6f, 0x6c, 0x69, + 0x63, 0x69, 0x65, 0x73, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x6f, 0x6c, 0x69, 0x63, + 0x79, 0x52, 0x65, 0x71, 0x1a, 0x19, 0x2e, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, 0x2e, + 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, 0x65, 0x73, 0x22, + 0x00, 0x12, 0x46, 0x0a, 0x0c, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x69, 0x65, + 0x73, 0x12, 0x19, 0x2e, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, 0x2e, 0x4c, 0x69, 0x73, + 0x74, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, 0x52, 0x65, 0x71, 0x1a, 0x19, 0x2e, 0x70, + 0x6f, 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x6f, 0x6c, 0x69, + 0x63, 0x69, 0x65, 0x73, 0x52, 0x65, 0x73, 0x22, 0x00, 0x42, 0x0c, 0x5a, 0x0a, 0x2e, 0x2f, 0x70, + 0x6f, 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_users_policies_clientauth_proto_rawDescOnce sync.Once + file_users_policies_clientauth_proto_rawDescData = file_users_policies_clientauth_proto_rawDesc +) + +func file_users_policies_clientauth_proto_rawDescGZIP() []byte { + file_users_policies_clientauth_proto_rawDescOnce.Do(func() { + file_users_policies_clientauth_proto_rawDescData = protoimpl.X.CompressGZIP(file_users_policies_clientauth_proto_rawDescData) + }) + return file_users_policies_clientauth_proto_rawDescData +} + +var file_users_policies_clientauth_proto_msgTypes = make([]protoimpl.MessageInfo, 11) +var file_users_policies_clientauth_proto_goTypes = []interface{}{ + (*AuthorizeReq)(nil), // 0: policies.AuthorizeReq + (*AuthorizeRes)(nil), // 1: policies.AuthorizeRes + (*IssueReq)(nil), // 2: policies.IssueReq + (*Token)(nil), // 3: policies.Token + (*UserIdentity)(nil), // 4: policies.UserIdentity + (*AddPolicyReq)(nil), // 5: policies.AddPolicyReq + (*AddPolicyRes)(nil), // 6: policies.AddPolicyRes + (*DeletePolicyReq)(nil), // 7: policies.DeletePolicyReq + (*DeletePolicyRes)(nil), // 8: policies.DeletePolicyRes + (*ListPoliciesReq)(nil), // 9: policies.ListPoliciesReq + (*ListPoliciesRes)(nil), // 10: policies.ListPoliciesRes +} +var file_users_policies_clientauth_proto_depIdxs = []int32{ + 0, // 0: policies.AuthService.Authorize:input_type -> policies.AuthorizeReq + 2, // 1: policies.AuthService.Issue:input_type -> policies.IssueReq + 3, // 2: policies.AuthService.Identify:input_type -> policies.Token + 5, // 3: policies.AuthService.AddPolicy:input_type -> policies.AddPolicyReq + 7, // 4: policies.AuthService.DeletePolicy:input_type -> policies.DeletePolicyReq + 9, // 5: policies.AuthService.ListPolicies:input_type -> policies.ListPoliciesReq + 1, // 6: policies.AuthService.Authorize:output_type -> policies.AuthorizeRes + 3, // 7: policies.AuthService.Issue:output_type -> policies.Token + 4, // 8: policies.AuthService.Identify:output_type -> policies.UserIdentity + 6, // 9: policies.AuthService.AddPolicy:output_type -> policies.AddPolicyRes + 8, // 10: policies.AuthService.DeletePolicy:output_type -> policies.DeletePolicyRes + 10, // 11: policies.AuthService.ListPolicies:output_type -> policies.ListPoliciesRes + 6, // [6:12] is the sub-list for method output_type + 0, // [0:6] is the sub-list for method input_type + 0, // [0:0] is the sub-list for extension type_name + 0, // [0:0] is the sub-list for extension extendee + 0, // [0:0] is the sub-list for field type_name +} + +func init() { file_users_policies_clientauth_proto_init() } +func file_users_policies_clientauth_proto_init() { + if File_users_policies_clientauth_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_users_policies_clientauth_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*AuthorizeReq); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_users_policies_clientauth_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*AuthorizeRes); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_users_policies_clientauth_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*IssueReq); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_users_policies_clientauth_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Token); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_users_policies_clientauth_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*UserIdentity); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_users_policies_clientauth_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*AddPolicyReq); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_users_policies_clientauth_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*AddPolicyRes); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_users_policies_clientauth_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*DeletePolicyReq); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_users_policies_clientauth_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*DeletePolicyRes); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_users_policies_clientauth_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ListPoliciesReq); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_users_policies_clientauth_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ListPoliciesRes); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_users_policies_clientauth_proto_rawDesc, + NumEnums: 0, + NumMessages: 11, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_users_policies_clientauth_proto_goTypes, + DependencyIndexes: file_users_policies_clientauth_proto_depIdxs, + MessageInfos: file_users_policies_clientauth_proto_msgTypes, + }.Build() + File_users_policies_clientauth_proto = out.File + file_users_policies_clientauth_proto_rawDesc = nil + file_users_policies_clientauth_proto_goTypes = nil + file_users_policies_clientauth_proto_depIdxs = nil +} diff --git a/users/policies/clientauth.proto b/users/policies/clientauth.proto new file mode 100644 index 00000000000..5a30bceaff6 --- /dev/null +++ b/users/policies/clientauth.proto @@ -0,0 +1,71 @@ +syntax = "proto3"; + +package policies; + +option go_package = "./policies"; + +service AuthService { + rpc Authorize(AuthorizeReq) returns (AuthorizeRes) {} + rpc Issue(IssueReq) returns (Token) {} + rpc Identify(Token) returns (UserIdentity) {} + rpc AddPolicy(AddPolicyReq) returns (AddPolicyRes) {} + rpc DeletePolicy(DeletePolicyReq) returns (DeletePolicyRes) {} + rpc ListPolicies(ListPoliciesReq) returns (ListPoliciesRes) {} +} + +message AuthorizeReq { + string sub = 1; + string obj = 2; + string act = 3; + string entityType = 4; +} + +message AuthorizeRes { + bool authorized = 1; +} + +message IssueReq { + string id = 1; + string email = 2; + uint32 type = 3; +} + +message Token { + string value = 1; +} + +message UserIdentity { + string id = 1; + string email = 2; +} + +message AddPolicyReq { + string token = 1; + string sub = 2; + string obj = 3; + repeated string act = 4; +} + +message AddPolicyRes { + bool authorized = 1; +} + +message DeletePolicyReq { + string sub = 1; + string obj = 2; + string act = 3; +} + +message DeletePolicyRes { + bool deleted = 1; +} + +message ListPoliciesReq { + string sub = 1; + string obj = 2; + string act = 3; +} + +message ListPoliciesRes { + repeated string objects = 1; +} diff --git a/users/policies/clientauth_grpc.pb.go b/users/policies/clientauth_grpc.pb.go new file mode 100644 index 00000000000..9ec5298b675 --- /dev/null +++ b/users/policies/clientauth_grpc.pb.go @@ -0,0 +1,285 @@ +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. +// versions: +// - protoc-gen-go-grpc v1.2.0 +// - protoc v3.21.12 +// source: users/policies/clientauth.proto + +package policies + +import ( + context "context" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" +) + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +// Requires gRPC-Go v1.32.0 or later. +const _ = grpc.SupportPackageIsVersion7 + +// AuthServiceClient is the client API for AuthService service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +type AuthServiceClient interface { + Authorize(ctx context.Context, in *AuthorizeReq, opts ...grpc.CallOption) (*AuthorizeRes, error) + Issue(ctx context.Context, in *IssueReq, opts ...grpc.CallOption) (*Token, error) + Identify(ctx context.Context, in *Token, opts ...grpc.CallOption) (*UserIdentity, error) + AddPolicy(ctx context.Context, in *AddPolicyReq, opts ...grpc.CallOption) (*AddPolicyRes, error) + DeletePolicy(ctx context.Context, in *DeletePolicyReq, opts ...grpc.CallOption) (*DeletePolicyRes, error) + ListPolicies(ctx context.Context, in *ListPoliciesReq, opts ...grpc.CallOption) (*ListPoliciesRes, error) +} + +type authServiceClient struct { + cc grpc.ClientConnInterface +} + +func NewAuthServiceClient(cc grpc.ClientConnInterface) AuthServiceClient { + return &authServiceClient{cc} +} + +func (c *authServiceClient) Authorize(ctx context.Context, in *AuthorizeReq, opts ...grpc.CallOption) (*AuthorizeRes, error) { + out := new(AuthorizeRes) + err := c.cc.Invoke(ctx, "/policies.AuthService/Authorize", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *authServiceClient) Issue(ctx context.Context, in *IssueReq, opts ...grpc.CallOption) (*Token, error) { + out := new(Token) + err := c.cc.Invoke(ctx, "/policies.AuthService/Issue", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *authServiceClient) Identify(ctx context.Context, in *Token, opts ...grpc.CallOption) (*UserIdentity, error) { + out := new(UserIdentity) + err := c.cc.Invoke(ctx, "/policies.AuthService/Identify", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *authServiceClient) AddPolicy(ctx context.Context, in *AddPolicyReq, opts ...grpc.CallOption) (*AddPolicyRes, error) { + out := new(AddPolicyRes) + err := c.cc.Invoke(ctx, "/policies.AuthService/AddPolicy", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *authServiceClient) DeletePolicy(ctx context.Context, in *DeletePolicyReq, opts ...grpc.CallOption) (*DeletePolicyRes, error) { + out := new(DeletePolicyRes) + err := c.cc.Invoke(ctx, "/policies.AuthService/DeletePolicy", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *authServiceClient) ListPolicies(ctx context.Context, in *ListPoliciesReq, opts ...grpc.CallOption) (*ListPoliciesRes, error) { + out := new(ListPoliciesRes) + err := c.cc.Invoke(ctx, "/policies.AuthService/ListPolicies", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// AuthServiceServer is the server API for AuthService service. +// All implementations must embed UnimplementedAuthServiceServer +// for forward compatibility +type AuthServiceServer interface { + Authorize(context.Context, *AuthorizeReq) (*AuthorizeRes, error) + Issue(context.Context, *IssueReq) (*Token, error) + Identify(context.Context, *Token) (*UserIdentity, error) + AddPolicy(context.Context, *AddPolicyReq) (*AddPolicyRes, error) + DeletePolicy(context.Context, *DeletePolicyReq) (*DeletePolicyRes, error) + ListPolicies(context.Context, *ListPoliciesReq) (*ListPoliciesRes, error) + mustEmbedUnimplementedAuthServiceServer() +} + +// UnimplementedAuthServiceServer must be embedded to have forward compatible implementations. +type UnimplementedAuthServiceServer struct { +} + +func (UnimplementedAuthServiceServer) Authorize(context.Context, *AuthorizeReq) (*AuthorizeRes, error) { + return nil, status.Errorf(codes.Unimplemented, "method Authorize not implemented") +} +func (UnimplementedAuthServiceServer) Issue(context.Context, *IssueReq) (*Token, error) { + return nil, status.Errorf(codes.Unimplemented, "method Issue not implemented") +} +func (UnimplementedAuthServiceServer) Identify(context.Context, *Token) (*UserIdentity, error) { + return nil, status.Errorf(codes.Unimplemented, "method Identify not implemented") +} +func (UnimplementedAuthServiceServer) AddPolicy(context.Context, *AddPolicyReq) (*AddPolicyRes, error) { + return nil, status.Errorf(codes.Unimplemented, "method AddPolicy not implemented") +} +func (UnimplementedAuthServiceServer) DeletePolicy(context.Context, *DeletePolicyReq) (*DeletePolicyRes, error) { + return nil, status.Errorf(codes.Unimplemented, "method DeletePolicy not implemented") +} +func (UnimplementedAuthServiceServer) ListPolicies(context.Context, *ListPoliciesReq) (*ListPoliciesRes, error) { + return nil, status.Errorf(codes.Unimplemented, "method ListPolicies not implemented") +} +func (UnimplementedAuthServiceServer) mustEmbedUnimplementedAuthServiceServer() {} + +// UnsafeAuthServiceServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to AuthServiceServer will +// result in compilation errors. +type UnsafeAuthServiceServer interface { + mustEmbedUnimplementedAuthServiceServer() +} + +func RegisterAuthServiceServer(s grpc.ServiceRegistrar, srv AuthServiceServer) { + s.RegisterService(&AuthService_ServiceDesc, srv) +} + +func _AuthService_Authorize_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(AuthorizeReq) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(AuthServiceServer).Authorize(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/policies.AuthService/Authorize", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(AuthServiceServer).Authorize(ctx, req.(*AuthorizeReq)) + } + return interceptor(ctx, in, info, handler) +} + +func _AuthService_Issue_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(IssueReq) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(AuthServiceServer).Issue(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/policies.AuthService/Issue", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(AuthServiceServer).Issue(ctx, req.(*IssueReq)) + } + return interceptor(ctx, in, info, handler) +} + +func _AuthService_Identify_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(Token) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(AuthServiceServer).Identify(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/policies.AuthService/Identify", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(AuthServiceServer).Identify(ctx, req.(*Token)) + } + return interceptor(ctx, in, info, handler) +} + +func _AuthService_AddPolicy_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(AddPolicyReq) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(AuthServiceServer).AddPolicy(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/policies.AuthService/AddPolicy", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(AuthServiceServer).AddPolicy(ctx, req.(*AddPolicyReq)) + } + return interceptor(ctx, in, info, handler) +} + +func _AuthService_DeletePolicy_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(DeletePolicyReq) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(AuthServiceServer).DeletePolicy(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/policies.AuthService/DeletePolicy", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(AuthServiceServer).DeletePolicy(ctx, req.(*DeletePolicyReq)) + } + return interceptor(ctx, in, info, handler) +} + +func _AuthService_ListPolicies_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ListPoliciesReq) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(AuthServiceServer).ListPolicies(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/policies.AuthService/ListPolicies", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(AuthServiceServer).ListPolicies(ctx, req.(*ListPoliciesReq)) + } + return interceptor(ctx, in, info, handler) +} + +// AuthService_ServiceDesc is the grpc.ServiceDesc for AuthService service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var AuthService_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "policies.AuthService", + HandlerType: (*AuthServiceServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "Authorize", + Handler: _AuthService_Authorize_Handler, + }, + { + MethodName: "Issue", + Handler: _AuthService_Issue_Handler, + }, + { + MethodName: "Identify", + Handler: _AuthService_Identify_Handler, + }, + { + MethodName: "AddPolicy", + Handler: _AuthService_AddPolicy_Handler, + }, + { + MethodName: "DeletePolicy", + Handler: _AuthService_DeletePolicy_Handler, + }, + { + MethodName: "ListPolicies", + Handler: _AuthService_ListPolicies_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "users/policies/clientauth.proto", +} diff --git a/users/policies/postgres/setup_test.go b/users/policies/postgres/setup_test.go index e377f955a89..7d5e7f83547 100644 --- a/users/policies/postgres/setup_test.go +++ b/users/policies/postgres/setup_test.go @@ -10,6 +10,7 @@ import ( "testing" "time" + _ "github.com/jackc/pgx/v5/stdlib" // required for SQL access "github.com/jmoiron/sqlx" pgClient "github.com/mainflux/mainflux/internal/clients/postgres" "github.com/mainflux/mainflux/internal/postgres" diff --git a/clients/postgres/common.go b/users/postgres/common.go similarity index 100% rename from clients/postgres/common.go rename to users/postgres/common.go diff --git a/clients/postgres/errors.go b/users/postgres/errors.go similarity index 100% rename from clients/postgres/errors.go rename to users/postgres/errors.go diff --git a/clients/postgres/tracing.go b/users/postgres/tracing.go similarity index 100% rename from clients/postgres/tracing.go rename to users/postgres/tracing.go